2064 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			2064 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* flock.cc.  NT specific implementation of advisory file locking.
 | |
| 
 | |
|    This file is part of Cygwin.
 | |
| 
 | |
|    This software is a copyrighted work licensed under the terms of the
 | |
|    Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | |
|    details. */
 | |
| 
 | |
| /* The basic mechanism as well as the datastructures used in the below
 | |
|    implementation are taken from the FreeBSD repository on 2008-03-18.
 | |
|    The essential code of the lf_XXX functions has been taken from the
 | |
|    module src/sys/kern/kern_lockf.c.  It has been adapted to use NT
 | |
|    global namespace subdirs and event objects for synchronization
 | |
|    purposes.
 | |
| 
 | |
|    So, the following copyright applies to most of the code in the lf_XXX
 | |
|    functions.
 | |
| 
 | |
|  * Copyright (c) 1982, 1986, 1989, 1993
 | |
|  *      The Regents of the University of California.  All rights reserved.
 | |
|  *
 | |
|  * This code is derived from software contributed to Berkeley by
 | |
|  * Scooter Morris at Genentech Inc.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  * 1. Redistributions of source code must retain the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer.
 | |
|  * 2. Redistributions in binary form must reproduce the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer in the
 | |
|  *    documentation and/or other materials provided with the distribution.
 | |
|  * 4. Neither the name of the University nor the names of its contributors
 | |
|  *    may be used to endorse or promote products derived from this software
 | |
|  *    without specific prior written permission.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 | |
|  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
|  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 | |
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | |
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | |
|  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | |
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | |
|  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | |
|  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | |
|  * SUCH DAMAGE.
 | |
|  *
 | |
|  *      @(#)ufs_lockf.c 8.3 (Berkeley) 1/6/94
 | |
| */
 | |
| 
 | |
| /*
 | |
|  * The flock() function is based upon source taken from the Red Hat
 | |
|  * implementation used in their imap-2002d SRPM.
 | |
|  *
 | |
|  * $RH: flock.c,v 1.2 2000/08/23 17:07:00 nalin Exp $
 | |
|  */
 | |
| 
 | |
| /* The lockf function is based upon FreeBSD sources with the following
 | |
|  * copyright.
 | |
|  */
 | |
| /*
 | |
|  * Copyright (c) 1997 The NetBSD Foundation, Inc.
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * This code is derived from software contributed to The NetBSD Foundation
 | |
|  * by Klaus Klein.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  * 1. Redistributions of source code must retain the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer.
 | |
|  * 2. Redistributions in binary form must reproduce the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer in the
 | |
|  *    documentation and/or other materials provided with the distribution.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 | |
|  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 | |
|  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | |
|  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 | |
|  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | |
|  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | |
|  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | |
|  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | |
|  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | |
|  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | |
|  * POSSIBILITY OF SUCH DAMAGE.
 | |
|  */
 | |
| 
 | |
| #include "winsup.h"
 | |
| #include <assert.h>
 | |
| #include <sys/file.h>
 | |
| #include <unistd.h>
 | |
| #include <stdlib.h>
 | |
| #include "cygerrno.h"
 | |
| #include "security.h"
 | |
| #include "shared_info.h"
 | |
| #include "path.h"
 | |
| #include "fhandler.h"
 | |
| #include "dtable.h"
 | |
| #include "cygheap.h"
 | |
| #include "pinfo.h"
 | |
| #include "sigproc.h"
 | |
| #include "cygtls.h"
 | |
| #include "tls_pbuf.h"
 | |
| #include "miscfuncs.h"
 | |
| #include "ntdll.h"
 | |
| #include <sys/queue.h>
 | |
| #include <wchar.h>
 | |
| 
 | |
| #define F_WAIT 0x10	/* Wait until lock is granted */
 | |
| #define F_FLOCK 0x20	/* Use flock(2) semantics for lock */
 | |
| #define F_POSIX	0x40	/* Use POSIX semantics for lock */
 | |
| 
 | |
| #ifndef OFF_MAX
 | |
| #define OFF_MAX LLONG_MAX
 | |
| #endif
 | |
| 
 | |
| static NO_COPY muto lockf_guard;
 | |
| 
 | |
| #define INODE_LIST_LOCK()	(lockf_guard.init ("lockf_guard")->acquire ())
 | |
| #define INODE_LIST_UNLOCK()	(lockf_guard.release ())
 | |
| 
 | |
| #define LOCK_DIR_NAME_FMT	L"flock-%08x-%016X"
 | |
| #define LOCK_DIR_NAME_LEN	31	/* Length of the resulting name */
 | |
| #define LOCK_DIR_NAME_DEV_OFF	 6	/* Offset to device number */
 | |
| #define LOCK_DIR_NAME_INO_OFF	15	/* Offset to inode number */
 | |
| 
 | |
| /* Don't change format without also changing lockf_t::from_obj_name! */
 | |
| #define LOCK_OBJ_NAME_FMT	L"%02x-%01x-%016X-%016X-%016X-%08x-%04x"
 | |
| #define LOCK_OBJ_NAME_LEN	69	/* Length of the resulting name */
 | |
| 
 | |
| #define FLOCK_INODE_DIR_ACCESS	(DIRECTORY_QUERY \
 | |
| 				 | DIRECTORY_TRAVERSE \
 | |
| 				 | DIRECTORY_CREATE_OBJECT \
 | |
| 				 | READ_CONTROL)
 | |
| 
 | |
| #define FLOCK_EVENT_ACCESS	(EVENT_QUERY_STATE \
 | |
| 				 | SYNCHRONIZE \
 | |
| 				 | READ_CONTROL)
 | |
| 
 | |
| /* This function takes the own process security descriptor DACL and adds
 | |
|    SYNCHRONIZE permissions for everyone.  This allows all processes
 | |
|    to wait for this process to die when blocking in a F_SETLKW on a lock
 | |
|    which is hold by this process. */
 | |
| static void
 | |
| allow_others_to_sync ()
 | |
| {
 | |
|   static NO_COPY bool done;
 | |
| 
 | |
|   if (done)
 | |
|     return;
 | |
| 
 | |
|   NTSTATUS status;
 | |
|   PACL dacl;
 | |
|   LPVOID ace;
 | |
|   ULONG len;
 | |
| 
 | |
|   /* Get this process DACL.  We use a rather small stack buffer here which
 | |
|      should be more than sufficient for process ACLs.  Can't use tls functions
 | |
|      at this point because this gets called during initialization when the tls
 | |
|      is not really available.  */
 | |
| #define MAX_PROCESS_SD_SIZE	3072
 | |
|   PISECURITY_DESCRIPTOR sd = (PISECURITY_DESCRIPTOR) alloca (MAX_PROCESS_SD_SIZE);
 | |
|   status = NtQuerySecurityObject (NtCurrentProcess (),
 | |
| 				  DACL_SECURITY_INFORMATION, sd,
 | |
| 				  MAX_PROCESS_SD_SIZE, &len);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       debug_printf ("NtQuerySecurityObject: %y", status);
 | |
|       return;
 | |
|     }
 | |
|   /* Create a valid dacl pointer and set its size to be as big as
 | |
|      there's room in the temporary buffer.  Note that the descriptor
 | |
|      is in self-relative format. */
 | |
|   BOOLEAN present, defaulted;
 | |
|   RtlGetDaclSecurityDescriptor (sd, &present, &dacl, &defaulted);
 | |
|   if (!present) /* If so, dacl has undefined value. */
 | |
|     {
 | |
|       dacl = (PACL) (sd + 1);
 | |
|       RtlCreateAcl (dacl, MAX_PROCESS_SD_SIZE - sizeof *sd, ACL_REVISION);
 | |
|     }
 | |
|   else if (dacl == NULL) /* Everyone has all access anyway */
 | |
|     {
 | |
|       done = true;
 | |
|       return;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       dacl->AclSize = MAX_PROCESS_SD_SIZE - ((PBYTE) dacl - (PBYTE) sd);
 | |
|     }
 | |
|   /* Allow everyone to SYNCHRONIZE with this process. */
 | |
|   status = RtlAddAccessAllowedAce (dacl, ACL_REVISION, SYNCHRONIZE,
 | |
| 				   well_known_world_sid);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       debug_printf ("RtlAddAccessAllowedAce: %y", status);
 | |
|       return;
 | |
|     }
 | |
|   /* Set the size of the DACL correctly. */
 | |
|   status = RtlFirstFreeAce (dacl, &ace);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       debug_printf ("RtlFirstFreeAce: %y", status);
 | |
|       return;
 | |
|     }
 | |
|   dacl->AclSize = (char *) ace - (char *) dacl;
 | |
|   /* Write the DACL back. */
 | |
|   status = NtSetSecurityObject (NtCurrentProcess (), DACL_SECURITY_INFORMATION, sd);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       debug_printf ("NtSetSecurityObject: %y", status);
 | |
|       return;
 | |
|     }
 | |
|   done = true;
 | |
| }
 | |
| 
 | |
| /* Get the handle count of an object. */
 | |
| static ULONG
 | |
| get_obj_handle_count (HANDLE h)
 | |
| {
 | |
|   OBJECT_BASIC_INFORMATION obi;
 | |
|   NTSTATUS status;
 | |
|   ULONG hdl_cnt = 0;
 | |
| 
 | |
|   status = NtQueryObject (h, ObjectBasicInformation, &obi, sizeof obi, NULL);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     debug_printf ("NtQueryObject: %y", status);
 | |
|   else
 | |
|     hdl_cnt = obi.HandleCount;
 | |
|   return hdl_cnt;
 | |
| }
 | |
| 
 | |
| /* Helper struct to construct a local OBJECT_ATTRIBUTES on the stack. */
 | |
| struct lockfattr_t
 | |
| {
 | |
|   OBJECT_ATTRIBUTES attr;
 | |
|   UNICODE_STRING uname;
 | |
|   WCHAR name[LOCK_OBJ_NAME_LEN + 1];
 | |
| };
 | |
| 
 | |
| /* Per lock class. */
 | |
| class lockf_t
 | |
| {
 | |
|   public:
 | |
|     uint16_t	    lf_flags; /* Semantics: F_POSIX, F_FLOCK, F_WAIT */
 | |
|     uint16_t	    lf_type;  /* Lock type: F_RDLCK, F_WRLCK */
 | |
|     off_t	    lf_start; /* Byte # of the start of the lock */
 | |
|     off_t	    lf_end;   /* Byte # of the end of the lock (-1=EOF) */
 | |
|     int64_t         lf_id;    /* Cygwin PID for POSIX locks, a unique id per
 | |
| 				 file table entry for BSD flock locks. */
 | |
|     DWORD	    lf_wid;   /* Win PID of the resource holding the lock */
 | |
|     uint16_t	    lf_ver;   /* Version number of the lock.  If a released
 | |
| 				 lock event yet exists because another process
 | |
| 				 is still waiting for it, we use the version
 | |
| 				 field to distinguish old from new locks. */
 | |
|     class lockf_t **lf_head;  /* Back pointer to the head of the lockf_t list */
 | |
|     class inode_t  *lf_inode; /* Back pointer to the inode_t */
 | |
|     class lockf_t  *lf_next;  /* Pointer to the next lock on this inode_t */
 | |
|     HANDLE	    lf_obj;   /* Handle to the lock event object. */
 | |
| 
 | |
|     lockf_t ()
 | |
|     : lf_flags (0), lf_type (0), lf_start (0), lf_end (0), lf_id (0),
 | |
|       lf_wid (0), lf_ver (0), lf_head (NULL), lf_inode (NULL),
 | |
|       lf_next (NULL), lf_obj (NULL)
 | |
|     {}
 | |
|     lockf_t (class inode_t *node, class lockf_t **head,
 | |
| 	     short flags, short type, off_t start, off_t end,
 | |
| 	     long long id, DWORD wid, uint16_t ver)
 | |
|     : lf_flags (flags), lf_type (type), lf_start (start), lf_end (end),
 | |
|       lf_id (id), lf_wid (wid), lf_ver (ver), lf_head (head), lf_inode (node),
 | |
|       lf_next (NULL), lf_obj (NULL)
 | |
|     {}
 | |
|     ~lockf_t ();
 | |
| 
 | |
|     bool from_obj_name (class inode_t *node, class lockf_t **head,
 | |
| 			const wchar_t *name);
 | |
| 
 | |
|     /* Used to create all locks list in a given TLS buffer. */
 | |
|     void *operator new (size_t size, void *p)
 | |
|     { return p; }
 | |
|     /* Used to store own lock list in the cygheap. */
 | |
|     void *operator new (size_t size)
 | |
|     { return cmalloc (HEAP_FHANDLER, sizeof (lockf_t)); }
 | |
|     /* Never call on node->i_all_lf! */
 | |
|     void operator delete (void *p)
 | |
|     { cfree (p); }
 | |
| 
 | |
|     POBJECT_ATTRIBUTES create_lock_obj_attr (lockfattr_t *attr,
 | |
| 					     ULONG flags, void *sd_buf);
 | |
| 
 | |
|     void create_lock_obj ();
 | |
|     bool open_lock_obj ();
 | |
|     void close_lock_obj () { NtClose (lf_obj); lf_obj = NULL; }
 | |
|     void del_lock_obj (HANDLE fhdl, bool signal = false);
 | |
| };
 | |
| 
 | |
| /* Per inode_t class */
 | |
| class inode_t
 | |
| {
 | |
|   friend class lockf_t;
 | |
| 
 | |
|   public:
 | |
|     LIST_ENTRY (inode_t) i_next;
 | |
|     lockf_t		*i_lockf;  /* List of locks of this process. */
 | |
|     lockf_t		*i_all_lf; /* Temp list of all locks for this file. */
 | |
| 
 | |
|     dev_t		 i_dev;    /* Device ID */
 | |
|     ino_t		 i_ino;    /* inode number */
 | |
| 
 | |
|   private:
 | |
|     HANDLE		 i_dir;
 | |
|     HANDLE		 i_mtx;
 | |
|     uint32_t		 i_cnt;    /* # of threads referencing this instance. */
 | |
| 
 | |
|   public:
 | |
|     inode_t (dev_t dev, ino_t ino);
 | |
|     ~inode_t ();
 | |
| 
 | |
|     void *operator new (size_t size)
 | |
|     { return cmalloc (HEAP_FHANDLER, sizeof (inode_t)); }
 | |
|     void operator delete (void *p)
 | |
|     { cfree (p); }
 | |
| 
 | |
|     static inode_t *get (dev_t dev, ino_t ino,
 | |
| 			 bool create_if_missing, bool lock);
 | |
| 
 | |
|     void LOCK () { WaitForSingleObject (i_mtx, INFINITE); }
 | |
|     void UNLOCK () { ReleaseMutex (i_mtx); }
 | |
| 
 | |
|     void use () { ++i_cnt; }
 | |
|     void unuse () { if (i_cnt > 0) --i_cnt; }
 | |
|     bool inuse () { return i_cnt > 0; }
 | |
|     void notused () { i_cnt = 0; }
 | |
| 
 | |
|     void unlock_and_remove_if_unused ();
 | |
| 
 | |
|     lockf_t *get_all_locks_list ();
 | |
| 
 | |
|     bool del_my_locks (long long id, HANDLE fhdl);
 | |
| };
 | |
| 
 | |
| inode_t::~inode_t ()
 | |
| {
 | |
|   lockf_t *lock, *n_lock;
 | |
|   for (lock = i_lockf; lock && (n_lock = lock->lf_next, 1); lock = n_lock)
 | |
|     delete lock;
 | |
|   NtClose (i_mtx);
 | |
|   NtClose (i_dir);
 | |
| }
 | |
| 
 | |
| void
 | |
| inode_t::unlock_and_remove_if_unused ()
 | |
| {
 | |
|   UNLOCK ();
 | |
|   INODE_LIST_LOCK ();
 | |
|   unuse ();
 | |
|   if (i_lockf == NULL && !inuse ())
 | |
|     {
 | |
|       LIST_REMOVE (this, i_next);
 | |
|       delete this;
 | |
|     }
 | |
|   INODE_LIST_UNLOCK ();
 | |
| }
 | |
| 
 | |
| bool
 | |
| inode_t::del_my_locks (long long id, HANDLE fhdl)
 | |
| {
 | |
|   lockf_t *lock, *n_lock;
 | |
|   lockf_t **prev = &i_lockf;
 | |
|   for (lock = *prev; lock && (n_lock = lock->lf_next, 1); lock = n_lock)
 | |
|     {
 | |
|       if (lock->lf_flags & F_POSIX)
 | |
| 	{
 | |
| 	  /* Delete all POSIX locks. */
 | |
| 	  *prev = n_lock;
 | |
| 	  /* When called during fork, the POSIX lock must get deleted but
 | |
| 	     *not* signalled.  The lock is still active and locked in the
 | |
| 	     parent.  So in case of fork, we call close_lock_obj explicitely,
 | |
| 	     since del_lock_obj is called from the destructor. */
 | |
| 	  if (!id)
 | |
| 	    lock->close_lock_obj ();
 | |
| 	  delete lock;
 | |
| 	}
 | |
|       else if (id && lock->lf_id == id)
 | |
| 	{
 | |
| 	  int cnt = 0;
 | |
| 	  cygheap_fdenum cfd (true);
 | |
| 	  while (cfd.next () >= 0)
 | |
| 	    if (cfd->get_unique_id () == lock->lf_id && ++cnt > 1)
 | |
| 	      break;
 | |
| 	  /* Delete BSD flock lock when no other fd in this process references
 | |
| 	     it anymore. */
 | |
| 	  if (cnt <= 1)
 | |
| 	    {
 | |
| 	      *prev = n_lock;
 | |
| 	      lock->del_lock_obj (fhdl);
 | |
| 	      delete lock;
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	prev = &lock->lf_next;
 | |
|     }
 | |
|   return i_lockf == NULL;
 | |
| }
 | |
| 
 | |
| /* Used to delete the locks on a file hold by this process.  Called from
 | |
|    close(2) and fixup_after_fork, as well as from fixup_after_exec in
 | |
|    case the close_on_exec flag is set.  The whole inode is deleted as
 | |
|    soon as no lock exists on it anymore. */
 | |
| void
 | |
| fhandler_base::del_my_locks (del_lock_called_from from)
 | |
| {
 | |
|   inode_t *node = inode_t::get (get_dev (), get_ino (), false, true);
 | |
|   if (node)
 | |
|     {
 | |
|       /* When we're called from fixup_after_exec, the fhandler is a
 | |
| 	 close-on-exec fhandler.  In this case our io handle is already
 | |
| 	 invalid.  We can't use it to test for the object reference count.
 | |
| 	 However, that shouldn't be necessary for the following reason.
 | |
| 	 After exec, there are no threads in the current process waiting for
 | |
| 	 the lock.  So, either we're the only process accessing the file table
 | |
| 	 entry and there are no threads which require signalling, or we have
 | |
| 	 a parent process still accessing the file object and signalling the
 | |
| 	 lock event would be premature. */
 | |
|       node->del_my_locks (from == after_fork ? 0 : get_unique_id (),
 | |
| 			  from == after_exec ? NULL : get_handle ());
 | |
|       node->unlock_and_remove_if_unused ();
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Called in an execed child.  The exec'ed process must allow SYNCHRONIZE
 | |
|    access to everyone if at least one inode exists.
 | |
|    The lock owner's Windows PID changed and all POSIX lock event objects
 | |
|    have to be relabeled so that waiting processes know which process to
 | |
|    wait on.  If the node has been abandoned due to close_on_exec on the
 | |
|    referencing fhandlers, remove the inode entirely. */
 | |
| void
 | |
| fixup_lockf_after_exec (bool exec)
 | |
| {
 | |
|   inode_t *node, *next_node;
 | |
| 
 | |
|   INODE_LIST_LOCK ();
 | |
|   if (LIST_FIRST (&cygheap->inode_list))
 | |
|     allow_others_to_sync ();
 | |
|   LIST_FOREACH_SAFE (node, &cygheap->inode_list, i_next, next_node)
 | |
|     {
 | |
|       node->notused ();
 | |
|       int cnt = 0;
 | |
|       cygheap_fdenum cfd (true);
 | |
|       while (cfd.next () >= 0)
 | |
| 	if (cfd->get_dev () == node->i_dev
 | |
| 	    && cfd->get_ino () == node->i_ino
 | |
| 	    && ++cnt >= 1)
 | |
| 	  break;
 | |
|       if (cnt == 0)
 | |
| 	{
 | |
| 	  LIST_REMOVE (node, i_next);
 | |
| 	  delete node;
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  node->LOCK ();
 | |
| 	  lockf_t *lock, *n_lock;
 | |
| 	  lockf_t **prev = &node->i_lockf;
 | |
| 	  for (lock = *prev; lock && (n_lock = lock->lf_next, 1); lock = n_lock)
 | |
| 	    if (lock->lf_flags & F_POSIX)
 | |
| 	      {
 | |
| 		if (exec)
 | |
| 		  {
 | |
| 		    /* The parent called exec.  The lock is passed to the child.
 | |
| 		       Recreate lock object with changed ownership. */
 | |
| 		    lock->del_lock_obj (NULL);
 | |
| 		    lock->lf_wid = myself->dwProcessId;
 | |
| 		    lock->lf_ver = 0;
 | |
| 		    lock->create_lock_obj ();
 | |
| 		  }
 | |
| 		else
 | |
| 		  {
 | |
| 		    /* The parent called spawn.  The parent continues to hold
 | |
| 		       the POSIX lock, ownership is not passed to the child.
 | |
| 		       Give up the lock in the child. */
 | |
| 		    *prev = n_lock;
 | |
| 		    lock->close_lock_obj ();
 | |
| 		    delete lock;
 | |
| 		  }
 | |
| 	      }
 | |
| 	  node->UNLOCK ();
 | |
| 	}
 | |
|     }
 | |
|   INODE_LIST_UNLOCK ();
 | |
| }
 | |
| 
 | |
| /* static method to return a pointer to the inode_t structure for a specific
 | |
|    file.  The file is specified by the device and inode_t number.  If inode_t
 | |
|    doesn't exist, create it. */
 | |
| inode_t *
 | |
| inode_t::get (dev_t dev, ino_t ino, bool create_if_missing, bool lock)
 | |
| {
 | |
|   inode_t *node;
 | |
| 
 | |
|   INODE_LIST_LOCK ();
 | |
|   LIST_FOREACH (node, &cygheap->inode_list, i_next)
 | |
|     if (node->i_dev == dev && node->i_ino == ino)
 | |
|       break;
 | |
|   if (!node && create_if_missing)
 | |
|     {
 | |
|       node = new inode_t (dev, ino);
 | |
|       if (node)
 | |
| 	LIST_INSERT_HEAD (&cygheap->inode_list, node, i_next);
 | |
|     }
 | |
|   if (node)
 | |
|     node->use ();
 | |
|   INODE_LIST_UNLOCK ();
 | |
|   if (node && lock)
 | |
|     node->LOCK ();
 | |
|   return node;
 | |
| }
 | |
| 
 | |
| inode_t::inode_t (dev_t dev, ino_t ino)
 | |
| : i_lockf (NULL), i_all_lf (NULL), i_dev (dev), i_ino (ino), i_cnt (0L)
 | |
| {
 | |
|   HANDLE parent_dir;
 | |
|   WCHAR name[48];
 | |
|   UNICODE_STRING uname;
 | |
|   OBJECT_ATTRIBUTES attr;
 | |
|   NTSTATUS status;
 | |
| 
 | |
|   parent_dir = get_shared_parent_dir ();
 | |
|   /* Create a subdir which is named after the device and inode_t numbers
 | |
|      of the given file, in hex notation. */
 | |
|   int len = __small_swprintf (name, LOCK_DIR_NAME_FMT, dev, ino);
 | |
|   RtlInitCountedUnicodeString (&uname, name, len * sizeof (WCHAR));
 | |
|   InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT | OBJ_OPENIF,
 | |
| 			      parent_dir, everyone_sd (FLOCK_INODE_DIR_ACCESS));
 | |
|   status = NtCreateDirectoryObject (&i_dir, FLOCK_INODE_DIR_ACCESS, &attr);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     api_fatal ("NtCreateDirectoryObject(inode): %y", status);
 | |
|   /* Create a mutex object in the file specific dir, which is used for
 | |
|      access synchronization on the dir and its objects. */
 | |
|   InitializeObjectAttributes (&attr, &ro_u_mtx, OBJ_INHERIT | OBJ_OPENIF, i_dir,
 | |
| 			      everyone_sd (CYG_MUTANT_ACCESS));
 | |
|   status = NtCreateMutant (&i_mtx, CYG_MUTANT_ACCESS, &attr, FALSE);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     api_fatal ("NtCreateMutant(inode): %y", status);
 | |
| }
 | |
| 
 | |
| /* Enumerate all lock event objects for this file and create a lockf_t
 | |
|    list in the i_all_lf member.  This list is searched in lf_getblock
 | |
|    for locks which potentially block our lock request. */
 | |
| 
 | |
| /* Number of lockf_t structs which fit in the temporary buffer. */
 | |
| #define MAX_LOCKF_CNT	((intptr_t)((NT_MAX_PATH * sizeof (WCHAR)) \
 | |
| 				    / sizeof (lockf_t)))
 | |
| 
 | |
| bool
 | |
| lockf_t::from_obj_name (inode_t *node, lockf_t **head, const wchar_t *name)
 | |
| {
 | |
|   wchar_t *endptr;
 | |
| 
 | |
|   /* "%02x-%01x-%016X-%016X-%016X-%08x-%04x",
 | |
|      lf_flags, lf_type, lf_start, lf_end, lf_id, lf_wid, lf_ver */
 | |
|   lf_flags = wcstol (name, &endptr, 16);
 | |
|   if ((lf_flags & ~(F_FLOCK | F_POSIX)) != 0
 | |
|       || ((lf_flags & (F_FLOCK | F_POSIX)) == (F_FLOCK | F_POSIX)))
 | |
|     return false;
 | |
|   lf_type = wcstol (endptr + 1, &endptr, 16);
 | |
|   if ((lf_type != F_RDLCK && lf_type != F_WRLCK) || !endptr || *endptr != L'-')
 | |
|     return false;
 | |
|   lf_start = (off_t) wcstoull (endptr + 1, &endptr, 16);
 | |
|   if (lf_start < 0 || !endptr || *endptr != L'-')
 | |
|     return false;
 | |
|   lf_end = (off_t) wcstoull (endptr + 1, &endptr, 16);
 | |
|   if (lf_end < -1LL
 | |
|       || (lf_end > 0 && lf_end < lf_start)
 | |
|       || !endptr || *endptr != L'-')
 | |
|     return false;
 | |
|   lf_id = wcstoll (endptr + 1, &endptr, 16);
 | |
|   if (!endptr || *endptr != L'-'
 | |
|       || ((lf_flags & F_POSIX) && (lf_id < 1 || lf_id > UINT32_MAX)))
 | |
|     return false;
 | |
|   lf_wid = wcstoul (endptr + 1, &endptr, 16);
 | |
|   if (!endptr || *endptr != L'-')
 | |
|     return false;
 | |
|   lf_ver = wcstoul (endptr + 1, &endptr, 16);
 | |
|   if (endptr && *endptr != L'\0')
 | |
|     return false;
 | |
|   lf_head = head;
 | |
|   lf_inode = node;
 | |
|   lf_next = NULL;
 | |
|   lf_obj = NULL;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| lockf_t *
 | |
| inode_t::get_all_locks_list ()
 | |
| {
 | |
|   struct fdbi
 | |
|   {
 | |
|     DIRECTORY_BASIC_INFORMATION dbi;
 | |
|     WCHAR buf[2][NAME_MAX + 1];
 | |
|   } f;
 | |
|   ULONG context;
 | |
|   NTSTATUS status;
 | |
|   lockf_t newlock, *lock = i_all_lf;
 | |
| 
 | |
|   for (BOOLEAN restart = TRUE;
 | |
|        NT_SUCCESS (status = NtQueryDirectoryObject (i_dir, &f, sizeof f, TRUE,
 | |
| 						    restart, &context, NULL));
 | |
|        restart = FALSE)
 | |
|     {
 | |
|       if (f.dbi.ObjectName.Length != LOCK_OBJ_NAME_LEN * sizeof (WCHAR))
 | |
| 	continue;
 | |
|       f.dbi.ObjectName.Buffer[LOCK_OBJ_NAME_LEN] = L'\0';
 | |
|       if (!newlock.from_obj_name (this, &i_all_lf, f.dbi.ObjectName.Buffer))
 | |
| 	continue;
 | |
|       if (lock - i_all_lf >= MAX_LOCKF_CNT)
 | |
| 	{
 | |
| 	  system_printf ("Warning, can't handle more than %d locks per file.",
 | |
| 			 MAX_LOCKF_CNT);
 | |
| 	  break;
 | |
| 	}
 | |
|       if (lock > i_all_lf)
 | |
| 	lock[-1].lf_next = lock;
 | |
|       new (lock++) lockf_t (newlock);
 | |
|     }
 | |
|   /* If no lock has been found, return NULL. */
 | |
|   if (lock == i_all_lf)
 | |
|     return NULL;
 | |
|   return i_all_lf;
 | |
| }
 | |
| 
 | |
| /* Create the lock object name.  The name is constructed from the lock
 | |
|    properties which identify it uniquely, all values in hex. */
 | |
| POBJECT_ATTRIBUTES
 | |
| lockf_t::create_lock_obj_attr (lockfattr_t *attr, ULONG flags, void *sd_buf)
 | |
| {
 | |
|   __small_swprintf (attr->name, LOCK_OBJ_NAME_FMT,
 | |
| 		    lf_flags & (F_POSIX | F_FLOCK), lf_type, lf_start, lf_end,
 | |
| 		    lf_id, lf_wid, lf_ver);
 | |
|   RtlInitCountedUnicodeString (&attr->uname, attr->name,
 | |
| 			       LOCK_OBJ_NAME_LEN * sizeof (WCHAR));
 | |
|   InitializeObjectAttributes (&attr->attr, &attr->uname, flags, lf_inode->i_dir,
 | |
| 			      _everyone_sd (sd_buf, FLOCK_EVENT_ACCESS));
 | |
|   return &attr->attr;
 | |
| }
 | |
| 
 | |
| DWORD WINAPI
 | |
| create_lock_in_parent (PVOID param)
 | |
| {
 | |
|   HANDLE lf_obj;
 | |
|   ULONG size;
 | |
|   OBJECT_NAME_INFORMATION *ntfn;
 | |
|   NTSTATUS status;
 | |
|   wchar_t *lockname, *inodename, *endptr;
 | |
|   dev_t dev;
 | |
|   ino_t ino;
 | |
|   inode_t *node;
 | |
|   lockf_t newlock, *lock;
 | |
|   int cnt;
 | |
| 
 | |
|   /* param is the handle to the lock object, created by caller. */
 | |
|   lf_obj = (HANDLE) param;
 | |
|   /* Fetch object path from handle.  Typically the length of the path
 | |
|      is 146 characters, starting with
 | |
|      "\BaseNamedObject\cygwin-1S5-<16-hex-digits>\..." */
 | |
|   size = sizeof (OBJECT_NAME_INFORMATION) + 256 * sizeof (WCHAR);
 | |
|   ntfn = (OBJECT_NAME_INFORMATION *) alloca (size);
 | |
|   memset (ntfn, 0, size);
 | |
|   status = NtQueryObject (lf_obj, ObjectNameInformation, ntfn, size, &size);
 | |
|   if (!NT_SUCCESS (status))
 | |
|     goto err;
 | |
|   ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
 | |
|   /* Sanity check so that we don't peek into unchartered territory. */
 | |
|   if (ntfn->Name.Length < LOCK_OBJ_NAME_LEN + LOCK_DIR_NAME_LEN + 1)
 | |
|     goto err;
 | |
|   /* The names have fixed size, so we know where the substrings start. */
 | |
|   lockname = ntfn->Name.Buffer + ntfn->Name.Length / sizeof (WCHAR)
 | |
| 			       - LOCK_OBJ_NAME_LEN;
 | |
|   inodename = lockname - LOCK_DIR_NAME_LEN - 1;
 | |
|   dev = wcstoul (inodename + LOCK_DIR_NAME_DEV_OFF, &endptr, 16);
 | |
|   if (*endptr != L'-')
 | |
|     goto err;
 | |
|   ino = wcstoull (inodename + LOCK_DIR_NAME_INO_OFF, &endptr, 16);
 | |
|   if (*endptr != L'\\')
 | |
|     goto err;
 | |
|   if (!newlock.from_obj_name (NULL, NULL, lockname))
 | |
|     goto err;
 | |
|   /* Check if we have an open file handle with the same unique id. */
 | |
|   {
 | |
|     cnt = 0;
 | |
|     cygheap_fdenum cfd (true);
 | |
|     while (cfd.next () >= 0)
 | |
|       if (cfd->get_unique_id () == newlock.lf_id && ++cnt > 0)
 | |
| 	break;
 | |
|   }
 | |
|   /* If not, close handle and return. */
 | |
|   if (!cnt)
 | |
|     {
 | |
|       NtClose (lf_obj);
 | |
|       return 0;
 | |
|     }
 | |
|   /* The handle gets created non-inheritable.  That's fine, unless the parent
 | |
|      starts another process accessing this object.  So, after it's clear we
 | |
|      have to store the handle for further use, make sure it gets inheritable
 | |
|      by child processes. */
 | |
|   if (!SetHandleInformation (lf_obj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
 | |
|     goto err;
 | |
|   /* otherwise generate inode from directory name... */
 | |
|   node = inode_t::get (dev, ino, true, false);
 | |
|   /* ...and generate lock from object name. */
 | |
|   lock = new lockf_t (newlock);
 | |
|   lock->lf_inode = node;
 | |
|   lock->lf_head = &node->i_lockf;
 | |
|   lock->lf_next = node->i_lockf;
 | |
|   lock->lf_obj = lf_obj;
 | |
|   node->i_lockf = lock;
 | |
|   node->unuse ();
 | |
|   return 0;
 | |
| 
 | |
| err:
 | |
|   system_printf ("Adding <%S> lock failed", &ntfn->Name);
 | |
|   NtClose (lf_obj);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| DWORD WINAPI
 | |
| delete_lock_in_parent (PVOID param)
 | |
| {
 | |
|   inode_t *node, *next_node;
 | |
|   lockf_t *lock, **prev;
 | |
| 
 | |
|   /* Scan list of all inodes, and reap stale BSD lock if lf_id matches.
 | |
|      Remove inode if empty. */
 | |
|   INODE_LIST_LOCK ();
 | |
|   LIST_FOREACH_SAFE (node, &cygheap->inode_list, i_next, next_node)
 | |
|     if (!node->inuse ())
 | |
|       {
 | |
| 	for (prev = &node->i_lockf, lock = *prev; lock; lock = *prev)
 | |
| 	  {
 | |
| 	    if ((lock->lf_flags & F_FLOCK) && IsEventSignalled (lock->lf_obj))
 | |
| 	      {
 | |
| 		*prev = lock->lf_next;
 | |
| 		delete lock;
 | |
| 	      }
 | |
| 	    else
 | |
| 	      prev = &lock->lf_next;
 | |
| 	  }
 | |
| 	if (node->i_lockf == NULL)
 | |
| 	  {
 | |
| 	    LIST_REMOVE (node, i_next);
 | |
| 	    delete node;
 | |
| 	  }
 | |
|       }
 | |
|   INODE_LIST_UNLOCK ();
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* Create the lock event object in the file's subdir in the NT global
 | |
|    namespace. */
 | |
| void
 | |
| lockf_t::create_lock_obj ()
 | |
| {
 | |
|   lockfattr_t attr;
 | |
|   NTSTATUS status;
 | |
|   PSECURITY_DESCRIPTOR sd_buf = alloca (SD_MIN_SIZE);
 | |
|   POBJECT_ATTRIBUTES lock_obj_attr;
 | |
| 
 | |
|   do
 | |
|     {
 | |
|       lock_obj_attr = create_lock_obj_attr (&attr, OBJ_INHERIT, sd_buf);
 | |
|       status = NtCreateEvent (&lf_obj, CYG_EVENT_ACCESS, lock_obj_attr,
 | |
| 			      NotificationEvent, FALSE);
 | |
|       if (!NT_SUCCESS (status))
 | |
| 	{
 | |
| 	  if (status != STATUS_OBJECT_NAME_COLLISION)
 | |
| 	    api_fatal ("NtCreateEvent(lock): %y", status);
 | |
| 	  /* If we get a STATUS_OBJECT_NAME_COLLISION, the event still exists
 | |
| 	     because some other process is waiting for it in lf_setlock.
 | |
| 	     If so, check the event's signal state.  If we can't open it, it
 | |
| 	     has been closed in the meantime, so just try again.  If we can
 | |
| 	     open it and the object is not signalled, it's surely a bug in the
 | |
| 	     code somewhere.  Otherwise, close the event and retry to create
 | |
| 	     a new event with another name. */
 | |
| 	  if (open_lock_obj ())
 | |
| 	    {
 | |
| 	      if (!IsEventSignalled (lf_obj))
 | |
| 		api_fatal ("NtCreateEvent(lock): %y", status);
 | |
| 	      close_lock_obj ();
 | |
| 	      /* Increment the lf_ver field until we have no collision. */
 | |
| 	      ++lf_ver;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|   while (!NT_SUCCESS (status));
 | |
|   /* For BSD locks, notify the parent process. */
 | |
|   if (lf_flags & F_FLOCK)
 | |
|     {
 | |
|       HANDLE parent_proc, parent_thread, parent_lf_obj;
 | |
| 
 | |
|       pinfo p (myself->ppid);
 | |
|       if (!p)	/* No access or not a Cygwin parent. */
 | |
|       	return;
 | |
| 
 | |
|       parent_proc = OpenProcess (PROCESS_DUP_HANDLE
 | |
| 				 | PROCESS_CREATE_THREAD
 | |
| 				 | PROCESS_QUERY_INFORMATION
 | |
| 				 | PROCESS_VM_OPERATION
 | |
| 				 | PROCESS_VM_WRITE
 | |
| 				 | PROCESS_VM_READ,
 | |
| 				 FALSE, p->dwProcessId);
 | |
|       if (!parent_proc)
 | |
| 	{
 | |
| 	  debug_printf ("OpenProcess (%u): %E", p->dwProcessId);
 | |
| 	  return;
 | |
| 	}
 | |
|       if (!DuplicateHandle (GetCurrentProcess (), lf_obj, parent_proc,
 | |
| 			    &parent_lf_obj, TRUE, FALSE, DUPLICATE_SAME_ACCESS))
 | |
| 	debug_printf ("DuplicateHandle (lf_obj): %E");
 | |
|       else
 | |
| 	{
 | |
| 	  parent_thread = CreateRemoteThread (parent_proc, NULL, 256 * 1024,
 | |
| 					      create_lock_in_parent,
 | |
| 					      parent_lf_obj,
 | |
| 					      STACK_SIZE_PARAM_IS_A_RESERVATION,
 | |
| 					      NULL);
 | |
| 	  if (!parent_thread)
 | |
| 	    {
 | |
| 	      debug_printf ("CreateRemoteThread: %E");
 | |
| 	      /* If thread didn't get started, close object handle in parent,
 | |
| 		 otherwise suffer handle leaks. */
 | |
| 	      DuplicateHandle (parent_proc, parent_lf_obj, parent_proc,
 | |
| 			       NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE);
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      /* Must wait to avoid race conditions. */
 | |
| 	      WaitForSingleObject (parent_thread, INFINITE);
 | |
| 	      CloseHandle (parent_thread);
 | |
| 	    }
 | |
| 	}
 | |
|       CloseHandle (parent_proc);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Open a lock event object for SYNCHRONIZE access (to wait for it). */
 | |
| bool
 | |
| lockf_t::open_lock_obj ()
 | |
| {
 | |
|   lockfattr_t attr;
 | |
|   NTSTATUS status;
 | |
| 
 | |
|   status = NtOpenEvent (&lf_obj, FLOCK_EVENT_ACCESS,
 | |
| 			create_lock_obj_attr (&attr, 0, alloca (SD_MIN_SIZE)));
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       SetLastError (RtlNtStatusToDosError (status));
 | |
|       lf_obj = NULL; /* Paranoia... */
 | |
|     }
 | |
|   return lf_obj != NULL;
 | |
| }
 | |
| 
 | |
| /* Delete a lock event handle.  The important thing here is to signal it
 | |
|    before closing the handle.  This way all threads waiting for this lock
 | |
|    can wake up. */
 | |
| void
 | |
| lockf_t::del_lock_obj (HANDLE fhdl, bool signal)
 | |
| {
 | |
|   if (lf_obj)
 | |
|     {
 | |
|       /* Only signal the event if it's either a POSIX lock, or, in case of
 | |
| 	 BSD flock locks, if it's an explicit unlock or if the calling fhandler
 | |
| 	 holds the last reference to the file table entry.  The file table
 | |
| 	 entry in UNIX terms is equivalent to the FILE_OBJECT in Windows NT
 | |
| 	 terms.  It's what the handle/descriptor references when calling
 | |
| 	 CreateFile/open.  Calling DuplicateHandle/dup only creates a new
 | |
| 	 handle/descriptor to the same FILE_OBJECT/file table entry. */
 | |
|       if ((lf_flags & F_POSIX) || signal
 | |
| 	  || (fhdl && get_obj_handle_count (fhdl) <= 1))
 | |
| 	{
 | |
| 	  NTSTATUS status = NtSetEvent (lf_obj, NULL);
 | |
| 	  if (!NT_SUCCESS (status))
 | |
| 	    system_printf ("NtSetEvent, %y", status);
 | |
| 	  /* For BSD locks, notify the parent process. */
 | |
| 	  if (lf_flags & F_FLOCK)
 | |
| 	    {
 | |
| 	      HANDLE parent_proc, parent_thread;
 | |
| 
 | |
| 	      pinfo p (myself->ppid);
 | |
| 	      if (p && (parent_proc = OpenProcess (PROCESS_CREATE_THREAD
 | |
| 					     | PROCESS_QUERY_INFORMATION
 | |
| 					     | PROCESS_VM_OPERATION
 | |
| 					     | PROCESS_VM_WRITE
 | |
| 					     | PROCESS_VM_READ,
 | |
| 					     FALSE, p->dwProcessId)))
 | |
| 		{
 | |
| 		  parent_thread = CreateRemoteThread (parent_proc, NULL,
 | |
| 					      256 * 1024, delete_lock_in_parent,
 | |
| 					      NULL,
 | |
| 					      STACK_SIZE_PARAM_IS_A_RESERVATION,
 | |
| 					      NULL);
 | |
| 		  if (parent_thread)
 | |
| 		    {
 | |
| 		      /* Must wait to avoid race conditions. */
 | |
| 		      WaitForSingleObject (parent_thread, INFINITE);
 | |
| 		      CloseHandle (parent_thread);
 | |
| 		    }
 | |
| 		  CloseHandle (parent_proc);
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|       close_lock_obj ();
 | |
|     }
 | |
| }
 | |
| 
 | |
| lockf_t::~lockf_t ()
 | |
| {
 | |
|   del_lock_obj (NULL);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * This variable controls the maximum number of processes that will
 | |
|  * be checked in doing deadlock detection.
 | |
|  */
 | |
| #ifndef __CYGWIN__
 | |
| #define MAXDEPTH 50
 | |
| static int maxlockdepth = MAXDEPTH;
 | |
| #endif
 | |
| 
 | |
| #define NOLOCKF (struct lockf_t *)0
 | |
| #define SELF    0x1
 | |
| #define OTHERS  0x2
 | |
| static int      lf_clearlock (lockf_t *, lockf_t **, HANDLE);
 | |
| static int      lf_findoverlap (lockf_t *, lockf_t *, int, lockf_t ***, lockf_t **);
 | |
| static lockf_t *lf_getblock (lockf_t *, inode_t *node);
 | |
| static int      lf_getlock (lockf_t *, inode_t *, struct flock *);
 | |
| static int      lf_setlock (lockf_t *, inode_t *, lockf_t **, HANDLE);
 | |
| static void     lf_split (lockf_t *, lockf_t *, lockf_t **);
 | |
| static void     lf_wakelock (lockf_t *, HANDLE);
 | |
| 
 | |
| /* This is the fcntl advisory lock implementation.  For the implementation
 | |
|    of mandatory locks using the Windows mandatory locking functions, see the
 | |
|    fhandler_disk_file::mand_lock method at the end of this file. */
 | |
| int
 | |
| fhandler_base::lock (int a_op, struct flock *fl)
 | |
| {
 | |
|   off_t start, end, oadd;
 | |
|   int error = 0;
 | |
| 
 | |
|   short a_flags = fl->l_type & (F_POSIX | F_FLOCK);
 | |
|   short type = fl->l_type & (F_RDLCK | F_WRLCK | F_UNLCK);
 | |
| 
 | |
|   if (!a_flags)
 | |
|     a_flags = F_POSIX; /* default */
 | |
| 
 | |
|   /* FIXME: For BSD flock(2) we need a valid, per file table entry OS handle.
 | |
|      Therefore we can't allow using flock(2) on nohandle devices and
 | |
|      pre-Windows 8 console handles (recognized by their odd handle value). */
 | |
|   if ((a_flags & F_FLOCK)
 | |
|       && (nohandle () || (((uintptr_t) get_handle () & 0x3) == 0x3)))
 | |
|     {
 | |
|       set_errno (EINVAL);
 | |
|       debug_printf ("BSD locking on nohandle and old-style console devices "
 | |
| 		    "not supported");
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   if (a_op == F_SETLKW)
 | |
|     {
 | |
|       a_op = F_SETLK;
 | |
|       a_flags |= F_WAIT;
 | |
|     }
 | |
|   if (a_op == F_SETLK)
 | |
|     switch (type)
 | |
|       {
 | |
|       case F_UNLCK:
 | |
| 	a_op = F_UNLCK;
 | |
| 	break;
 | |
|       case F_RDLCK:
 | |
| 	/* flock semantics don't specify a requirement that the file has
 | |
| 	   been opened with a specific open mode, in contrast to POSIX locks
 | |
| 	   which require that a file is opened for reading to place a read
 | |
| 	   lock and opened for writing to place a write lock. */
 | |
| 	/* CV 2013-10-22: Test POSIX R/W mode flags rather than Windows R/W
 | |
| 	   access flags.  The reason is that POSIX mode flags are set for
 | |
| 	   all types of fhandlers, while Windows access flags are only set
 | |
| 	   for most of the actual Windows device backed fhandlers. */
 | |
| 	if ((a_flags & F_POSIX)
 | |
| 	    && ((get_flags () & O_ACCMODE) == O_WRONLY))
 | |
| 	  {
 | |
| 	    debug_printf ("request F_RDLCK on O_WRONLY file: EBADF");
 | |
| 	    set_errno (EBADF);
 | |
| 	    return -1;
 | |
| 	  }
 | |
| 	break;
 | |
|       case F_WRLCK:
 | |
| 	/* See above comment. */
 | |
| 	if ((a_flags & F_POSIX)
 | |
| 	    && ((get_flags () & O_ACCMODE) == O_RDONLY))
 | |
| 	  {
 | |
| 	    debug_printf ("request F_WRLCK on O_RDONLY file: EBADF");
 | |
| 	    set_errno (EBADF);
 | |
| 	    return -1;
 | |
| 	  }
 | |
| 	break;
 | |
|       default:
 | |
| 	set_errno (EINVAL);
 | |
| 	return -1;
 | |
|       }
 | |
| 
 | |
|   /*
 | |
|    * Convert the flock structure into a start and end.
 | |
|    */
 | |
|   switch (fl->l_whence)
 | |
|     {
 | |
|     case SEEK_SET:
 | |
|       start = fl->l_start;
 | |
|       break;
 | |
| 
 | |
|     case SEEK_CUR:
 | |
|       if ((start = lseek (0, SEEK_CUR)) == ILLEGAL_SEEK)
 | |
| 	start = 0;
 | |
|       break;
 | |
| 
 | |
|     case SEEK_END:
 | |
|       if (get_device () != FH_FS)
 | |
|       	start = 0;
 | |
|       else
 | |
| 	{
 | |
| 	  NTSTATUS status;
 | |
| 	  IO_STATUS_BLOCK io;
 | |
| 	  FILE_STANDARD_INFORMATION fsi;
 | |
| 
 | |
| 	  status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
 | |
| 					   FileStandardInformation);
 | |
| 	  if (!NT_SUCCESS (status))
 | |
| 	    {
 | |
| 	      __seterrno_from_nt_status (status);
 | |
| 	      return -1;
 | |
| 	    }
 | |
| 	  if (fl->l_start > 0 && fsi.EndOfFile.QuadPart > OFF_MAX - fl->l_start)
 | |
| 	    {
 | |
| 	      set_errno (EOVERFLOW);
 | |
| 	      return -1;
 | |
| 	    }
 | |
| 	  start = fsi.EndOfFile.QuadPart + fl->l_start;
 | |
| 	}
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       return (EINVAL);
 | |
|     }
 | |
|   if (start < 0)
 | |
|     {
 | |
|       set_errno (EINVAL);
 | |
|       return -1;
 | |
|     }
 | |
|   if (fl->l_len < 0)
 | |
|     {
 | |
|       if (start == 0)
 | |
| 	{
 | |
| 	  set_errno (EINVAL);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       end = start - 1;
 | |
|       start += fl->l_len;
 | |
|       if (start < 0)
 | |
| 	{
 | |
| 	  set_errno (EINVAL);
 | |
| 	  return -1;
 | |
| 	}
 | |
|     }
 | |
|   else if (fl->l_len == 0)
 | |
|     end = -1;
 | |
|   else
 | |
|     {
 | |
|       oadd = fl->l_len - 1;
 | |
|       if (oadd > OFF_MAX - start)
 | |
| 	{
 | |
| 	  set_errno (EOVERFLOW);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       end = start + oadd;
 | |
|     }
 | |
| 
 | |
| restart:	/* Entry point after a restartable signal came in. */
 | |
| 
 | |
|   inode_t *node = inode_t::get (get_dev (), get_ino (), true, true);
 | |
|   if (!node)
 | |
|     {
 | |
|       set_errno (ENOLCK);
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   /* Unlock the fd table which has been locked in fcntl_worker/lock_worker,
 | |
|      otherwise a blocking F_SETLKW never wakes up on a signal. */
 | |
|   cygheap->fdtab.unlock ();
 | |
| 
 | |
|   lockf_t **head = &node->i_lockf;
 | |
| 
 | |
| #if 0
 | |
|   /*
 | |
|    * Avoid the common case of unlocking when inode_t has no locks.
 | |
|    *
 | |
|    * This shortcut is invalid for Cygwin because the above inode_t::get
 | |
|    * call returns with an empty lock list if this process has no locks
 | |
|    * on the file yet.
 | |
|    */
 | |
|   if (*head == NULL)
 | |
|     {
 | |
|       if (a_op != F_SETLK)
 | |
| 	{
 | |
| 	  node->UNLOCK ();
 | |
| 	  fl->l_type = F_UNLCK;
 | |
| 	  return 0;
 | |
| 	}
 | |
|     }
 | |
| #endif
 | |
|   /*
 | |
|    * Allocate a spare structure in case we have to split.
 | |
|    */
 | |
|   lockf_t *clean = NULL;
 | |
|   if (a_op == F_SETLK || a_op == F_UNLCK)
 | |
|     {
 | |
|       clean = new lockf_t ();
 | |
|       if (!clean)
 | |
| 	{
 | |
| 	  node->unlock_and_remove_if_unused ();
 | |
| 	  set_errno (ENOLCK);
 | |
| 	  return -1;
 | |
| 	}
 | |
|     }
 | |
|   /*
 | |
|    * Create the lockf_t structure
 | |
|    */
 | |
|   lockf_t *lock = new lockf_t (node, head, a_flags, type, start, end,
 | |
| 			       (a_flags & F_FLOCK) ? get_unique_id ()
 | |
| 						   : getpid (),
 | |
| 			       myself->dwProcessId, 0);
 | |
|   if (!lock)
 | |
|     {
 | |
|       node->unlock_and_remove_if_unused ();
 | |
|       set_errno (ENOLCK);
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   switch (a_op)
 | |
|     {
 | |
|     case F_SETLK:
 | |
|       error = lf_setlock (lock, node, &clean, get_handle ());
 | |
|       break;
 | |
| 
 | |
|     case F_UNLCK:
 | |
|       error = lf_clearlock (lock, &clean, get_handle ());
 | |
|       lock->lf_next = clean;
 | |
|       clean = lock;
 | |
|       break;
 | |
| 
 | |
|     case F_GETLK:
 | |
|       error = lf_getlock (lock, node, fl);
 | |
|       lock->lf_next = clean;
 | |
|       clean = lock;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       lock->lf_next = clean;
 | |
|       clean = lock;
 | |
|       error = EINVAL;
 | |
|       break;
 | |
|     }
 | |
|   for (lock = clean; lock != NULL; )
 | |
|     {
 | |
|       lockf_t *n = lock->lf_next;
 | |
|       lock->del_lock_obj (get_handle (), a_op == F_UNLCK);
 | |
|       delete lock;
 | |
|       lock = n;
 | |
|     }
 | |
|   node->unlock_and_remove_if_unused ();
 | |
|   switch (error)
 | |
|     {
 | |
|     case 0:		/* All is well. */
 | |
|       need_fork_fixup (true);
 | |
|       return 0;
 | |
|     case EINTR:		/* Signal came in. */
 | |
|       if (_my_tls.call_signal_handler ())
 | |
| 	goto restart;
 | |
|       break;
 | |
|     case ECANCELED:	/* The thread has been sent a cancellation request. */
 | |
|       pthread::static_cancel_self ();
 | |
|       /*NOTREACHED*/
 | |
|     default:
 | |
|       break;
 | |
|     }
 | |
|   set_errno (error);
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Set a byte-range lock.
 | |
|  */
 | |
| static int
 | |
| lf_setlock (lockf_t *lock, inode_t *node, lockf_t **clean, HANDLE fhdl)
 | |
| {
 | |
|   lockf_t *block;
 | |
|   lockf_t **head = lock->lf_head;
 | |
|   lockf_t **prev, *overlap;
 | |
|   int ovcase, priority, old_prio, needtolink;
 | |
|   tmp_pathbuf tp;
 | |
| 
 | |
|   /*
 | |
|    * Set the priority
 | |
|    */
 | |
|   priority = old_prio = GetThreadPriority (GetCurrentThread ());
 | |
|   if (lock->lf_type == F_WRLCK && priority <= THREAD_PRIORITY_ABOVE_NORMAL)
 | |
|     priority = THREAD_PRIORITY_HIGHEST;
 | |
|   /*
 | |
|    * Scan lock list for this file looking for locks that would block us.
 | |
|    */
 | |
|   /* Create temporary space for the all locks list. */
 | |
|   node->i_all_lf = (lockf_t *) (void *) tp.w_get ();
 | |
|   while ((block = lf_getblock(lock, node)))
 | |
|     {
 | |
|       HANDLE obj = block->lf_obj;
 | |
|       block->lf_obj = NULL;
 | |
| 
 | |
|       /*
 | |
|        * Free the structure and return if nonblocking.
 | |
|        */
 | |
|       if ((lock->lf_flags & F_WAIT) == 0)
 | |
| 	{
 | |
| 	  NtClose (obj);
 | |
| 	  lock->lf_next = *clean;
 | |
| 	  *clean = lock;
 | |
| 	  return EAGAIN;
 | |
| 	}
 | |
|       /*
 | |
|        * We are blocked. Since flock style locks cover
 | |
|        * the whole file, there is no chance for deadlock.
 | |
|        * For byte-range locks we must check for deadlock.
 | |
|        *
 | |
|        * Deadlock detection is done by looking through the
 | |
|        * wait channels to see if there are any cycles that
 | |
|        * involve us. MAXDEPTH is set just to make sure we
 | |
|        * do not go off into neverland.
 | |
|        */
 | |
|       /* FIXME: We check the handle count of all the lock event objects
 | |
| 		this process holds.  If it's > 1, another process is
 | |
| 		waiting for one of our locks.  This method isn't overly
 | |
| 		intelligent.  If it turns out to be too dumb, we might
 | |
| 		have to remove it or to find another method. */
 | |
|       if (lock->lf_flags & F_POSIX)
 | |
| 	for (lockf_t *lk = node->i_lockf; lk; lk = lk->lf_next)
 | |
| 	  if ((lk->lf_flags & F_POSIX) && get_obj_handle_count (lk->lf_obj) > 1)
 | |
| 	    {
 | |
| 	      NtClose (obj);
 | |
| 	      return EDEADLK;
 | |
| 	    }
 | |
| 
 | |
|       /*
 | |
|        * For flock type locks, we must first remove
 | |
|        * any shared locks that we hold before we sleep
 | |
|        * waiting for an exclusive lock.
 | |
|        */
 | |
|       if ((lock->lf_flags & F_FLOCK) && lock->lf_type == F_WRLCK)
 | |
| 	{
 | |
| 	  lock->lf_type = F_UNLCK;
 | |
| 	  (void) lf_clearlock (lock, clean, fhdl);
 | |
| 	  lock->lf_type = F_WRLCK;
 | |
| 	}
 | |
| 
 | |
|       /*
 | |
|        * Add our lock to the blocked list and sleep until we're free.
 | |
|        * Remember who blocked us (for deadlock detection).
 | |
|        */
 | |
|       /* Cygwin:  No locked list.  See deadlock recognition above. */
 | |
| 
 | |
|       node->UNLOCK ();
 | |
| 
 | |
|       /* Create list of objects to wait for. */
 | |
|       HANDLE w4[4] = { obj, NULL, NULL, NULL };
 | |
|       DWORD wait_count = 1;
 | |
| 
 | |
|       DWORD timeout;
 | |
|       HANDLE proc = NULL;
 | |
|       if (lock->lf_flags & F_POSIX)
 | |
| 	{
 | |
| 	  proc = OpenProcess (SYNCHRONIZE, FALSE, block->lf_wid);
 | |
| 	  if (!proc)
 | |
| 	    timeout = 0L;
 | |
| 	  else
 | |
| 	    {
 | |
| 	      w4[wait_count++] = proc;
 | |
| 	      timeout = INFINITE;
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	timeout = 100L;
 | |
| 
 | |
|       DWORD WAIT_SIGNAL_ARRIVED = WAIT_OBJECT_0 + wait_count;
 | |
|       wait_signal_arrived here (w4[wait_count++]);
 | |
| 
 | |
|       DWORD WAIT_THREAD_CANCELED = WAIT_TIMEOUT + 1;
 | |
|       HANDLE cancel_event = pthread::get_cancel_event ();
 | |
|       if (cancel_event)
 | |
| 	{
 | |
| 	  WAIT_THREAD_CANCELED = WAIT_OBJECT_0 + wait_count;
 | |
| 	  w4[wait_count++] = cancel_event;
 | |
| 	}
 | |
| 
 | |
|       /* Wait for the blocking object and, for POSIX locks, its holding process.
 | |
| 	 Unfortunately, since BSD flock locks are not attached to a specific
 | |
| 	 process, we can't recognize an abandoned lock by sync'ing with the
 | |
| 	 creator process.  We have to make sure the event object is in a
 | |
| 	 signalled state, or that it has gone away.  The latter we can only
 | |
| 	 recognize by retrying to fetch the block list, so we must not wait
 | |
| 	 infinitely.  For POSIX locks, if the process has already exited,
 | |
| 	 just check if a signal or a thread cancel request arrived. */
 | |
|       SetThreadPriority (GetCurrentThread (), priority);
 | |
|       DWORD ret = WaitForMultipleObjects (wait_count, w4, FALSE, timeout);
 | |
|       SetThreadPriority (GetCurrentThread (), old_prio);
 | |
|       if (proc)
 | |
| 	CloseHandle (proc);
 | |
|       node->LOCK ();
 | |
|       /* Never close lock object handle outside of node lock! */
 | |
|       NtClose (obj);
 | |
|       if (ret == WAIT_SIGNAL_ARRIVED)
 | |
| 	{
 | |
| 	  /* A signal came in. */
 | |
| 	  lock->lf_next = *clean;
 | |
| 	  *clean = lock;
 | |
| 	  return EINTR;
 | |
| 	}
 | |
|       else if (ret == WAIT_THREAD_CANCELED)
 | |
| 	{
 | |
| 	  /* The thread has been sent a cancellation request. */
 | |
| 	  lock->lf_next = *clean;
 | |
| 	  *clean = lock;
 | |
| 	  return ECANCELED;
 | |
| 	}
 | |
|       else
 | |
| 	/* The lock object has been set to signalled or ...
 | |
| 	   for POSIX locks, the process holding the lock has exited, or ...
 | |
| 	   just a timeout.  Just retry. */
 | |
| 	continue;
 | |
|     }
 | |
|   allow_others_to_sync ();
 | |
|   /*
 | |
|    * No blocks!!  Add the lock.  Note that we will
 | |
|    * downgrade or upgrade any overlapping locks this
 | |
|    * process already owns.
 | |
|    *
 | |
|    * Handle any locks that overlap.
 | |
|    */
 | |
|   prev = head;
 | |
|   block = *head;
 | |
|   needtolink = 1;
 | |
|   for (;;)
 | |
|     {
 | |
|       ovcase = lf_findoverlap (block, lock, SELF, &prev, &overlap);
 | |
|       if (ovcase)
 | |
| 	block = overlap->lf_next;
 | |
|       /*
 | |
|        * Six cases:
 | |
|        *  0) no overlap
 | |
|        *  1) overlap == lock
 | |
|        *  2) overlap contains lock
 | |
|        *  3) lock contains overlap
 | |
|        *  4) overlap starts before lock
 | |
|        *  5) overlap ends after lock
 | |
|        */
 | |
|       switch (ovcase)
 | |
| 	{
 | |
| 	case 0: /* no overlap */
 | |
| 	  if (needtolink)
 | |
| 	    {
 | |
| 	      *prev = lock;
 | |
| 	      lock->lf_next = overlap;
 | |
| 	      lock->create_lock_obj ();
 | |
| 	    }
 | |
| 	    break;
 | |
| 
 | |
| 	case 1: /* overlap == lock */
 | |
| 	  /*
 | |
| 	   * If downgrading lock, others may be
 | |
| 	   * able to acquire it.
 | |
| 	   * Cygwin: Always wake lock.
 | |
| 	   */
 | |
| 	  lf_wakelock (overlap, fhdl);
 | |
| 	  overlap->lf_type = lock->lf_type;
 | |
| 	  overlap->create_lock_obj ();
 | |
| 	  lock->lf_next = *clean;
 | |
| 	  *clean = lock;
 | |
| 	  break;
 | |
| 
 | |
| 	case 2: /* overlap contains lock */
 | |
| 	  /*
 | |
| 	   * Check for common starting point and different types.
 | |
| 	   */
 | |
| 	  if (overlap->lf_type == lock->lf_type)
 | |
| 	    {
 | |
| 	      lock->lf_next = *clean;
 | |
| 	      *clean = lock;
 | |
| 	      break;
 | |
| 	    }
 | |
| 	  if (overlap->lf_start == lock->lf_start)
 | |
| 	    {
 | |
| 	      *prev = lock;
 | |
| 	      lock->lf_next = overlap;
 | |
| 	      overlap->lf_start = lock->lf_end + 1;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    lf_split (overlap, lock, clean);
 | |
| 	  lf_wakelock (overlap, fhdl);
 | |
| 	  overlap->create_lock_obj ();
 | |
| 	  lock->create_lock_obj ();
 | |
| 	  if (lock->lf_next && !lock->lf_next->lf_obj)
 | |
| 	    lock->lf_next->create_lock_obj ();
 | |
| 	  break;
 | |
| 
 | |
| 	case 3: /* lock contains overlap */
 | |
| 	  /*
 | |
| 	   * If downgrading lock, others may be able to
 | |
| 	   * acquire it, otherwise take the list.
 | |
| 	   * Cygwin: Always wake old lock and create new lock.
 | |
| 	   */
 | |
| 	  lf_wakelock (overlap, fhdl);
 | |
| 	  /*
 | |
| 	   * Add the new lock if necessary and delete the overlap.
 | |
| 	   */
 | |
| 	  if (needtolink)
 | |
| 	    {
 | |
| 	      *prev = lock;
 | |
| 	      lock->lf_next = overlap->lf_next;
 | |
| 	      prev = &lock->lf_next;
 | |
| 	      lock->create_lock_obj ();
 | |
| 	      needtolink = 0;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    *prev = overlap->lf_next;
 | |
| 	  overlap->lf_next = *clean;
 | |
| 	  *clean = overlap;
 | |
| 	  continue;
 | |
| 
 | |
| 	case 4: /* overlap starts before lock */
 | |
| 	  /*
 | |
| 	   * Add lock after overlap on the list.
 | |
| 	   */
 | |
| 	  lock->lf_next = overlap->lf_next;
 | |
| 	  overlap->lf_next = lock;
 | |
| 	  overlap->lf_end = lock->lf_start - 1;
 | |
| 	  prev = &lock->lf_next;
 | |
| 	  lf_wakelock (overlap, fhdl);
 | |
| 	  overlap->create_lock_obj ();
 | |
| 	  lock->create_lock_obj ();
 | |
| 	  needtolink = 0;
 | |
| 	  continue;
 | |
| 
 | |
| 	case 5: /* overlap ends after lock */
 | |
| 	  /*
 | |
| 	   * Add the new lock before overlap.
 | |
| 	   */
 | |
| 	  if (needtolink) {
 | |
| 	      *prev = lock;
 | |
| 	      lock->lf_next = overlap;
 | |
| 	  }
 | |
| 	  overlap->lf_start = lock->lf_end + 1;
 | |
| 	  lf_wakelock (overlap, fhdl);
 | |
| 	  lock->create_lock_obj ();
 | |
| 	  overlap->create_lock_obj ();
 | |
| 	  break;
 | |
| 	}
 | |
|       break;
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Remove a byte-range lock on an inode_t.
 | |
|  *
 | |
|  * Generally, find the lock (or an overlap to that lock)
 | |
|  * and remove it (or shrink it), then wakeup anyone we can.
 | |
|  */
 | |
| static int
 | |
| lf_clearlock (lockf_t *unlock, lockf_t **clean, HANDLE fhdl)
 | |
| {
 | |
|   lockf_t **head = unlock->lf_head;
 | |
|   lockf_t *lf = *head;
 | |
|   lockf_t *overlap, **prev;
 | |
|   int ovcase;
 | |
| 
 | |
|   if (lf == NOLOCKF)
 | |
|     return 0;
 | |
|   prev = head;
 | |
|   while ((ovcase = lf_findoverlap (lf, unlock, SELF, &prev, &overlap)))
 | |
|     {
 | |
|       /*
 | |
|        * Wakeup the list of locks to be retried.
 | |
|        */
 | |
|       lf_wakelock (overlap, fhdl);
 | |
| 
 | |
|       switch (ovcase)
 | |
| 	{
 | |
| 	case 1: /* overlap == lock */
 | |
| 	  *prev = overlap->lf_next;
 | |
| 	  overlap->lf_next = *clean;
 | |
| 	  *clean = overlap;
 | |
| 	  break;
 | |
| 
 | |
| 	case 2: /* overlap contains lock: split it */
 | |
| 	  if (overlap->lf_start == unlock->lf_start)
 | |
| 	    {
 | |
| 	      overlap->lf_start = unlock->lf_end + 1;
 | |
| 	      overlap->create_lock_obj ();
 | |
| 	      break;
 | |
| 	    }
 | |
| 	  lf_split (overlap, unlock, clean);
 | |
| 	  overlap->lf_next = unlock->lf_next;
 | |
| 	  overlap->create_lock_obj ();
 | |
| 	  if (overlap->lf_next && !overlap->lf_next->lf_obj)
 | |
| 	    overlap->lf_next->create_lock_obj ();
 | |
| 	  break;
 | |
| 
 | |
| 	case 3: /* lock contains overlap */
 | |
| 	  *prev = overlap->lf_next;
 | |
| 	  lf = overlap->lf_next;
 | |
| 	  overlap->lf_next = *clean;
 | |
| 	  *clean = overlap;
 | |
| 	  continue;
 | |
| 
 | |
| 	case 4: /* overlap starts before lock */
 | |
| 	    overlap->lf_end = unlock->lf_start - 1;
 | |
| 	    prev = &overlap->lf_next;
 | |
| 	    lf = overlap->lf_next;
 | |
| 	    overlap->create_lock_obj ();
 | |
| 	    continue;
 | |
| 
 | |
| 	case 5: /* overlap ends after lock */
 | |
| 	    overlap->lf_start = unlock->lf_end + 1;
 | |
| 	    overlap->create_lock_obj ();
 | |
| 	    break;
 | |
| 	}
 | |
|       break;
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Check whether there is a blocking lock,
 | |
|  * and if so return its process identifier.
 | |
|  */
 | |
| static int
 | |
| lf_getlock (lockf_t *lock, inode_t *node, struct flock *fl)
 | |
| {
 | |
|   lockf_t *block;
 | |
|   tmp_pathbuf tp;
 | |
| 
 | |
|   /* Create temporary space for the all locks list. */
 | |
|   node->i_all_lf = (lockf_t *) (void * ) tp.w_get ();
 | |
|   if ((block = lf_getblock (lock, node)))
 | |
|     {
 | |
|       if (block->lf_obj)
 | |
| 	block->close_lock_obj ();
 | |
|       fl->l_type = block->lf_type;
 | |
|       fl->l_whence = SEEK_SET;
 | |
|       fl->l_start = block->lf_start;
 | |
|       if (block->lf_end == -1)
 | |
| 	fl->l_len = 0;
 | |
|       else
 | |
| 	fl->l_len = block->lf_end - block->lf_start + 1;
 | |
|       if (block->lf_flags & F_POSIX)
 | |
| 	fl->l_pid = (pid_t) block->lf_id;
 | |
|       else
 | |
| 	fl->l_pid = -1;
 | |
|     }
 | |
|   else
 | |
|     fl->l_type = F_UNLCK;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Walk the list of locks for an inode_t and
 | |
|  * return the first blocking lock.
 | |
|  */
 | |
| static lockf_t *
 | |
| lf_getblock (lockf_t *lock, inode_t *node)
 | |
| {
 | |
|   lockf_t **prev, *overlap;
 | |
|   lockf_t *lf = node->get_all_locks_list ();
 | |
|   int ovcase;
 | |
| 
 | |
|   prev = lock->lf_head;
 | |
|   while ((ovcase = lf_findoverlap (lf, lock, OTHERS, &prev, &overlap)))
 | |
|     {
 | |
|       /*
 | |
|        * We've found an overlap, see if it blocks us
 | |
|        */
 | |
|       if ((lock->lf_type == F_WRLCK || overlap->lf_type == F_WRLCK))
 | |
| 	{
 | |
| 	  /* Open the event object for synchronization. */
 | |
| 	  if (overlap->open_lock_obj ())
 | |
| 	    {
 | |
| 	      /* Check if the event object is signalled.  If so, the overlap
 | |
| 		 doesn't actually exist anymore.  There are just a few open
 | |
| 		 handles left. */
 | |
| 	      if (!IsEventSignalled (overlap->lf_obj))
 | |
| 		return overlap;
 | |
| 	      overlap->close_lock_obj ();
 | |
| 	    }
 | |
| 	}
 | |
|       /*
 | |
|        * Nope, point to the next one on the list and
 | |
|        * see if it blocks us
 | |
|        */
 | |
|       lf = overlap->lf_next;
 | |
|     }
 | |
|   return NOLOCKF;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Walk the list of locks for an inode_t to
 | |
|  * find an overlapping lock (if any).
 | |
|  *
 | |
|  * NOTE: this returns only the FIRST overlapping lock.  There
 | |
|  *   may be more than one.
 | |
|  */
 | |
| static int
 | |
| lf_findoverlap (lockf_t *lf, lockf_t *lock, int type, lockf_t ***prev,
 | |
| 		lockf_t **overlap)
 | |
| {
 | |
|   off_t start, end;
 | |
| 
 | |
|   *overlap = lf;
 | |
|   if (lf == NOLOCKF)
 | |
|     return 0;
 | |
| 
 | |
|   start = lock->lf_start;
 | |
|   end = lock->lf_end;
 | |
|   while (lf != NOLOCKF)
 | |
|     {
 | |
|       if (((type & SELF) && lf->lf_id != lock->lf_id)
 | |
| 	  || ((type & OTHERS) && lf->lf_id == lock->lf_id)
 | |
| 	  /* As on Linux: POSIX locks and BSD flock locks don't interact. */
 | |
| 	  || (lf->lf_flags & (F_POSIX | F_FLOCK))
 | |
| 	     != (lock->lf_flags & (F_POSIX | F_FLOCK)))
 | |
| 	{
 | |
| 	  *prev = &lf->lf_next;
 | |
| 	  *overlap = lf = lf->lf_next;
 | |
| 	  continue;
 | |
| 	}
 | |
|       /*
 | |
|        * OK, check for overlap
 | |
|        *
 | |
|        * Six cases:
 | |
|        *  0) no overlap
 | |
|        *  1) overlap == lock
 | |
|        *  2) overlap contains lock
 | |
|        *  3) lock contains overlap
 | |
|        *  4) overlap starts before lock
 | |
|        *  5) overlap ends after lock
 | |
|        */
 | |
|       if ((lf->lf_end != -1 && start > lf->lf_end) ||
 | |
| 	  (end != -1 && lf->lf_start > end))
 | |
| 	{
 | |
| 	  /* Case 0 */
 | |
| 	  if ((type & SELF) && end != -1 && lf->lf_start > end)
 | |
| 	    return 0;
 | |
| 	  *prev = &lf->lf_next;
 | |
| 	  *overlap = lf = lf->lf_next;
 | |
| 	  continue;
 | |
| 	}
 | |
|       if ((lf->lf_start == start) && (lf->lf_end == end))
 | |
| 	{
 | |
| 	  /* Case 1 */
 | |
| 	  return 1;
 | |
| 	}
 | |
|       if ((lf->lf_start <= start) && (end != -1) &&
 | |
| 	  ((lf->lf_end >= end) || (lf->lf_end == -1)))
 | |
| 	{
 | |
| 	  /* Case 2 */
 | |
| 	  return 2;
 | |
| 	}
 | |
|       if (start <= lf->lf_start && (end == -1 ||
 | |
| 	  (lf->lf_end != -1 && end >= lf->lf_end)))
 | |
| 	{
 | |
| 	  /* Case 3 */
 | |
| 	  return 3;
 | |
| 	}
 | |
|       if ((lf->lf_start < start) &&
 | |
| 	  ((lf->lf_end >= start) || (lf->lf_end == -1)))
 | |
| 	{
 | |
| 	  /* Case 4 */
 | |
| 	  return 4;
 | |
| 	}
 | |
|       if ((lf->lf_start > start) && (end != -1) &&
 | |
| 	  ((lf->lf_end > end) || (lf->lf_end == -1)))
 | |
| 	{
 | |
| 	  /* Case 5 */
 | |
| 	  return 5;
 | |
| 	}
 | |
|       api_fatal ("lf_findoverlap: default\n");
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Split a lock and a contained region into
 | |
|  * two or three locks as necessary.
 | |
|  */
 | |
| static void
 | |
| lf_split (lockf_t *lock1, lockf_t *lock2, lockf_t **split)
 | |
| {
 | |
|   lockf_t *splitlock;
 | |
| 
 | |
|   /*
 | |
|    * Check to see if spliting into only two pieces.
 | |
|    */
 | |
|   if (lock1->lf_start == lock2->lf_start)
 | |
|     {
 | |
|       lock1->lf_start = lock2->lf_end + 1;
 | |
|       lock2->lf_next = lock1;
 | |
|       return;
 | |
|     }
 | |
|   if (lock1->lf_end == lock2->lf_end)
 | |
|     {
 | |
|       lock1->lf_end = lock2->lf_start - 1;
 | |
|       lock2->lf_next = lock1->lf_next;
 | |
|       lock1->lf_next = lock2;
 | |
|       return;
 | |
|     }
 | |
|   /*
 | |
|    * Make a new lock consisting of the last part of
 | |
|    * the encompassing lock.  We use the preallocated
 | |
|    * splitlock so we don't have to block.
 | |
|    */
 | |
|   splitlock = *split;
 | |
|   assert (splitlock != NULL);
 | |
|   *split = splitlock->lf_next;
 | |
|   memcpy (splitlock, lock1, sizeof *splitlock);
 | |
|   /* We have to unset the obj HANDLE here which has been copied by the
 | |
|      above memcpy, so that the calling function recognizes the new object.
 | |
|      See post-lf_split handling in lf_setlock and lf_clearlock. */
 | |
|   splitlock->lf_obj = NULL;
 | |
|   splitlock->lf_start = lock2->lf_end + 1;
 | |
|   lock1->lf_end = lock2->lf_start - 1;
 | |
|   /*
 | |
|    * OK, now link it in
 | |
|    */
 | |
|   splitlock->lf_next = lock1->lf_next;
 | |
|   lock2->lf_next = splitlock;
 | |
|   lock1->lf_next = lock2;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Wakeup a blocklist
 | |
|  * Cygwin: Just signal the lock which gets removed.  This unblocks
 | |
|  * all threads waiting for this lock.
 | |
|  */
 | |
| static void
 | |
| lf_wakelock (lockf_t *listhead, HANDLE fhdl)
 | |
| {
 | |
|   listhead->del_lock_obj (fhdl, true);
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| flock (int fd, int operation)
 | |
| {
 | |
|   int res = -1;
 | |
|   int cmd;
 | |
|   struct flock fl = { 0, SEEK_SET, 0, 0, 0 };
 | |
| 
 | |
|   __try
 | |
|     {
 | |
|       cygheap_fdget cfd (fd);
 | |
|       if (cfd < 0)
 | |
| 	__leave;
 | |
| 
 | |
|       cmd = (operation & LOCK_NB) ? F_SETLK : F_SETLKW;
 | |
|       switch (operation & (~LOCK_NB))
 | |
| 	{
 | |
| 	case LOCK_EX:
 | |
| 	  fl.l_type = F_WRLCK;
 | |
| 	  break;
 | |
| 	case LOCK_SH:
 | |
| 	  fl.l_type = F_RDLCK;
 | |
| 	  break;
 | |
| 	case LOCK_UN:
 | |
| 	  fl.l_type = F_UNLCK;
 | |
| 	  break;
 | |
| 	default:
 | |
| 	  set_errno (EINVAL);
 | |
| 	  __leave;
 | |
| 	}
 | |
|       if (!cfd->mandatory_locking ())
 | |
| 	fl.l_type |= F_FLOCK;
 | |
|       res = cfd->mandatory_locking () ? cfd->mand_lock (cmd, &fl)
 | |
| 				      : cfd->lock (cmd, &fl);
 | |
|       if ((res == -1) && ((get_errno () == EAGAIN) || (get_errno () == EACCES)))
 | |
| 	set_errno (EWOULDBLOCK);
 | |
|     }
 | |
|   __except (EFAULT) {}
 | |
|   __endtry
 | |
|   syscall_printf ("%R = flock(%d, %d)", res, fd, operation);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| lockf (int filedes, int function, off_t size)
 | |
| {
 | |
|   int res = -1;
 | |
|   int cmd;
 | |
|   struct flock fl;
 | |
| 
 | |
|   pthread_testcancel ();
 | |
| 
 | |
|   __try
 | |
|     {
 | |
|       cygheap_fdget cfd (filedes);
 | |
|       if (cfd < 0)
 | |
| 	__leave;
 | |
| 
 | |
|       fl.l_start = 0;
 | |
|       fl.l_len = size;
 | |
|       fl.l_whence = SEEK_CUR;
 | |
| 
 | |
|       switch (function)
 | |
| 	{
 | |
| 	case F_ULOCK:
 | |
| 	  cmd = F_SETLK;
 | |
| 	  fl.l_type = F_UNLCK;
 | |
| 	  break;
 | |
| 	case F_LOCK:
 | |
| 	  cmd = F_SETLKW;
 | |
| 	  fl.l_type = F_WRLCK;
 | |
| 	  break;
 | |
| 	case F_TLOCK:
 | |
| 	  cmd = F_SETLK;
 | |
| 	  fl.l_type = F_WRLCK;
 | |
| 	  break;
 | |
| 	case F_TEST:
 | |
| 	  fl.l_type = F_WRLCK;
 | |
| 	  if (cfd->lock (F_GETLK, &fl) == -1)
 | |
| 	    __leave;
 | |
| 	  if (fl.l_type == F_UNLCK || fl.l_pid == getpid ())
 | |
| 	    res = 0;
 | |
| 	  else
 | |
| 	    errno = EAGAIN;
 | |
| 	  __leave;
 | |
| 	  /* NOTREACHED */
 | |
| 	default:
 | |
| 	  errno = EINVAL;
 | |
| 	  __leave;
 | |
| 	  /* NOTREACHED */
 | |
| 	}
 | |
|       res = cfd->mandatory_locking () ? cfd->mand_lock (cmd, &fl)
 | |
| 				      : cfd->lock (cmd, &fl);
 | |
|     }
 | |
|   __except (EFAULT) {}
 | |
|   __endtry
 | |
|   syscall_printf ("%R = lockf(%d, %d, %D)", res, filedes, function, size);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| /* This is the fcntl lock implementation for mandatory locks using the
 | |
|    Windows mandatory locking functions.  For the UNIX-like advisory locking
 | |
|    implementation see the fhandler_disk_file::lock method earlier in this
 | |
|    file. */
 | |
| struct lock_parms {
 | |
|   HANDLE	   h;
 | |
|   PIO_STATUS_BLOCK pio;
 | |
|   PLARGE_INTEGER   poff;
 | |
|   PLARGE_INTEGER   plen;
 | |
|   BOOL		   type;
 | |
|   NTSTATUS	   status;
 | |
| };
 | |
| 
 | |
| static DWORD WINAPI
 | |
| blocking_lock_thr (LPVOID param)
 | |
| {
 | |
|   struct lock_parms *lp = (struct lock_parms *) param;
 | |
|   lp->status = NtLockFile (lp->h, NULL, NULL, NULL, lp->pio, lp->poff,
 | |
| 			   lp->plen, 0, FALSE, lp->type);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_base::mand_lock (int, struct flock *)
 | |
| {
 | |
|   set_errno (EINVAL);
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int
 | |
| fhandler_disk_file::mand_lock (int a_op, struct flock *fl)
 | |
| {
 | |
|   NTSTATUS status;
 | |
|   IO_STATUS_BLOCK io;
 | |
|   FILE_POSITION_INFORMATION fpi;
 | |
|   FILE_STANDARD_INFORMATION fsi;
 | |
|   off_t startpos;
 | |
|   LARGE_INTEGER offset;
 | |
|   LARGE_INTEGER length;
 | |
| 
 | |
|   /* Calculate where to start from, then adjust this by fl->l_start. */
 | |
|   switch (fl->l_whence)
 | |
|   {
 | |
|     case SEEK_SET:
 | |
|       startpos = 0;
 | |
|       break;
 | |
|     case SEEK_CUR:
 | |
|       status = NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
 | |
| 				       FilePositionInformation);
 | |
|       if (!NT_SUCCESS (status))
 | |
| 	{
 | |
| 	  __seterrno_from_nt_status (status);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       startpos = fpi.CurrentByteOffset.QuadPart;
 | |
|       break;
 | |
|     case SEEK_END:
 | |
|       status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
 | |
| 				       FileStandardInformation);
 | |
|       if (!NT_SUCCESS (status))
 | |
| 	{
 | |
| 	  __seterrno_from_nt_status (status);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       startpos = fsi.EndOfFile.QuadPart;
 | |
|       break;
 | |
|     default:
 | |
|       set_errno (EINVAL);
 | |
|       return -1;
 | |
|   }
 | |
|   /* Adjust start and length until they make sense. */
 | |
|   offset.QuadPart = startpos + fl->l_start;
 | |
|   if (fl->l_len < 0)
 | |
|     {
 | |
|       offset.QuadPart -= fl->l_len;
 | |
|       length.QuadPart = -fl->l_len;
 | |
|     }
 | |
|   else
 | |
|     length.QuadPart = fl->l_len;
 | |
|   if (offset.QuadPart < 0)
 | |
|     {
 | |
|       length.QuadPart -= -offset.QuadPart;
 | |
|       if (length.QuadPart <= 0)
 | |
|         {
 | |
|           set_errno (EINVAL);
 | |
|           return -1;
 | |
|         }
 | |
|       offset.QuadPart = 0;
 | |
|     }
 | |
|   /* Special case if len == 0.  For POSIX this means lock to the end of
 | |
|      the entire file, even when file grows later. */
 | |
|   if (length.QuadPart == 0)
 | |
|     length.QuadPart = UINT64_MAX;
 | |
|   /* Action! */
 | |
|   if (fl->l_type == F_UNLCK)
 | |
|     {
 | |
|       status = NtUnlockFile (get_handle (), &io, &offset, &length, 0);
 | |
|       if (status == STATUS_RANGE_NOT_LOCKED)	/* Not an error */
 | |
| 	status = STATUS_SUCCESS;
 | |
|     }
 | |
|   else if (a_op == F_SETLKW)
 | |
|     {
 | |
|       /* We open file handles synchronously.  To allow asynchronous operation
 | |
| 	 the file locking functions require a file handle opened in asynchronous
 | |
| 	 mode.  Since Windows locks are per-process/per-file object, we can't
 | |
| 	 open another handle asynchrously and lock/unlock using that handle:
 | |
| 	 The original file handle would not have placed the lock and would be
 | |
| 	 restricted by the lock like any other file handle.
 | |
| 	 So, what we do here is to start a thread which calls the potentially
 | |
| 	 blocking NtLockFile call.  Then we wait for thread completion in an
 | |
| 	 interruptible fashion. */
 | |
|       OBJECT_ATTRIBUTES attr;
 | |
|       HANDLE evt;
 | |
|       struct lock_parms lp = { get_handle (), &io, &offset, &length,
 | |
| 			       fl->l_type == F_WRLCK, 0 };
 | |
|       cygthread *thr = NULL;
 | |
| 
 | |
|       InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL);
 | |
|       status = NtCreateEvent (&evt, EVENT_ALL_ACCESS, &attr,
 | |
| 			      NotificationEvent, FALSE);
 | |
|       if (evt)
 | |
| 	thr = new cygthread (blocking_lock_thr, &lp, "blk_lock", evt);
 | |
|       if (!thr)
 | |
| 	{
 | |
| 	  /* Thread creation failed.  Fall back to blocking lock call. */
 | |
| 	  if (evt)
 | |
| 	    NtClose (evt);
 | |
| 	  status = NtLockFile (get_handle (), NULL, NULL, NULL, &io, &offset,
 | |
| 			       &length, 0, FALSE, fl->l_type == F_WRLCK);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  /* F_SETLKW and lock cannot be established.  Wait until the lock can
 | |
| 	     be established, or a signal request arrived.  We deliberately
 | |
| 	     don't handle thread cancel requests here. */
 | |
| 	  DWORD wait_res = cygwait (evt, INFINITE, cw_sig | cw_sig_eintr);
 | |
| 	  NtClose (evt);
 | |
| 	  switch (wait_res)
 | |
| 	    {
 | |
| 	    case WAIT_OBJECT_0:
 | |
| 	      /* Fetch completion status. */
 | |
| 	      status = lp.status;
 | |
| 	      thr->detach ();
 | |
| 	      break;
 | |
| 	    default:
 | |
| 	      /* Signal arrived.
 | |
| 		 If CancelSynchronousIo works we wait for the thread to exit.
 | |
| 		 lp.status will be either STATUS_SUCCESS, or STATUS_CANCELLED.
 | |
| 		 We only call NtUnlockFile in the first case.
 | |
| 		 If CancelSynchronousIo fails we terminated the thread and
 | |
| 		 call NtUnlockFile since lp.status was 0 to begin with. */
 | |
| 	      if (CancelSynchronousIo (thr->thread_handle ()))
 | |
| 		thr->detach ();
 | |
| 	      else
 | |
| 	      	thr->terminate_thread ();
 | |
| 	      if (NT_SUCCESS (lp.status))
 | |
| 		NtUnlockFile (get_handle (), &io, &offset, &length, 0);
 | |
| 	      /* Per SUSv4: If a signal is received while fcntl is waiting,
 | |
| 		 fcntl shall be interrupted.  Upon return from the signal
 | |
| 		 handler, fcntl shall return -1 with errno set to EINTR,
 | |
| 		 and the lock operation shall not be done. */
 | |
| 	      _my_tls.call_signal_handler ();
 | |
| 	      set_errno (EINTR);
 | |
| 	      return -1;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       status = NtLockFile (get_handle (), NULL, NULL, NULL, &io, &offset,
 | |
| 			   &length, 0, TRUE, fl->l_type == F_WRLCK);
 | |
|       if (a_op == F_GETLK)
 | |
| 	{
 | |
| 	  /* This is non-atomic, but there's no other way on Windows to detect
 | |
| 	     if another lock is blocking our lock, other than trying to place
 | |
| 	     the lock, and then having to unlock it again. */
 | |
| 	  if (NT_SUCCESS (status))
 | |
| 	    {
 | |
| 	      NtUnlockFile (get_handle (), &io, &offset, &length, 0);
 | |
| 	      fl->l_type = F_UNLCK;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      /* FAKE! FAKE! FAKE! */
 | |
| 	      fl->l_type = F_WRLCK;
 | |
| 	      fl->l_whence = SEEK_SET;
 | |
| 	      fl->l_start = offset.QuadPart;
 | |
| 	      fl->l_len = length.QuadPart;
 | |
| 	      fl->l_pid = (pid_t) -1;
 | |
| 	    }
 | |
| 	  status = STATUS_SUCCESS;
 | |
| 	}
 | |
|     }
 | |
|   if (!NT_SUCCESS (status))
 | |
|     {
 | |
|       __seterrno_from_nt_status (status);
 | |
|       return -1;
 | |
|     }
 | |
|   return 0;
 | |
| }
 |