Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 8255
b: refs/heads/master
c: 1e9a4ed
h: refs/heads/master
i:
  8253: 7776c1d
  8251: a2e7a57
  8247: f6893b6
  8239: 7a6fded
  8223: b68a5f1
  8191: 08f97ba
v: v3
  • Loading branch information
Miklos Szeredi authored and Linus Torvalds committed Sep 9, 2005
1 parent 342c918 commit b7201ac
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 68 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: b6aeadeda22a9aa322fdfcd3f4c69ccf0da5cbdd
refs/heads/master: 1e9a4ed9396e9c31139721b639550ffb1df17065
78 changes: 59 additions & 19 deletions trunk/fs/fuse/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ static inline struct fuse_conn *fuse_get_conn(struct file *file)
struct fuse_conn *fc;
spin_lock(&fuse_lock);
fc = file->private_data;
if (fc && !fc->sb)
if (fc && !fc->mounted)
fc = NULL;
spin_unlock(&fuse_lock);
return fc;
Expand Down Expand Up @@ -148,6 +148,17 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
fuse_putback_request(fc, req);
}

void fuse_release_background(struct fuse_req *req)
{
iput(req->inode);
iput(req->inode2);
if (req->file)
fput(req->file);
spin_lock(&fuse_lock);
list_del(&req->bg_entry);
spin_unlock(&fuse_lock);
}

/*
* This function is called when a request is finished. Either a reply
* has arrived or it was interrupted (and not yet sent) or some error
Expand All @@ -166,12 +177,10 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
putback = atomic_dec_and_test(&req->count);
spin_unlock(&fuse_lock);
if (req->background) {
if (req->inode)
iput(req->inode);
if (req->inode2)
iput(req->inode2);
if (req->file)
fput(req->file);
down_read(&fc->sbput_sem);
if (fc->mounted)
fuse_release_background(req);
up_read(&fc->sbput_sem);
}
wake_up(&req->waitq);
if (req->in.h.opcode == FUSE_INIT) {
Expand All @@ -191,11 +200,39 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
fuse_putback_request(fc, req);
}

static void background_request(struct fuse_req *req)
/*
* Unfortunately request interruption not just solves the deadlock
* problem, it causes problems too. These stem from the fact, that an
* interrupted request is continued to be processed in userspace,
* while all the locks and object references (inode and file) held
* during the operation are released.
*
* To release the locks is exactly why there's a need to interrupt the
* request, so there's not a lot that can be done about this, except
* introduce additional locking in userspace.
*
* More important is to keep inode and file references until userspace
* has replied, otherwise FORGET and RELEASE could be sent while the
* inode/file is still used by the filesystem.
*
* For this reason the concept of "background" request is introduced.
* An interrupted request is backgrounded if it has been already sent
* to userspace. Backgrounding involves getting an extra reference to
* inode(s) or file used in the request, and adding the request to
* fc->background list. When a reply is received for a background
* request, the object references are released, and the request is
* removed from the list. If the filesystem is unmounted while there
* are still background requests, the list is walked and references
* are released as if a reply was received.
*
* There's one more use for a background request. The RELEASE message is
* always sent as background, since it doesn't return an error or
* data.
*/
static void background_request(struct fuse_conn *fc, struct fuse_req *req)
{
/* Need to get hold of the inode(s) and/or file used in the
request, so FORGET and RELEASE are not sent too early */
req->background = 1;
list_add(&req->bg_entry, &fc->background);
if (req->inode)
req->inode = igrab(req->inode);
if (req->inode2)
Expand All @@ -215,7 +252,8 @@ static int request_wait_answer_nonint(struct fuse_req *req)
}

/* Called with fuse_lock held. Releases, and then reacquires it. */
static void request_wait_answer(struct fuse_req *req, int interruptible)
static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req,
int interruptible)
{
int intr;

Expand Down Expand Up @@ -255,7 +293,7 @@ static void request_wait_answer(struct fuse_req *req, int interruptible)
list_del(&req->list);
__fuse_put_request(req);
} else if (!req->finished && req->sent)
background_request(req);
background_request(fc, req);
}

static unsigned len_args(unsigned numargs, struct fuse_arg *args)
Expand Down Expand Up @@ -297,7 +335,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
{
req->isreply = 1;
spin_lock(&fuse_lock);
if (!fc->file)
if (!fc->connected)
req->out.h.error = -ENOTCONN;
else if (fc->conn_error)
req->out.h.error = -ECONNREFUSED;
Expand All @@ -307,7 +345,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
after request_end() */
__fuse_get_request(req);

request_wait_answer(req, interruptible);
request_wait_answer(fc, req, interruptible);
}
spin_unlock(&fuse_lock);
}
Expand All @@ -330,7 +368,7 @@ void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req)
static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
{
spin_lock(&fuse_lock);
if (fc->file) {
if (fc->connected) {
queue_request(fc, req);
spin_unlock(&fuse_lock);
} else {
Expand All @@ -348,7 +386,9 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
void request_send_background(struct fuse_conn *fc, struct fuse_req *req)
{
req->isreply = 1;
background_request(req);
spin_lock(&fuse_lock);
background_request(fc, req);
spin_unlock(&fuse_lock);
request_send_nowait(fc, req);
}

Expand Down Expand Up @@ -583,7 +623,7 @@ static void request_wait(struct fuse_conn *fc)
DECLARE_WAITQUEUE(wait, current);

add_wait_queue_exclusive(&fc->waitq, &wait);
while (fc->sb && list_empty(&fc->pending)) {
while (fc->mounted && list_empty(&fc->pending)) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current))
break;
Expand Down Expand Up @@ -622,7 +662,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
goto err_unlock;
request_wait(fc);
err = -ENODEV;
if (!fc->sb)
if (!fc->mounted)
goto err_unlock;
err = -ERESTARTSYS;
if (list_empty(&fc->pending))
Expand Down Expand Up @@ -839,7 +879,7 @@ static int fuse_dev_release(struct inode *inode, struct file *file)
spin_lock(&fuse_lock);
fc = file->private_data;
if (fc) {
fc->file = NULL;
fc->connected = 0;
end_requests(fc, &fc->pending);
end_requests(fc, &fc->processing);
fuse_release_conn(fc);
Expand Down
35 changes: 32 additions & 3 deletions trunk/fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ static int fuse_revalidate(struct dentry *entry)
struct fuse_conn *fc = get_fuse_conn(inode);

if (get_node_id(inode) == FUSE_ROOT_ID) {
if (current->fsuid != fc->user_id)
if (!(fc->flags & FUSE_ALLOW_OTHER) &&
current->fsuid != fc->user_id)
return -EACCES;
} else if (time_before_eq(jiffies, fi->i_time))
return 0;
Expand All @@ -430,9 +431,31 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd)
{
struct fuse_conn *fc = get_fuse_conn(inode);

if (current->fsuid != fc->user_id)
if (!(fc->flags & FUSE_ALLOW_OTHER) && current->fsuid != fc->user_id)
return -EACCES;
else {
else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
int err = generic_permission(inode, mask, NULL);

/* If permission is denied, try to refresh file
attributes. This is also needed, because the root
node will at first have no permissions */
if (err == -EACCES) {
err = fuse_do_getattr(inode);
if (!err)
err = generic_permission(inode, mask, NULL);
}

/* FIXME: Need some mechanism to revoke permissions:
currently if the filesystem suddenly changes the
file mode, we will not be informed about it, and
continue to allow access to the file/directory.
This is actually not so grave, since the user can
simply keep access to the file/directory anyway by
keeping it open... */

return err;
} else {
int mode = inode->i_mode;
if ((mask & MAY_WRITE) && IS_RDONLY(inode) &&
(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
Expand Down Expand Up @@ -636,6 +659,12 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
int err;
int is_truncate = 0;

if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
err = inode_change_ok(inode, attr);
if (err)
return err;
}

if (attr->ia_valid & ATTR_SIZE) {
unsigned long limit;
is_truncate = 1;
Expand Down
2 changes: 1 addition & 1 deletion trunk/fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static int fuse_open(struct inode *inode, struct file *file)
else
request_send(fc, req);
err = req->out.h.error;
if (!err)
if (!err && !(fc->flags & FUSE_KERNEL_CACHE))
invalidate_inode_pages(inode->i_mapping);
if (err) {
fuse_request_free(ff->release_req);
Expand Down
45 changes: 40 additions & 5 deletions trunk/fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
/** If more requests are outstanding, then the operation will block */
#define FUSE_MAX_OUTSTANDING 10

/** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem
module will check permissions based on the file mode. Otherwise no
permission checking is done in the kernel */
#define FUSE_DEFAULT_PERMISSIONS (1 << 0)

/** If the FUSE_ALLOW_OTHER flag is given, then not only the user
doing the mount will be allowed to access the filesystem */
#define FUSE_ALLOW_OTHER (1 << 1)

/** If the FUSE_KERNEL_CACHE flag is given, then cached data will not
be flushed on open */
#define FUSE_KERNEL_CACHE (1 << 2)

/** FUSE inode */
struct fuse_inode {
/** Inode data */
Expand Down Expand Up @@ -109,6 +122,9 @@ struct fuse_req {
lists in fuse_conn */
struct list_head list;

/** Entry on the background list */
struct list_head bg_entry;

/** refcount */
atomic_t count;

Expand Down Expand Up @@ -176,15 +192,15 @@ struct fuse_req {
* unmounted.
*/
struct fuse_conn {
/** The superblock of the mounted filesystem */
struct super_block *sb;

/** The opened client device */
struct file *file;
/** Reference count */
int count;

/** The user id for this mount */
uid_t user_id;

/** The fuse mount flags for this mount */
unsigned flags;

/** Readers of the connection are waiting on this */
wait_queue_head_t waitq;

Expand All @@ -194,19 +210,32 @@ struct fuse_conn {
/** The list of requests being processed */
struct list_head processing;

/** Requests put in the background (RELEASE or any other
interrupted request) */
struct list_head background;

/** Controls the maximum number of outstanding requests */
struct semaphore outstanding_sem;

/** This counts the number of outstanding requests if
outstanding_sem would go negative */
unsigned outstanding_debt;

/** RW semaphore for exclusion with fuse_put_super() */
struct rw_semaphore sbput_sem;

/** The list of unused requests */
struct list_head unused_list;

/** The next unique request id */
u64 reqctr;

/** Mount is active */
unsigned mounted : 1;

/** Connection established */
unsigned connected : 1;

/** Connection failed (version mismatch) */
unsigned conn_error : 1;

Expand Down Expand Up @@ -261,6 +290,7 @@ extern struct file_operations fuse_dev_operations;
* - the private_data field of the device file
* - the s_fs_info field of the super block
* - unused_list, pending, processing lists in fuse_conn
* - background list in fuse_conn
* - the unique request ID counter reqctr in fuse_conn
* - the sb (super_block) field in fuse_conn
* - the file (device file) field in fuse_conn
Expand Down Expand Up @@ -371,6 +401,11 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req);
*/
void request_send_background(struct fuse_conn *fc, struct fuse_req *req);

/**
* Release inodes and file assiciated with background request
*/
void fuse_release_background(struct fuse_req *req);

/**
* Get the attributes of a file
*/
Expand Down
Loading

0 comments on commit b7201ac

Please sign in to comment.