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, &realvp))
558 return (error);
559
560 /* no devices allowed */
561 if (IS_DEVVP(realvp)) {
562 VN_RELE(realvp);
563 return (ENODEV);
564 }
565
566 /*
567 * realvp may be an AUTOFS node, in which case we perform a VOP_ACCESS
568 * to trigger the mount of the intended filesystem. This causes a
569 * loopback mount of the intended filesystem instead of the AUTOFS
570 * filesystem.
571 */
572 if ((error = VOP_ACCESS(realvp, 0, 0, cr, NULL)) != 0) {
573 VN_RELE(realvp);
574 return (error);
575 }
576
577 /*
578 * We're interested in the top most filesystem. This is specially
579 * important when fspath is a trigger AUTOFS node, since we're really
580 * interested in mounting the filesystem AUTOFS mounted as result of
581 * the VOP_ACCESS() call not the AUTOFS node itself.
582 */
583 if (vn_mountedvfs(realvp) != NULL) {
584 if (error = traverse(&realvp)) {
585 VN_RELE(realvp);
586 return (error);
587 }
588 }
589
590 va.va_type = VNON;
591 /*
592 * If the target name is a path, make sure we have all of the
593 * intermediate directories, creating them if necessary.
594 */
595 dvp = vp;
596 pnm = p = fsname;
597
598 /* path cannot be absolute */
599 if (*p == '/') {
600 VN_RELE(realvp);
601 return (EINVAL);
602 }
603
604 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) {
605 if (va.va_type == VNON)
606 /* use the top-level dir as the template va for mkdir */
607 if ((error = VOP_GETATTR(vp, &va, 0, cr, NULL)) != 0) {
608 VN_RELE(realvp);
609 return (error);
610 }
611
612 *p = '\0';
613
614 /* Path component cannot be empty or relative */
615 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) {
616 VN_RELE(realvp);
617 return (EINVAL);
618 }
619
620 if ((error = hyprlofs_mkdir(dvp, pnm, &va, &dvp, cr)) != 0 &&
621 error != EEXIST) {
622 VN_RELE(realvp);
623 return (error);
624 }
625
626 *p = '/';
627 pnm = p + 1;
628 }
629
630 /* The file name is required */
631 if (pnm[0] == '\0') {
632 VN_RELE(realvp);
633 return (EINVAL);
634 }
635
636 /* Now use the real file's va as the template va */
637 if ((error = VOP_GETATTR(realvp, &va, 0, cr, NULL)) != 0) {
638 VN_RELE(realvp);
639 return (error);
640 }
641
642 /* Make the vnode */
643 error = hyprlofs_loopback(dvp, realvp, pnm, &va, va.va_mode, cr, ct);
644 if (error != 0)
645 VN_RELE(realvp);
646 return (error);
647 }
648
649 /*
650 * Remove a looped in file from the namespace.
651 */
652 static int
653 hyprlofs_rm_entry(vnode_t *dvp, char *fsname, cred_t *cr, caller_context_t *ct,
654 int flags)
655 {
656 int error;
657 char *p, *pnm;
658 hlnode_t *parent;
659 hlnode_t *fndtp;
660
661 pnm = p = fsname;
662
663 /* path cannot be absolute */
664 if (*p == '/')
665 return (EINVAL);
666
667 /*
668 * If the target name is a path, get the containing dir and simple
669 * file name.
670 */
671 parent = (hlnode_t *)VTOHLN(dvp);
672 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) {
673 *p = '\0';
674
675 /* Path component cannot be empty or relative */
676 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.'))
677 return (EINVAL);
678
679 if ((error = hyprlofs_dirlookup(parent, pnm, &fndtp, cr)) != 0)
680 return (error);
681
682 dvp = HLNTOV(fndtp);
683 parent = fndtp;
684 pnm = p + 1;
685 }
686
687 /* The file name is required */
688 if (pnm[0] == '\0')
689 return (EINVAL);
690
691 /* Remove the entry from the parent dir */
692 return (hyprlofs_remove(dvp, pnm, cr, ct, flags));
693 }
694
695 /*
696 * Remove all looped in files from the namespace.
697 */
698 static int
699 hyprlofs_rm_all(vnode_t *dvp, cred_t *cr, caller_context_t *ct,
700 int flags)
701 {
702 int error = 0;
703 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp);
704 hldirent_t *hdp;
705
706 hlnode_hold(hp);
707
708 /*
709 * There's a window here where someone could have removed
710 * all the entries in the directory after we put a hold on the
711 * vnode but before we grabbed the rwlock. Just return.
712 */
713 if (hp->hln_dir == NULL) {
714 if (hp->hln_nlink) {
715 panic("empty directory 0x%p", (void *)hp);
716 /*NOTREACHED*/
717 }
718 goto done;
719 }
720
721 hdp = hp->hln_dir;
722 while (hdp) {
723 hlnode_t *fndhp;
724
725 if (strcmp(hdp->hld_name, ".") == 0 ||
726 strcmp(hdp->hld_name, "..") == 0) {
727 hdp = hdp->hld_next;
728 continue;
729 }
730
731 /* This holds the fndhp vnode */
732 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr);
733 if (error != 0)
734 goto done;
735 hlnode_rele(fndhp);
736
737 if (fndhp->hln_looped == 0) {
738 /* recursively remove contents of this subdir */
739 if (fndhp->hln_type == VDIR) {
740 vnode_t *tvp = HLNTOV(fndhp);
741
742 error = hyprlofs_rm_all(tvp, cr, ct, flags);
743 if (error != 0)
744 goto done;
745 }
746 }
747
748 /* remove the entry */
749 error = hyprlofs_remove(dvp, hdp->hld_name, cr, ct, flags);
750 if (error != 0)
751 goto done;
752
753 hdp = hp->hln_dir;
754 }
755
756 done:
757 hlnode_rele(hp);
758 return (error);
759 }
760
761 /*
762 * Get a list of all looped in files in the namespace.
763 */
764 static int
765 hyprlofs_get_all_entries(vnode_t *dvp, hyprlofs_curr_entry_t *hcp,
766 char *prefix, int *pcnt, int n_max,
767 cred_t *cr, caller_context_t *ct, int flags)
768 {
769 int error = 0;
770 int too_big = 0;
771 int cnt;
772 int len;
773 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp);
774 hldirent_t *hdp;
775 char *path;
776
777 cnt = *pcnt;
778 path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
779
780 hlnode_hold(hp);
781
782 /*
783 * There's a window here where someone could have removed
784 * all the entries in the directory after we put a hold on the
785 * vnode but before we grabbed the rwlock. Just return.
786 */
787 if (hp->hln_dir == NULL) {
788 if (hp->hln_nlink) {
789 panic("empty directory 0x%p", (void *)hp);
790 /*NOTREACHED*/
791 }
792 goto done;
793 }
794
795 hdp = hp->hln_dir;
796 while (hdp) {
797 hlnode_t *fndhp;
798 vnode_t *tvp;
799
800 if (strcmp(hdp->hld_name, ".") == 0 ||
801 strcmp(hdp->hld_name, "..") == 0) {
802 hdp = hdp->hld_next;
803 continue;
804 }
805
806 /* This holds the fndhp vnode */
807 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr);
808 if (error != 0)
809 goto done;
810 hlnode_rele(fndhp);
811
812 if (fndhp->hln_looped == 0) {
813 /* recursively get contents of this subdir */
814 VERIFY(fndhp->hln_type == VDIR);
815 tvp = HLNTOV(fndhp);
816
817 if (*prefix == '\0')
818 (void) strlcpy(path, hdp->hld_name, MAXPATHLEN);
819 else
820 (void) snprintf(path, MAXPATHLEN, "%s/%s",
821 prefix, hdp->hld_name);
822
823 error = hyprlofs_get_all_entries(tvp, hcp, path,
824 &cnt, n_max, cr, ct, flags);
825
826 if (error == E2BIG) {
827 too_big = 1;
828 error = 0;
829 }
830 if (error != 0)
831 goto done;
832 } else {
833 if (cnt < n_max) {
834 char *p;
835
836 if (*prefix == '\0')
837 (void) strlcpy(path, hdp->hld_name,
838 MAXPATHLEN);
839 else
840 (void) snprintf(path, MAXPATHLEN,
841 "%s/%s", prefix, hdp->hld_name);
842
843 len = strlen(path);
844 ASSERT(len <= MAXPATHLEN);
845 if (copyout(path, (void *)(hcp[cnt].hce_name),
846 len)) {
847 error = EFAULT;
848 goto done;
849 }
850
851 tvp = REALVP(HLNTOV(fndhp));
852 if (tvp->v_path == NULL) {
853 p = "<unknown>";
854 } else {
855 p = tvp->v_path;
856 }
857 len = strlen(p);
858 ASSERT(len <= MAXPATHLEN);
859 if (copyout(p, (void *)(hcp[cnt].hce_path),
860 len)) {
861 error = EFAULT;
862 goto done;
863 }
864 }
865
866 cnt++;
867 if (cnt > n_max)
868 too_big = 1;
869 }
870
871 hdp = hdp->hld_next;
872 }
873
874 done:
875 hlnode_rele(hp);
876 kmem_free(path, MAXPATHLEN);
877
878 *pcnt = cnt;
879 if (error == 0 && too_big == 1)
880 error = E2BIG;
881
882 return (error);
883 }
884
885 /*
886 * Return a list of all looped in files in the namespace.
887 */
888 static int
889 hyprlofs_get_all(vnode_t *dvp, intptr_t data, cred_t *cr, caller_context_t *ct,
890 int flags)
891 {
892 int limit, cnt, error;
893 model_t model;
894 hyprlofs_curr_entry_t *e;
895
896 model = get_udatamodel();
897
898 if (model == DATAMODEL_NATIVE) {
899 hyprlofs_curr_entries_t ebuf;
900
901 if (copyin((void *)data, &ebuf, sizeof (ebuf)))
902 return (EFAULT);
903 limit = ebuf.hce_cnt;
904 e = ebuf.hce_entries;
905 if (limit > MAX_IOCTL_PARAMS)
906 return (EINVAL);
907
908 } else {
909 hyprlofs_curr_entries32_t ebuf32;
910
911 if (copyin((void *)data, &ebuf32, sizeof (ebuf32)))
912 return (EFAULT);
913
914 limit = ebuf32.hce_cnt;
915 e = (hyprlofs_curr_entry_t *)(unsigned long)
916 (ebuf32.hce_entries);
917 if (limit > MAX_IOCTL_PARAMS)
918 return (EINVAL);
919 }
920
921 cnt = 0;
922 error = hyprlofs_get_all_entries(dvp, e, "", &cnt, limit, cr, ct,
923 flags);
924
925 if (error == 0 || error == E2BIG) {
926 if (model == DATAMODEL_NATIVE) {
927 hyprlofs_curr_entries_t ebuf;
928
929 ebuf.hce_cnt = cnt;
930 if (copyout(&ebuf, (void *)data, sizeof (ebuf)))
931 return (EFAULT);
932
933 } else {
934 hyprlofs_curr_entries32_t ebuf32;
935
936 ebuf32.hce_cnt = cnt;
937 if (copyout(&ebuf32, (void *)data, sizeof (ebuf32)))
938 return (EFAULT);
939 }
940 }
941
942 return (error);
943 }
944
945 /* ARGSUSED3 */
946 static int
947 hyprlofs_remove(vnode_t *dvp, char *nm, cred_t *cr, caller_context_t *ct,
948 int flags)
949 {
950 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp);
951 int error;
952 hlnode_t *hp = NULL;
953
954 /* This holds the hp vnode */
955 error = hyprlofs_dirlookup(parent, nm, &hp, cr);
956 if (error)
957 return (error);
958
959 ASSERT(hp);
960 rw_enter(&parent->hln_rwlock, RW_WRITER);
961 rw_enter(&hp->hln_rwlock, RW_WRITER);
962
963 error = hyprlofs_dirdelete(parent, hp, nm, DR_REMOVE, cr);
964
965 rw_exit(&hp->hln_rwlock);
966 rw_exit(&parent->hln_rwlock);
967 vnevent_remove(HLNTOV(hp), dvp, nm, ct);
968
969 /*
970 * We've now dropped the dir link so by rele-ing our vnode we should
971 * clean up in hyprlofs_inactive.
972 */
973 hlnode_rele(hp);
974
975 return (error);
976 }
977
978 /* ARGSUSED4 */
979 static int
980 hyprlofs_rmdir(vnode_t *dvp, char *nm, vnode_t *cdir, cred_t *cr,
981 caller_context_t *ct, int flags)
982 {
983 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp);
984 hlnode_t *self = NULL;
985 vnode_t *vp;
986 int error = 0;
987
988 /* Return error if removing . or .. */
989 if (strcmp(nm, ".") == 0)
990 return (EINVAL);
991 if (strcmp(nm, "..") == 0)
992 return (EEXIST); /* Should be ENOTEMPTY */
993 error = hyprlofs_dirlookup(parent, nm, &self, cr);
994 if (error)
995 return (error);
996
997 rw_enter(&parent->hln_rwlock, RW_WRITER);
998 rw_enter(&self->hln_rwlock, RW_WRITER);
999
1000 vp = HLNTOV(self);
1001 if (vp == dvp || vp == cdir) {
1002 error = EINVAL;
1003 goto done1;
1004 }
1005 if (self->hln_type != VDIR) {
1006 error = ENOTDIR;
1007 goto done1;
1008 }
1009
1010 /*
1011 * When a dir is looped in, we only remove the in-memory dir, not the
1012 * backing dir.
1013 */
1014 if (self->hln_looped == 0) {
1015 mutex_enter(&self->hln_tlock);
1016 if (self->hln_nlink > 2) {
1017 mutex_exit(&self->hln_tlock);
1018 error = EEXIST;
1019 goto done1;
1020 }
1021 mutex_exit(&self->hln_tlock);
1022
1023 if (vn_vfswlock(vp)) {
1024 error = EBUSY;
1025 goto done1;
1026 }
1027 if (vn_mountedvfs(vp) != NULL) {
1028 error = EBUSY;
1029 goto done;
1030 }
1031
1032 /*
1033 * Check for an empty directory, i.e. only includes entries for
1034 * "." and ".."
1035 */
1036 if (self->hln_dirents > 2) {
1037 error = EEXIST; /* SIGH should be ENOTEMPTY */
1038 /*
1039 * Update atime because checking hln_dirents is
1040 * equivalent to reading the directory
1041 */
1042 gethrestime(&self->hln_atime);
1043 goto done;
1044 }
1045
1046 error = hyprlofs_dirdelete(parent, self, nm, DR_RMDIR, cr);
1047 } else {
1048 error = hyprlofs_dirdelete(parent, self, nm, DR_REMOVE, cr);
1049 }
1050
1051 done:
1052 if (self->hln_looped == 0)
1053 vn_vfsunlock(vp);
1054 done1:
1055 rw_exit(&self->hln_rwlock);
1056 rw_exit(&parent->hln_rwlock);
1057 vnevent_rmdir(HLNTOV(self), dvp, nm, ct);
1058
1059 /*
1060 * We've now dropped the dir link so by rele-ing our vnode we should
1061 * clean up in hyprlofs_inactive.
1062 */
1063 hlnode_rele(self);
1064
1065 return (error);
1066 }
1067
1068 static int
1069 hyprlofs_readdir(vnode_t *vp, struct uio *uiop, cred_t *cr, int *eofp,
1070 caller_context_t *ct, int flags)
1071 {
1072 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1073 hldirent_t *hdp;
1074 int error = 0;
1075 size_t namelen;
1076 struct dirent64 *dp;
1077 ulong_t offset;
1078 ulong_t total_bytes_wanted;
1079 long outcount = 0;
1080 long bufsize;
1081 int reclen;
1082 caddr_t outbuf;
1083
1084 if (VTOHLN(vp)->hln_looped == 1)
1085 return (VOP_READDIR(REALVP(vp), uiop, cr, eofp, ct, flags));
1086
1087 if (uiop->uio_loffset >= MAXOFF_T) {
1088 if (eofp)
1089 *eofp = 1;
1090 return (0);
1091 }
1092 /* assuming syscall has already called hln_rwlock */
1093 ASSERT(RW_READ_HELD(&hp->hln_rwlock));
1094
1095 if (uiop->uio_iovcnt != 1)
1096 return (EINVAL);
1097
1098 if (vp->v_type != VDIR)
1099 return (ENOTDIR);
1100
1101 /*
1102 * There's a window here where someone could have removed
1103 * all the entries in the directory after we put a hold on the
1104 * vnode but before we grabbed the rwlock. Just return.
1105 */
1106 if (hp->hln_dir == NULL) {
1107 if (hp->hln_nlink) {
1108 panic("empty directory 0x%p", (void *)hp);
1109 /*NOTREACHED*/
1110 }
1111 return (0);
1112 }
1113
1114 /* Get space for multiple dir entries */
1115 total_bytes_wanted = uiop->uio_iov->iov_len;
1116 bufsize = total_bytes_wanted + sizeof (struct dirent64);
1117 outbuf = kmem_alloc(bufsize, KM_SLEEP);
1118
1119 dp = (struct dirent64 *)((uintptr_t)outbuf);
1120
1121 offset = 0;
1122 hdp = hp->hln_dir;
1123 while (hdp) {
1124 namelen = strlen(hdp->hld_name); /* no +1 needed */
1125 offset = hdp->hld_offset;
1126 if (offset >= uiop->uio_offset) {
1127 reclen = (int)DIRENT64_RECLEN(namelen);
1128 if (outcount + reclen > total_bytes_wanted) {
1129 if (!outcount)
1130 /* Buffer too small for any entries. */
1131 error = EINVAL;
1132 break;
1133 }
1134 ASSERT(hdp->hld_hlnode != NULL);
1135
1136 /* zero out uninitialized bytes */
1137 (void) strncpy(dp->d_name, hdp->hld_name,
1138 DIRENT64_NAMELEN(reclen));
1139 dp->d_reclen = (ushort_t)reclen;
1140 dp->d_ino = (ino64_t)hdp->hld_hlnode->hln_nodeid;
1141 dp->d_off = (offset_t)hdp->hld_offset + 1;
1142 dp = (struct dirent64 *)
1143 ((uintptr_t)dp + dp->d_reclen);
1144 outcount += reclen;
1145 ASSERT(outcount <= bufsize);
1146 }
1147 hdp = hdp->hld_next;
1148 }
1149
1150 if (!error)
1151 error = uiomove(outbuf, outcount, UIO_READ, uiop);
1152
1153 if (!error) {
1154 /*
1155 * If we reached the end of the list our offset should now be
1156 * just past the end.
1157 */
1158 if (!hdp) {
1159 offset += 1;
1160 if (eofp)
1161 *eofp = 1;
1162 } else if (eofp)
1163 *eofp = 0;
1164 uiop->uio_offset = offset;
1165 }
1166 gethrestime(&hp->hln_atime);
1167 kmem_free(outbuf, bufsize);
1168 return (error);
1169 }
1170
1171 static int
1172 hyprlofs_fsync(vnode_t *vp, int syncflag, cred_t *cr, caller_context_t *ct)
1173 {
1174 if (VTOHLN(vp)->hln_looped == 1)
1175 return (VOP_FSYNC(REALVP(vp), syncflag, cr, ct));
1176 return (0);
1177 }
1178
1179 /* ARGSUSED */
1180 static void
1181 hyprlofs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
1182 {
1183 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1184 hlfsmount_t *hm = (hlfsmount_t *)VFSTOHLM(vp->v_vfsp);
1185
1186 rw_enter(&hp->hln_rwlock, RW_WRITER);
1187
1188 mutex_enter(&hp->hln_tlock);
1189 mutex_enter(&vp->v_lock);
1190 ASSERT(vp->v_count >= 1);
1191
1192 /*
1193 * If we don't have the last hold or the link count is non-zero,
1194 * there's nothing to do except drop our hold.
1195 */
1196 if (vp->v_count > 1 || hp->hln_nlink != 0) {
1197 vp->v_count--;
1198 mutex_exit(&vp->v_lock);
1199 mutex_exit(&hp->hln_tlock);
1200 rw_exit(&hp->hln_rwlock);
1201 return;
1202 }
1203
1204 mutex_exit(&vp->v_lock);
1205 mutex_exit(&hp->hln_tlock);
1206
1207 /* release hold on the real vnode now */
1208 if (hp->hln_looped == 1 && hp->hln_realvp != NULL)
1209 VN_RELE(hp->hln_realvp);
1210
1211 /* Here's our chance to send invalid event while we're between locks */
1212 vn_invalid(HLNTOV(hp));
1213
1214 mutex_enter(&hm->hlm_contents);
1215 if (hp->hln_forw == NULL)
1216 hm->hlm_rootnode->hln_back = hp->hln_back;
1217 else
1218 hp->hln_forw->hln_back = hp->hln_back;
1219 hp->hln_back->hln_forw = hp->hln_forw;
1220 mutex_exit(&hm->hlm_contents);
1221 rw_exit(&hp->hln_rwlock);
1222 rw_destroy(&hp->hln_rwlock);
1223 mutex_destroy(&hp->hln_tlock);
1224 vn_free(HLNTOV(hp));
1225 hyprlofs_memfree(hp, sizeof (hlnode_t));
1226 }
1227
1228 static int
1229 hyprlofs_fid(vnode_t *vp, struct fid *fidp, caller_context_t *ct)
1230 {
1231 hlnode_t *hp = (hlnode_t *)VTOHLN(vp);
1232 hlfid_t *hfid;
1233
1234 if (VTOHLN(vp)->hln_looped == 1)
1235 return (VOP_FID(REALVP(vp), fidp, ct));
1236
1237 if (fidp->fid_len < (sizeof (hlfid_t) - sizeof (ushort_t))) {
1238 fidp->fid_len = sizeof (hlfid_t) - sizeof (ushort_t);
1239 return (ENOSPC);
1240 }
1241
1242 hfid = (hlfid_t *)fidp;
1243 bzero(hfid, sizeof (hlfid_t));
1244 hfid->hlfid_len = (int)sizeof (hlfid_t) - sizeof (ushort_t);
1245
1246 hfid->hlfid_ino = hp->hln_nodeid;
1247 hfid->hlfid_gen = hp->hln_gen;
1248
1249 return (0);
1250 }
1251
1252 static int
1253 hyprlofs_getpage(vnode_t *vp, offset_t off, size_t len, uint_t *protp,
1254 page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr, enum seg_rw rw,
1255 cred_t *cr, caller_context_t *ct)
1256 {
1257 ASSERT(VTOHLN(vp)->hln_looped == 1);
1258 return (VOP_GETPAGE(REALVP(vp), off, len, protp, pl, plsz, seg, addr,
1259 rw, cr, ct));
1260 }
1261
1262 int
1263 hyprlofs_putpage(vnode_t *vp, offset_t off, size_t len, int flags,
1264 cred_t *cr, caller_context_t *ct)
1265 {
1266 ASSERT(VTOHLN(vp)->hln_looped == 1);
1267 return (VOP_PUTPAGE(REALVP(vp), off, len, flags, cr, ct));
1268 }
1269
1270 static int
1271 hyprlofs_map(vnode_t *vp, offset_t off, struct as *as, caddr_t *addrp,
1272 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr,
1273 caller_context_t *ct)
1274 {
1275 ASSERT(VTOHLN(vp)->hln_looped == 1);
1276 return (VOP_MAP(REALVP(vp), off, as, addrp, len, prot, maxprot, flags,
1277 cr, ct));
1278 }
1279
1280 static int
1281 hyprlofs_addmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr,
1282 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr,
1283 caller_context_t *ct)
1284 {
1285 ASSERT(VTOHLN(vp)->hln_looped == 1);
1286 return (VOP_ADDMAP(REALVP(vp), off, as, addr, len, prot, maxprot,
1287 flags, cr, ct));
1288 }
1289
1290 static int
1291 hyprlofs_delmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr,
1292 size_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *cr,
1293 caller_context_t *ct)
1294 {
1295 ASSERT(VTOHLN(vp)->hln_looped == 1);
1296 return (VOP_DELMAP(REALVP(vp), off, as, addr, len, prot, maxprot,
1297 flags, cr, ct));
1298 }
1299
1300 static int
1301 hyprlofs_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag,
1302 offset_t offset, cred_t *cr, caller_context_t *ct)
1303 {
1304 ASSERT(VTOHLN(vp)->hln_looped == 1);
1305 return (VOP_SPACE(REALVP(vp), cmd, bfp, flag, offset, cr, ct));
1306 }
1307
1308 static int
1309 hyprlofs_seek(vnode_t *vp, offset_t ooff, offset_t *noffp,
1310 caller_context_t *ct)
1311 {
1312 if (VTOHLN(vp)->hln_looped == 0)
1313 return ((*noffp < 0 || *noffp > MAXOFFSET_T) ? EINVAL : 0);
1314
1315 return (VOP_SEEK(REALVP(vp), ooff, noffp, ct));
1316 }
1317
1318 static int
1319 hyprlofs_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct)
1320 {
1321 hlnode_t *hp = VTOHLN(vp);
1322
1323 if (hp->hln_looped == 1)
1324 return (VOP_RWLOCK(REALVP(vp), write_lock, ct));
1325
1326 if (write_lock) {
1327 rw_enter(&hp->hln_rwlock, RW_WRITER);
1328 } else {
1329 rw_enter(&hp->hln_rwlock, RW_READER);
1330 }
1331 return (write_lock);
1332 }
1333
1334 static void
1335 hyprlofs_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct)
1336 {
1337 hlnode_t *hp = VTOHLN(vp);
1338
1339 if (hp->hln_looped == 1) {
1340 VOP_RWUNLOCK(REALVP(vp), write_lock, ct);
1341 return;
1342 }
1343
1344 rw_exit(&hp->hln_rwlock);
1345 }
1346
1347 static int
1348 hyprlofs_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr,
1349 caller_context_t *ct)
1350 {
1351 int error;
1352
1353 if (VTOHLN(vp)->hln_looped == 1)
1354 return (VOP_PATHCONF(REALVP(vp), cmd, valp, cr, ct));
1355
1356 switch (cmd) {
1357 case _PC_XATTR_ENABLED:
1358 case _PC_XATTR_EXISTS:
1359 case _PC_SATTR_ENABLED:
1360 case _PC_SATTR_EXISTS:
1361 error = EINVAL;
1362 break;
1363 case _PC_TIMESTAMP_RESOLUTION:
1364 /* nanosecond timestamp resolution */
1365 *valp = 1L;
1366 error = 0;
1367 break;
1368 default:
1369 error = fs_pathconf(vp, cmd, valp, cr, ct);
1370 }
1371 return (error);
1372 }
1373
1374
1375 struct vnodeops *hyprlofs_vnodeops;
1376
1377 const fs_operation_def_t hyprlofs_vnodeops_template[] = {
1378 VOPNAME_OPEN, { .vop_open = hyprlofs_open },
1379 VOPNAME_CLOSE, { .vop_close = hyprlofs_close },
1380 VOPNAME_READ, { .vop_read = hyprlofs_read },
1381 VOPNAME_WRITE, { .vop_write = hyprlofs_write },
1382 VOPNAME_IOCTL, { .vop_ioctl = hyprlofs_ioctl },
1383 VOPNAME_GETATTR, { .vop_getattr = hyprlofs_getattr },
1384 VOPNAME_SETATTR, { .vop_setattr = hyprlofs_setattr },
1385 VOPNAME_ACCESS, { .vop_access = hyprlofs_access },
1386 VOPNAME_LOOKUP, { .vop_lookup = hyprlofs_lookup },
1387 VOPNAME_CREATE, { .error = fs_error },
1388 VOPNAME_REMOVE, { .vop_remove = hyprlofs_remove },
1389 VOPNAME_LINK, { .error = fs_error },
1390 VOPNAME_RENAME, { .error = fs_error },
1391 VOPNAME_MKDIR, { .error = fs_error },
1392 VOPNAME_RMDIR, { .vop_rmdir = hyprlofs_rmdir },
1393 VOPNAME_READDIR, { .vop_readdir = hyprlofs_readdir },
1394 VOPNAME_SYMLINK, { .error = fs_error },
1395 VOPNAME_READLINK, { .error = fs_error },
1396 VOPNAME_FSYNC, { .vop_fsync = hyprlofs_fsync },
1397 VOPNAME_INACTIVE, { .vop_inactive = hyprlofs_inactive },
1398 VOPNAME_FID, { .vop_fid = hyprlofs_fid },
1399 VOPNAME_RWLOCK, { .vop_rwlock = hyprlofs_rwlock },
1400 VOPNAME_RWUNLOCK, { .vop_rwunlock = hyprlofs_rwunlock },
1401 VOPNAME_SEEK, { .vop_seek = hyprlofs_seek },
1402 VOPNAME_SPACE, { .vop_space = hyprlofs_space },
1403 VOPNAME_GETPAGE, { .vop_getpage = hyprlofs_getpage },
1404 VOPNAME_PUTPAGE, { .vop_putpage = hyprlofs_putpage },
1405 VOPNAME_MAP, { .vop_map = hyprlofs_map },
1406 VOPNAME_ADDMAP, { .vop_addmap = hyprlofs_addmap },
1407 VOPNAME_DELMAP, { .vop_delmap = hyprlofs_delmap },
1408 VOPNAME_PATHCONF, { .vop_pathconf = hyprlofs_pathconf },
1409 VOPNAME_VNEVENT, { .vop_vnevent = fs_vnevent_support },
1410 NULL, NULL
1411 };