Implemented advisory file locking, POSIX style (ie. accessible via fcntl()).
Should work correctly, although there is a big race condition left when you're using F_SETLKW. Will be fixed next. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@11868 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
+326
-5
@@ -85,6 +85,7 @@ struct vnode {
|
|||||||
uint8 remove : 1;
|
uint8 remove : 1;
|
||||||
uint8 busy : 1;
|
uint8 busy : 1;
|
||||||
uint8 unpublished : 1;
|
uint8 unpublished : 1;
|
||||||
|
struct advisory_locking *advisory_locking;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vnode_hash_key {
|
struct vnode_hash_key {
|
||||||
@@ -111,6 +112,19 @@ struct fs_mount {
|
|||||||
bool owns_file_device;
|
bool owns_file_device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct advisory_locking {
|
||||||
|
struct mutex mutex;
|
||||||
|
sem_id wait_sem;
|
||||||
|
struct list locks;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct advisory_lock {
|
||||||
|
list_link link;
|
||||||
|
team_id team;
|
||||||
|
off_t offset;
|
||||||
|
off_t length;
|
||||||
|
bool shared;
|
||||||
|
};
|
||||||
|
|
||||||
static mutex sFileSystemsMutex;
|
static mutex sFileSystemsMutex;
|
||||||
|
|
||||||
@@ -812,6 +826,272 @@ put_vnode(struct vnode *vnode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
create_advisory_locking(struct vnode *vnode)
|
||||||
|
{
|
||||||
|
status_t status;
|
||||||
|
|
||||||
|
struct advisory_locking *locking = (struct advisory_locking *)malloc(sizeof(struct advisory_locking));
|
||||||
|
if (locking == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
locking->wait_sem = create_sem(0, "advisory lock");
|
||||||
|
if (locking->wait_sem < B_OK) {
|
||||||
|
status = locking->wait_sem;
|
||||||
|
goto err1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = mutex_init(&locking->mutex, "advisory locking");
|
||||||
|
if (status < B_OK)
|
||||||
|
goto err2;
|
||||||
|
|
||||||
|
list_init(&locking->locks);
|
||||||
|
vnode->advisory_locking = locking;
|
||||||
|
return B_OK;
|
||||||
|
|
||||||
|
err2:
|
||||||
|
delete_sem(locking->wait_sem);
|
||||||
|
err1:
|
||||||
|
free(locking);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
put_advisory_locking(struct advisory_locking *locking)
|
||||||
|
{
|
||||||
|
mutex_unlock(&locking->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static struct advisory_locking *
|
||||||
|
get_advisory_locking(struct vnode *vnode)
|
||||||
|
{
|
||||||
|
mutex_lock(&sVnodeMutex);
|
||||||
|
|
||||||
|
struct advisory_locking *locking = vnode->advisory_locking;
|
||||||
|
if (locking != NULL)
|
||||||
|
mutex_lock(&locking->mutex);
|
||||||
|
|
||||||
|
mutex_unlock(&sVnodeMutex);
|
||||||
|
return locking;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
get_advisory_lock(struct vnode *vnode, struct flock *flock)
|
||||||
|
{
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Removes the specified lock, or all locks of the calling team
|
||||||
|
* if \a flock is NULL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
release_advisory_lock(struct vnode *vnode, struct flock *flock)
|
||||||
|
{
|
||||||
|
FUNCTION(("release_advisory_lock(vnode = %p, flock = %p)\n", vnode, flock));
|
||||||
|
|
||||||
|
struct advisory_locking *locking = get_advisory_locking(vnode);
|
||||||
|
if (locking == NULL)
|
||||||
|
return flock != NULL ? B_BAD_VALUE : B_OK;
|
||||||
|
|
||||||
|
team_id team = team_get_current_team_id();
|
||||||
|
|
||||||
|
// find matching lock entry
|
||||||
|
|
||||||
|
status_t status = B_BAD_VALUE;
|
||||||
|
struct advisory_lock *lock = NULL;
|
||||||
|
while ((lock = (struct advisory_lock *)list_get_next_item(&locking->locks, lock)) != NULL) {
|
||||||
|
if (lock->team == team && (flock == NULL || (flock != NULL
|
||||||
|
&& lock->offset == flock->l_start
|
||||||
|
&& lock->length == flock->l_len))) {
|
||||||
|
// we found our lock, free it
|
||||||
|
list_remove_item(&locking->locks, lock);
|
||||||
|
free(lock);
|
||||||
|
status = B_OK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removeLocking = list_is_empty(&locking->locks);
|
||||||
|
release_sem_etc(locking->wait_sem, 1, B_RELEASE_ALL);
|
||||||
|
|
||||||
|
put_advisory_locking(locking);
|
||||||
|
|
||||||
|
if (status < B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if (removeLocking) {
|
||||||
|
// we can remove the whole advisory locking structure; it's no longer used
|
||||||
|
mutex_lock(&sVnodeMutex);
|
||||||
|
locking = vnode->advisory_locking;
|
||||||
|
if (locking != NULL)
|
||||||
|
mutex_lock(&locking->mutex);
|
||||||
|
|
||||||
|
// the locking could have been changed in the mean time
|
||||||
|
if (list_is_empty(&locking->locks))
|
||||||
|
vnode->advisory_locking = NULL;
|
||||||
|
else {
|
||||||
|
removeLocking = false;
|
||||||
|
mutex_unlock(&locking->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&sVnodeMutex);
|
||||||
|
}
|
||||||
|
if (removeLocking) {
|
||||||
|
// we've detached the locking from the vnode, so we can safely delete it
|
||||||
|
mutex_destroy(&locking->mutex);
|
||||||
|
delete_sem(locking->wait_sem);
|
||||||
|
free(locking);
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
acquire_advisory_lock(struct vnode *vnode, struct flock *flock, bool wait)
|
||||||
|
{
|
||||||
|
FUNCTION(("acquire_advisory_lock(vnode = %p, flock = %p, wait = %s)\n",
|
||||||
|
vnode, flock, wait ? "yes" : "no"));
|
||||||
|
|
||||||
|
bool shared = flock->l_type == F_RDLCK;
|
||||||
|
status_t status = B_OK;
|
||||||
|
|
||||||
|
restart:
|
||||||
|
// if this vnode has an advisory_locking structure attached,
|
||||||
|
// lock that one and search for any colliding lock
|
||||||
|
struct advisory_locking *locking = get_advisory_locking(vnode);
|
||||||
|
sem_id waitForLock = -1;
|
||||||
|
|
||||||
|
if (locking != NULL) {
|
||||||
|
// test for collisions
|
||||||
|
struct advisory_lock *lock = NULL;
|
||||||
|
while ((lock = (struct advisory_lock *)list_get_next_item(&locking->locks, lock)) != NULL) {
|
||||||
|
if (lock->offset <= flock->l_start + flock->l_len
|
||||||
|
&& lock->offset + lock->length > flock->l_start) {
|
||||||
|
// locks do overlap
|
||||||
|
if (!shared || !lock->shared) {
|
||||||
|
// we need to wait
|
||||||
|
waitForLock = locking->wait_sem;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
put_advisory_locking(locking);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the lock if we have to, or else return immediately
|
||||||
|
|
||||||
|
if (waitForLock >= B_OK) {
|
||||||
|
if (!wait)
|
||||||
|
status = B_PERMISSION_DENIED;
|
||||||
|
else {
|
||||||
|
// ToDo: this is the standard condvar problem; there is a big race
|
||||||
|
// condition here: between here and the put_advisory_locking() call
|
||||||
|
// above, a release could already have happened!
|
||||||
|
status = acquire_sem_etc(waitForLock, 1, B_CAN_INTERRUPT, 0);
|
||||||
|
if (status == B_OK) {
|
||||||
|
// see if we're still colliding
|
||||||
|
goto restart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status < B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
// install new lock
|
||||||
|
|
||||||
|
mutex_lock(&sVnodeMutex);
|
||||||
|
|
||||||
|
locking = vnode->advisory_locking;
|
||||||
|
if (locking == NULL) {
|
||||||
|
status = create_advisory_locking(vnode);
|
||||||
|
locking = vnode->advisory_locking;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locking != NULL)
|
||||||
|
mutex_lock(&locking->mutex);
|
||||||
|
|
||||||
|
mutex_unlock(&sVnodeMutex);
|
||||||
|
|
||||||
|
if (status < B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct advisory_lock *lock = (struct advisory_lock *)malloc(sizeof(struct advisory_lock));
|
||||||
|
if (lock == NULL) {
|
||||||
|
if (waitForLock >= B_OK)
|
||||||
|
release_sem_etc(waitForLock, 1, B_RELEASE_ALL);
|
||||||
|
mutex_unlock(&locking->mutex);
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->team = team_get_current_team_id();
|
||||||
|
// values must already be normalized when getting here
|
||||||
|
lock->offset = flock->l_start;
|
||||||
|
lock->length = flock->l_len;
|
||||||
|
lock->shared = shared;
|
||||||
|
|
||||||
|
list_add_item(&locking->locks, lock);
|
||||||
|
mutex_unlock(&locking->mutex);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
normalize_flock(struct file_descriptor *descriptor, struct flock *flock)
|
||||||
|
{
|
||||||
|
switch (flock->l_whence) {
|
||||||
|
case SEEK_SET:
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
flock->l_start += descriptor->pos;
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
{
|
||||||
|
struct vnode *vnode = descriptor->u.vnode;
|
||||||
|
struct stat stat;
|
||||||
|
status_t status;
|
||||||
|
|
||||||
|
if (FS_CALL(vnode, read_stat) == NULL)
|
||||||
|
return EOPNOTSUPP;
|
||||||
|
|
||||||
|
status = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, &stat);
|
||||||
|
if (status < B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
flock->l_start += stat.st_size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flock->l_start < 0)
|
||||||
|
flock->l_start = 0;
|
||||||
|
if (flock->l_len == 0)
|
||||||
|
flock->l_len = OFF_MAX;
|
||||||
|
|
||||||
|
// don't let the offset and length overflow
|
||||||
|
if (flock->l_start > 0 && OFF_MAX - flock->l_start < flock->l_len)
|
||||||
|
flock->l_len = OFF_MAX - flock->l_start;
|
||||||
|
|
||||||
|
if (flock->l_len < 0) {
|
||||||
|
// a negative length reverses the region
|
||||||
|
flock->l_start += flock->l_len;
|
||||||
|
flock->l_len = -flock->l_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** \brief Resolves a mount point vnode to the volume root vnode it is covered
|
/** \brief Resolves a mount point vnode to the volume root vnode it is covered
|
||||||
* by.
|
* by.
|
||||||
*
|
*
|
||||||
@@ -823,6 +1103,7 @@ put_vnode(struct vnode *vnode)
|
|||||||
* \return The volume root vnode the vnode cover is covered by, if it is
|
* \return The volume root vnode the vnode cover is covered by, if it is
|
||||||
* indeed a mount point, or \c NULL otherwise.
|
* indeed a mount point, or \c NULL otherwise.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static struct vnode *
|
static struct vnode *
|
||||||
resolve_mount_point_to_volume_root(struct vnode *vnode)
|
resolve_mount_point_to_volume_root(struct vnode *vnode)
|
||||||
{
|
{
|
||||||
@@ -862,6 +1143,7 @@ resolve_mount_point_to_volume_root(struct vnode *vnode)
|
|||||||
* - \c B_OK, if everything went fine,
|
* - \c B_OK, if everything went fine,
|
||||||
* - another error code, if something went wrong.
|
* - another error code, if something went wrong.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
resolve_mount_point_to_volume_root(mount_id mountID, vnode_id nodeID,
|
resolve_mount_point_to_volume_root(mount_id mountID, vnode_id nodeID,
|
||||||
mount_id *resolvedMountID, vnode_id *resolvedNodeID)
|
mount_id *resolvedMountID, vnode_id *resolvedNodeID)
|
||||||
@@ -2771,14 +3053,19 @@ static status_t
|
|||||||
file_close(struct file_descriptor *descriptor)
|
file_close(struct file_descriptor *descriptor)
|
||||||
{
|
{
|
||||||
struct vnode *vnode = descriptor->u.vnode;
|
struct vnode *vnode = descriptor->u.vnode;
|
||||||
|
status_t status = B_OK;
|
||||||
|
|
||||||
FUNCTION(("file_close(descriptor = %p)\n", descriptor));
|
FUNCTION(("file_close(descriptor = %p)\n", descriptor));
|
||||||
|
|
||||||
cache_node_closed(vnode, FDTYPE_FILE, vnode->cache, vnode->device, vnode->id);
|
cache_node_closed(vnode, FDTYPE_FILE, vnode->cache, vnode->device, vnode->id);
|
||||||
if (FS_CALL(vnode, close))
|
if (FS_CALL(vnode, close))
|
||||||
return FS_CALL(vnode, close)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
|
status = FS_CALL(vnode, close)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
|
||||||
|
|
||||||
return B_OK;
|
if (status == B_OK) {
|
||||||
|
// remove all outstanding locks for this team
|
||||||
|
release_advisory_lock(vnode, NULL);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3110,6 +3397,7 @@ common_fcntl(int fd, int op, uint32 argument, bool kernel)
|
|||||||
{
|
{
|
||||||
struct file_descriptor *descriptor;
|
struct file_descriptor *descriptor;
|
||||||
struct vnode *vnode;
|
struct vnode *vnode;
|
||||||
|
struct flock flock;
|
||||||
status_t status;
|
status_t status;
|
||||||
|
|
||||||
FUNCTION(("common_fcntl(fd = %d, op = %d, argument = %lx, %s)\n",
|
FUNCTION(("common_fcntl(fd = %d, op = %d, argument = %lx, %s)\n",
|
||||||
@@ -3119,15 +3407,22 @@ common_fcntl(int fd, int op, uint32 argument, bool kernel)
|
|||||||
if (descriptor == NULL)
|
if (descriptor == NULL)
|
||||||
return B_FILE_ERROR;
|
return B_FILE_ERROR;
|
||||||
|
|
||||||
|
if (op == F_SETLK || op == F_SETLKW || op == F_GETLK) {
|
||||||
|
if (descriptor->type != FDTYPE_FILE)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
if (user_memcpy(&flock, (struct flock *)argument, sizeof(struct flock)) < B_OK)
|
||||||
|
return B_BAD_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case F_SETFD:
|
case F_SETFD:
|
||||||
// Set file descriptor flags
|
// Set file descriptor flags
|
||||||
|
|
||||||
// O_CLOEXEC is the only flag available at this time
|
// O_CLOEXEC is the only flag available at this time
|
||||||
if (argument == FD_CLOEXEC)
|
if (argument == FD_CLOEXEC)
|
||||||
descriptor->open_mode |= O_CLOEXEC;
|
atomic_or(&descriptor->open_mode, O_CLOEXEC);
|
||||||
else
|
else
|
||||||
descriptor->open_mode &= O_CLOEXEC;
|
atomic_and(&descriptor->open_mode, O_CLOEXEC);
|
||||||
|
|
||||||
status = B_OK;
|
status = B_OK;
|
||||||
break;
|
break;
|
||||||
@@ -3163,7 +3458,33 @@ common_fcntl(int fd, int op, uint32 argument, bool kernel)
|
|||||||
atomic_add(&descriptor->ref_count, 1);
|
atomic_add(&descriptor->ref_count, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ToDo: add support for more ops
|
case F_GETLK:
|
||||||
|
status = get_advisory_lock(descriptor->u.vnode, &flock);
|
||||||
|
if (status == B_OK) {
|
||||||
|
// copy back flock structure
|
||||||
|
status = user_memcpy((struct flock *)argument, &flock, sizeof(struct flock));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case F_SETLK:
|
||||||
|
case F_SETLKW:
|
||||||
|
status = normalize_flock(descriptor, &flock);
|
||||||
|
if (status < B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (flock.l_type == F_UNLCK)
|
||||||
|
status = release_advisory_lock(descriptor->u.vnode, &flock);
|
||||||
|
else {
|
||||||
|
// the open mode must match the lock type
|
||||||
|
if ((descriptor->open_mode & O_RWMASK) == O_RDONLY && flock.l_type == F_WRLCK
|
||||||
|
|| (descriptor->open_mode & O_RWMASK) == O_WRONLY && flock.l_type == F_RDLCK)
|
||||||
|
status = B_FILE_ERROR;
|
||||||
|
else
|
||||||
|
status = acquire_advisory_lock(descriptor->u.vnode, &flock, op == F_SETLKW);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ToDo: add support for more ops?
|
||||||
|
|
||||||
default:
|
default:
|
||||||
status = B_BAD_VALUE;
|
status = B_BAD_VALUE;
|
||||||
|
|||||||
Reference in New Issue
Block a user