1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * VirtualBox Guest Shared Folders support: Directory inode and file operations |
4 | * |
5 | * Copyright (C) 2006-2018 Oracle Corporation |
6 | */ |
7 | |
8 | #include <linux/namei.h> |
9 | #include <linux/vbox_utils.h> |
10 | #include "vfsmod.h" |
11 | |
12 | static int vboxsf_dir_open(struct inode *inode, struct file *file) |
13 | { |
14 | struct vboxsf_sbi *sbi = VBOXSF_SBI(inode->i_sb); |
15 | struct shfl_createparms params = {}; |
16 | struct vboxsf_dir_info *sf_d; |
17 | int err; |
18 | |
19 | sf_d = vboxsf_dir_info_alloc(); |
20 | if (!sf_d) |
21 | return -ENOMEM; |
22 | |
23 | params.handle = SHFL_HANDLE_NIL; |
24 | params.create_flags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_OPEN_IF_EXISTS | |
25 | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ; |
26 | |
27 | err = vboxsf_create_at_dentry(dentry: file_dentry(file), params: ¶ms); |
28 | if (err) |
29 | goto err_free_dir_info; |
30 | |
31 | if (params.result != SHFL_FILE_EXISTS) { |
32 | err = -ENOENT; |
33 | goto err_close; |
34 | } |
35 | |
36 | err = vboxsf_dir_read_all(sbi, sf_d, handle: params.handle); |
37 | if (err) |
38 | goto err_close; |
39 | |
40 | vboxsf_close(root: sbi->root, handle: params.handle); |
41 | file->private_data = sf_d; |
42 | return 0; |
43 | |
44 | err_close: |
45 | vboxsf_close(root: sbi->root, handle: params.handle); |
46 | err_free_dir_info: |
47 | vboxsf_dir_info_free(p: sf_d); |
48 | return err; |
49 | } |
50 | |
51 | static int vboxsf_dir_release(struct inode *inode, struct file *file) |
52 | { |
53 | if (file->private_data) |
54 | vboxsf_dir_info_free(p: file->private_data); |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static unsigned int vboxsf_get_d_type(u32 mode) |
60 | { |
61 | unsigned int d_type; |
62 | |
63 | switch (mode & SHFL_TYPE_MASK) { |
64 | case SHFL_TYPE_FIFO: |
65 | d_type = DT_FIFO; |
66 | break; |
67 | case SHFL_TYPE_DEV_CHAR: |
68 | d_type = DT_CHR; |
69 | break; |
70 | case SHFL_TYPE_DIRECTORY: |
71 | d_type = DT_DIR; |
72 | break; |
73 | case SHFL_TYPE_DEV_BLOCK: |
74 | d_type = DT_BLK; |
75 | break; |
76 | case SHFL_TYPE_FILE: |
77 | d_type = DT_REG; |
78 | break; |
79 | case SHFL_TYPE_SYMLINK: |
80 | d_type = DT_LNK; |
81 | break; |
82 | case SHFL_TYPE_SOCKET: |
83 | d_type = DT_SOCK; |
84 | break; |
85 | case SHFL_TYPE_WHITEOUT: |
86 | d_type = DT_WHT; |
87 | break; |
88 | default: |
89 | d_type = DT_UNKNOWN; |
90 | break; |
91 | } |
92 | return d_type; |
93 | } |
94 | |
95 | static bool vboxsf_dir_emit(struct file *dir, struct dir_context *ctx) |
96 | { |
97 | struct vboxsf_sbi *sbi = VBOXSF_SBI(file_inode(dir)->i_sb); |
98 | struct vboxsf_dir_info *sf_d = dir->private_data; |
99 | struct shfl_dirinfo *info; |
100 | struct vboxsf_dir_buf *b; |
101 | unsigned int d_type; |
102 | loff_t i, cur = 0; |
103 | ino_t fake_ino; |
104 | void *end; |
105 | int err; |
106 | |
107 | list_for_each_entry(b, &sf_d->info_list, head) { |
108 | try_next_entry: |
109 | if (ctx->pos >= cur + b->entries) { |
110 | cur += b->entries; |
111 | continue; |
112 | } |
113 | |
114 | /* |
115 | * Note the vboxsf_dir_info objects we are iterating over here |
116 | * are variable sized, so the info pointer may end up being |
117 | * unaligned. This is how we get the data from the host. |
118 | * Since vboxsf is only supported on x86 machines this is not |
119 | * a problem. |
120 | */ |
121 | for (i = 0, info = b->buf; i < ctx->pos - cur; i++) { |
122 | end = &info->name.string.utf8[info->name.size]; |
123 | /* Only happens if the host gives us corrupt data */ |
124 | if (WARN_ON(end > (b->buf + b->used))) |
125 | return false; |
126 | info = end; |
127 | } |
128 | |
129 | end = &info->name.string.utf8[info->name.size]; |
130 | if (WARN_ON(end > (b->buf + b->used))) |
131 | return false; |
132 | |
133 | /* Info now points to the right entry, emit it. */ |
134 | d_type = vboxsf_get_d_type(mode: info->info.attr.mode); |
135 | |
136 | /* |
137 | * On 32-bit systems pos is 64-bit signed, while ino is 32-bit |
138 | * unsigned so fake_ino may overflow, check for this. |
139 | */ |
140 | if ((ino_t)(ctx->pos + 1) != (u64)(ctx->pos + 1)) { |
141 | vbg_err(fmt: "vboxsf: fake ino overflow, truncating dir\n" ); |
142 | return false; |
143 | } |
144 | fake_ino = ctx->pos + 1; |
145 | |
146 | if (sbi->nls) { |
147 | char d_name[NAME_MAX]; |
148 | |
149 | err = vboxsf_nlscpy(sbi, name: d_name, NAME_MAX, |
150 | utf8_name: info->name.string.utf8, |
151 | utf8_len: info->name.length); |
152 | if (err) { |
153 | /* skip erroneous entry and proceed */ |
154 | ctx->pos += 1; |
155 | goto try_next_entry; |
156 | } |
157 | |
158 | return dir_emit(ctx, name: d_name, strlen(d_name), |
159 | ino: fake_ino, type: d_type); |
160 | } |
161 | |
162 | return dir_emit(ctx, name: info->name.string.utf8, namelen: info->name.length, |
163 | ino: fake_ino, type: d_type); |
164 | } |
165 | |
166 | return false; |
167 | } |
168 | |
169 | static int vboxsf_dir_iterate(struct file *dir, struct dir_context *ctx) |
170 | { |
171 | bool emitted; |
172 | |
173 | do { |
174 | emitted = vboxsf_dir_emit(dir, ctx); |
175 | if (emitted) |
176 | ctx->pos += 1; |
177 | } while (emitted); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | WRAP_DIR_ITER(vboxsf_dir_iterate) // FIXME! |
183 | const struct file_operations vboxsf_dir_fops = { |
184 | .open = vboxsf_dir_open, |
185 | .iterate_shared = shared_vboxsf_dir_iterate, |
186 | .release = vboxsf_dir_release, |
187 | .read = generic_read_dir, |
188 | .llseek = generic_file_llseek, |
189 | }; |
190 | |
191 | /* |
192 | * This is called during name resolution/lookup to check if the @dentry in |
193 | * the cache is still valid. the job is handled by vboxsf_inode_revalidate. |
194 | */ |
195 | static int vboxsf_dentry_revalidate(struct dentry *dentry, unsigned int flags) |
196 | { |
197 | if (flags & LOOKUP_RCU) |
198 | return -ECHILD; |
199 | |
200 | if (d_really_is_positive(dentry)) |
201 | return vboxsf_inode_revalidate(dentry) == 0; |
202 | else |
203 | return vboxsf_stat_dentry(dentry, NULL) == -ENOENT; |
204 | } |
205 | |
206 | const struct dentry_operations vboxsf_dentry_ops = { |
207 | .d_revalidate = vboxsf_dentry_revalidate |
208 | }; |
209 | |
210 | /* iops */ |
211 | |
212 | static struct dentry *vboxsf_dir_lookup(struct inode *parent, |
213 | struct dentry *dentry, |
214 | unsigned int flags) |
215 | { |
216 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
217 | struct shfl_fsobjinfo fsinfo; |
218 | struct inode *inode; |
219 | int err; |
220 | |
221 | dentry->d_time = jiffies; |
222 | |
223 | err = vboxsf_stat_dentry(dentry, info: &fsinfo); |
224 | if (err) { |
225 | inode = (err == -ENOENT) ? NULL : ERR_PTR(error: err); |
226 | } else { |
227 | inode = vboxsf_new_inode(sb: parent->i_sb); |
228 | if (!IS_ERR(ptr: inode)) |
229 | vboxsf_init_inode(sbi, inode, info: &fsinfo, reinit: false); |
230 | } |
231 | |
232 | return d_splice_alias(inode, dentry); |
233 | } |
234 | |
235 | static int vboxsf_dir_instantiate(struct inode *parent, struct dentry *dentry, |
236 | struct shfl_fsobjinfo *info) |
237 | { |
238 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
239 | struct vboxsf_inode *sf_i; |
240 | struct inode *inode; |
241 | |
242 | inode = vboxsf_new_inode(sb: parent->i_sb); |
243 | if (IS_ERR(ptr: inode)) |
244 | return PTR_ERR(ptr: inode); |
245 | |
246 | sf_i = VBOXSF_I(inode); |
247 | /* The host may have given us different attr then requested */ |
248 | sf_i->force_restat = 1; |
249 | vboxsf_init_inode(sbi, inode, info, reinit: false); |
250 | |
251 | d_instantiate(dentry, inode); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int vboxsf_dir_create(struct inode *parent, struct dentry *dentry, |
257 | umode_t mode, bool is_dir, bool excl, u64 *handle_ret) |
258 | { |
259 | struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent); |
260 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
261 | struct shfl_createparms params = {}; |
262 | int err; |
263 | |
264 | params.handle = SHFL_HANDLE_NIL; |
265 | params.create_flags = SHFL_CF_ACT_CREATE_IF_NEW | SHFL_CF_ACCESS_READWRITE; |
266 | if (is_dir) |
267 | params.create_flags |= SHFL_CF_DIRECTORY; |
268 | if (excl) |
269 | params.create_flags |= SHFL_CF_ACT_FAIL_IF_EXISTS; |
270 | |
271 | params.info.attr.mode = (mode & 0777) | |
272 | (is_dir ? SHFL_TYPE_DIRECTORY : SHFL_TYPE_FILE); |
273 | params.info.attr.additional = SHFLFSOBJATTRADD_NOTHING; |
274 | |
275 | err = vboxsf_create_at_dentry(dentry, params: ¶ms); |
276 | if (err) |
277 | return err; |
278 | |
279 | if (params.result != SHFL_FILE_CREATED) |
280 | return -EPERM; |
281 | |
282 | err = vboxsf_dir_instantiate(parent, dentry, info: ¶ms.info); |
283 | if (err) |
284 | goto out; |
285 | |
286 | /* parent directory access/change time changed */ |
287 | sf_parent_i->force_restat = 1; |
288 | |
289 | out: |
290 | if (err == 0 && handle_ret) |
291 | *handle_ret = params.handle; |
292 | else |
293 | vboxsf_close(root: sbi->root, handle: params.handle); |
294 | |
295 | return err; |
296 | } |
297 | |
298 | static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, |
299 | struct inode *parent, struct dentry *dentry, |
300 | umode_t mode, bool excl) |
301 | { |
302 | return vboxsf_dir_create(parent, dentry, mode, is_dir: false, excl, NULL); |
303 | } |
304 | |
305 | static int vboxsf_dir_mkdir(struct mnt_idmap *idmap, |
306 | struct inode *parent, struct dentry *dentry, |
307 | umode_t mode) |
308 | { |
309 | return vboxsf_dir_create(parent, dentry, mode, is_dir: true, excl: true, NULL); |
310 | } |
311 | |
312 | static int vboxsf_dir_atomic_open(struct inode *parent, struct dentry *dentry, |
313 | struct file *file, unsigned int flags, umode_t mode) |
314 | { |
315 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
316 | struct vboxsf_handle *sf_handle; |
317 | struct dentry *res = NULL; |
318 | u64 handle; |
319 | int err; |
320 | |
321 | if (d_in_lookup(dentry)) { |
322 | res = vboxsf_dir_lookup(parent, dentry, flags: 0); |
323 | if (IS_ERR(ptr: res)) |
324 | return PTR_ERR(ptr: res); |
325 | |
326 | if (res) |
327 | dentry = res; |
328 | } |
329 | |
330 | /* Only creates */ |
331 | if (!(flags & O_CREAT) || d_really_is_positive(dentry)) |
332 | return finish_no_open(file, dentry: res); |
333 | |
334 | err = vboxsf_dir_create(parent, dentry, mode, is_dir: false, excl: flags & O_EXCL, handle_ret: &handle); |
335 | if (err) |
336 | goto out; |
337 | |
338 | sf_handle = vboxsf_create_sf_handle(inode: d_inode(dentry), handle, SHFL_CF_ACCESS_READWRITE); |
339 | if (IS_ERR(ptr: sf_handle)) { |
340 | vboxsf_close(root: sbi->root, handle); |
341 | err = PTR_ERR(ptr: sf_handle); |
342 | goto out; |
343 | } |
344 | |
345 | err = finish_open(file, dentry, open: generic_file_open); |
346 | if (err) { |
347 | /* This also closes the handle passed to vboxsf_create_sf_handle() */ |
348 | vboxsf_release_sf_handle(inode: d_inode(dentry), sf_handle); |
349 | goto out; |
350 | } |
351 | |
352 | file->private_data = sf_handle; |
353 | file->f_mode |= FMODE_CREATED; |
354 | out: |
355 | dput(res); |
356 | return err; |
357 | } |
358 | |
359 | static int vboxsf_dir_unlink(struct inode *parent, struct dentry *dentry) |
360 | { |
361 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
362 | struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent); |
363 | struct inode *inode = d_inode(dentry); |
364 | struct shfl_string *path; |
365 | u32 flags; |
366 | int err; |
367 | |
368 | if (S_ISDIR(inode->i_mode)) |
369 | flags = SHFL_REMOVE_DIR; |
370 | else |
371 | flags = SHFL_REMOVE_FILE; |
372 | |
373 | if (S_ISLNK(inode->i_mode)) |
374 | flags |= SHFL_REMOVE_SYMLINK; |
375 | |
376 | path = vboxsf_path_from_dentry(sbi, dentry); |
377 | if (IS_ERR(ptr: path)) |
378 | return PTR_ERR(ptr: path); |
379 | |
380 | err = vboxsf_remove(root: sbi->root, parsed_path: path, flags); |
381 | __putname(path); |
382 | if (err) |
383 | return err; |
384 | |
385 | /* parent directory access/change time changed */ |
386 | sf_parent_i->force_restat = 1; |
387 | |
388 | return 0; |
389 | } |
390 | |
391 | static int vboxsf_dir_rename(struct mnt_idmap *idmap, |
392 | struct inode *old_parent, |
393 | struct dentry *old_dentry, |
394 | struct inode *new_parent, |
395 | struct dentry *new_dentry, |
396 | unsigned int flags) |
397 | { |
398 | struct vboxsf_sbi *sbi = VBOXSF_SBI(old_parent->i_sb); |
399 | struct vboxsf_inode *sf_old_parent_i = VBOXSF_I(old_parent); |
400 | struct vboxsf_inode *sf_new_parent_i = VBOXSF_I(new_parent); |
401 | u32 shfl_flags = SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS; |
402 | struct shfl_string *old_path, *new_path; |
403 | int err; |
404 | |
405 | if (flags) |
406 | return -EINVAL; |
407 | |
408 | old_path = vboxsf_path_from_dentry(sbi, dentry: old_dentry); |
409 | if (IS_ERR(ptr: old_path)) |
410 | return PTR_ERR(ptr: old_path); |
411 | |
412 | new_path = vboxsf_path_from_dentry(sbi, dentry: new_dentry); |
413 | if (IS_ERR(ptr: new_path)) { |
414 | err = PTR_ERR(ptr: new_path); |
415 | goto err_put_old_path; |
416 | } |
417 | |
418 | if (d_inode(dentry: old_dentry)->i_mode & S_IFDIR) |
419 | shfl_flags = 0; |
420 | |
421 | err = vboxsf_rename(root: sbi->root, src_path: old_path, dest_path: new_path, flags: shfl_flags); |
422 | if (err == 0) { |
423 | /* parent directories access/change time changed */ |
424 | sf_new_parent_i->force_restat = 1; |
425 | sf_old_parent_i->force_restat = 1; |
426 | } |
427 | |
428 | __putname(new_path); |
429 | err_put_old_path: |
430 | __putname(old_path); |
431 | return err; |
432 | } |
433 | |
434 | static int vboxsf_dir_symlink(struct mnt_idmap *idmap, |
435 | struct inode *parent, struct dentry *dentry, |
436 | const char *symname) |
437 | { |
438 | struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent); |
439 | struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb); |
440 | int symname_size = strlen(symname) + 1; |
441 | struct shfl_string *path, *ssymname; |
442 | struct shfl_fsobjinfo info; |
443 | int err; |
444 | |
445 | path = vboxsf_path_from_dentry(sbi, dentry); |
446 | if (IS_ERR(ptr: path)) |
447 | return PTR_ERR(ptr: path); |
448 | |
449 | ssymname = kmalloc(SHFLSTRING_HEADER_SIZE + symname_size, GFP_KERNEL); |
450 | if (!ssymname) { |
451 | __putname(path); |
452 | return -ENOMEM; |
453 | } |
454 | ssymname->length = symname_size - 1; |
455 | ssymname->size = symname_size; |
456 | memcpy(ssymname->string.utf8, symname, symname_size); |
457 | |
458 | err = vboxsf_symlink(root: sbi->root, new_path: path, old_path: ssymname, buf: &info); |
459 | kfree(objp: ssymname); |
460 | __putname(path); |
461 | if (err) { |
462 | /* -EROFS means symlinks are note support -> -EPERM */ |
463 | return (err == -EROFS) ? -EPERM : err; |
464 | } |
465 | |
466 | err = vboxsf_dir_instantiate(parent, dentry, info: &info); |
467 | if (err) |
468 | return err; |
469 | |
470 | /* parent directory access/change time changed */ |
471 | sf_parent_i->force_restat = 1; |
472 | return 0; |
473 | } |
474 | |
475 | const struct inode_operations vboxsf_dir_iops = { |
476 | .lookup = vboxsf_dir_lookup, |
477 | .create = vboxsf_dir_mkfile, |
478 | .mkdir = vboxsf_dir_mkdir, |
479 | .atomic_open = vboxsf_dir_atomic_open, |
480 | .rmdir = vboxsf_dir_unlink, |
481 | .unlink = vboxsf_dir_unlink, |
482 | .rename = vboxsf_dir_rename, |
483 | .symlink = vboxsf_dir_symlink, |
484 | .getattr = vboxsf_getattr, |
485 | .setattr = vboxsf_setattr, |
486 | }; |
487 | |