diff options
Diffstat (limited to 'common/dotlock.c')
-rw-r--r-- | common/dotlock.c | 322 |
1 files changed, 301 insertions, 21 deletions
diff --git a/common/dotlock.c b/common/dotlock.c index 74186b776..fe6d7fe71 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -291,6 +291,7 @@ # include <sys/types.h> # include <sys/stat.h> # include <sys/utsname.h> +# include <dirent.h> #endif #include <sys/types.h> #include <sys/time.h> @@ -393,9 +394,18 @@ struct dotlock_handle unsigned int locked:1; /* Lock status. */ unsigned int disable:1; /* If true, locking is disabled. */ unsigned int use_o_excl:1; /* Use open (O_EXCL) for locking. */ + unsigned int by_parent:1; /* Parent does the locking. */ + unsigned int no_write:1; /* No write to the lockfile. */ int extra_fd; /* A place for the caller to store an FD. */ + /* An optional info callback - see dotlock_set_info_cb. */ + int (*info_cb)(dotlock_t, void *, + enum dotlock_reasons reason, + const char *,...); + void *info_cb_value; + + #ifdef HAVE_DOSISH_SYSTEM HANDLE lockhd; /* The W32 handle of the lock file. */ #else /*!HAVE_DOSISH_SYSTEM */ @@ -545,8 +555,15 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) if ( (fd = open (h->lockname, O_RDONLY)) == -1 ) { int e = errno; - my_info_2 ("error opening lockfile '%s': %s\n", - h->lockname, strerror(errno) ); + if (errno != ENOENT) + { + my_info_2 ("error opening lockfile '%s': %s\n", + h->lockname, strerror(errno) ); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "error opening lockfile '%s': %s\n", + h->lockname, strerror (errno) ); + } if (buffer != buffer_space) xfree (buffer); my_set_errno (e); /* Need to return ERRNO here. */ @@ -564,6 +581,10 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) { int e = errno; my_info_1 ("error reading lockfile '%s'\n", h->lockname ); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "error reading lockfile '%s': %s\n", + h->lockname, strerror (errno) ); close (fd); if (buffer != buffer_space) xfree (buffer); @@ -583,6 +604,9 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) if (nread < 11) { my_info_1 ("invalid size of lockfile '%s'\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE, + "invalid size of lockfile '%s'\n", h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); @@ -594,6 +618,9 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) || !pid ) { my_error_2 ("invalid pid %d in lockfile '%s'\n", pid, h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE, + "invalid pid %d in lockfile '%s'\n", pid, h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); @@ -655,6 +682,80 @@ use_hardlinks_p (const char *tname) #ifdef HAVE_POSIX_SYSTEM +static int +dotlock_get_process_id (dotlock_t h) +{ + return h->by_parent? (int)getppid(): (int)getpid(); +} + +static int +dotlock_detect_tname (dotlock_t h) +{ + struct stat sb; + DIR *dir; + char *dirname; + char *basename; + struct dirent *d; + int r; + + if (stat (h->lockname, &sb)) + return -1; + + basename = make_basename (h->lockname, NULL); + dirname = make_dirname (h->lockname); + + dir = opendir (dirname); + if (dir == NULL) + { + xfree (basename); + xfree (dirname); + return -1; + } + + while ((d = readdir (dir))) + if (sb.st_ino == d->d_ino && strcmp (d->d_name, basename)) + break; + + if (d) + { + int len = strlen (h->tname); + int dlen = strlen (d->d_name); + const char *tname_path; + + if (dlen > len) + { + xfree (basename); + xfree (dirname); + return -1; + } + + strcpy (stpcpy (stpcpy (h->tname, dirname), DIRSEP_S), d->d_name); + h->use_o_excl = 0; + tname_path = strchr (h->tname + strlen (dirname) + 2, '.'); + if (!tname_path) + { + xfree (basename); + xfree (dirname); + return -1; + } + h->nodename_off = tname_path - h->tname + 1; + } + else + h->use_o_excl = 1; + + r = closedir (dir); + if (r) + { + xfree (basename); + xfree (dirname); + return r; + } + + xfree (basename); + xfree (dirname); + return 0; +} + /* Locking core for Unix. It used a temporary file and the link system call to make locking an atomic operation. */ static dotlock_t @@ -667,8 +768,10 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) int dirpartlen; struct utsname utsbuf; size_t tnamelen; + int pid; - snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() ); + pid = dotlock_get_process_id (h); + snprintf (pidstr, sizeof pidstr, "%10d\n", pid); /* Create a temporary file. */ if ( uname ( &utsbuf ) ) @@ -702,10 +805,17 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) } h->nodename_len = strlen (nodename); + if (h->no_write) + { + memset (h->tname, '_', tnamelen); + h->tname[tnamelen] = 0; + goto skip_write; + } + snprintf (h->tname, tnamelen, "%.*s/.#lk%p.", dirpartlen, dirpart, h ); h->nodename_off = strlen (h->tname); snprintf (h->tname+h->nodename_off, tnamelen - h->nodename_off, - "%s.%d", nodename, (int)getpid ()); + "%s.%d", nodename, pid); do { @@ -722,6 +832,10 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) UNLOCK_all_lockfiles (); my_error_2 (_("failed to create temporary file '%s': %s\n"), h->tname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("failed to create temporary file '%s': %s\n"), + h->tname, strerror (errno)); xfree (h->tname); xfree (h); my_set_errno (saveerrno); @@ -755,11 +869,16 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) int saveerrno = errno; my_error_2 ("can't check whether hardlinks are supported for '%s': %s\n" , h->tname, strerror (saveerrno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_CONFIG_TEST, + "can't check whether hardlinks are supported for '%s': %s\n" + , h->tname, strerror (saveerrno)); my_set_errno (saveerrno); } goto write_failed; } + skip_write: h->lockname = xtrymalloc (strlen (file_to_lock) + 6 ); if (!h->lockname) { @@ -775,6 +894,20 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock"); UNLOCK_all_lockfiles (); + if (h->no_write) + { + if (dotlock_detect_tname (h) < 0) + { + xfree (h->lockname); + xfree (h->tname); + xfree (h); + my_set_errno (EACCES); + return NULL; + } + + h->locked = 1; + } + return h; write_failed: @@ -783,6 +916,11 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("error writing to '%s': %s\n"), h->tname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("error writing to '%s': %s\n"), + h->tname, strerror (errno)); + if ( fd != -1 ) close (fd); unlink (h->tname); @@ -849,6 +987,10 @@ dotlock_create_w32 (dotlock_t h, const char *file_to_lock) all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("can't create '%s': %s\n"), h->lockname, w32_strerror (-1)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("can't create '%s': %s\n"), + h->lockname, w32_strerror (-1)); xfree (h->lockname); xfree (h); my_set_errno (saveerrno); @@ -873,7 +1015,15 @@ dotlock_create_w32 (dotlock_t h, const char *file_to_lock) POSIX systems a temporary file ".#lk.<hostname>.pid[.threadid] is used. - FLAGS must be 0. + FLAGS may include DOTLOCK_PREPARE_CREATE bit, which only allocates + the handle and requires a further call to dotlock_finish_create. + This can be used to set a callback between these calls. + + FLAGS may include DOTLOCK_LOCK_BY_PARENT bit, when it's the parent + process controlling the lock. This is used by dotlock util. + + FLAGS may include DOTLOCK_LOCKED bit, when it should not create the + lockfile, but to unlock. This is used by dotlock util. The function returns an new handle which needs to be released using destroy_dotlock but gets also released at the termination of the @@ -885,8 +1035,13 @@ dotlock_create (const char *file_to_lock, unsigned int flags) { static int initialized; dotlock_t h; +#ifndef HAVE_DOSISH_SYSTEM + int by_parent = 0; + int no_write = 0; +#endif - if ( !initialized ) + if ( !(flags & DOTLOCK_LOCK_BY_PARENT) + && !initialized ) { atexit (dotlock_remove_lockfiles); initialized = 1; @@ -895,7 +1050,15 @@ dotlock_create (const char *file_to_lock, unsigned int flags) if ( !file_to_lock ) return NULL; /* Only initialization was requested. */ - if (flags) +#ifndef HAVE_DOSISH_SYSTEM + if ((flags & DOTLOCK_LOCK_BY_PARENT) || (flags & DOTLOCK_LOCKED)) + { + by_parent = !!(flags & DOTLOCK_LOCK_BY_PARENT); + no_write = !!(flags & DOTLOCK_LOCKED); + flags &= ~(DOTLOCK_LOCK_BY_PARENT | DOTLOCK_LOCKED); + } +#endif + if ((flags & ~DOTLOCK_PREPARE_CREATE)) { my_set_errno (EINVAL); return NULL; @@ -905,6 +1068,10 @@ dotlock_create (const char *file_to_lock, unsigned int flags) if (!h) return NULL; h->extra_fd = -1; +#ifndef HAVE_DOSISH_SYSTEM + h->by_parent = by_parent; + h->no_write = no_write; +#endif if (never_lock) { @@ -916,6 +1083,24 @@ dotlock_create (const char *file_to_lock, unsigned int flags) return h; } + if ((flags & DOTLOCK_PREPARE_CREATE)) + return h; + else + return dotlock_finish_create (h, file_to_lock); +} + + +/* This function may be used along with dotlock_create (file_name, + * DOTLOCK_PREPARE_CREATE) to finish the creation call. The given + * filename shall be the same as passed to dotlock_create. On success + * the same handle H is returned, on error NULL is returned and H is + * released. */ +dotlock_t +dotlock_finish_create (dotlock_t h, const char *file_to_lock) +{ + if (!h || !file_to_lock) + return NULL; + #ifdef HAVE_DOSISH_SYSTEM return dotlock_create_w32 (h, file_to_lock); #else /*!HAVE_DOSISH_SYSTEM */ @@ -942,6 +1127,24 @@ dotlock_get_fd (dotlock_t h) } +/* Set a callback function for info diagnostics. The callback + * function CB is called with the handle, the opaque value OPAQUE, a + * reason code, and a format string with its arguments. The callback + * shall return 0 to continue operation or true in which case the + * current function will be terminated with an error. */ +void +dotlock_set_info_cb (dotlock_t h, + int (*cb)(dotlock_t, void *, + enum dotlock_reasons reason, + const char *,...), + void *opaque) +{ + h->info_cb = cb; + h->info_cb_value = opaque; +} + + + #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of destroy_dotlock. */ @@ -952,7 +1155,6 @@ dotlock_destroy_unix (dotlock_t h) unlink (h->lockname); if (h->tname && !h->use_o_excl) unlink (h->tname); - xfree (h->tname); } #endif /*HAVE_POSIX_SYSTEM*/ @@ -998,15 +1200,28 @@ dotlock_destroy (dotlock_t h) UNLOCK_all_lockfiles (); /* Then destroy the lock. */ - if (!h->disable) + if (!h->disable + && (!h->by_parent || h->no_write)) { + /* NOTE: under the condition of (by_parent && !no_write), + it doesn't come here. So, the lock file remains. */ #ifdef HAVE_DOSISH_SYSTEM dotlock_destroy_w32 (h); #else /* !HAVE_DOSISH_SYSTEM */ dotlock_destroy_unix (h); #endif /* HAVE_DOSISH_SYSTEM */ - xfree (h->lockname); } + +#ifdef HAVE_POSIX_SYSTEM + /* When DOTLOCK_LOCK_BY_PARENT and lock fails, + the temporary file created should be removed. */ + if (h->by_parent && !h->no_write && !h->locked) + if (h->tname && !h->use_o_excl) + unlink (h->tname); + + xfree (h->tname); +#endif + xfree (h->lockname); xfree(h); } @@ -1061,6 +1276,7 @@ static int dotlock_take_unix (dotlock_t h, long timeout) { int wtime = 0; + int timedout = 0; int sumtime = 0; int pid; int lastpid = -1; @@ -1089,6 +1305,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_2 ("lock not made: open(O_EXCL) of '%s' failed: %s\n", h->lockname, strerror (saveerrno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "lock not made: open(O_EXCL) of '%s' failed: %s\n", + h->lockname, strerror (saveerrno)); my_set_errno (saveerrno); return -1; } @@ -1096,7 +1316,8 @@ dotlock_take_unix (dotlock_t h, long timeout) { char pidstr[16]; - snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid()); + snprintf (pidstr, sizeof pidstr, "%10d\n", + dotlock_get_process_id (h)); if (write (fd, pidstr, 11 ) == 11 && write (fd, h->tname + h->nodename_off,h->nodename_len) == h->nodename_len @@ -1110,6 +1331,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_2 ("lock not made: writing to '%s' failed: %s\n", h->lockname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "lock not made: writing to '%s' failed: %s\n", + h->lockname, strerror (errno)); close (fd); unlink (h->lockname); my_set_errno (saveerrno); @@ -1128,6 +1353,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_1 ("lock not made: Oops: stat of tmp file failed: %s\n", strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "lock not made: Oops: stat of tmp file failed: %s\n", + strerror (errno)); /* In theory this might be a severe error: It is possible that link succeeded but stat failed due to changed permissions. We can't do anything about it, though. */ @@ -1149,16 +1378,19 @@ dotlock_take_unix (dotlock_t h, long timeout) { saveerrno = errno; my_info_0 ("cannot read lockfile\n"); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "cannot read lockfile\n"); my_set_errno (saveerrno); return -1; } my_info_0 ("lockfile disappeared\n"); goto again; } - else if ( (pid == getpid() && same_node) + else if ( (pid == dotlock_get_process_id (h) && same_node && !h->by_parent) || (same_node && kill (pid, 0) && errno == ESRCH) ) - /* Stale lockfile is detected. */ { + /* Stale lockfile is detected. */ struct stat sb; /* Check if it's unlocked during examining the lockfile. */ @@ -1201,6 +1433,9 @@ dotlock_take_unix (dotlock_t h, long timeout) unlink (h->lockname); my_info_1 (_("removing stale lockfile (created by %d)\n"), pid); close (fd); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_STALE_REMOVED, + _("removing stale lockfile (created by %d)\n"), pid); goto again; } @@ -1218,6 +1453,8 @@ dotlock_take_unix (dotlock_t h, long timeout) wtime = 0; /* Reset because owner chnaged. */ wtimereal = next_wait_interval (&wtime, &timeout); + if (!timeout) + timedout = 1; /* remember. */ sumtime += wtimereal; if (sumtime >= 1500) @@ -1225,6 +1462,15 @@ dotlock_take_unix (dotlock_t h, long timeout) sumtime = 0; my_info_3 (_("waiting for lock (held by %d%s) %s...\n"), pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):""); + if (h->info_cb + && h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("waiting for lock (held by %d%s) %s...\n"), + pid, maybe_dead, + maybe_deadlock(h)? _("(deadlock?) "):"")) + { + my_set_errno (ECANCELED); + return -1; + } } tv.tv_sec = wtimereal / 1000; @@ -1233,7 +1479,7 @@ dotlock_take_unix (dotlock_t h, long timeout) goto again; } - my_set_errno (EACCES); + my_set_errno (timedout? ETIMEDOUT : EACCES); return -1; } #endif /*HAVE_POSIX_SYSTEM*/ @@ -1246,6 +1492,7 @@ static int dotlock_take_w32 (dotlock_t h, long timeout) { int wtime = 0; + int timedout = 0; int w32err; OVERLAPPED ovl; @@ -1264,7 +1511,11 @@ dotlock_take_w32 (dotlock_t h, long timeout) { my_error_2 (_("lock '%s' not made: %s\n"), h->lockname, w32_strerror (w32err)); - my_set_errno (map_w32_to_errno (w32err)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("lock '%s' not made: %s\n"), + h->lockname, w32_strerror (w32err)); + _set_errno (map_w32_to_errno (w32err)); return -1; } @@ -1273,15 +1524,26 @@ dotlock_take_w32 (dotlock_t h, long timeout) int wtimereal; wtimereal = next_wait_interval (&wtime, &timeout); + if (!timeout) + timedout = 1; /* remember. */ if (wtime >= 800) - my_info_1 (_("waiting for lock %s...\n"), h->lockname); + { + my_info_1 (_("waiting for lock %s...\n"), h->lockname); + if (h->info_cb + && h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("waiting for lock %s...\n"), h->lockname)) + { + my_set_errno (ECANCELED); + return -1; + } + } Sleep (wtimereal); goto again; } - my_set_errno (EACCES); + my_set_errno (timedout? ETIMEDOUT : EACCES); return -1; } #endif /*HAVE_DOSISH_SYSTEM*/ @@ -1328,12 +1590,18 @@ dotlock_release_unix (dotlock_t h) { saveerrno = errno; my_error_0 ("release_dotlock: lockfile error\n"); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: lockfile error\n"); my_set_errno (saveerrno); return -1; } - if ( pid != getpid() || !same_node ) + if ( pid != dotlock_get_process_id (h) || !same_node ) { my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_CONFLICT, + "release_dotlock: not our lock (pid=%d)\n", pid); my_set_errno (EACCES); return -1; } @@ -1343,6 +1611,10 @@ dotlock_release_unix (dotlock_t h) saveerrno = errno; my_error_1 ("release_dotlock: error removing lockfile '%s'\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: error removing lockfile '%s'\n", + h->lockname); my_set_errno (saveerrno); return -1; } @@ -1363,10 +1635,15 @@ dotlock_release_w32 (dotlock_t h) memset (&ovl, 0, sizeof ovl); if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl)) { - int saveerrno = map_w32_to_errno (GetLastError ()); + int ec = (int)GetLastError (); + my_error_2 ("release_dotlock: error removing lockfile '%s': %s\n", - h->lockname, w32_strerror (-1)); - my_set_errno (saveerrno); + h->lockname, w32_strerror (ec)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: error removing lockfile '%s': %s\n", + h->lockname, w32_strerror (ec)); + my_set_errno (map_w32_to_errno (ec)); return -1; } @@ -1397,6 +1674,9 @@ dotlock_release (dotlock_t h) if ( !h->locked ) { my_debug_1 ("Oops, '%s' is not locked\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_NOT_LOCKED, + "Oops, '%s' is not locked\n", h->lockname); return 0; } |