27 марта 2012 г. 15:36 пользователь Pavel Shilovsky
<[email protected]> написал:
> We can deadlock if we have a write oplock and two processes
> use the same file handle. In this case the first process can't
> unlock its lock if another process blocked on the lock in the
> same time.
>
> Fix this by removing lock_mutex protection from waiting on a
> blocked lock and protect only posix_lock_file call.
>
> Cc: [email protected]
> Signed-off-by: Pavel Shilovsky <[email protected]>
> ---
> fs/cifs/file.c | 56
> ++++++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 files changed, 52 insertions(+), 4 deletions(-)
>
> diff --git a/fs/cifs/file.c b/fs/cifs/file.c
> index 159fcc5..2bf04e9 100644
> --- a/fs/cifs/file.c
> +++ b/fs/cifs/file.c
> @@ -671,6 +671,21 @@ cifs_del_lock_waiters(struct cifsLockInfo *lock)
> }
> }
>
> +/*
> + * Copied from fs/locks.c with small changes.
> + * Remove waiter from blocker's block list.
> + * When blocker ends up pointing to itself then the list is empty.
> + */
> +static void
> +cifs_locks_delete_block(struct file_lock *waiter)
> +{
> + lock_flocks();
> + list_del_init(&waiter->fl_block);
> + list_del_init(&waiter->fl_link);
> + waiter->fl_next = NULL;
> + unlock_flocks();
> +}
> +
> static bool
> __cifs_find_lock_conflict(struct cifsInodeInfo *cinode, __u64 offset,
> __u64 length, __u8 type, __u16 netfid,
> @@ -820,6 +835,39 @@ cifs_posix_lock_test(struct file *file, struct file_lock
> *flock)
> return rc;
> }
>
> +/* Called with locked lock_mutex, return with unlocked. */
> +static int
> +cifs_posix_lock_file_wait_locked(struct file *file, struct file_lock *flock)
> +{
> + struct cifsInodeInfo *cinode = CIFS_I(file->f_path.dentry->d_inode);
> + int rc;
> +
> + while (true) {
> + rc = posix_lock_file(file, flock, NULL);
> + mutex_unlock(&cinode->lock_mutex);
> + if (rc != FILE_LOCK_DEFERRED)
> + break;
> + rc = wait_event_interruptible(flock->fl_wait,
> !flock->fl_next);
> + if (!rc) {
> + mutex_lock(&cinode->lock_mutex);
> + continue;
> + }
> + cifs_locks_delete_block(flock);
> + break;
> + }
> + return rc;
> +}
> +
> +static int
> +cifs_posix_lock_file_wait(struct file *file, struct file_lock *flock)
> +{
> + struct cifsInodeInfo *cinode = CIFS_I(file->f_path.dentry->d_inode);
> +
> + mutex_lock(&cinode->lock_mutex);
> + /* lock_mutex will be released by the function below */
> + return cifs_posix_lock_file_wait_locked(file, flock);
> +}
> +
> /*
> * Set the byte-range lock (posix style). Returns:
> * 1) 0, if we set the lock and don't need to request to the server;
> @@ -840,9 +888,9 @@ cifs_posix_lock_set(struct file *file, struct file_lock
> *flock)
> mutex_unlock(&cinode->lock_mutex);
> return rc;
> }
> - rc = posix_lock_file_wait(file, flock);
> - mutex_unlock(&cinode->lock_mutex);
> - return rc;
> +
> + /* lock_mutex will be released by the function below */
> + return cifs_posix_lock_file_wait_locked(file, flock);
> }
>
> static int
> @@ -1338,7 +1386,7 @@ cifs_setlk(struct file *file, struct file_lock *flock,
> __u8 type,
>
> out:
> if (flock->fl_flags & FL_POSIX)
> - posix_lock_file_wait(file, flock);
> + cifs_posix_lock_file_wait(file, flock);
> return rc;
> }
>
> --
> 1.7.1
>
Include the posix_lock_bug.c file that reproduces the problem.
--
Best regards,
Pavel Shilovsky.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int args, char **argv) {
int fd, rc, pid;
struct flock flock;
char *name;
if (args == 2)
name = argv[1];
else {
perror("no file name\n");
return 1;
}
fd = open(name, O_CREAT | O_RDWR);
if (!fd) {
perror("can't open a file\n");
return 1;
}
flock.l_start = 0;
flock.l_len = 1;
flock.l_type = F_WRLCK;
flock.l_whence = 0;
rc = fcntl(fd, F_SETLK, &flock);
if (rc) {
perror("can't lock\n");
return 1;
}
pid = fork();
if (pid) {
/* parent process */
printf("parent process started\n");
sleep(2);
flock.l_type = F_UNLCK;
rc = fcntl(fd, F_SETLK, &flock);
if (rc) {
perror("can't lock\n");
return 1;
}
printf("parent process ended\n");
} else {
/* child process */
printf("child process started\n");
rc = fcntl(fd, F_SETLKW, &flock);
if (rc) {
perror("can't lock\n");
return 1;
}
printf("child process ended\n");
}
return 0;
}