2066 lines
58 KiB
C++
2066 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. */
|
|
/* Starting with Vista, CancelSynchronousIo works, and 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.
|
|
Prior to Vista, CancelSynchronousIo doesn't exist, so we
|
|
terminated the thread and always 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;
|
|
}
|