Commit fd72faac95d7e47610e981d7ed7b3c1529e55c88
Committed by
Linus Torvalds
1 parent
31d40d74b4
Exists in
master
and in
7 other branches
[PATCH] FUSE: atomic create+open
This patch adds an atomic create+open operation. This does not yet work if the file type changes between lookup and create+open, but solves the permission checking problems for the separte create and open methods. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Showing 5 changed files with 214 additions and 50 deletions Side-by-side Diff
fs/fuse/dev.c
... | ... | @@ -184,6 +184,13 @@ |
184 | 184 | fuse_putback_request() */ |
185 | 185 | for (i = 1; i < FUSE_MAX_OUTSTANDING; i++) |
186 | 186 | up(&fc->outstanding_sem); |
187 | + } else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) { | |
188 | + /* Special case for failed iget in CREATE */ | |
189 | + u64 nodeid = req->in.h.nodeid; | |
190 | + __fuse_get_request(req); | |
191 | + fuse_reset_request(req); | |
192 | + fuse_send_forget(fc, req, nodeid, 1); | |
193 | + putback = 0; | |
187 | 194 | } |
188 | 195 | if (putback) |
189 | 196 | fuse_putback_request(fc, req); |
fs/fuse/dir.c
... | ... | @@ -13,6 +13,7 @@ |
13 | 13 | #include <linux/gfp.h> |
14 | 14 | #include <linux/sched.h> |
15 | 15 | #include <linux/namei.h> |
16 | +#include <linux/mount.h> | |
16 | 17 | |
17 | 18 | static inline unsigned long time_to_jiffies(unsigned long sec, |
18 | 19 | unsigned long nsec) |
... | ... | @@ -134,6 +135,101 @@ |
134 | 135 | entry->d_time = jiffies - 1; |
135 | 136 | } |
136 | 137 | |
138 | +static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, | |
139 | + struct nameidata *nd) | |
140 | +{ | |
141 | + int err; | |
142 | + struct inode *inode; | |
143 | + struct fuse_conn *fc = get_fuse_conn(dir); | |
144 | + struct fuse_req *req; | |
145 | + struct fuse_open_in inarg; | |
146 | + struct fuse_open_out outopen; | |
147 | + struct fuse_entry_out outentry; | |
148 | + struct fuse_inode *fi; | |
149 | + struct fuse_file *ff; | |
150 | + struct file *file; | |
151 | + int flags = nd->intent.open.flags - 1; | |
152 | + | |
153 | + err = -ENOSYS; | |
154 | + if (fc->no_create) | |
155 | + goto out; | |
156 | + | |
157 | + err = -ENAMETOOLONG; | |
158 | + if (entry->d_name.len > FUSE_NAME_MAX) | |
159 | + goto out; | |
160 | + | |
161 | + err = -EINTR; | |
162 | + req = fuse_get_request(fc); | |
163 | + if (!req) | |
164 | + goto out; | |
165 | + | |
166 | + ff = fuse_file_alloc(); | |
167 | + if (!ff) | |
168 | + goto out_put_request; | |
169 | + | |
170 | + flags &= ~O_NOCTTY; | |
171 | + memset(&inarg, 0, sizeof(inarg)); | |
172 | + inarg.flags = flags; | |
173 | + inarg.mode = mode; | |
174 | + req->in.h.opcode = FUSE_CREATE; | |
175 | + req->in.h.nodeid = get_node_id(dir); | |
176 | + req->inode = dir; | |
177 | + req->in.numargs = 2; | |
178 | + req->in.args[0].size = sizeof(inarg); | |
179 | + req->in.args[0].value = &inarg; | |
180 | + req->in.args[1].size = entry->d_name.len + 1; | |
181 | + req->in.args[1].value = entry->d_name.name; | |
182 | + req->out.numargs = 2; | |
183 | + req->out.args[0].size = sizeof(outentry); | |
184 | + req->out.args[0].value = &outentry; | |
185 | + req->out.args[1].size = sizeof(outopen); | |
186 | + req->out.args[1].value = &outopen; | |
187 | + request_send(fc, req); | |
188 | + err = req->out.h.error; | |
189 | + if (err) { | |
190 | + if (err == -ENOSYS) | |
191 | + fc->no_create = 1; | |
192 | + goto out_free_ff; | |
193 | + } | |
194 | + | |
195 | + err = -EIO; | |
196 | + if (!S_ISREG(outentry.attr.mode)) | |
197 | + goto out_free_ff; | |
198 | + | |
199 | + inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, | |
200 | + &outentry.attr); | |
201 | + err = -ENOMEM; | |
202 | + if (!inode) { | |
203 | + flags &= ~(O_CREAT | O_EXCL | O_TRUNC); | |
204 | + ff->fh = outopen.fh; | |
205 | + fuse_send_release(fc, ff, outentry.nodeid, NULL, flags, 0); | |
206 | + goto out_put_request; | |
207 | + } | |
208 | + fuse_put_request(fc, req); | |
209 | + entry->d_time = time_to_jiffies(outentry.entry_valid, | |
210 | + outentry.entry_valid_nsec); | |
211 | + fi = get_fuse_inode(inode); | |
212 | + fi->i_time = time_to_jiffies(outentry.attr_valid, | |
213 | + outentry.attr_valid_nsec); | |
214 | + | |
215 | + d_instantiate(entry, inode); | |
216 | + file = lookup_instantiate_filp(nd, entry, generic_file_open); | |
217 | + if (IS_ERR(file)) { | |
218 | + ff->fh = outopen.fh; | |
219 | + fuse_send_release(fc, ff, outentry.nodeid, inode, flags, 0); | |
220 | + return PTR_ERR(file); | |
221 | + } | |
222 | + fuse_finish_open(inode, file, ff, &outopen); | |
223 | + return 0; | |
224 | + | |
225 | + out_free_ff: | |
226 | + fuse_file_free(ff); | |
227 | + out_put_request: | |
228 | + fuse_put_request(fc, req); | |
229 | + out: | |
230 | + return err; | |
231 | +} | |
232 | + | |
137 | 233 | static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, |
138 | 234 | struct inode *dir, struct dentry *entry, |
139 | 235 | int mode) |
... | ... | @@ -208,6 +304,12 @@ |
208 | 304 | static int fuse_create(struct inode *dir, struct dentry *entry, int mode, |
209 | 305 | struct nameidata *nd) |
210 | 306 | { |
307 | + if (nd && (nd->flags & LOOKUP_CREATE)) { | |
308 | + int err = fuse_create_open(dir, entry, mode, nd); | |
309 | + if (err != -ENOSYS) | |
310 | + return err; | |
311 | + /* Fall back on mknod */ | |
312 | + } | |
211 | 313 | return fuse_mknod(dir, entry, mode, 0); |
212 | 314 | } |
213 | 315 | |
... | ... | @@ -767,7 +869,9 @@ |
767 | 869 | struct nameidata *nd) |
768 | 870 | { |
769 | 871 | struct inode *inode; |
770 | - int err = fuse_lookup_iget(dir, entry, &inode); | |
872 | + int err; | |
873 | + | |
874 | + err = fuse_lookup_iget(dir, entry, &inode); | |
771 | 875 | if (err) |
772 | 876 | return ERR_PTR(err); |
773 | 877 | if (inode && S_ISDIR(inode->i_mode)) { |
fs/fuse/file.c
... | ... | @@ -14,11 +14,69 @@ |
14 | 14 | |
15 | 15 | static struct file_operations fuse_direct_io_file_operations; |
16 | 16 | |
17 | -int fuse_open_common(struct inode *inode, struct file *file, int isdir) | |
17 | +static int fuse_send_open(struct inode *inode, struct file *file, int isdir, | |
18 | + struct fuse_open_out *outargp) | |
18 | 19 | { |
19 | 20 | struct fuse_conn *fc = get_fuse_conn(inode); |
20 | - struct fuse_req *req; | |
21 | 21 | struct fuse_open_in inarg; |
22 | + struct fuse_req *req; | |
23 | + int err; | |
24 | + | |
25 | + req = fuse_get_request(fc); | |
26 | + if (!req) | |
27 | + return -EINTR; | |
28 | + | |
29 | + memset(&inarg, 0, sizeof(inarg)); | |
30 | + inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); | |
31 | + req->in.h.opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN; | |
32 | + req->in.h.nodeid = get_node_id(inode); | |
33 | + req->inode = inode; | |
34 | + req->in.numargs = 1; | |
35 | + req->in.args[0].size = sizeof(inarg); | |
36 | + req->in.args[0].value = &inarg; | |
37 | + req->out.numargs = 1; | |
38 | + req->out.args[0].size = sizeof(*outargp); | |
39 | + req->out.args[0].value = outargp; | |
40 | + request_send(fc, req); | |
41 | + err = req->out.h.error; | |
42 | + fuse_put_request(fc, req); | |
43 | + | |
44 | + return err; | |
45 | +} | |
46 | + | |
47 | +struct fuse_file *fuse_file_alloc(void) | |
48 | +{ | |
49 | + struct fuse_file *ff; | |
50 | + ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL); | |
51 | + if (ff) { | |
52 | + ff->release_req = fuse_request_alloc(); | |
53 | + if (!ff->release_req) { | |
54 | + kfree(ff); | |
55 | + ff = NULL; | |
56 | + } | |
57 | + } | |
58 | + return ff; | |
59 | +} | |
60 | + | |
61 | +void fuse_file_free(struct fuse_file *ff) | |
62 | +{ | |
63 | + fuse_request_free(ff->release_req); | |
64 | + kfree(ff); | |
65 | +} | |
66 | + | |
67 | +void fuse_finish_open(struct inode *inode, struct file *file, | |
68 | + struct fuse_file *ff, struct fuse_open_out *outarg) | |
69 | +{ | |
70 | + if (outarg->open_flags & FOPEN_DIRECT_IO) | |
71 | + file->f_op = &fuse_direct_io_file_operations; | |
72 | + if (!(outarg->open_flags & FOPEN_KEEP_CACHE)) | |
73 | + invalidate_inode_pages(inode->i_mapping); | |
74 | + ff->fh = outarg->fh; | |
75 | + file->private_data = ff; | |
76 | +} | |
77 | + | |
78 | +int fuse_open_common(struct inode *inode, struct file *file, int isdir) | |
79 | +{ | |
22 | 80 | struct fuse_open_out outarg; |
23 | 81 | struct fuse_file *ff; |
24 | 82 | int err; |
25 | 83 | |
26 | 84 | |
27 | 85 | |
28 | 86 | |
29 | 87 | |
30 | 88 | |
31 | 89 | |
32 | 90 | |
33 | 91 | |
... | ... | @@ -34,73 +92,53 @@ |
34 | 92 | /* If opening the root node, no lookup has been performed on |
35 | 93 | it, so the attributes must be refreshed */ |
36 | 94 | if (get_node_id(inode) == FUSE_ROOT_ID) { |
37 | - int err = fuse_do_getattr(inode); | |
95 | + err = fuse_do_getattr(inode); | |
38 | 96 | if (err) |
39 | 97 | return err; |
40 | 98 | } |
41 | 99 | |
42 | - req = fuse_get_request(fc); | |
43 | - if (!req) | |
44 | - return -EINTR; | |
45 | - | |
46 | - err = -ENOMEM; | |
47 | - ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL); | |
100 | + ff = fuse_file_alloc(); | |
48 | 101 | if (!ff) |
49 | - goto out_put_request; | |
102 | + return -ENOMEM; | |
50 | 103 | |
51 | - ff->release_req = fuse_request_alloc(); | |
52 | - if (!ff->release_req) { | |
53 | - kfree(ff); | |
54 | - goto out_put_request; | |
104 | + err = fuse_send_open(inode, file, isdir, &outarg); | |
105 | + if (err) | |
106 | + fuse_file_free(ff); | |
107 | + else { | |
108 | + if (isdir) | |
109 | + outarg.open_flags &= ~FOPEN_DIRECT_IO; | |
110 | + fuse_finish_open(inode, file, ff, &outarg); | |
55 | 111 | } |
56 | 112 | |
57 | - memset(&inarg, 0, sizeof(inarg)); | |
58 | - inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); | |
59 | - req->in.h.opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN; | |
60 | - req->in.h.nodeid = get_node_id(inode); | |
61 | - req->inode = inode; | |
62 | - req->in.numargs = 1; | |
63 | - req->in.args[0].size = sizeof(inarg); | |
64 | - req->in.args[0].value = &inarg; | |
65 | - req->out.numargs = 1; | |
66 | - req->out.args[0].size = sizeof(outarg); | |
67 | - req->out.args[0].value = &outarg; | |
68 | - request_send(fc, req); | |
69 | - err = req->out.h.error; | |
70 | - if (err) { | |
71 | - fuse_request_free(ff->release_req); | |
72 | - kfree(ff); | |
73 | - } else { | |
74 | - if (!isdir && (outarg.open_flags & FOPEN_DIRECT_IO)) | |
75 | - file->f_op = &fuse_direct_io_file_operations; | |
76 | - if (!(outarg.open_flags & FOPEN_KEEP_CACHE)) | |
77 | - invalidate_inode_pages(inode->i_mapping); | |
78 | - ff->fh = outarg.fh; | |
79 | - file->private_data = ff; | |
80 | - } | |
81 | - | |
82 | - out_put_request: | |
83 | - fuse_put_request(fc, req); | |
84 | 113 | return err; |
85 | 114 | } |
86 | 115 | |
87 | -int fuse_release_common(struct inode *inode, struct file *file, int isdir) | |
116 | +void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff, | |
117 | + u64 nodeid, struct inode *inode, int flags, int isdir) | |
88 | 118 | { |
89 | - struct fuse_conn *fc = get_fuse_conn(inode); | |
90 | - struct fuse_file *ff = file->private_data; | |
91 | - struct fuse_req *req = ff->release_req; | |
119 | + struct fuse_req * req = ff->release_req; | |
92 | 120 | struct fuse_release_in *inarg = &req->misc.release_in; |
93 | 121 | |
94 | 122 | inarg->fh = ff->fh; |
95 | - inarg->flags = file->f_flags & ~O_EXCL; | |
123 | + inarg->flags = flags; | |
96 | 124 | req->in.h.opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE; |
97 | - req->in.h.nodeid = get_node_id(inode); | |
125 | + req->in.h.nodeid = nodeid; | |
98 | 126 | req->inode = inode; |
99 | 127 | req->in.numargs = 1; |
100 | 128 | req->in.args[0].size = sizeof(struct fuse_release_in); |
101 | 129 | req->in.args[0].value = inarg; |
102 | 130 | request_send_background(fc, req); |
103 | 131 | kfree(ff); |
132 | +} | |
133 | + | |
134 | +int fuse_release_common(struct inode *inode, struct file *file, int isdir) | |
135 | +{ | |
136 | + struct fuse_file *ff = file->private_data; | |
137 | + if (ff) { | |
138 | + struct fuse_conn *fc = get_fuse_conn(inode); | |
139 | + u64 nodeid = get_node_id(inode); | |
140 | + fuse_send_release(fc, ff, nodeid, inode, file->f_flags, isdir); | |
141 | + } | |
104 | 142 | |
105 | 143 | /* Return value is ignored by VFS */ |
106 | 144 | return 0; |
fs/fuse/fuse_i.h
... | ... | @@ -269,6 +269,9 @@ |
269 | 269 | /** Is access not implemented by fs? */ |
270 | 270 | unsigned no_access : 1; |
271 | 271 | |
272 | + /** Is create not implemented by fs? */ | |
273 | + unsigned no_create : 1; | |
274 | + | |
272 | 275 | /** Backing dev info */ |
273 | 276 | struct backing_dev_info bdi; |
274 | 277 | }; |
... | ... | @@ -339,6 +342,17 @@ |
339 | 342 | * Send OPEN or OPENDIR request |
340 | 343 | */ |
341 | 344 | int fuse_open_common(struct inode *inode, struct file *file, int isdir); |
345 | + | |
346 | +struct fuse_file *fuse_file_alloc(void); | |
347 | +void fuse_file_free(struct fuse_file *ff); | |
348 | +void fuse_finish_open(struct inode *inode, struct file *file, | |
349 | + struct fuse_file *ff, struct fuse_open_out *outarg); | |
350 | + | |
351 | +/** | |
352 | + * Send a RELEASE request | |
353 | + */ | |
354 | +void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff, | |
355 | + u64 nodeid, struct inode *inode, int flags, int isdir); | |
342 | 356 | |
343 | 357 | /** |
344 | 358 | * Send RELEASE or RELEASEDIR request |
include/linux/fuse.h
... | ... | @@ -100,7 +100,8 @@ |
100 | 100 | FUSE_READDIR = 28, |
101 | 101 | FUSE_RELEASEDIR = 29, |
102 | 102 | FUSE_FSYNCDIR = 30, |
103 | - FUSE_ACCESS = 34 | |
103 | + FUSE_ACCESS = 34, | |
104 | + FUSE_CREATE = 35 | |
104 | 105 | }; |
105 | 106 | |
106 | 107 | /* Conservative buffer size for the client */ |
... | ... | @@ -158,7 +159,7 @@ |
158 | 159 | |
159 | 160 | struct fuse_open_in { |
160 | 161 | __u32 flags; |
161 | - __u32 padding; | |
162 | + __u32 mode; | |
162 | 163 | }; |
163 | 164 | |
164 | 165 | struct fuse_open_out { |