Subject: link(2) and atomic file creation on NFS
From: James Youngman <[email-masked]>
To: [email-masked]
Cc: [email-masked]
Date: 29 Aug 2001 21:34:42 +0100

I have some existing code which uses link(2) to achieve atomic locking
on Unix over NFS (since O_EXCL does not work over NFS).  The
application (a GPLed SCCS clone) now compiles under Cygwin, but one
remaining problem is that the lockfile code uses link(2) to create a
hard link to an open file.  

This seems not to work under Cygwin B20.1 (over NT4).  The link count
of the file stays at 1 (I think that errno=EPERM, but I could be
wrong).  

The link(2) usage is only relevant over NFS, but even if there is an
NFS client for NT [*does* one exist?], I would assume that the Cygwin
link(2) implementation would not correspond with a link(2) operation
via the NT NFS client.   So, the link(2) implementation loses under
NT.  Fine. 

Is there a way of safely creating a lock file without race conditions
that will also work under Cygwin?   For the sake of the discussion,
here is the current code :-

(explanation: "mystring" is a C++ class a lot like the standard string
class, and in fact usually *is* the standard string class)

TIA for any helpful hints!

/* WARNING: If you use fstat() on the fd to get the link count, the
 * wrong value is returned for Linux 2.0.34 and glibc-2.0.7, the
 * second time we perform the fstat().  Therefore we use stat(2)
 * rather than fstat(2).
 */
static long get_nlinks(const char *name)
{
  struct stat st;

  if (0 == stat(name, &st))
    {
      return (long)st.st_nlink;
    }
  else
    {
      return -1L;
    }
}

static int atomic_nfs_create(const mystring& path, int flags, int perms)
{
  mystring dirname, basename;
  char buf[32];
  const char *pstr = path.c_str();
  
  split_filename(path, dirname, basename);

  /* Rely (slightly) on only 11 characters of filename. */
  for (long attempt=0; attempt < 10000; ++attempt)
    {
      /* form the name of a lock file. */
      sprintf(buf, "nfslck%ld", attempt);
      const mystring lockname = dirname + mystring(buf);
      const char *lockstr = lockname.c_str();

      errno = 0;
      int fd = open(lockstr, flags, perms);
      if (fd >= 0)
        {
          if (1 == get_nlinks(lockstr))
            {
              int link_errno = 0;
              errno = 0;
              if (-1 == link(lockstr, pstr))
                link_errno = errno;

              /* ignore other responses */
              
              if (2 == get_nlinks(lockstr))
                {
                  unlink(lockstr); 
                  return fd;    /* success! */
                }
              else              /* link(2) failed. */
                {
                  if (EPERM == link_errno)
                    {
                      /* containing filesystem does not support hard links. */
                      close(fd);
                      unlink(lockstr);

                      /* assume that the filesystem supports O_EXCL if it does
                       * not supprort link(2).
                       */
                      return open(pstr, flags, perms);
                    }
                }
            }
          close(fd);
          unlink(lockstr); 
        }
      else                      /* open() failed. */
        {
          switch (errno)
            {
            case EEXIST: 
              /* someone else got that lock first; they may in fact not
               * be trying to lock the same s-file (but instead another 
               * s-file in the same directory)
               *
               * Try again.  Sleep first if we're not doing well,
               * but try to avoid pathalogical cases...
               */
              if ( (attempt > 4) && (attempt & 1) == (getpid() & 1) )
                {
                  errormsg("Sleeping for one second while "
                           "waiting for lock\n");
                  sleep(1);
                }
              break;
              
            default:            /* hard failure. */
              /* fall back on the less-safe method, which will
               * probably still fail
               */
              return open(pstr, flags, perms);
            }
        }
    }
  return -1;
}

-- 
James Youngman
Manchester, UK.  +44 161 226 7339
PGP (GPG) key ID for <[email-masked]> is 64A95EE5 (F1B83152).

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Bug reporting:         http://cygwin.com/bugs.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/