LCOV - code coverage report
Current view: top level - backends - flint_lock.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 47 101 46.5 %
Date: 2011-08-21 Functions: 3 3 100.0 %
Branches: 23 86 26.7 %

           Branch data     Line data    Source code
       1                 :            : /** @file flint_lock.cc
       2                 :            :  * @brief Flint-compatible database locking.
       3                 :            :  */
       4                 :            : /* Copyright (C) 2005,2006,2007,2008,2009,2010 Olly Betts
       5                 :            :  *
       6                 :            :  * This program is free software; you can redistribute it and/or
       7                 :            :  * modify it under the terms of the GNU General Public License as
       8                 :            :  * published by the Free Software Foundation; either version 2 of the
       9                 :            :  * License, or (at your option) any later version.
      10                 :            :  *
      11                 :            :  * This program is distributed in the hope that it will be useful,
      12                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14                 :            :  * GNU General Public License for more details.
      15                 :            :  *
      16                 :            :  * You should have received a copy of the GNU General Public License
      17                 :            :  * along with this program; if not, write to the Free Software
      18                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      19                 :            :  * USA
      20                 :            :  */
      21                 :            : 
      22                 :            : #include <config.h>
      23                 :            : 
      24                 :            : #include "flint_lock.h"
      25                 :            : 
      26                 :            : #ifndef __WIN32__
      27                 :            : #include "safeerrno.h"
      28                 :            : 
      29                 :            : #include "safefcntl.h"
      30                 :            : #include <unistd.h>
      31                 :            : #include <cstdlib>
      32                 :            : #include <sys/types.h>
      33                 :            : #include <sys/socket.h>
      34                 :            : #include <sys/wait.h>
      35                 :            : #include <signal.h>
      36                 :            : #include <cstring>
      37                 :            : #endif
      38                 :            : 
      39                 :            : #include "closefrom.h"
      40                 :            : #include "omassert.h"
      41                 :            : 
      42                 :            : #ifdef __CYGWIN__
      43                 :            : #include <sys/cygwin.h>
      44                 :            : #endif
      45                 :            : 
      46                 :            : #include "xapian/error.h"
      47                 :            : 
      48                 :            : using namespace std;
      49                 :            : 
      50                 :            : FlintLock::reason
      51                 :       2133 : FlintLock::lock(bool exclusive, string & explanation) {
      52                 :            :     // Currently we only support exclusive locks.
      53                 :            :     (void)exclusive;
      54                 :            :     Assert(exclusive);
      55                 :            : #if defined __CYGWIN__ || defined __WIN32__
      56                 :            :     Assert(hFile == INVALID_HANDLE_VALUE);
      57                 :            : #ifdef __CYGWIN__
      58                 :            :     char fnm[MAX_PATH];
      59                 :            :     cygwin_conv_to_win32_path(filename.c_str(), fnm);
      60                 :            : #else
      61                 :            :     const char *fnm = filename.c_str();
      62                 :            : #endif
      63                 :            :     hFile = CreateFile(fnm, GENERIC_WRITE, FILE_SHARE_READ,
      64                 :            :                        NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
      65                 :            :     if (hFile != INVALID_HANDLE_VALUE) return SUCCESS;
      66                 :            :     if (GetLastError() == ERROR_ALREADY_EXISTS) return INUSE;
      67                 :            :     explanation = string();
      68                 :            :     return UNKNOWN;
      69                 :            : #elif defined __EMX__
      70                 :            :     APIRET rc;
      71                 :            :     ULONG ulAction;
      72                 :            :     rc = DosOpen((PCSZ)filename.c_str(), &hFile, &ulAction, 0, FILE_NORMAL,
      73                 :            :                  OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
      74                 :            :                  OPEN_SHARE_DENYWRITE | OPEN_ACCESS_WRITEONLY,
      75                 :            :                  NULL);
      76                 :            :     if (rc == NO_ERROR) return SUCCESS;
      77                 :            :     if (rc == ERROR_ACCESS_DENIED) return INUSE;
      78                 :            :     explanation = string();
      79                 :            :     return UNKNOWN;
      80                 :            : #else
      81                 :            :     Assert(fd == -1);
      82                 :       2133 :     int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
      83         [ +  + ]:       2133 :     if (lockfd < 0) {
      84                 :            :         // Couldn't open lockfile.
      85                 :          2 :         explanation = string("Couldn't open lockfile: ") + strerror(errno);
      86   [ +  -  -  + ]:          2 :         return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
      87                 :            :     }
      88                 :            : 
      89                 :            :     // If stdin and/or stdout have been closed, it is possible that lockfd could
      90                 :            :     // be 0 or 1.  We need fds 0 and 1 to be available in the child process to
      91                 :            :     // be stdin and stdout, and we can't use dup() on lockfd after locking it,
      92                 :            :     // as the lock won't be transferred, so we handle this corner case here by
      93                 :            :     // using dup() once or twice to get lockfd to be >= 2.
      94         [ +  + ]:       2131 :     if (rare(lockfd < 2)) {
      95                 :            :         // Note this temporarily requires one or two spare fds to work, but
      96                 :            :         // then we need two spare for socketpair() to succeed below anyway.
      97                 :          9 :         int lockfd_dup = dup(lockfd);
      98         [ +  + ]:          9 :         if (rare(lockfd_dup < 2)) {
      99                 :          3 :             int eno = 0;
     100         [ -  + ]:          3 :             if (lockfd_dup < 0) {
     101                 :          0 :                 eno = errno;
     102                 :          0 :                 close(lockfd);
     103                 :            :             } else {
     104                 :          3 :                 int lockfd_dup2 = dup(lockfd);
     105         [ -  + ]:          3 :                 if (lockfd_dup2 < 0) {
     106                 :          0 :                     eno = errno;
     107                 :            :                 }
     108                 :          3 :                 close(lockfd);
     109                 :          3 :                 close(lockfd_dup);
     110                 :          3 :                 lockfd = lockfd_dup2;
     111                 :            :             }
     112         [ -  + ]:          3 :             if (eno) {
     113 [ #  # ][ #  # ]:          0 :                 return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
     114                 :            :             }
     115                 :            :         } else {
     116                 :          6 :             close(lockfd);
     117                 :          6 :             lockfd = lockfd_dup;
     118                 :            :         }
     119                 :            :     }
     120                 :            : 
     121                 :            :     int fds[2];
     122         [ -  + ]:       2131 :     if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0) {
     123                 :            :         // Couldn't create socketpair.
     124                 :          0 :         explanation = string("Couldn't create socketpair: ") + strerror(errno);
     125   [ #  #  #  # ]:          0 :         reason why = ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
     126                 :          0 :         (void)close(lockfd);
     127                 :          0 :         return why;
     128                 :            :     }
     129                 :            : 
     130                 :       2131 :     pid_t child = fork();
     131                 :            : 
     132         [ -  + ]:       2131 :     if (child == 0) {
     133                 :            :         // Child process.
     134                 :          0 :         close(fds[0]);
     135                 :            : 
     136                 :          0 :         reason why = SUCCESS;
     137                 :            :         {
     138                 :            :             struct flock fl;
     139                 :          0 :             fl.l_type = F_WRLCK;
     140                 :          0 :             fl.l_whence = SEEK_SET;
     141                 :          0 :             fl.l_start = 0;
     142                 :          0 :             fl.l_len = 1;
     143         [ #  # ]:          0 :             while (fcntl(lockfd, F_SETLK, &fl) == -1) {
     144         [ #  # ]:          0 :                 if (errno != EINTR) {
     145                 :            :                     // Lock failed - translate known errno values into a reason
     146                 :            :                     // code.
     147 [ #  # ][ #  # ]:          0 :                     if (errno == EACCES || errno == EAGAIN) {
     148                 :          0 :                         why = INUSE;
     149         [ #  # ]:          0 :                     } else if (errno == ENOLCK) {
     150                 :          0 :                         why = UNSUPPORTED;
     151                 :            :                     } else {
     152                 :          0 :                         _exit(0);
     153                 :            :                     }
     154                 :          0 :                     break;
     155                 :            :                 }
     156                 :            :             }
     157                 :            :         }
     158                 :            : 
     159                 :            :         {
     160                 :            :             // Tell the parent if we got the lock, and if not, why not.
     161                 :          0 :             char ch = static_cast<char>(why);
     162         [ #  # ]:          0 :             while (write(fds[1], &ch, 1) < 0) {
     163                 :            :                 // EINTR means a signal interrupted us, so retry.
     164                 :            :                 // Otherwise we're DOOMED!  The best we can do is just exit
     165                 :            :                 // and the parent process should get EOF and know the lock
     166                 :            :                 // failed.
     167         [ #  # ]:          0 :                 if (errno != EINTR) _exit(1);
     168                 :            :             }
     169         [ #  # ]:          0 :             if (why != SUCCESS) _exit(0);
     170                 :            :         }
     171                 :            : 
     172                 :            :         // Connect pipe to stdin and stdout.
     173                 :          0 :         dup2(fds[1], 0);
     174                 :          0 :         dup2(fds[1], 1);
     175                 :            : 
     176                 :            :         // Make sure we don't block unmount() of partition holding the current
     177                 :            :         // directory.
     178                 :          0 :         if (chdir("/") < 0) {
     179                 :            :             // We can't usefully do anything in response to an error, so just
     180                 :            :             // ignore it - the worst harm it can do is make it impossible to
     181                 :            :             // unmount a partition.
     182                 :            :             //
     183                 :            :             // We need the if statement because glibc's _FORTIFY_SOURCE mode
     184                 :            :             // gives a warning even if we cast the result to void.
     185                 :            :         }
     186                 :            : 
     187                 :            :         // Make sure we don't hang on to open files which may get deleted but
     188                 :            :         // not have their disk space released until we exit.
     189         [ #  # ]:          0 :         for (int i = 2; i < lockfd; ++i) {
     190                 :            :             // Retry on EINTR; just ignore other errors (we'll get
     191                 :            :             // EBADF if the fd isn't open so that's OK).
     192 [ #  # ][ #  # ]:          0 :             while (close(i) < 0 && errno == EINTR) { }
                 [ #  # ]
     193                 :            :         }
     194                 :          0 :         closefrom(lockfd + 1);
     195                 :            : 
     196                 :            :         // FIXME: use special statically linked helper instead of cat.
     197                 :          0 :         execl("/bin/cat", "/bin/cat", static_cast<void*>(NULL));
     198                 :            :         // Emulate cat ourselves (we try to avoid this to reduce VM overhead).
     199                 :            :         char ch;
     200         [ #  # ]:          0 :         while (read(0, &ch, 1) != 0) { /* Do nothing */ }
     201                 :          0 :         _exit(0);
     202                 :            :     }
     203                 :            : 
     204                 :       2131 :     close(lockfd);
     205                 :       2131 :     close(fds[1]);
     206                 :            : 
     207         [ -  + ]:       2131 :     if (child == -1) {
     208                 :            :         // Couldn't fork.
     209                 :          0 :         explanation = string("Couldn't fork: ") + strerror(errno);
     210                 :          0 :         close(fds[0]);
     211                 :          0 :         return UNKNOWN;
     212                 :            :     }
     213                 :            : 
     214                 :       2131 :     reason why = UNKNOWN;
     215                 :            : 
     216                 :            :     // Parent process.
     217                 :          0 :     while (true) {
     218                 :            :         char ch;
     219                 :       2131 :         ssize_t n = read(fds[0], &ch, 1);
     220         [ +  - ]:       2131 :         if (n == 1) {
     221                 :       2131 :             why = static_cast<reason>(ch);
     222         [ +  + ]:       2131 :             if (why != SUCCESS) break;
     223                 :            :             // Got the lock.
     224                 :       2116 :             fd = fds[0];
     225                 :       2116 :             pid = child;
     226                 :       2116 :             return SUCCESS;
     227                 :            :         }
     228         [ #  # ]:          0 :         if (n == 0) {
     229                 :            :             // EOF means the lock failed.
     230                 :          0 :             explanation.assign("Got EOF reading from child process");
     231                 :          0 :             break;
     232                 :            :         }
     233         [ #  # ]:          0 :         if (errno != EINTR) {
     234                 :            :             // Treat unexpected errors from read() as failure to get the lock.
     235                 :          0 :             explanation = string("Error reading from child process: ") + strerror(errno);
     236                 :          0 :             break;
     237                 :            :         }
     238                 :            :     }
     239                 :            : 
     240                 :         15 :     close(fds[0]);
     241                 :            : 
     242                 :            :     int status;
     243         [ -  + ]:         15 :     while (waitpid(child, &status, 0) < 0) {
     244         [ #  # ]:          0 :         if (errno != EINTR) break;
     245                 :            :     }
     246                 :            : 
     247                 :       2133 :     return why;
     248                 :            : #endif
     249                 :            : }
     250                 :            : 
     251                 :            : void
     252                 :       6884 : FlintLock::release() {
     253                 :            : #if defined __CYGWIN__ || defined __WIN32__
     254                 :            :     if (hFile == INVALID_HANDLE_VALUE) return;
     255                 :            :     CloseHandle(hFile);
     256                 :            :     hFile = INVALID_HANDLE_VALUE;
     257                 :            : #elif defined __EMX__
     258                 :            :     if (hFile == NULLHANDLE) return;
     259                 :            :     DosClose(hFile);
     260                 :            :     hFile = NULLHANDLE;
     261                 :            : #else
     262         [ +  + ]:       6884 :     if (fd < 0) return;
     263                 :       2116 :     close(fd);
     264                 :       2116 :     fd = -1;
     265                 :            :     // The only likely error from kill is ESRCH.  The other possibilities
     266                 :            :     // (according to the Linux man page) are EINVAL (invalid signal) and EPERM
     267                 :            :     // (don't have permission to SIGHUP the process) but in none of the cases
     268                 :            :     // does calling waitpid do us any good!
     269         [ +  - ]:       2116 :     if (kill(pid, SIGHUP) == 0) {
     270                 :            :         int status;
     271         [ -  + ]:       6884 :         while (waitpid(pid, &status, 0) < 0) {
     272         [ #  # ]:          0 :             if (errno != EINTR) break;
     273                 :            :         }
     274                 :            :     }
     275                 :            : #endif
     276                 :            : }
     277                 :            : 
     278                 :            : void
     279                 :         15 : FlintLock::throw_databaselockerror(FlintLock::reason why,
     280                 :            :                                    const string & db_dir,
     281                 :            :                                    const string & explanation)
     282                 :            : {
     283                 :         15 :     string msg("Unable to get write lock on ");
     284                 :         15 :     msg += db_dir;
     285         [ +  - ]:         15 :     if (why == FlintLock::INUSE) {
     286                 :         15 :         msg += ": already locked";
     287         [ #  # ]:          0 :     } else if (why == FlintLock::UNSUPPORTED) {
     288                 :          0 :         msg += ": locking probably not supported by this FS";
     289         [ #  # ]:          0 :     } else if (why == FlintLock::FDLIMIT) {
     290                 :          0 :         msg += ": too many open files";
     291         [ #  # ]:          0 :     } else if (why == FlintLock::UNKNOWN) {
     292         [ #  # ]:          0 :         if (!explanation.empty())
     293                 :          0 :             msg += ": " + explanation;
     294                 :            :     }
     295                 :         30 :     throw Xapian::DatabaseLockError(msg);
     296                 :            : }

Generated by: LCOV version 1.8