Improved the dotlock module.
authorWerner Koch <wk@gnupg.org>
Tue, 27 Sep 2011 15:18:56 +0000 (17:18 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 27 Sep 2011 15:18:56 +0000 (17:18 +0200)
- It is now more portable and may be used outside of GnuPG
- vfat file systems are now supported.
- The use of link(2) is more robust.
- Wrote extensive documentation.

common/ChangeLog
common/dotlock.c
common/dotlock.h
common/t-dotlock.c [new file with mode: 0644]

index 7d80366..e0fc2dd 100644 (file)
@@ -1,3 +1,11 @@
+2011-09-27  Werner Koch  <wk@g10code.com>
+
+       * dotlock.c (dotlock_take_unix): Check only the link count and not
+       the error return from link.
+       (use_hardlinks_p): New.
+       (dotlock_create_unix): Test for hardlinks.
+       (dotlock_take_unix): Implement O_EXCL locking.
+
 2011-09-23  Werner Koch  <wk@g10code.com>
 
        * dotlock.c: Factor Unix and W32 specific code out into specific
index 7d0ac1f..97a3f74 100644 (file)
  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+/*
+   Overview:
+   =========
+
+   This module implements advisory file locking in a portable way.
+   Due to the problems with POSIX fcntl locking a separate lock file
+   is used.  It would be possible to use fcntl locking on this lock
+   file and thus avoid the weird auto unlock bug of POSIX while still
+   having an unproved better performance of fcntl locking.  However
+   there are still problems left, thus we resort to use a hardlink
+   which has the well defined property that a link call will fail if
+   the target file already exists.
+
+   Given that hardlinks are also available on NTFS file systems since
+   Windows XP; it will be possible to enhance this module to use
+   hardlinks even on Windows and thus allow Windows and Posix clients
+   to use locking on the same directory.  This is not yet implemented;
+   instead we use a lockfile on Windows along with W32 style file
+   locking.
+
+   On FAT file systems hardlinks are not supported.  Thus this method
+   does not work.  Our solution is to use a O_EXCL locking instead.
+   Querying the type of the file system is not easy to do in a
+   portable way (e.g. Linux has a statfs, BSDs have a the same call
+   but using different structures and constants).  What we do instead
+   is to check at runtime whether link(2) works for a specific lock
+   file.
+
+
+   How to use:
+   ===========
+
+   At program initialization time, the module should be explicitly
+   initialized:
+
+      dotlock_create (NULL);
+
+   This installs an atexit handler and may also initialize mutex etc.
+   It is optional for non-threaded applications.  Only the first call
+   has an effect.
+
+   To create a lock file (which  prepares it but does not take the
+   lock) you do:
+
+     dotlock_t h
+
+     h = dotlock_create (fname);
+     if (!h)
+       error ("error creating lock file: %s\n", strerror (errno));
+
+   It is important to handle the error.  For example on a read-only
+   file system a lock can't be created (but is usually not needed).
+   FNAME is the file you want to lock; the actual lockfile is that
+   name with the suffix ".lock" appended.  This call creates a unique
+   file temporary file (".#lk*") in the same directory as FNAME and
+   returns a handle for further operations.  The module keeps track of
+   theses unique files so that they will be unlinked using the atexit
+   handler.  If you don't need the lock file anymore, you may also
+   explicitly remove it with a call to:
+
+     dotlock_destroy (h);
+
+   To actually lock the file, you use:
+
+     if (dotlock_take (h, -1))
+       error ("error taking lock: %s\n", strerror (errno));
+
+   This function will wait until the lock is acquired.  If an
+   unexpected error occurs if will return non-zero and set ERRNO.  If
+   you pass (0) instead of (-1) the function does not wait if the file
+   is already locked but returns -1 and sets ERRNO to EACCES.
+
+   To release the lock you call:
+
+     if (dotlock_release (h))
+       error ("error releasing lock: %s\n", strerror (errno));
+
+   or, if the lock file is not anymore needed, you may call
+   dotlock_destroy.
+
+   If you want to explicitly destroy all lock files you may call
+
+     dotlock_remove_lockfiles ();
+
+   which is the core of the installed atexit handler.  In case your
+   application wants to disable locking completely it may call
+
+     disable_locking ()
+
+   before any locks are created.
+
+
+   How to build:
+   =============
+
+   This module was originally developed for GnuPG but later changed to
+   allow its use without any GnuPG dependency.  If you want to use it
+   with you application you may simply use it and it should figure out
+   most things automagically.
+
+   You may use the common config.h file to pass macros, but take care
+   to pass -DHAVE_CONFIG_H to the compiler.  Macros used by this
+   module are:
+
+     DOTLOCK_GLIB_LOGGING - Define this to use Glib logging functions.
+
+     GNUPG_MAJOR_VERSION - Defined when used by GnuPG.
+
+     HAVE_DOSISH_SYSTEM   - Defined for Windows etc.  Will be
+                            automatically defined if a the target is
+                            Windows.
+     HAVE_POSIX_SYSTEM    - Internally defined to !HAVE_DOSISH_SYSTEM.
+
+     HAVE_SIGNAL_H        - Should be defined on Posix systems.  If config.h
+                            is not used defaults to defined.
+
+     DIRSEP_C            - Separation character for file name parts.
+                           Usually not redefined.
+     EXTSEP_S "."        - Separation string for file name suffixes.
+                           Usually not redefined.
+
+     HAVE_W32CE_SYSTEM   - Currently only used by GnuPG.
+
+   Note that there is a test program t-dotlock which has compile
+   instructions at its end.  At least for SMBFS and CIFS it is
+   important that 64 bit versions of stat are used; most programming
+   environments do this these days, just in case you want to compile
+   it on the command line, remember to pass -D_FILE_OFFSET_BITS=64
+
+
+   Miscellaneous notes:
+   ====================
+
+   On hardlinks:
+   - Hardlinks are supported under Windows with NTFS since XP/Server2003.
+   - In Linux 2.6.33 both SMBFS and CIFS seem to support hardlinks.
+   - NFS supports hard links.  But there are solvable problems.
+   - FAT does not support links
+
+   On the file locking API:
+   - CIFS on Linux 2.6.33 supports several locking methods.
+     SMBFS seems not to support locking.  No closer checks done.
+   - NFS supports Posix locks.  flock is emulated in the server.
+     However there are a couple of problems; see below.
+   - FAT does not support locks.
+   - An advantage of fcntl locking is that R/W locks can be
+     implemented which is not easy with a straight lock file.
+
+   On O_EXCL:
+   - Does not work reliable on NFS
+   - Should work on CIFS and SMBFS but how can we delete lockfiles?
+
+   On NFS problems:
+   - Locks vanish if the server crashes and reboots.
+   - Client crashes keep the lock in the server until the client
+     re-connects.
+   - Communication problems may return unreliable error codes.  The
+     MUA Postfix's workaround is to compare the link count after
+     seeing an error for link.  However that gives a race.  If using a
+     unique file to link to a lockfile and using stat to check the
+     link count instead of looking at the error return of link(2) is
+     the best solution.
+   - O_EXCL seems to have a race and may re-create a file anyway.
+
+*/
+
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 # define HAVE_POSIX_SYSTEM 1
 #endif
 
+/* With no config.h assume that we have sitgnal.h.  */
+#if !defined (HAVE_CONFIG_H) && defined (HAVE_POSIX_SYSTEM)
+# define HAVE_SIGNAL_H 1
+#endif
 
 /* Standard headers.  */
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <errno.h>
 #include <ctype.h>
 # define WIN32_LEAN_AND_MEAN  /* We only need the OS core stuff.  */
 # include <windows.h>
 #else
+# include <sys/types.h>
+# include <sys/stat.h>
 # include <sys/utsname.h>
 #endif
 #include <sys/types.h>
 # include <signal.h>
 #endif
 
+#ifdef DOTLOCK_GLIB_LOGGING
+# include <glib.h>
+#endif
 
-#include "libjnlib-config.h"
-#include "stringhelp.h"
-#include "dotlock.h"
+#ifdef GNUPG_MAJOR_VERSION
+# include "libjnlib-config.h"
+#endif
 #ifdef HAVE_W32CE_SYSTEM
 # include "utf8conv.h"  /* WindowsCE requires filename conversion.  */
 #endif
 
+#include "dotlock.h"
+
 
 /* Define constants for file name construction.  */
 #if !defined(DIRSEP_C) && !defined(EXTSEP_S)
 # endif
 #endif
 
+/* Gettext macro replacement.  */
+#ifndef _
+# define _(a) (a)
+#endif
+
+#ifdef GNUPG_MAJOR_VERSION
+# define my_info_0(a)       log_info ((a))
+# define my_info_1(a,b)     log_info ((a), (b))
+# define my_info_2(a,b,c)   log_info ((a), (b), (c))
+# define my_info_3(a,b,c,d) log_info ((a), (b), (c), (d))
+# define my_error_0(a)      log_error ((a))
+# define my_error_1(a,b)    log_error ((a), (b))
+# define my_error_2(a,b,c)  log_error ((a), (b), (c))
+# define my_debug_1(a,b)    log_debug ((a), (b))
+#elif defined (DOTLOCK_GLIB_LOGGING)
+# define my_info_0(a)       g_message ((a))
+# define my_info_1(a,b)     g_message ((a), (b))
+# define my_info_2(a,b,c)   g_message ((a), (b), (c))
+# define my_info_3(a,b,c,d) g_message ((a), (b), (c), (d))
+# define my_error_0(a)      g_warning ((a))
+# define my_error_1(a,b)    g_warning ((a), (b))
+# define my_error_2(a,b,c   g_warning ((a), (b), (c))
+# define my_debug_1(a,b)    g_debug ((a), (b))
+#else
+# define my_info_0(a)       fprintf (stderr, (a))
+# define my_info_1(a,b)     fprintf (stderr, (a), (b))
+# define my_info_2(a,b,c)   fprintf (stderr, (a), (b), (c))
+# define my_info_3(a,b,c,d) fprintf (stderr, (a), (b), (c), (d))
+# define my_error_0(a)      fprintf (stderr, (a))
+# define my_error_1(a,b)    fprintf (stderr, (a), (b))
+# define my_error_2(a,b,c)  fprintf (stderr, (a), (b), (c))
+# define my_debug_1(a,b)    fprintf (stderr, (a), (b))
+#endif
+
+
+
 
 \f
 /* The object describing a lock.  */
 struct dotlock_handle
 {
   struct dotlock_handle *next;
-  char *lockname;      /* Name of the actual lockfile.          */
-  int locked;          /* Lock status.                          */
-  int disable;         /* If true, locking is disabled.         */
+  char *lockname;            /* Name of the actual lockfile.          */
+  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.        */
 
 #ifdef HAVE_DOSISH_SYSTEM
   HANDLE lockhd;       /* The W32 handle of the lock file.      */
@@ -175,8 +390,8 @@ read_lockfile (dotlock_t h, int *same_node )
   if ( (fd = open (h->lockname, O_RDONLY)) == -1 )
     {
       int e = errno;
-      log_info ("error opening lockfile `%s': %s\n",
-                h->lockname, strerror(errno) );
+      my_info_2 ("error opening lockfile `%s': %s\n",
+                 h->lockname, strerror(errno) );
       if (buffer != buffer_space)
         jnlib_free (buffer);
       jnlib_set_errno (e); /* Need to return ERRNO here. */
@@ -192,7 +407,7 @@ read_lockfile (dotlock_t h, int *same_node )
         continue;
       if (res < 0)
         {
-          log_info ("error reading lockfile `%s'", h->lockname );
+          my_info_1 ("error reading lockfile `%s'\n", h->lockname );
           close (fd);
           if (buffer != buffer_space)
             jnlib_free (buffer);
@@ -207,7 +422,7 @@ read_lockfile (dotlock_t h, int *same_node )
 
   if (nread < 11)
     {
-      log_info ("invalid size of lockfile `%s'", h->lockname );
+      my_info_1 ("invalid size of lockfile `%s'\n", h->lockname);
       if (buffer != buffer_space)
         jnlib_free (buffer);
       jnlib_set_errno (0); /* Better don't return an inappropriate ERRNO. */
@@ -218,7 +433,7 @@ read_lockfile (dotlock_t h, int *same_node )
       || (buffer[10] = 0, pid = atoi (buffer)) == -1
       || !pid )
     {
-      log_error ("invalid pid %d in lockfile `%s'", pid, h->lockname );
+      my_error_2 ("invalid pid %d in lockfile `%s'\n", pid, h->lockname);
       if (buffer != buffer_space)
         jnlib_free (buffer);
       jnlib_set_errno (0);
@@ -237,6 +452,46 @@ read_lockfile (dotlock_t h, int *same_node )
 #endif /*HAVE_POSIX_SYSTEM */
 
 
+/* Check whether the file system which stores TNAME supports
+   hardlinks.  Instead of using the non-portable statsfs call which
+   differs between various Unix versions, we do a runtime test.
+   Returns: 0 supports hardlinks; 1 no hardlink support, -1 unknown
+   (test error).  */
+#ifdef HAVE_POSIX_SYSTEM
+static int
+use_hardlinks_p (const char *tname)
+{
+  char *lname;
+  struct stat sb;
+  unsigned int nlink;
+  int res;
+
+  if (stat (tname, &sb))
+    return -1;
+  nlink = (unsigned int)sb.st_nlink;
+
+  lname = jnlib_malloc (strlen (tname) + 1 + 1);
+  if (!lname)
+    return -1;
+  strcpy (lname, tname);
+  strcat (lname, "x");
+
+  link (tname, lname);
+
+  if (stat (tname, &sb))
+    res = -1;  /* Ooops.  */
+  else if (sb.st_nlink == nlink + 1)
+    res = 0;   /* Yeah, hardlinks are supported.  */
+  else
+    res = 1;   /* No hardlink support.  */
+
+  unlink (lname);
+  jnlib_free (lname);
+  return res;
+}
+#endif /*HAVE_POSIX_SYSTEM */
+
+
 \f
 #ifdef  HAVE_POSIX_SYSTEM
 /* Locking core for Unix.  It used a temporary file and the link
@@ -277,7 +532,7 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock)
   h->next = all_lockfiles;
   all_lockfiles = h;
 
-  tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10;
+  tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10 + 1;
   h->tname = jnlib_malloc (tnamelen + 1);
   if (!h->tname)
     {
@@ -303,7 +558,7 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock)
   if ( fd == -1 )
     {
       all_lockfiles = h->next;
-      log_error (_("failed to create temporary file `%s': %s\n"),
+      my_error_2 (_("failed to create temporary file `%s': %s\n"),
                   h->tname, strerror(errno));
       jnlib_free (h->tname);
       jnlib_free (h);
@@ -318,10 +573,25 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock)
   if ( close (fd) )
     goto write_failed;
 
+  /* Check whether we support hard links.  */
+  switch (use_hardlinks_p (h->tname))
+    {
+    case 0: /* Yes.  */
+      break;
+    case 1: /* No.  */
+      unlink (h->tname);
+      h->use_o_excl = 1;
+      break;
+    default:
+      my_error_2 ("can't check whether hardlinks are supported for `%s': %s\n",
+                  h->tname, strerror(errno));
+      goto write_failed;
+    }
+
 # ifdef _REENTRANT
   /* release mutex */
 # endif
-  h->lockname = jnlib_malloc ( strlen (file_to_lock) + 6 );
+  h->lockname = jnlib_malloc (strlen (file_to_lock) + 6 );
   if (!h->lockname)
     {
       all_lockfiles = h->next;
@@ -331,6 +601,9 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock)
       return NULL;
     }
   strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock");
+  if (h->use_o_excl)
+    my_debug_1 ("locking for `%s' done via O_EXCL\n", h->lockname);
+
   return h;
 
  write_failed:
@@ -338,7 +611,7 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock)
 # ifdef _REENTRANT
   /* fixme: release mutex */
 # endif
-  log_error ( _("error writing to `%s': %s\n"), h->tname, strerror(errno) );
+  my_error_2 (_("error writing to `%s': %s\n"), h->tname, strerror (errno));
   close (fd);
   unlink (h->tname);
   jnlib_free (h->tname);
@@ -396,7 +669,7 @@ dotlock_create_w32 (dotlock_t h, const char *file_to_lock)
   }
   if (h->lockhd == INVALID_HANDLE_VALUE)
     {
-      log_error (_("can't create `%s': %s\n"), h->lockname, w32_strerror (-1));
+      my_error_2 (_("can't create `%s': %s\n"), h->lockname, w32_strerror (-1));
       all_lockfiles = h->next;
       jnlib_free (h->lockname);
       jnlib_free (h);
@@ -471,7 +744,7 @@ dotlock_destroy_unix (dotlock_t h)
 {
   if (h->locked && h->lockname)
     unlink (h->lockname);
-  if (h->tname)
+  if (h->tname && !h->use_o_excl)
     unlink (h->tname);
   jnlib_free (h->tname);
 }
@@ -541,37 +814,95 @@ dotlock_take_unix (dotlock_t h, long timeout, int *backoff)
   const char *maybe_dead="";
   int same_node;
 
-  if ( !link(h->tname, h->lockname) )
+  if (h->use_o_excl)
     {
-      /* fixme: better use stat to check the link count */
-      h->locked = 1;
-      return 0; /* okay */
+      /* No hardlink support - use open(O_EXCL).  */
+      int fd;
+
+      do
+        {
+          jnlib_set_errno (0);
+          fd = open (h->lockname, O_WRONLY|O_CREAT|O_EXCL,
+                     S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
+        }
+      while (fd == -1 && errno == EINTR);
+
+      if (fd == -1 && errno == EEXIST)
+        ; /* Lock held by another process.  */
+      else if (fd == -1)
+        {
+          my_error_2 ("lock not made: open(O_EXCL) of `%s' failed: %s\n",
+                      h->lockname, strerror (errno));
+          return -1;
+        }
+      else
+        {
+          char pidstr[16];
+
+          snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid());
+          if (write (fd, pidstr, 11 ) == 11
+              && write (fd, h->tname + h->nodename_off,h->nodename_len)
+              == h->nodename_len
+              && write (fd, "\n", 1) == 1
+              && !close (fd))
+            {
+              h->locked = 1;
+              return 0;
+            }
+          /* Write error.  */
+          my_error_2 ("lock not made: writing to `%s' failed: %s\n",
+                      h->lockname, strerror (errno));
+          close (fd);
+          unlink (h->lockname);
+          return -1;
+        }
     }
-  if ( errno != EEXIST )
+  else /* Standard method:  Use hardlinks.  */
     {
-      log_error ( "lock not made: link() failed: %s\n", strerror(errno) );
-      return -1;
+      struct stat sb;
+
+      link (h->tname, h->lockname);
+
+      if (stat (h->tname, &sb))
+        {
+          my_error_1 ("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.  */
+          return -1;
+        }
+
+      if (sb.st_nlink == 2)
+        {
+          h->locked = 1;
+          return 0; /* Okay.  */
+        }
     }
 
+  /* Check for stale lock files.  */
   if ( (pid = read_lockfile (h, &same_node)) == -1 )
     {
       if ( errno != ENOENT )
         {
-          log_info ("cannot read lockfile\n");
+          my_info_0 ("cannot read lockfile\n");
           return -1;
         }
-      log_info( "lockfile disappeared\n");
+      my_info_0 ("lockfile disappeared\n");
       return 1; /* Try again.  */
     }
   else if ( pid == getpid() && same_node )
     {
-      log_info( "Oops: lock already held by us\n");
+      my_info_0 ("Oops: lock already held by us\n");
       h->locked = 1;
       return 0; /* okay */
     }
   else if ( same_node && kill (pid, 0) && errno == ESRCH )
     {
-      log_info (_("removing stale lockfile (created by %d)\n"), pid );
+      /* Note: It is unlikley that we get a race here unless a pid is
+         reused too fast or a new process with the same pid as the one
+         of the stale file tries to lock right at the same time as we.  */
+      my_info_1 (_("removing stale lockfile (created by %d)\n"), pid);
       unlink (h->lockname);
       return 1; /* Try again.  */
     }
@@ -581,8 +912,8 @@ dotlock_take_unix (dotlock_t h, long timeout, int *backoff)
       /* Wait until lock has been released. */
       struct timeval tv;
 
-      log_info (_("waiting for lock (held by %d%s) %s...\n"),
-                pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):"");
+      my_info_3 (_("waiting for lock (held by %d%s) %s...\n"),
+                 pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):"");
 
       /* We can't use sleep, cause signals may be blocked. */
       tv.tv_sec = 1 + *backoff;
@@ -620,15 +951,15 @@ dotlock_take_w32 (dotlock_t h, long timeout, int *backoff)
   w32err = GetLastError ();
   if (w32err != ERROR_LOCK_VIOLATION)
     {
-      log_error (_("lock `%s' not made: %s\n"),
-                 h->lockname, w32_strerror (w32err));
+      my_error_2 (_("lock `%s' not made: %s\n"),
+                  h->lockname, w32_strerror (w32err));
       return -1;
     }
 
   if ( timeout == -1 )
     {
       /* Wait until lock has been released. */
-      log_info (_("waiting for lock %s...\n"), h->lockname);
+      my_info_1 (_("waiting for lock %s...\n"), h->lockname);
       Sleep ((1 + *backoff)*1000);
       if ( *backoff < 10 )
         ++*backoff;
@@ -655,7 +986,7 @@ dotlock_take (dotlock_t h, long timeout)
 
   if ( h->locked )
     {
-      log_debug ("Oops, `%s' is already locked\n", h->lockname);
+      my_debug_1 ("Oops, `%s' is already locked\n", h->lockname);
       return 0;
     }
 
@@ -684,19 +1015,19 @@ dotlock_release_unix (dotlock_t h)
   pid = read_lockfile (h, &same_node);
   if ( pid == -1 )
     {
-      log_error( "release_dotlock: lockfile error\n");
+      my_error_0 ("release_dotlock: lockfile error\n");
       return -1;
     }
   if ( pid != getpid() || !same_node )
     {
-      log_error( "release_dotlock: not our lock (pid=%d)\n", pid);
+      my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid);
       return -1;
     }
 
   if ( unlink( h->lockname ) )
     {
-      log_error ("release_dotlock: error removing lockfile `%s'\n",
-                 h->lockname);
+      my_error_1 ("release_dotlock: error removing lockfile `%s'\n",
+                  h->lockname);
       return -1;
     }
   /* Fixme: As an extra check we could check whether the link count is
@@ -716,8 +1047,8 @@ dotlock_release_w32 (dotlock_t h)
   memset (&ovl, 0, sizeof ovl);
   if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl))
     {
-      log_error ("release_dotlock: error removing lockfile `%s': %s\n",
-                 h->lockname, w32_strerror (-1));
+      my_error_2 ("release_dotlock: error removing lockfile `%s': %s\n",
+                  h->lockname, w32_strerror (-1));
       return -1;
     }
 
@@ -744,7 +1075,7 @@ dotlock_release (dotlock_t h)
 
   if ( !h->locked )
     {
-      log_debug("Oops, `%s' is not locked\n", h->lockname);
+      my_debug_1 ("Oops, `%s' is not locked\n", h->lockname);
       return 0;
     }
 
index 276a6cd..5fb7891 100644 (file)
@@ -1,5 +1,5 @@
-/* dotlock.h - dotfile locking
- *     Copyright (C) 2000, 2001, 2006, 2011 Free Software Foundation, Inc.
+/* dotlock.h - dotfile locking declarations
+ * Copyright (C) 2000, 2001, 2006, 2011 Free Software Foundation, Inc.
  *
  * This file is part of JNLIB, which is a subsystem of GnuPG.
  *
@@ -20,6 +20,8 @@
 #ifndef LIBJNLIB_DOTLOCK_H
 #define LIBJNLIB_DOTLOCK_H
 
+/* See dotlock.c for a description.  */
+
 struct dotlock_handle;
 typedef struct dotlock_handle *dotlock_t;
 
diff --git a/common/t-dotlock.c b/common/t-dotlock.c
new file mode 100644 (file)
index 0000000..a352f6e
--- /dev/null
@@ -0,0 +1,145 @@
+/* t-dotlock.c - Module test for dotlock.c
+ * Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Note: This is a standalone test program which does not rely on any
+   GnuPG helper files.  However, it may also be build as part of the
+   GnuPG build system.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Some quick replacements for stuff we usually expect to be defined
+   in config.h.  Define HAVE_POSIX_SYSTEM for better readability. */
+#if !defined (HAVE_DOSISH_SYSTEM) && defined(_WIN32)
+# define HAVE_DOSISH_SYSTEM 1
+#endif
+#if !defined (HAVE_DOSISH_SYSTEM) && !defined (HAVE_POSIX_SYSTEM)
+# define HAVE_POSIX_SYSTEM 1
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "dotlock.h"
+
+#define PGM "t-dotlock"
+
+
+static volatile int ctrl_c_pending;
+
+static void
+control_c_handler (int signo)
+{
+  (void)signo;
+  ctrl_c_pending = 1;
+}
+
+
+
+static void
+die (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format);
+  fprintf (stderr, PGM "[%lu]: ", (unsigned long)getpid ());
+  vfprintf (stderr, format, arg_ptr);
+  putc ('\n', stderr);
+  va_end (arg_ptr);
+  exit (1);
+}
+
+
+static void
+inf (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format);
+  fprintf (stderr, PGM "[%lu]: ", (unsigned long)getpid ());
+  vfprintf (stderr, format, arg_ptr);
+  putc ('\n', stderr);
+  va_end (arg_ptr);
+}
+
+
+static void
+lock_and_unlock (const char *fname)
+{
+  dotlock_t h;
+
+  h = dotlock_create (fname);
+  if (!h)
+    die ("error creating lock file for `%s': %s", fname, strerror (errno));
+  inf ("lock created");
+
+  while (!ctrl_c_pending)
+    {
+      if (dotlock_take (h, -1))
+        die ("error taking lock");
+      inf ("lock taken");
+      sleep (1);
+      if (dotlock_release (h))
+        die ("error releasing lock");
+      inf ("lock released");
+      sleep (1);
+    }
+  dotlock_destroy (h);
+  inf ("lock destroyed");
+}
+
+
+int
+main (int argc, char **argv)
+{
+  const char *fname;
+
+  if (argc > 1)
+    fname = argv[1];
+  else
+    fname = "t-dotlock.tmp";
+
+  {
+    struct sigaction nact;
+
+    nact.sa_handler = control_c_handler;
+    nact.sa_flags = 0;
+    sigaction (SIGINT, &nact, NULL);
+  }
+
+  dotlock_create (NULL);  /* Initialize (optional).  */
+
+  lock_and_unlock (fname);
+
+
+  return 0;
+}
+
+
+/*
+Local Variables:
+compile-command: "cc -Wall -O2 -D_FILE_OFFSET_BITS=64 -o t-dotlock t-dotlock.c dotlock.c"
+End:
+*/