aboutsummaryrefslogtreecommitdiffstats
path: root/common/dotlock.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/dotlock.c')
-rw-r--r--common/dotlock.c322
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;
}