1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2012 Joyent, Inc. All rights reserved.
24 */
25
26 #include <sys/types.h>
27 #include <sys/param.h>
28 #include <sys/t_lock.h>
29 #include <sys/systm.h>
30 #include <sys/sysmacros.h>
31 #include <sys/user.h>
32 #include <sys/time.h>
33 #include <sys/vfs.h>
34 #include <sys/vfs_opreg.h>
35 #include <sys/vnode.h>
36 #include <sys/file.h>
37 #include <sys/fcntl.h>
38 #include <sys/flock.h>
39 #include <sys/kmem.h>
40 #include <sys/errno.h>
41 #include <sys/stat.h>
42 #include <sys/cred.h>
43 #include <sys/dirent.h>
44 #include <sys/pathname.h>
45 #include <sys/fs/hyprlofs.h>
46 #include <sys/fs/hyprlofs_info.h>
47 #include <sys/mman.h>
48 #include <vm/pvn.h>
49 #include <sys/cmn_err.h>
50 #include <sys/buf.h>
51 #include <sys/policy.h>
52 #include <fs/fs_subr.h>
53 #include <sys/ddi.h>
54 #include <sys/sunddi.h>
55
56 static int hyprlofs_add_entry(vnode_t *, char *, char *, cred_t *,
57 caller_context_t *);
58 static int hyprlofs_rm_entry(vnode_t *, char *, cred_t *, caller_context_t *,
59 int);
60 static int hyprlofs_rm_all(vnode_t *, cred_t *, caller_context_t *, int);
61 static int hyprlofs_remove(vnode_t *, char *, cred_t *, caller_context_t *,
62 int);
63 static int hyprlofs_get_all(vnode_t *, intptr_t, cred_t *, caller_context_t *,
64 int);
65
66 /*
67 * This is a somewhat arbitrary upper limit on the number of entries we can
68 * pass in on a single add/rm ioctl call. This is only used to validate that
69 * the input list looks sane.
70 */
71 #define MAX_IOCTL_PARAMS 100000
72
73 static int
74 hyprlofs_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct)
75 {
76 vnode_t *rvp;
77 int error;
78
79 rvp = REALVP(*vpp);
80
81 if (VTOHLN(*vpp)->hln_looped == 0)
82 return (0);
83
84 /*
85 * looped back, pass through to real vnode. Need to hold new reference
86 * to vp since VOP_OPEN() may decide to release it.
87 */
88 VN_HOLD(rvp);
89 error = VOP_OPEN(&rvp, flag, cr, ct);
90 ASSERT(rvp->v_count > 1);
91 VN_RELE(rvp);
92
93 return (error);
94 }
95
96 static int
97 hyprlofs_close(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr,
98 caller_context_t *ct)
99 {
100 if (VTOHLN(vp)->hln_looped == 0) {
101 cleanlocks(vp, ttoproc(curthread)->p_pid, 0);
102 cleanshares(vp, ttoproc(curthread)->p_pid);
103 return (0);
104 }
105
106 return (VOP_CLOSE(REALVP(vp), flag, count, offset, cr, ct));
107 }
108
109 static int
110 hyprlofs_read(vnode_t *vp, struct uio *uiop, int ioflag, cred_t *cr,
111 caller_context_t *ct)
112 {
113 return (VOP_READ(REALVP(vp), uiop, ioflag, cr, ct));
114 }
115
116 static int
117 hyprlofs_write(vnode_t *vp, struct uio *uiop, int ioflag, cred_t *cr,
118 caller_context_t *ct)
119 {
120 /* We don't support writing to non-regular files */
121 if (vp->v_type != VREG)
122 return (EINVAL);
123
124 if (vn_is_readonly(vp))
125 return (EROFS);
126
127 return (VOP_WRITE(REALVP(vp), uiop, ioflag, cr, ct));
128 }
129
130 /* ARGSUSED */
131 static int
132 hyprlofs_ioctl(vnode_t *vp, int cmd, intptr_t data, int flag,
133 cred_t *cr, int *rvalp, caller_context_t *ct)
134 {
135 int len, cnt, error;
136 int i;
137 model_t model;
138 char path[MAXPATHLEN];
139 char nm[MAXPATHLEN];
140
141 /* We only support the hyprlofs ioctls on the root vnode */
142 if (!(vp->v_flag & VROOT))
143 return (ENOTTY);
144
145 /*
146 * Check if managing hyprlofs is allowed.
147 */
148 if (secpolicy_hyprlofs_control(cr) != 0)
149 return (EPERM);
150
151 if (cmd == HYPRLOFS_ADD_ENTRIES || cmd == HYPRLOFS_RM_ENTRIES) {
152 model = get_udatamodel();
153
154 if (model == DATAMODEL_NATIVE) {
155 hyprlofs_entries_t ebuf;
156 hyprlofs_entry_t *e;
157
158 if (copyin((void *)data, &ebuf, sizeof (ebuf)))
159 return (EFAULT);
160 cnt = ebuf.hle_len;
161 if (cnt > MAX_IOCTL_PARAMS)
162 return (EINVAL);
163 len = sizeof (hyprlofs_entry_t) * cnt;
164
165 e = kmem_alloc(len, KM_SLEEP);
166 if (copyin((void *)(ebuf.hle_entries), e, len)) {
167 kmem_free(e, len);
168 return (EFAULT);
169 }
170
171 for (i = 0; i < cnt; i++) {
172 if (e[i].hle_nlen == 0 ||
173 e[i].hle_nlen > MAXPATHLEN)
174 return (EINVAL);
175
176 if (copyin(e[i].hle_name, nm, e[i].hle_nlen)
177 != 0) {
178 kmem_free(e, len);
179 return (EFAULT);
180 }
181 nm[e[i].hle_nlen] = '\0';
182
183 if (cmd == HYPRLOFS_ADD_ENTRIES) {
184 if (e[i].hle_plen == 0 ||
185 e[i].hle_plen > MAXPATHLEN)
186 return (EINVAL);
187
188 if (copyin(e[i].hle_path, path,
189 e[i].hle_plen) != 0) {
190 kmem_free(e, len);
191 return (EFAULT);
192 }
193 path[e[i].hle_plen] = '\0';
194
195 if ((error = hyprlofs_add_entry(vp,
196 path, nm, cr, ct)) != 0) {
197 kmem_free(e, len);
198 return (error);
199 }
200 } else {
201 if ((error = hyprlofs_rm_entry(vp, nm,
202 cr, ct, flag)) != 0) {
203 kmem_free(e, len);
204 return (error);
205 }
206 }
207 }
208
209 kmem_free(e, len);
210 return (0);
211
212 } else {
213 hyprlofs_entries32_t ebuf32;
214 hyprlofs_entry32_t *e32;
215
216 if (copyin((void *)data, &ebuf32, sizeof (ebuf32)))
217 return (EFAULT);
218
219 cnt = ebuf32.hle_len;
220 if (cnt > MAX_IOCTL_PARAMS)
221 return (EINVAL);
222 len = sizeof (hyprlofs_entry32_t) * cnt;
223
224 e32 = kmem_alloc(len, KM_SLEEP);
225 if (copyin((void *)(unsigned long)(ebuf32.hle_entries),
226 e32, len)) {
227 kmem_free(e32, len);
228 return (EFAULT);
229 }
230
231 for (i = 0; i < cnt; i++) {
232 if (e32[i].hle_nlen == 0 ||
233 e32[i].hle_nlen > MAXPATHLEN)
234 return (EINVAL);
235
236 if (copyin((void *)(unsigned long)
237 e32[i].hle_name, nm,
238 e32[i].hle_nlen) != 0) {
239 kmem_free(e32, len);
240 return (EFAULT);
241 }
242 nm[e32[i].hle_nlen] = '\0';
243
244 if (cmd == HYPRLOFS_ADD_ENTRIES) {
245 if (e32[i].hle_plen == 0 ||
246 e32[i].hle_plen > MAXPATHLEN)
247 return (EINVAL);
248
249 if (copyin((void *)(unsigned long)
250 e32[i].hle_path, path,
251 e32[i].hle_plen) != 0) {
252 kmem_free(e32, len);
253 return (EFAULT);
254 }
255 path[e32[i].hle_plen] = '\0';
256
257 if ((error = hyprlofs_add_entry(vp,
258 path, nm, cr, ct)) != 0) {
259 kmem_free(e32, len);
260 return (error);
261 }
262 } else {
263 if ((error = hyprlofs_rm_entry(vp, nm,
264 cr, ct, flag)) != 0) {
265 kmem_free(e32, len);
266 return (error);
267 }
268 }
269 }
270
271 kmem_free(e32, len);
272 return (0);
273 }
274 }
275
276 if (cmd == HYPRLOFS_RM_ALL) {
277 return (hyprlofs_rm_all(vp, cr, ct, flag));
278 }
279
280 if (cmd == HYPRLOFS_GET_ENTRIES) {
281 return (hyprlofs_get_all(vp, data, cr, ct, flag));
282 }
283
284 return (ENOTTY);
285 }
286
287 /*ARGSUSED2*/
288 static int
289 hyprlofs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
290 caller_context_t *ct)
291 {
292 hlnode_t *tp = (hlnode_t *)VTOHLN(vp);
293
294 mutex_enter(&tp->hln_tlock);
295 vap->va_type = vp->v_type;
296 vap->va_mode = tp->hln_mode & MODEMASK;
297 vap->va_uid = tp->hln_uid;
298 vap->va_gid = tp->hln_gid;
299 vap->va_fsid = tp->hln_fsid;
300 vap->va_nodeid = (ino64_t)tp->hln_nodeid;
301 vap->va_nlink = tp->hln_nlink;
302 vap->va_size = (u_offset_t)tp->hln_size;
303 vap->va_atime = tp->hln_atime;
304 vap->va_mtime = tp->hln_mtime;
305 vap->va_ctime = tp->hln_ctime;
306 vap->va_blksize = PAGESIZE;
307 vap->va_rdev = tp->hln_rdev;
308 vap->va_seq = tp->hln_seq;
309
310 vap->va_nblocks = (fsblkcnt64_t)btodb(ptob(btopr(vap->va_size)));
311 mutex_exit(&tp->hln_tlock);
312 return (0);
313 }
314
315 /*ARGSUSED4*/
316 static int
317 hyprlofs_setattr(vnode_t *vp, vattr_t *vap, int flags,
318 cred_t *cr, caller_context_t *ct)
319 {
320 hlnode_t *tp = (hlnode_t *)VTOHLN(vp);
321 int error = 0;
322 vattr_t *get;
323 long mask;
324
325 /*
326 * Cannot set these attributes
327 */
328 if ((vap->va_mask & AT_NOSET) || (vap->va_mask & AT_XVATTR))
329 return (EINVAL);
330
331 mutex_enter(&tp->hln_tlock);
332
333 get = &tp->hln_attr;
334 /*
335 * Change file access modes. Must be owner or have sufficient
336 * privileges.
337 */
338 error = secpolicy_vnode_setattr(cr, vp, vap, get, flags,
339 hyprlofs_taccess, tp);
340
341 if (error)
342 goto out;
343
344 mask = vap->va_mask;
345
346 if (mask & AT_MODE) {
347 get->va_mode &= S_IFMT;
348 get->va_mode |= vap->va_mode & ~S_IFMT;
349 }
350
351 if (mask & AT_UID)
352 get->va_uid = vap->va_uid;
353 if (mask & AT_GID)
354 get->va_gid = vap->va_gid;
355 if (mask & AT_ATIME)
356 get->va_atime = vap->va_atime;
357 if (mask & AT_MTIME)
358 get->va_mtime = vap->va_mtime;
359
360 if (mask & (AT_UID | AT_GID | AT_MODE | AT_MTIME))
361 gethrestime(&tp->hln_ctime);
362
363 out:
364 mutex_exit(&tp->hln_tlock);
365 return (error);
366 }
367
368 static int
369 hyprlofs_access(vnode_t *vp, int mode, int flags, cred_t *cr,
370 caller_context_t *ct)
371 {
372 hlnode_t *tp = (hlnode_t *)VTOHLN(vp);
373 int error;
374
375 if (mode & VWRITE) {
376 if (vp->v_type == VREG && vn_is_readonly(vp))
377 return (EROFS);
378 }
379 if (VTOHLN(vp)->hln_looped == 1)
380 return (VOP_ACCESS(REALVP(vp), mode, flags, cr, ct));
381
382 mutex_enter(&tp->hln_tlock);
383 error = hyprlofs_taccess(tp, mode, cr);
384 mutex_exit(&tp->hln_tlock);
385 return (error);
386 }
387
388 /* ARGSUSED3 */
389 static int
390 hyprlofs_lookup(vnode_t *dvp, char *nm, vnode_t **vpp, struct pathname *pnp,
391 int flags, vnode_t *rdir, cred_t *cr, caller_context_t *ct,
392 int *direntflags, pathname_t *realpnp)
393 {
394 hlnode_t *tp = (hlnode_t *)VTOHLN(dvp);
395 hlnode_t *ntp = NULL;
396 int error;
397
398 if (VTOHLN(dvp)->hln_looped == 1)
399 return (VOP_LOOKUP(REALVP(dvp), nm, vpp, pnp, flags, rdir,
400 cr, ct, direntflags, realpnp));
401
402 if (flags & LOOKUP_XATTR)
403 return (EINVAL);
404
405 /* Null component name is a synonym for directory being searched. */
406 if (*nm == '\0') {
407 VN_HOLD(dvp);
408 *vpp = dvp;
409 return (0);
410 }
411 ASSERT(tp);
412
413 if ((error = hyprlofs_dirlookup(tp, nm, &ntp, cr)) == 0) {
414 ASSERT(ntp);
415 *vpp = HLNTOV(ntp);
416 }
417 return (error);
418 }
419
420 /*
421 * Create the loopback from the hyprlofs vnode to the real vnode.
422 */
423 static int
424 hyprlofs_loopback(vnode_t *dvp, vnode_t *rvp, char *nm, vattr_t *vap,
425 int mode, cred_t *cr, caller_context_t *ct)
426 {
427 hlnode_t *parent;
428 hlfsmount_t *tm;
429 int error;
430 hlnode_t *oldtp;
431 vnode_t *vp;
432
433 parent = (hlnode_t *)VTOHLN(dvp);
434 tm = (hlfsmount_t *)VTOHLM(dvp);
435 error = 0;
436 oldtp = NULL;
437
438 if (vap->va_type == VREG && (vap->va_mode & VSVTX)) {
439 /* we don't support the sticky bit */
440 vap->va_mode &= ~VSVTX;
441 } else if (vap->va_type == VNON) {
442 return (EINVAL);
443 }
444
445 /* Null component name is a synonym for directory being searched. */
446 if (*nm == '\0') {
447 VN_HOLD(dvp);
448 oldtp = parent;
449 } else {
450 error = hyprlofs_dirlookup(parent, nm, &oldtp, cr);
451 }
452
453 if (error == 0) { /* name found */
454 ASSERT(oldtp);
455
456 rw_enter(&oldtp->hln_rwlock, RW_WRITER);
457
458 /*
459 * if create/read-only an existing directory, allow it
460 */
461 if ((oldtp->hln_type == VDIR) && (mode & VWRITE))
462 error = EISDIR;
463 else {
464 error = hyprlofs_taccess(oldtp, mode, cr);
465 }
466
467 if (error) {
468 rw_exit(&oldtp->hln_rwlock);
469 hlnode_rele(oldtp);
470 return (error);
471 }
472
473 vp = HLNTOV(oldtp);
474 rw_exit(&oldtp->hln_rwlock);
475
476 if (vp->v_type == VREG) {
477 hlnode_rele(oldtp);
478 return (EEXIST);
479 }
480
481 vnevent_create(vp, ct);
482 return (0);
483 }
484
485 if (error != ENOENT)
486 return (error);
487
488 rw_enter(&parent->hln_rwlock, RW_WRITER);
489 error = hyprlofs_direnter(tm, parent, nm, DE_CREATE, rvp, vap, NULL,
490 cr);
491 rw_exit(&parent->hln_rwlock);
492
493 return (error);
494 }
495
496 /*
497 * Create an in-memory directory based on the add-entry ioctl name.
498 * If the dir exists, return EEXIST but still also return node in vpp.
499 */
500 static int
501 hyprlofs_mkdir(vnode_t *dvp, char *nm, vattr_t *va, vnode_t **vpp, cred_t *cr)
502 {
503 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp);
504 hlnode_t *self = NULL;
505 hlfsmount_t *tm = (hlfsmount_t *)VTOHLM(dvp);
506 int error;
507
508 /*
509 * Might be dangling directory. Catch it here, because a ENOENT return
510 * from hyprlofs_dirlookup() is a valid return.
511 */
512 if (parent->hln_nlink == 0)
513 return (ENOENT);
514
515 error = hyprlofs_dirlookup(parent, nm, &self, cr);
516 if (error == 0) {
517 ASSERT(self);
518 hlnode_rele(self);
519 /* We can't loop in under a looped in directory */
520 if (self->hln_looped)
521 return (EACCES);
522 *vpp = HLNTOV(self);
523 return (EEXIST);
524 }
525 if (error != ENOENT)
526 return (error);
527
528 rw_enter(&parent->hln_rwlock, RW_WRITER);
529 error = hyprlofs_direnter(tm, parent, nm, DE_MKDIR, (vnode_t *)NULL,
530 va, &self, cr);
531 rw_exit(&parent->hln_rwlock);
532
533 if (error == 0 || error == EEXIST) {
534 hlnode_rele(self);
535 *vpp = HLNTOV(self);
536 }
537
538 return (error);
539 }
540
541 /*
542 * Loop in a file or directory into the namespace.
543 */
544 static int
545 hyprlofs_add_entry(vnode_t *vp, char *fspath, char *fsname,
546 cred_t *cr, caller_context_t *ct)
547 {
548 int error;
549 char *p, *pnm;
550 vnode_t *realvp, *dvp;
551 vattr_t va;
552
553 /*
554 * Get vnode for the real file/dir. We'll have a hold on realvp which
555 * we won't vn_rele until hyprlofs_inactive.
556 */
557 if ((error = lookupname(fspath, UIO_SYSSPACE, FOLLOW, NULLVPP,
558 &realvp)) != 0)
559 return (error);
560
561 /* no devices allowed */
562 if (IS_DEVVP(realvp)) {
563 VN_RELE(realvp);
564 return (ENODEV);
565 }
566
567 /*
568 * realvp may be an AUTOFS node, in which case we perform a VOP_ACCESS
569 * to trigger the mount of the intended filesystem. This causes a
570 * loopback mount of the intended filesystem instead of the AUTOFS
571 * filesystem.
572 */
573 if ((error = VOP_ACCESS(realvp, 0, 0, cr, NULL)) != 0) {
574 VN_RELE(realvp);
575 return (error);
576 }
577
578 /*
579 * We're interested in the top most filesystem. This is specially
580 * important when fspath is a trigger AUTOFS node, since we're really
581 * interested in mounting the filesystem AUTOFS mounted as result of
582 * the VOP_ACCESS() call not the AUTOFS node itself.
583 */
584 if (vn_mountedvfs(realvp) != NULL) {
585 if ((error = traverse(&realvp)) != 0) {
586 VN_RELE(realvp);
587 return (error);
588 }
589 }
590
591 va.va_type = VNON;
592 /*
593 * If the target name is a path, make sure we have all of the
594 * intermediate directories, creating them if necessary.
595 */
596 dvp = vp;
597 pnm = p = fsname;
598
599 /* path cannot be absolute */
600 if (*p == '/') {
601 VN_RELE(realvp);
602 return (EINVAL);
603 }
604
605 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) {
606 if (va.va_type == VNON)
607 /* use the top-level dir as the template va for mkdir */
608 if ((error = VOP_GETATTR(vp, &va, 0, cr, NULL)) != 0) {
609 VN_RELE(realvp);
610 return (error);
611 }
612
613 *p = '\0';
614
615 /* Path component cannot be empty or relative */
616 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) {
617 VN_RELE(realvp);
618 return (EINVAL);
619 }
620
621 if ((error = hyprlofs_mkdir(dvp, pnm, &va, &dvp, cr)) != 0 &&
622 error != EEXIST) {
623 VN_RELE(realvp);
624 return (error);
625 }
626
627 *p = '/';
628 pnm = p + 1;
629 }
630
631 /* The file name is required */
632 if (pnm[0] == '\0') {
633 VN_RELE(realvp);
634 return (EINVAL);
635 }
636
637 /* Now use the real file's va as the template va */
638 if ((error = VOP_GETATTR(realvp, &va, 0, cr, NULL)) != 0) {
639 VN_RELE(realvp);
640 return (error);
641 }
642
643 /* Make the vnode */
644 error = hyprlofs_loopback(dvp, realvp, pnm, &va, va.va_mode, cr, ct);
645 if (error != 0)
646 VN_RELE(realvp);
647 return (error);
648 }
649
650 /*
651 * Remove a looped in file from the namespace.
652 */
653 static int
654 hyprlofs_rm_entry(vnode_t *dvp, char *fsname, cred_t *cr, caller_context_t *ct,
655 int flags)
656 {
657 int error;
658 char *p, *pnm;
659 hlnode_t *parent;
660 hlnode_t *fndtp;
661
662 pnm = p = fsname;
663
664 /* path cannot be absolute */
665 if (*p == '/')
666 return (EINVAL);
667
668 /*
669 * If the target name is a path, get the containing dir and simple
670 * file name.
671 */
672 parent = (hlnode_t *)VTOHLN(dvp);
673 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) {
674 *p = '\0';
675
676 /* Path component cannot be empty or relative */
677 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.'))
678 return (EINVAL);
679
680 if ((error = hyprlofs_dirlookup(parent, pnm, &fndtp, cr)) != 0)
681 return (error);
682
683 dvp = HLNTOV(fndtp);
684 parent = fndtp;
685 pnm = p + 1;
686 }
687
688 /* The file name is required */
689 if (pnm[0] == '\0')
690 return (EINVAL);
691
692 /* Remove the entry from the parent dir */
693 return (hyprlofs_remove(dvp, pnm, cr, ct, flags));
694 }
695
696 /*
697 * Remove all looped in files from the namespace.
698 */
699 static int
700 hyprlofs_rm_all(vnode_t *dvp, cred_t *cr, caller_context_t *ct,
701 int flags)
702 {
703 int error = 0;
704 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp);
705 hldirent_t *hdp;
706
707 hlnode_hold(hp);
708
709 /*
710 * There's a window here where someone could have removed
711 * all the entries in the directory after we put a hold on the
712 * vnode but before we grabbed the rwlock. Just return.
713 */
714 if (hp->hln_dir == NULL) {
715 if (hp->hln_nlink) {
716 panic("empty directory 0x%p", (void *)hp);
717 /*NOTREACHED*/
718 }
719 goto done;
720 }
721
722 hdp = hp->hln_dir;
723 while (hdp) {
724 hlnode_t *fndhp;
725
726 if (strcmp(hdp->hld_name, ".") == 0 ||
727 strcmp(hdp->hld_name, "..") == 0) {
728 hdp = hdp->hld_next;
729 continue;
730 }
731
732 /* This holds the fndhp vnode */
733 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr);
734 if (error != 0)
735 goto done;
736 hlnode_rele(fndhp);
737
738 if (fndhp->hln_looped == 0) {
739 /* recursively remove contents of this subdir */
740 if (fndhp->hln_type == VDIR) {
741 vnode_t *tvp = HLNTOV(fndhp);
742
743 error = hyprlofs_rm_all(tvp, cr, ct, flags);
744 if (error != 0)
745 goto done;
746 }
747 }
748
749 /* remove the entry */
750 error = hyprlofs_remove(dvp, hdp->hld_name, cr, ct, flags);
751 if (error != 0)
752 goto done;
753
754 hdp = hp->hln_dir;
755 }
756
757 done:
758 hlnode_rele(hp);
759 return (error);
760 }
761
762 /*
763 * Get a list of all looped in files in the namespace.
764 */
765 static int
766 hyprlofs_get_all_entries(vnode_t *dvp, hyprlofs_curr_entry_t *hcp,
767 char *prefix, int *pcnt, int n_max,
768 cred_t *cr, caller_context_t *ct, int flags)
769 {
770 int error = 0;
771 int too_big = 0;
772 int cnt;
773 int len;
774 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp);
775 hldirent_t *hdp;
776 char *path;
777
778 cnt = *pcnt;
779 path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
780
781 hlnode_hold(hp);
782
783 /*
784 * There's a window here where someone could have removed
785 * all the entries in the directory after we put a hold on the
786 * vnode but before we grabbed the rwlock. Just return.
787 */
788 if (hp->hln_dir == NULL) {
789 if (hp->hln_nlink) {
790 panic("empty directory 0x%p", (void *)hp);
791 /*NOTREACHED*/
792 }
793 goto done;
794 }
795
796 hdp = hp->hln_dir;
797 while (hdp) {
798 hlnode_t *fndhp;
799 vnode_t *tvp;
800
801 if (strcmp(hdp->hld_name, ".") == 0 ||
802 strcmp(hdp->hld_name, "..") == 0) {
803 hdp = hdp->hld_next;
804 continue;
805 }
806
807 /* This holds the fndhp vnode */
808 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr);
809 if (error != 0)
810 goto done;
811 hlnode_rele(fndhp);
812
813 if (fndhp->hln_looped == 0) {
814 /* recursively get contents of this subdir */
815 VERIFY(fndhp->hln_type == VDIR);
816 tvp = HLNTOV(fndhp);
817
818 if (*prefix == '\0')
819 (void) strlcpy(path, hdp->hld_name, MAXPATHLEN);
820 else
821 (void) snprintf(path, MAXPATHLEN, "%s/%s",
822 prefix, hdp->hld_name);
823
824 error = hyprlofs_get_all_entries(tvp, hcp, path,
825 &cnt, n_max, cr, ct, flags);
826
827 if (error == E2BIG) {
828 too_big = 1;
829 error = 0;
830 }
831 if (error != 0)
832 goto done;
833 } else {
834 if (cnt < n_max) {
835 char *p;
836
837 if (*prefix == '\0')
838 (void) strlcpy(path, hdp->hld_name,
839 MAXPATHLEN);
840 else
841 (void) snprintf(path, MAXPATHLEN,
842 "%s/%s", prefix, hdp->hld_name);
843
844 len = strlen(path);
845 ASSERT(len <= MAXPATHLEN);
846 if (copyout(path, (void *)(hcp[cnt].hce_name),
847 len)) {
848 error = EFAULT;
849 goto done;
850 }
851
852 tvp = REALVP(HLNTOV(fndhp));
853 if (tvp->v_path == NULL) {
854 p = "<unknown>";
855 } else {
856 p = tvp->v_path;
857 }
858 len = strlen(p);
859 ASSERT(len <= MAXPATHLEN);
860 if (copyout(p, (void *)(hcp[cnt].hce_path),
861 len)) {
862 error = EFAULT;
863 goto done;
864 }
865 }
866
867 cnt++;
868 if (cnt > n_max)
869 too_big = 1;
870 }
871
872 hdp = hdp->hld_next;
873 }
874
875 done:
876 hlnode_rele(hp);
877 kmem_free(path, MAXPATHLEN);
878
879 *pcnt = cnt;
880 if (error == 0 && too_big == 1)
881 error = E2BIG;
882
883 return (error);
884 }
885
886 /*
887 * Return a list of all looped in files in the namespace.
888 */
889 static int
890 hyprlofs_get_all(vnode_t *dvp, intptr_t data, cred_t *cr, caller_context_t *ct,
891 int flags)
892 {
893 int limit, cnt, error;
894 model_t model;
895 hyprlofs_curr_entry_t *e;
896
897 model = get_udatamodel();
898
899 if (model == DATAMODEL_NATIVE) {
900 hyprlofs_curr_entries_t ebuf;
901
902 if (copyin((void *)data, &ebuf, sizeof (ebuf)))
903 return (EFAULT);
904 limit = ebuf.hce_cnt;
905 e = ebuf.hce_entries;
906 if (limit > MAX_IOCTL_PARAMS)
907 return (EINVAL);
908
909 } else {
910 hyprlofs_curr_entries32_t ebuf32;
911
912 if (copyin((void *)data, &ebuf32, sizeof (ebuf32)))
913 return (EFAULT);
914
915 limit = ebuf32.hce_cnt;
916 e = (hyprlofs_curr_entry_t *)(unsigned long)
917 (ebuf32.hce_entries);
918 if (limit > MAX_IOCTL_PARAMS)
919 return (EINVAL);
920 }
921
922 cnt = 0;
923 error = hyprlofs_get_all_entries(dvp, e, "", &cnt, limit, cr, ct,
924 flags);
925
926 if (error == 0 || error == E2BIG) {
927 if (model == DATAMODEL_NATIVE) {
928 hyprlofs_curr_entries_t ebuf;
929
930 ebuf.hce_cnt = cnt;
931 if (copyout(&ebuf, (void *)data, sizeof (ebuf)))
932 return (EFAULT);
933
934 } else {
935 hyprlofs_curr_entries32_t ebuf32;
936
937 ebuf32.hce_cnt = cnt;
938 if (copyout(&ebuf32, (void *)data, sizeof (ebuf32)))
939 return (EFAULT);
940 }
941 }
942
943 return (error);
944 }
945
946 /* ARGSUSED3 */
947 static int
948 hyprlofs_remove(vnode_t *dvp, char *nm, cred_t *cr, caller_context_t *ct,
949 int flags)
950 {
951 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp);
952 int error;
953 hlnode_t *hp = NULL;
954
955 /* This holds the hp vnode */
956 error = hyprlofs_dirlookup(parent, nm, &hp, cr);
957 if (error)
958 return (error);
959
960 ASSERT(hp);
961 rw_enter(&parent->hln_rwlock, RW_WRITER);
962 rw_enter(&hp->hln_rwlock, RW_WRITER);
963
964 error = hyprlofs_dirdelete(parent, hp, nm, DR_REMOVE, cr);
965
966 rw_exit(&hp->hln_rwlock);
967 rw_exit(&parent->hln_rwlock);
968 vnevent_remove(HLNTOV(hp), dvp, nm, ct);
969
970 /*
971 * We've now dropped the dir link so by rele-ing our vnode we should
972 * clean up in hyprlofs_inactive.
973 */
974 hlnode_rele(hp);
975
976 return (error);
977 }
978
979 /* ARGSUSED4 */
980 static int
981 hyprlofs_rmdir(vnode_t *dvp, char *nm, vnode_t *cdir, cred_t *cr,
982 caller_context_t *ct, int flags)
983 {
984 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp);
985 hlnode_t *self = NULL;
986 vnode_t *vp;
987 int error = 0;
988
989 /* Return error if removing . or .. */
990 if (strcmp(nm, ".") == 0)
991 return (EINVAL);
992 if (strcmp(nm, "..") == 0)
993 return (EEXIST); /* Should be ENOTEMPTY */
994 error = hyprlofs_dirlookup(parent, nm, &self, cr);
995 if (error)
996 return (error);
997
998 rw_enter(&parent->hln_rwlock, RW_WRITER);
999 rw_enter(&self->hln_rwlock, RW_WRITER);
1000
1001 vp = HLNTOV(self);
1002 if (vp == dvp || vp == cdir) {
1003 error = EINVAL;
1004 goto done1;
1005 }
1006 if (self->hln_type != VDIR) {
1007 error = ENOTDIR;
1008 goto done1;
1009 }
1010
1011 /*
1012 * When a dir is looped in, we only remove the in-memory dir, not the
1013 * backing dir.
1014 */
1015 if (self->hln_looped == 0) {
1016 mutex_enter(&self->hln_tlock);
1017 if (self->hln_nlink > 2) {
1018 mutex_exit(&self->hln_tlock);
1019 error = EEXIST;
1020 goto done1;
1021 }
1022 mutex_exit(&self->hln_tlock);
1023
1024 if (vn_vfswlock(vp)) {
1025 error = EBUSY;
1026 goto done1;
1027 }
1028 if (vn_mountedvfs(vp) != NULL) {
1029 error = EBUSY;
1030 goto done;
1031 }
1032
1033 /*
1034 * Check for an empty directory, i.e. only includes entries for
1035 * "." and ".."
1036 */
1037 if (self->hln_dirents > 2) {
1038 error = EEXIST; /* SIGH should be ENOTEMPTY */
1039 /*
1040 * Update atime because checking hln_dirents is
1041 * equivalent to reading the directory
1042 */
1043 gethrestime(&self->hln_atime);
1044 goto done;
1045 }
1046
1047 error = hyprlofs_dirdelete(parent, self, nm, DR_RMDIR, cr);
1048 } else {
1049 error = hyprlofs_dirdelete(parent, self, nm, DR_REMOVE, cr);
1050 }
1051
1052 done:
1053 if (self->hln_looped == 0)
1054 vn_vfsunlock(vp);
1055 done1:
1056 rw_exit(&self->hln_rwlock);
1057 rw_exit(&parent->hln_rwlock);
1058 vnevent_rmdir(HLNTOV(self), dvp, nm, ct);
1059
1060 /*
1061 * We've now dropped the dir link so by rele-ing our vnode we should
1062 * clean up in hyprlofs_inactive.
1063 */
1064 hlnode_rele(self);
1065
1066 return (error);
1067 }
1068
1069 static int
1070 hyprlofs_readdir(vnode_t *vp, struct uio *uiop, cred_t *cr, int *eofp,
1071 caller_context_t *ct, int flags)
1072 {
1073 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1074 hldirent_t *hdp;
1075 int error = 0;
1076 size_t namelen;
1077 struct dirent64 *dp;
1078 ulong_t offset;
1079 ulong_t total_bytes_wanted;
1080 long outcount = 0;
1081 long bufsize;
1082 int reclen;
1083 caddr_t outbuf;
1084
1085 if (VTOHLN(vp)->hln_looped == 1)
1086 return (VOP_READDIR(REALVP(vp), uiop, cr, eofp, ct, flags));
1087
1088 if (uiop->uio_loffset >= MAXOFF_T) {
1089 if (eofp)
1090 *eofp = 1;
1091 return (0);
1092 }
1093 /* assuming syscall has already called hln_rwlock */
1094 ASSERT(RW_READ_HELD(&hp->hln_rwlock));
1095
1096 if (uiop->uio_iovcnt != 1)
1097 return (EINVAL);
1098
1099 if (vp->v_type != VDIR)
1100 return (ENOTDIR);
1101
1102 /*
1103 * There's a window here where someone could have removed
1104 * all the entries in the directory after we put a hold on the
1105 * vnode but before we grabbed the rwlock. Just return.
1106 */
1107 if (hp->hln_dir == NULL) {
1108 if (hp->hln_nlink) {
1109 panic("empty directory 0x%p", (void *)hp);
1110 /*NOTREACHED*/
1111 }
1112 return (0);
1113 }
1114
1115 /* Get space for multiple dir entries */
1116 total_bytes_wanted = uiop->uio_iov->iov_len;
1117 bufsize = total_bytes_wanted + sizeof (struct dirent64);
1118 outbuf = kmem_alloc(bufsize, KM_SLEEP);
1119
1120 dp = (struct dirent64 *)((uintptr_t)outbuf);
1121
1122 offset = 0;
1123 hdp = hp->hln_dir;
1124 while (hdp) {
1125 namelen = strlen(hdp->hld_name); /* no +1 needed */
1126 offset = hdp->hld_offset;
1127 if (offset >= uiop->uio_offset) {
1128 reclen = (int)DIRENT64_RECLEN(namelen);
1129 if (outcount + reclen > total_bytes_wanted) {
1130 if (!outcount)
1131 /* Buffer too small for any entries. */
1132 error = EINVAL;
1133 break;
1134 }
1135 ASSERT(hdp->hld_hlnode != NULL);
1136
1137 /* zero out uninitialized bytes */
1138 (void) strncpy(dp->d_name, hdp->hld_name,
1139 DIRENT64_NAMELEN(reclen));
1140 dp->d_reclen = (ushort_t)reclen;
1141 dp->d_ino = (ino64_t)hdp->hld_hlnode->hln_nodeid;
1142 dp->d_off = (offset_t)hdp->hld_offset + 1;
1143 dp = (struct dirent64 *)
1144 ((uintptr_t)dp + dp->d_reclen);
1145 outcount += reclen;
1146 ASSERT(outcount <= bufsize);
1147 }
1148 hdp = hdp->hld_next;
1149 }
1150
1151 if (!error)
1152 error = uiomove(outbuf, outcount, UIO_READ, uiop);
1153
1154 if (!error) {
1155 /*
1156 * If we reached the end of the list our offset should now be
1157 * just past the end.
1158 */
1159 if (!hdp) {
1160 offset += 1;
1161 if (eofp)
1162 *eofp = 1;
1163 } else if (eofp)
1164 *eofp = 0;
1165 uiop->uio_offset = offset;
1166 }
1167 gethrestime(&hp->hln_atime);
1168 kmem_free(outbuf, bufsize);
1169 return (error);
1170 }
1171
1172 static int
1173 hyprlofs_fsync(vnode_t *vp, int syncflag, cred_t *cr, caller_context_t *ct)
1174 {
1175 if (VTOHLN(vp)->hln_looped == 1)
1176 return (VOP_FSYNC(REALVP(vp), syncflag, cr, ct));
1177 return (0);
1178 }
1179
1180 /* ARGSUSED */
1181 static void
1182 hyprlofs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
1183 {
1184 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1185 hlfsmount_t *hm = (hlfsmount_t *)VFSTOHLM(vp->v_vfsp);
1186
1187 rw_enter(&hp->hln_rwlock, RW_WRITER);
1188
1189 mutex_enter(&hp->hln_tlock);
1190 mutex_enter(&vp->v_lock);
1191 ASSERT(vp->v_count >= 1);
1192
1193 /*
1194 * If we don't have the last hold or the link count is non-zero,
1195 * there's nothing to do except drop our hold.
1196 */
1197 if (vp->v_count > 1 || hp->hln_nlink != 0) {
1198 vp->v_count--;
1199 mutex_exit(&vp->v_lock);
1200 mutex_exit(&hp->hln_tlock);
1201 rw_exit(&hp->hln_rwlock);
1202 return;
1203 }
1204
1205 mutex_exit(&vp->v_lock);
1206 mutex_exit(&hp->hln_tlock);
1207
1208 /* release hold on the real vnode now */
1209 if (hp->hln_looped == 1 && hp->hln_realvp != NULL)
1210 VN_RELE(hp->hln_realvp);
1211
1212 /* Here's our chance to send invalid event while we're between locks */
1213 vn_invalid(HLNTOV(hp));
1214
1215 mutex_enter(&hm->hlm_contents);
1216 if (hp->hln_forw == NULL)
1217 hm->hlm_rootnode->hln_back = hp->hln_back;
1218 else
1219 hp->hln_forw->hln_back = hp->hln_back;
1220 hp->hln_back->hln_forw = hp->hln_forw;
1221 mutex_exit(&hm->hlm_contents);
1222 rw_exit(&hp->hln_rwlock);
1223 rw_destroy(&hp->hln_rwlock);
1224 mutex_destroy(&hp->hln_tlock);
1225 vn_free(HLNTOV(hp));
1226 hyprlofs_memfree(hp, sizeof (hlnode_t));
1227 }
1228
1229 static int
1230 hyprlofs_fid(vnode_t *vp, struct fid *fidp, caller_context_t *ct)
1231 {
1232 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1233 hlfid_t *hfid;
1234
1235 if (VTOHLN(vp)->hln_looped == 1)
1236 return (VOP_FID(REALVP(vp), fidp, ct));
1237
1238 if (fidp->fid_len < (sizeof (hlfid_t) - sizeof (ushort_t))) {
1239 fidp->fid_len = sizeof (hlfid_t) - sizeof (ushort_t);
1240 return (ENOSPC);
1241 }
1242
1243 hfid = (hlfid_t *)fidp;
1244 bzero(hfid, sizeof (hlfid_t));
1245 hfid->hlfid_len = (int)sizeof (hlfid_t) - sizeof (ushort_t);
1246
1247 hfid->hlfid_ino = hp->hln_nodeid;
1248 hfid->hlfid_gen = hp->hln_gen;
1249
1250 return (0);
1251 }
1252
1253 static int
1254 hyprlofs_getpage(vnode_t *vp, offset_t off, size_t len, uint_t *protp,
1255 page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr, enum seg_rw rw,
1256 cred_t *cr, caller_context_t *ct)
1257 {
1258 ASSERT(VTOHLN(vp)->hln_looped == 1);
1259 return (VOP_GETPAGE(REALVP(vp), off, len, protp, pl, plsz, seg, addr,
1260 rw, cr, ct));
1261 }
1262
1263 int
1264 hyprlofs_putpage(vnode_t *vp, offset_t off, size_t len, int flags,
1265 cred_t *cr, caller_context_t *ct)
1266 {
1267 ASSERT(VTOHLN(vp)->hln_looped == 1);
1268 return (VOP_PUTPAGE(REALVP(vp), off, len, flags, cr, ct));
1269 }
1270
1271 static int
1272 hyprlofs_map(vnode_t *vp, offset_t off, struct as *as, caddr_t *addrp,
1273 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr,
1274 caller_context_t *ct)
1275 {
1276 ASSERT(VTOHLN(vp)->hln_looped == 1);
1277 return (VOP_MAP(REALVP(vp), off, as, addrp, len, prot, maxprot, flags,
1278 cr, ct));
1279 }
1280
1281 static int
1282 hyprlofs_addmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr,
1283 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr,
1284 caller_context_t *ct)
1285 {
1286 ASSERT(VTOHLN(vp)->hln_looped == 1);
1287 return (VOP_ADDMAP(REALVP(vp), off, as, addr, len, prot, maxprot,
1288 flags, cr, ct));
1289 }
1290
1291 static int
1292 hyprlofs_delmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr,
1293 size_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *cr,
1294 caller_context_t *ct)
1295 {
1296 ASSERT(VTOHLN(vp)->hln_looped == 1);
1297 return (VOP_DELMAP(REALVP(vp), off, as, addr, len, prot, maxprot,
1298 flags, cr, ct));
1299 }
1300
1301 static int
1302 hyprlofs_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag,
1303 offset_t offset, cred_t *cr, caller_context_t *ct)
1304 {
1305 ASSERT(VTOHLN(vp)->hln_looped == 1);
1306 return (VOP_SPACE(REALVP(vp), cmd, bfp, flag, offset, cr, ct));
1307 }
1308
1309 static int
1310 hyprlofs_seek(vnode_t *vp, offset_t ooff, offset_t *noffp,
1311 caller_context_t *ct)
1312 {
1313 if (VTOHLN(vp)->hln_looped == 0)
1314 return ((*noffp < 0 || *noffp > MAXOFFSET_T) ? EINVAL : 0);
1315
1316 return (VOP_SEEK(REALVP(vp), ooff, noffp, ct));
1317 }
1318
1319 static int
1320 hyprlofs_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct)
1321 {
1322 hlnode_t *hp = VTOHLN(vp);
1323
1324 if (hp->hln_looped == 1)
1325 return (VOP_RWLOCK(REALVP(vp), write_lock, ct));
1326
1327 if (write_lock) {
1328 rw_enter(&hp->hln_rwlock, RW_WRITER);
1329 } else {
1330 rw_enter(&hp->hln_rwlock, RW_READER);
1331 }
1332 return (write_lock);
1333 }
1334
1335 static void
1336 hyprlofs_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct)
1337 {
1338 hlnode_t *hp = VTOHLN(vp);
1339
1340 if (hp->hln_looped == 1) {
1341 VOP_RWUNLOCK(REALVP(vp), write_lock, ct);
1342 return;
1343 }
1344
1345 rw_exit(&hp->hln_rwlock);
1346 }
1347
1348 static int
1349 hyprlofs_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr,
1350 caller_context_t *ct)
1351 {
1352 int error;
1353
1354 if (VTOHLN(vp)->hln_looped == 1)
1355 return (VOP_PATHCONF(REALVP(vp), cmd, valp, cr, ct));
1356
1357 switch (cmd) {
1358 case _PC_XATTR_ENABLED:
1359 case _PC_XATTR_EXISTS:
1360 case _PC_SATTR_ENABLED:
1361 case _PC_SATTR_EXISTS:
1362 error = EINVAL;
1363 break;
1364 case _PC_TIMESTAMP_RESOLUTION:
1365 /* nanosecond timestamp resolution */
1366 *valp = 1L;
1367 error = 0;
1368 break;
1369 default:
1370 error = fs_pathconf(vp, cmd, valp, cr, ct);
1371 }
1372 return (error);
1373 }
1374
1375
1376 struct vnodeops *hyprlofs_vnodeops;
1377
1378 const fs_operation_def_t hyprlofs_vnodeops_template[] = {
1379 VOPNAME_OPEN, { .vop_open = hyprlofs_open },
1380 VOPNAME_CLOSE, { .vop_close = hyprlofs_close },
1381 VOPNAME_READ, { .vop_read = hyprlofs_read },
1382 VOPNAME_WRITE, { .vop_write = hyprlofs_write },
1383 VOPNAME_IOCTL, { .vop_ioctl = hyprlofs_ioctl },
1384 VOPNAME_GETATTR, { .vop_getattr = hyprlofs_getattr },
1385 VOPNAME_SETATTR, { .vop_setattr = hyprlofs_setattr },
1386 VOPNAME_ACCESS, { .vop_access = hyprlofs_access },
1387 VOPNAME_LOOKUP, { .vop_lookup = hyprlofs_lookup },
1388 VOPNAME_CREATE, { .error = fs_error },
1389 VOPNAME_REMOVE, { .vop_remove = hyprlofs_remove },
1390 VOPNAME_LINK, { .error = fs_error },
1391 VOPNAME_RENAME, { .error = fs_error },
1392 VOPNAME_MKDIR, { .error = fs_error },
1393 VOPNAME_RMDIR, { .vop_rmdir = hyprlofs_rmdir },
1394 VOPNAME_READDIR, { .vop_readdir = hyprlofs_readdir },
1395 VOPNAME_SYMLINK, { .error = fs_error },
1396 VOPNAME_READLINK, { .error = fs_error },
1397 VOPNAME_FSYNC, { .vop_fsync = hyprlofs_fsync },
1398 VOPNAME_INACTIVE, { .vop_inactive = hyprlofs_inactive },
1399 VOPNAME_FID, { .vop_fid = hyprlofs_fid },
1400 VOPNAME_RWLOCK, { .vop_rwlock = hyprlofs_rwlock },
1401 VOPNAME_RWUNLOCK, { .vop_rwunlock = hyprlofs_rwunlock },
1402 VOPNAME_SEEK, { .vop_seek = hyprlofs_seek },
1403 VOPNAME_SPACE, { .vop_space = hyprlofs_space },
1404 VOPNAME_GETPAGE, { .vop_getpage = hyprlofs_getpage },
1405 VOPNAME_PUTPAGE, { .vop_putpage = hyprlofs_putpage },
1406 VOPNAME_MAP, { .vop_map = hyprlofs_map },
1407 VOPNAME_ADDMAP, { .vop_addmap = hyprlofs_addmap },
1408 VOPNAME_DELMAP, { .vop_delmap = hyprlofs_delmap },
1409 VOPNAME_PATHCONF, { .vop_pathconf = hyprlofs_pathconf },
1410 VOPNAME_VNEVENT, { .vop_vnevent = fs_vnevent_support },
1411 NULL, NULL
1412 };