1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * VirtualBox Guest Shared Folders support: Utility functions. |
4 | * Mainly conversion from/to VirtualBox/Linux data structures. |
5 | * |
6 | * Copyright (C) 2006-2018 Oracle Corporation |
7 | */ |
8 | |
9 | #include <linux/namei.h> |
10 | #include <linux/nls.h> |
11 | #include <linux/sizes.h> |
12 | #include <linux/pagemap.h> |
13 | #include <linux/vfs.h> |
14 | #include "vfsmod.h" |
15 | |
16 | struct inode *vboxsf_new_inode(struct super_block *sb) |
17 | { |
18 | struct vboxsf_sbi *sbi = VBOXSF_SBI(sb); |
19 | struct inode *inode; |
20 | unsigned long flags; |
21 | int cursor, ret; |
22 | u32 gen; |
23 | |
24 | inode = new_inode(sb); |
25 | if (!inode) |
26 | return ERR_PTR(error: -ENOMEM); |
27 | |
28 | idr_preload(GFP_KERNEL); |
29 | spin_lock_irqsave(&sbi->ino_idr_lock, flags); |
30 | cursor = idr_get_cursor(idr: &sbi->ino_idr); |
31 | ret = idr_alloc_cyclic(&sbi->ino_idr, ptr: inode, start: 1, end: 0, GFP_ATOMIC); |
32 | if (ret >= 0 && ret < cursor) |
33 | sbi->next_generation++; |
34 | gen = sbi->next_generation; |
35 | spin_unlock_irqrestore(lock: &sbi->ino_idr_lock, flags); |
36 | idr_preload_end(); |
37 | |
38 | if (ret < 0) { |
39 | iput(inode); |
40 | return ERR_PTR(error: ret); |
41 | } |
42 | |
43 | inode->i_ino = ret; |
44 | inode->i_generation = gen; |
45 | return inode; |
46 | } |
47 | |
48 | /* set [inode] attributes based on [info], uid/gid based on [sbi] */ |
49 | int vboxsf_init_inode(struct vboxsf_sbi *sbi, struct inode *inode, |
50 | const struct shfl_fsobjinfo *info, bool reinit) |
51 | { |
52 | const struct shfl_fsobjattr *attr; |
53 | s64 allocated; |
54 | umode_t mode; |
55 | |
56 | attr = &info->attr; |
57 | |
58 | #define mode_set(r) ((attr->mode & (SHFL_UNIX_##r)) ? (S_##r) : 0) |
59 | |
60 | mode = mode_set(IRUSR); |
61 | mode |= mode_set(IWUSR); |
62 | mode |= mode_set(IXUSR); |
63 | |
64 | mode |= mode_set(IRGRP); |
65 | mode |= mode_set(IWGRP); |
66 | mode |= mode_set(IXGRP); |
67 | |
68 | mode |= mode_set(IROTH); |
69 | mode |= mode_set(IWOTH); |
70 | mode |= mode_set(IXOTH); |
71 | |
72 | #undef mode_set |
73 | |
74 | /* We use the host-side values for these */ |
75 | inode->i_flags |= S_NOATIME | S_NOCMTIME; |
76 | inode->i_mapping->a_ops = &vboxsf_reg_aops; |
77 | |
78 | if (SHFL_IS_DIRECTORY(attr->mode)) { |
79 | if (sbi->o.dmode_set) |
80 | mode = sbi->o.dmode; |
81 | mode &= ~sbi->o.dmask; |
82 | mode |= S_IFDIR; |
83 | if (!reinit) { |
84 | inode->i_op = &vboxsf_dir_iops; |
85 | inode->i_fop = &vboxsf_dir_fops; |
86 | /* |
87 | * XXX: this probably should be set to the number of entries |
88 | * in the directory plus two (. ..) |
89 | */ |
90 | set_nlink(inode, nlink: 1); |
91 | } else if (!S_ISDIR(inode->i_mode)) |
92 | return -ESTALE; |
93 | inode->i_mode = mode; |
94 | } else if (SHFL_IS_SYMLINK(attr->mode)) { |
95 | if (sbi->o.fmode_set) |
96 | mode = sbi->o.fmode; |
97 | mode &= ~sbi->o.fmask; |
98 | mode |= S_IFLNK; |
99 | if (!reinit) { |
100 | inode->i_op = &vboxsf_lnk_iops; |
101 | set_nlink(inode, nlink: 1); |
102 | } else if (!S_ISLNK(inode->i_mode)) |
103 | return -ESTALE; |
104 | inode->i_mode = mode; |
105 | } else { |
106 | if (sbi->o.fmode_set) |
107 | mode = sbi->o.fmode; |
108 | mode &= ~sbi->o.fmask; |
109 | mode |= S_IFREG; |
110 | if (!reinit) { |
111 | inode->i_op = &vboxsf_reg_iops; |
112 | inode->i_fop = &vboxsf_reg_fops; |
113 | set_nlink(inode, nlink: 1); |
114 | } else if (!S_ISREG(inode->i_mode)) |
115 | return -ESTALE; |
116 | inode->i_mode = mode; |
117 | } |
118 | |
119 | inode->i_uid = sbi->o.uid; |
120 | inode->i_gid = sbi->o.gid; |
121 | |
122 | inode->i_size = info->size; |
123 | inode->i_blkbits = 12; |
124 | /* i_blocks always in units of 512 bytes! */ |
125 | allocated = info->allocated + 511; |
126 | do_div(allocated, 512); |
127 | inode->i_blocks = allocated; |
128 | |
129 | inode_set_atime_to_ts(inode, |
130 | ts: ns_to_timespec64(nsec: info->access_time.ns_relative_to_unix_epoch)); |
131 | inode_set_ctime_to_ts(inode, |
132 | ts: ns_to_timespec64(nsec: info->change_time.ns_relative_to_unix_epoch)); |
133 | inode_set_mtime_to_ts(inode, |
134 | ts: ns_to_timespec64(nsec: info->modification_time.ns_relative_to_unix_epoch)); |
135 | return 0; |
136 | } |
137 | |
138 | int vboxsf_create_at_dentry(struct dentry *dentry, |
139 | struct shfl_createparms *params) |
140 | { |
141 | struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb); |
142 | struct shfl_string *path; |
143 | int err; |
144 | |
145 | path = vboxsf_path_from_dentry(sbi, dentry); |
146 | if (IS_ERR(ptr: path)) |
147 | return PTR_ERR(ptr: path); |
148 | |
149 | err = vboxsf_create(root: sbi->root, parsed_path: path, create_parms: params); |
150 | __putname(path); |
151 | |
152 | return err; |
153 | } |
154 | |
155 | int vboxsf_stat(struct vboxsf_sbi *sbi, struct shfl_string *path, |
156 | struct shfl_fsobjinfo *info) |
157 | { |
158 | struct shfl_createparms params = {}; |
159 | int err; |
160 | |
161 | params.handle = SHFL_HANDLE_NIL; |
162 | params.create_flags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW; |
163 | |
164 | err = vboxsf_create(root: sbi->root, parsed_path: path, create_parms: ¶ms); |
165 | if (err) |
166 | return err; |
167 | |
168 | if (params.result != SHFL_FILE_EXISTS) |
169 | return -ENOENT; |
170 | |
171 | if (info) |
172 | *info = params.info; |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info) |
178 | { |
179 | struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb); |
180 | struct shfl_string *path; |
181 | int err; |
182 | |
183 | path = vboxsf_path_from_dentry(sbi, dentry); |
184 | if (IS_ERR(ptr: path)) |
185 | return PTR_ERR(ptr: path); |
186 | |
187 | err = vboxsf_stat(sbi, path, info); |
188 | __putname(path); |
189 | return err; |
190 | } |
191 | |
192 | int vboxsf_inode_revalidate(struct dentry *dentry) |
193 | { |
194 | struct vboxsf_sbi *sbi; |
195 | struct vboxsf_inode *sf_i; |
196 | struct shfl_fsobjinfo info; |
197 | struct timespec64 mtime, prev_mtime; |
198 | struct inode *inode; |
199 | int err; |
200 | |
201 | if (!dentry || !d_really_is_positive(dentry)) |
202 | return -EINVAL; |
203 | |
204 | inode = d_inode(dentry); |
205 | prev_mtime = inode_get_mtime(inode); |
206 | sf_i = VBOXSF_I(inode); |
207 | sbi = VBOXSF_SBI(dentry->d_sb); |
208 | if (!sf_i->force_restat) { |
209 | if (time_before(jiffies, dentry->d_time + sbi->o.ttl)) |
210 | return 0; |
211 | } |
212 | |
213 | err = vboxsf_stat_dentry(dentry, info: &info); |
214 | if (err) |
215 | return err; |
216 | |
217 | dentry->d_time = jiffies; |
218 | sf_i->force_restat = 0; |
219 | err = vboxsf_init_inode(sbi, inode, info: &info, reinit: true); |
220 | if (err) |
221 | return err; |
222 | |
223 | /* |
224 | * If the file was changed on the host side we need to invalidate the |
225 | * page-cache for it. Note this also gets triggered by our own writes, |
226 | * this is unavoidable. |
227 | */ |
228 | mtime = inode_get_mtime(inode); |
229 | if (timespec64_compare(lhs: &mtime, rhs: &prev_mtime) > 0) |
230 | invalidate_inode_pages2(mapping: inode->i_mapping); |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | int vboxsf_getattr(struct mnt_idmap *idmap, const struct path *path, |
236 | struct kstat *kstat, u32 request_mask, unsigned int flags) |
237 | { |
238 | int err; |
239 | struct dentry *dentry = path->dentry; |
240 | struct inode *inode = d_inode(dentry); |
241 | struct vboxsf_inode *sf_i = VBOXSF_I(inode); |
242 | |
243 | switch (flags & AT_STATX_SYNC_TYPE) { |
244 | case AT_STATX_DONT_SYNC: |
245 | err = 0; |
246 | break; |
247 | case AT_STATX_FORCE_SYNC: |
248 | sf_i->force_restat = 1; |
249 | fallthrough; |
250 | default: |
251 | err = vboxsf_inode_revalidate(dentry); |
252 | } |
253 | if (err) |
254 | return err; |
255 | |
256 | generic_fillattr(&nop_mnt_idmap, request_mask, d_inode(dentry), kstat); |
257 | return 0; |
258 | } |
259 | |
260 | int vboxsf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, |
261 | struct iattr *iattr) |
262 | { |
263 | struct vboxsf_inode *sf_i = VBOXSF_I(d_inode(dentry)); |
264 | struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb); |
265 | struct shfl_createparms params = {}; |
266 | struct shfl_fsobjinfo info = {}; |
267 | u32 buf_len; |
268 | int err; |
269 | |
270 | params.handle = SHFL_HANDLE_NIL; |
271 | params.create_flags = SHFL_CF_ACT_OPEN_IF_EXISTS | |
272 | SHFL_CF_ACT_FAIL_IF_NEW | |
273 | SHFL_CF_ACCESS_ATTR_WRITE; |
274 | |
275 | /* this is at least required for Posix hosts */ |
276 | if (iattr->ia_valid & ATTR_SIZE) |
277 | params.create_flags |= SHFL_CF_ACCESS_WRITE; |
278 | |
279 | err = vboxsf_create_at_dentry(dentry, params: ¶ms); |
280 | if (err || params.result != SHFL_FILE_EXISTS) |
281 | return err ? err : -ENOENT; |
282 | |
283 | #define mode_set(r) ((iattr->ia_mode & (S_##r)) ? SHFL_UNIX_##r : 0) |
284 | |
285 | /* |
286 | * Setting the file size and setting the other attributes has to |
287 | * be handled separately. |
288 | */ |
289 | if (iattr->ia_valid & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME)) { |
290 | if (iattr->ia_valid & ATTR_MODE) { |
291 | info.attr.mode = mode_set(IRUSR); |
292 | info.attr.mode |= mode_set(IWUSR); |
293 | info.attr.mode |= mode_set(IXUSR); |
294 | info.attr.mode |= mode_set(IRGRP); |
295 | info.attr.mode |= mode_set(IWGRP); |
296 | info.attr.mode |= mode_set(IXGRP); |
297 | info.attr.mode |= mode_set(IROTH); |
298 | info.attr.mode |= mode_set(IWOTH); |
299 | info.attr.mode |= mode_set(IXOTH); |
300 | |
301 | if (iattr->ia_mode & S_IFDIR) |
302 | info.attr.mode |= SHFL_TYPE_DIRECTORY; |
303 | else |
304 | info.attr.mode |= SHFL_TYPE_FILE; |
305 | } |
306 | |
307 | if (iattr->ia_valid & ATTR_ATIME) |
308 | info.access_time.ns_relative_to_unix_epoch = |
309 | timespec64_to_ns(ts: &iattr->ia_atime); |
310 | |
311 | if (iattr->ia_valid & ATTR_MTIME) |
312 | info.modification_time.ns_relative_to_unix_epoch = |
313 | timespec64_to_ns(ts: &iattr->ia_mtime); |
314 | |
315 | /* |
316 | * Ignore ctime (inode change time) as it can't be set |
317 | * from userland anyway. |
318 | */ |
319 | |
320 | buf_len = sizeof(info); |
321 | err = vboxsf_fsinfo(root: sbi->root, handle: params.handle, |
322 | SHFL_INFO_SET | SHFL_INFO_FILE, buf_len: &buf_len, |
323 | buf: &info); |
324 | if (err) { |
325 | vboxsf_close(root: sbi->root, handle: params.handle); |
326 | return err; |
327 | } |
328 | |
329 | /* the host may have given us different attr then requested */ |
330 | sf_i->force_restat = 1; |
331 | } |
332 | |
333 | #undef mode_set |
334 | |
335 | if (iattr->ia_valid & ATTR_SIZE) { |
336 | memset(&info, 0, sizeof(info)); |
337 | info.size = iattr->ia_size; |
338 | buf_len = sizeof(info); |
339 | err = vboxsf_fsinfo(root: sbi->root, handle: params.handle, |
340 | SHFL_INFO_SET | SHFL_INFO_SIZE, buf_len: &buf_len, |
341 | buf: &info); |
342 | if (err) { |
343 | vboxsf_close(root: sbi->root, handle: params.handle); |
344 | return err; |
345 | } |
346 | |
347 | /* the host may have given us different attr then requested */ |
348 | sf_i->force_restat = 1; |
349 | } |
350 | |
351 | vboxsf_close(root: sbi->root, handle: params.handle); |
352 | |
353 | /* Update the inode with what the host has actually given us. */ |
354 | if (sf_i->force_restat) |
355 | vboxsf_inode_revalidate(dentry); |
356 | |
357 | return 0; |
358 | } |
359 | |
360 | /* |
361 | * [dentry] contains string encoded in coding system that corresponds |
362 | * to [sbi]->nls, we must convert it to UTF8 here. |
363 | * Returns a shfl_string allocated through __getname (must be freed using |
364 | * __putname), or an ERR_PTR on error. |
365 | */ |
366 | struct shfl_string *vboxsf_path_from_dentry(struct vboxsf_sbi *sbi, |
367 | struct dentry *dentry) |
368 | { |
369 | struct shfl_string *shfl_path; |
370 | int path_len, out_len, nb; |
371 | char *buf, *path; |
372 | wchar_t uni; |
373 | u8 *out; |
374 | |
375 | buf = __getname(); |
376 | if (!buf) |
377 | return ERR_PTR(error: -ENOMEM); |
378 | |
379 | path = dentry_path_raw(dentry, buf, PATH_MAX); |
380 | if (IS_ERR(ptr: path)) { |
381 | __putname(buf); |
382 | return ERR_CAST(ptr: path); |
383 | } |
384 | path_len = strlen(path); |
385 | |
386 | if (sbi->nls) { |
387 | shfl_path = __getname(); |
388 | if (!shfl_path) { |
389 | __putname(buf); |
390 | return ERR_PTR(error: -ENOMEM); |
391 | } |
392 | |
393 | out = shfl_path->string.utf8; |
394 | out_len = PATH_MAX - SHFLSTRING_HEADER_SIZE - 1; |
395 | |
396 | while (path_len) { |
397 | nb = sbi->nls->char2uni(path, path_len, &uni); |
398 | if (nb < 0) { |
399 | __putname(shfl_path); |
400 | __putname(buf); |
401 | return ERR_PTR(error: -EINVAL); |
402 | } |
403 | path += nb; |
404 | path_len -= nb; |
405 | |
406 | nb = utf32_to_utf8(u: uni, s: out, maxlen: out_len); |
407 | if (nb < 0) { |
408 | __putname(shfl_path); |
409 | __putname(buf); |
410 | return ERR_PTR(error: -ENAMETOOLONG); |
411 | } |
412 | out += nb; |
413 | out_len -= nb; |
414 | } |
415 | *out = 0; |
416 | shfl_path->length = out - shfl_path->string.utf8; |
417 | shfl_path->size = shfl_path->length + 1; |
418 | __putname(buf); |
419 | } else { |
420 | if ((SHFLSTRING_HEADER_SIZE + path_len + 1) > PATH_MAX) { |
421 | __putname(buf); |
422 | return ERR_PTR(error: -ENAMETOOLONG); |
423 | } |
424 | /* |
425 | * dentry_path stores the name at the end of buf, but the |
426 | * shfl_string string we return must be properly aligned. |
427 | */ |
428 | shfl_path = (struct shfl_string *)buf; |
429 | memmove(shfl_path->string.utf8, path, path_len); |
430 | shfl_path->string.utf8[path_len] = 0; |
431 | shfl_path->length = path_len; |
432 | shfl_path->size = path_len + 1; |
433 | } |
434 | |
435 | return shfl_path; |
436 | } |
437 | |
438 | int vboxsf_nlscpy(struct vboxsf_sbi *sbi, char *name, size_t name_bound_len, |
439 | const unsigned char *utf8_name, size_t utf8_len) |
440 | { |
441 | const char *in; |
442 | char *out; |
443 | size_t out_bound_len; |
444 | size_t in_bound_len; |
445 | |
446 | in = utf8_name; |
447 | in_bound_len = utf8_len; |
448 | |
449 | out = name; |
450 | /* Reserve space for terminating 0 */ |
451 | out_bound_len = name_bound_len - 1; |
452 | |
453 | while (in_bound_len) { |
454 | int nb; |
455 | unicode_t uni; |
456 | |
457 | nb = utf8_to_utf32(s: in, len: in_bound_len, pu: &uni); |
458 | if (nb < 0) |
459 | return -EINVAL; |
460 | |
461 | in += nb; |
462 | in_bound_len -= nb; |
463 | |
464 | nb = sbi->nls->uni2char(uni, out, out_bound_len); |
465 | if (nb < 0) |
466 | return nb; |
467 | |
468 | out += nb; |
469 | out_bound_len -= nb; |
470 | } |
471 | |
472 | *out = 0; |
473 | |
474 | return 0; |
475 | } |
476 | |
477 | static struct vboxsf_dir_buf *vboxsf_dir_buf_alloc(struct list_head *list) |
478 | { |
479 | struct vboxsf_dir_buf *b; |
480 | |
481 | b = kmalloc(size: sizeof(*b), GFP_KERNEL); |
482 | if (!b) |
483 | return NULL; |
484 | |
485 | b->buf = kmalloc(DIR_BUFFER_SIZE, GFP_KERNEL); |
486 | if (!b->buf) { |
487 | kfree(objp: b); |
488 | return NULL; |
489 | } |
490 | |
491 | b->entries = 0; |
492 | b->used = 0; |
493 | b->free = DIR_BUFFER_SIZE; |
494 | list_add(new: &b->head, head: list); |
495 | |
496 | return b; |
497 | } |
498 | |
499 | static void vboxsf_dir_buf_free(struct vboxsf_dir_buf *b) |
500 | { |
501 | list_del(entry: &b->head); |
502 | kfree(objp: b->buf); |
503 | kfree(objp: b); |
504 | } |
505 | |
506 | struct vboxsf_dir_info *vboxsf_dir_info_alloc(void) |
507 | { |
508 | struct vboxsf_dir_info *p; |
509 | |
510 | p = kmalloc(size: sizeof(*p), GFP_KERNEL); |
511 | if (!p) |
512 | return NULL; |
513 | |
514 | INIT_LIST_HEAD(list: &p->info_list); |
515 | return p; |
516 | } |
517 | |
518 | void vboxsf_dir_info_free(struct vboxsf_dir_info *p) |
519 | { |
520 | struct list_head *list, *pos, *tmp; |
521 | |
522 | list = &p->info_list; |
523 | list_for_each_safe(pos, tmp, list) { |
524 | struct vboxsf_dir_buf *b; |
525 | |
526 | b = list_entry(pos, struct vboxsf_dir_buf, head); |
527 | vboxsf_dir_buf_free(b); |
528 | } |
529 | kfree(objp: p); |
530 | } |
531 | |
532 | int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d, |
533 | u64 handle) |
534 | { |
535 | struct vboxsf_dir_buf *b; |
536 | u32 entries, size; |
537 | int err = 0; |
538 | void *buf; |
539 | |
540 | /* vboxsf_dirinfo returns 1 on end of dir */ |
541 | while (err == 0) { |
542 | b = vboxsf_dir_buf_alloc(list: &sf_d->info_list); |
543 | if (!b) { |
544 | err = -ENOMEM; |
545 | break; |
546 | } |
547 | |
548 | buf = b->buf; |
549 | size = b->free; |
550 | |
551 | err = vboxsf_dirinfo(root: sbi->root, handle, NULL, flags: 0, index: 0, |
552 | buf_len: &size, buf, file_count: &entries); |
553 | if (err < 0) |
554 | break; |
555 | |
556 | b->entries += entries; |
557 | b->free -= size; |
558 | b->used += size; |
559 | } |
560 | |
561 | if (b && b->used == 0) |
562 | vboxsf_dir_buf_free(b); |
563 | |
564 | /* -EILSEQ means the host could not translate a filename, ignore */ |
565 | if (err > 0 || err == -EILSEQ) |
566 | err = 0; |
567 | |
568 | return err; |
569 | } |
570 | |