genfs_vnops.c revision 1.210 1 /* $NetBSD: genfs_vnops.c,v 1.210 2020/09/05 16:30:12 riastradh Exp $ */
2
3 /*-
4 * Copyright (c) 2008 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /*
30 * Copyright (c) 1982, 1986, 1989, 1993
31 * The Regents of the University of California. All rights reserved.
32 *
33 * Redistribution and use in source and binary forms, with or without
34 * modification, are permitted provided that the following conditions
35 * are met:
36 * 1. Redistributions of source code must retain the above copyright
37 * notice, this list of conditions and the following disclaimer.
38 * 2. Redistributions in binary form must reproduce the above copyright
39 * notice, this list of conditions and the following disclaimer in the
40 * documentation and/or other materials provided with the distribution.
41 * 3. Neither the name of the University nor the names of its contributors
42 * may be used to endorse or promote products derived from this software
43 * without specific prior written permission.
44 *
45 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55 * SUCH DAMAGE.
56 *
57 */
58
59 #include <sys/cdefs.h>
60 __KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.210 2020/09/05 16:30:12 riastradh Exp $");
61
62 #include <sys/param.h>
63 #include <sys/systm.h>
64 #include <sys/proc.h>
65 #include <sys/kernel.h>
66 #include <sys/mount.h>
67 #include <sys/fstrans.h>
68 #include <sys/namei.h>
69 #include <sys/vnode_impl.h>
70 #include <sys/fcntl.h>
71 #include <sys/kmem.h>
72 #include <sys/poll.h>
73 #include <sys/mman.h>
74 #include <sys/file.h>
75 #include <sys/kauth.h>
76 #include <sys/stat.h>
77 #include <sys/extattr.h>
78
79 #include <miscfs/genfs/genfs.h>
80 #include <miscfs/genfs/genfs_node.h>
81 #include <miscfs/specfs/specdev.h>
82
83 static void filt_genfsdetach(struct knote *);
84 static int filt_genfsread(struct knote *, long);
85 static int filt_genfsvnode(struct knote *, long);
86
87 int
88 genfs_poll(void *v)
89 {
90 struct vop_poll_args /* {
91 struct vnode *a_vp;
92 int a_events;
93 struct lwp *a_l;
94 } */ *ap = v;
95
96 return (ap->a_events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM));
97 }
98
99 int
100 genfs_seek(void *v)
101 {
102 struct vop_seek_args /* {
103 struct vnode *a_vp;
104 off_t a_oldoff;
105 off_t a_newoff;
106 kauth_cred_t cred;
107 } */ *ap = v;
108
109 if (ap->a_newoff < 0)
110 return (EINVAL);
111
112 return (0);
113 }
114
115 int
116 genfs_abortop(void *v)
117 {
118 struct vop_abortop_args /* {
119 struct vnode *a_dvp;
120 struct componentname *a_cnp;
121 } */ *ap = v;
122
123 (void)ap;
124
125 return (0);
126 }
127
128 int
129 genfs_fcntl(void *v)
130 {
131 struct vop_fcntl_args /* {
132 struct vnode *a_vp;
133 u_int a_command;
134 void *a_data;
135 int a_fflag;
136 kauth_cred_t a_cred;
137 struct lwp *a_l;
138 } */ *ap = v;
139
140 if (ap->a_command == F_SETFL)
141 return (0);
142 else
143 return (EOPNOTSUPP);
144 }
145
146 /*ARGSUSED*/
147 int
148 genfs_badop(void *v)
149 {
150
151 panic("genfs: bad op");
152 }
153
154 /*ARGSUSED*/
155 int
156 genfs_nullop(void *v)
157 {
158
159 return (0);
160 }
161
162 /*ARGSUSED*/
163 int
164 genfs_einval(void *v)
165 {
166
167 return (EINVAL);
168 }
169
170 /*
171 * Called when an fs doesn't support a particular vop.
172 * This takes care to vrele, vput, or vunlock passed in vnodes
173 * and calls VOP_ABORTOP for a componentname (in non-rename VOP).
174 */
175 int
176 genfs_eopnotsupp(void *v)
177 {
178 struct vop_generic_args /*
179 struct vnodeop_desc *a_desc;
180 / * other random data follows, presumably * /
181 } */ *ap = v;
182 struct vnodeop_desc *desc = ap->a_desc;
183 struct vnode *vp, *vp_last = NULL;
184 int flags, i, j, offset_cnp, offset_vp;
185
186 KASSERT(desc->vdesc_offset != VOP_LOOKUP_DESCOFFSET);
187 KASSERT(desc->vdesc_offset != VOP_ABORTOP_DESCOFFSET);
188
189 /*
190 * Abort any componentname that lookup potentially left state in.
191 *
192 * As is logical, componentnames for VOP_RENAME are handled by
193 * the caller of VOP_RENAME. Yay, rename!
194 */
195 if (desc->vdesc_offset != VOP_RENAME_DESCOFFSET &&
196 (offset_vp = desc->vdesc_vp_offsets[0]) != VDESC_NO_OFFSET &&
197 (offset_cnp = desc->vdesc_componentname_offset) != VDESC_NO_OFFSET){
198 struct componentname *cnp;
199 struct vnode *dvp;
200
201 dvp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap);
202 cnp = *VOPARG_OFFSETTO(struct componentname **, offset_cnp, ap);
203
204 VOP_ABORTOP(dvp, cnp);
205 }
206
207 flags = desc->vdesc_flags;
208 for (i = 0; i < VDESC_MAX_VPS; flags >>=1, i++) {
209 if ((offset_vp = desc->vdesc_vp_offsets[i]) == VDESC_NO_OFFSET)
210 break; /* stop at end of list */
211 if ((j = flags & VDESC_VP0_WILLPUT)) {
212 vp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap);
213
214 /* Skip if NULL */
215 if (!vp)
216 continue;
217
218 switch (j) {
219 case VDESC_VP0_WILLPUT:
220 /* Check for dvp == vp cases */
221 if (vp == vp_last)
222 vrele(vp);
223 else {
224 vput(vp);
225 vp_last = vp;
226 }
227 break;
228 case VDESC_VP0_WILLRELE:
229 vrele(vp);
230 break;
231 }
232 }
233 }
234
235 return (EOPNOTSUPP);
236 }
237
238 /*ARGSUSED*/
239 int
240 genfs_ebadf(void *v)
241 {
242
243 return (EBADF);
244 }
245
246 /* ARGSUSED */
247 int
248 genfs_enoioctl(void *v)
249 {
250
251 return (EPASSTHROUGH);
252 }
253
254
255 /*
256 * Eliminate all activity associated with the requested vnode
257 * and with all vnodes aliased to the requested vnode.
258 */
259 int
260 genfs_revoke(void *v)
261 {
262 struct vop_revoke_args /* {
263 struct vnode *a_vp;
264 int a_flags;
265 } */ *ap = v;
266
267 #ifdef DIAGNOSTIC
268 if ((ap->a_flags & REVOKEALL) == 0)
269 panic("genfs_revoke: not revokeall");
270 #endif
271 vrevoke(ap->a_vp);
272 return (0);
273 }
274
275 /*
276 * Lock the node (for deadfs).
277 */
278 int
279 genfs_deadlock(void *v)
280 {
281 struct vop_lock_args /* {
282 struct vnode *a_vp;
283 int a_flags;
284 } */ *ap = v;
285 vnode_t *vp = ap->a_vp;
286 vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
287 int flags = ap->a_flags;
288 krw_t op;
289
290 if (! ISSET(flags, LK_RETRY))
291 return ENOENT;
292
293 if (ISSET(flags, LK_DOWNGRADE)) {
294 rw_downgrade(&vip->vi_lock);
295 } else if (ISSET(flags, LK_UPGRADE)) {
296 KASSERT(ISSET(flags, LK_NOWAIT));
297 if (!rw_tryupgrade(&vip->vi_lock)) {
298 return EBUSY;
299 }
300 } else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) {
301 op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER);
302 if (ISSET(flags, LK_NOWAIT)) {
303 if (!rw_tryenter(&vip->vi_lock, op))
304 return EBUSY;
305 } else {
306 rw_enter(&vip->vi_lock, op);
307 }
308 }
309 VSTATE_ASSERT_UNLOCKED(vp, VS_RECLAIMED);
310 return 0;
311 }
312
313 /*
314 * Unlock the node (for deadfs).
315 */
316 int
317 genfs_deadunlock(void *v)
318 {
319 struct vop_unlock_args /* {
320 struct vnode *a_vp;
321 } */ *ap = v;
322 vnode_t *vp = ap->a_vp;
323 vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
324
325 rw_exit(&vip->vi_lock);
326
327 return 0;
328 }
329
330 /*
331 * Lock the node.
332 */
333 int
334 genfs_lock(void *v)
335 {
336 struct vop_lock_args /* {
337 struct vnode *a_vp;
338 int a_flags;
339 } */ *ap = v;
340 vnode_t *vp = ap->a_vp;
341 vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
342 int flags = ap->a_flags;
343 krw_t op;
344
345 if (ISSET(flags, LK_DOWNGRADE)) {
346 rw_downgrade(&vip->vi_lock);
347 } else if (ISSET(flags, LK_UPGRADE)) {
348 KASSERT(ISSET(flags, LK_NOWAIT));
349 if (!rw_tryupgrade(&vip->vi_lock)) {
350 return EBUSY;
351 }
352 } else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) {
353 op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER);
354 if (ISSET(flags, LK_NOWAIT)) {
355 if (!rw_tryenter(&vip->vi_lock, op))
356 return EBUSY;
357 } else {
358 rw_enter(&vip->vi_lock, op);
359 }
360 }
361 VSTATE_ASSERT_UNLOCKED(vp, VS_ACTIVE);
362 return 0;
363 }
364
365 /*
366 * Unlock the node.
367 */
368 int
369 genfs_unlock(void *v)
370 {
371 struct vop_unlock_args /* {
372 struct vnode *a_vp;
373 } */ *ap = v;
374 vnode_t *vp = ap->a_vp;
375 vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
376
377 rw_exit(&vip->vi_lock);
378
379 return 0;
380 }
381
382 /*
383 * Return whether or not the node is locked.
384 */
385 int
386 genfs_islocked(void *v)
387 {
388 struct vop_islocked_args /* {
389 struct vnode *a_vp;
390 } */ *ap = v;
391 vnode_t *vp = ap->a_vp;
392 vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
393
394 if (rw_write_held(&vip->vi_lock))
395 return LK_EXCLUSIVE;
396
397 if (rw_read_held(&vip->vi_lock))
398 return LK_SHARED;
399
400 return 0;
401 }
402
403 /*
404 * Stubs to use when there is no locking to be done on the underlying object.
405 */
406 int
407 genfs_nolock(void *v)
408 {
409
410 return (0);
411 }
412
413 int
414 genfs_nounlock(void *v)
415 {
416
417 return (0);
418 }
419
420 int
421 genfs_noislocked(void *v)
422 {
423
424 return (0);
425 }
426
427 int
428 genfs_mmap(void *v)
429 {
430
431 return (0);
432 }
433
434 /*
435 * VOP_PUTPAGES() for vnodes which never have pages.
436 */
437
438 int
439 genfs_null_putpages(void *v)
440 {
441 struct vop_putpages_args /* {
442 struct vnode *a_vp;
443 voff_t a_offlo;
444 voff_t a_offhi;
445 int a_flags;
446 } */ *ap = v;
447 struct vnode *vp = ap->a_vp;
448
449 KASSERT(vp->v_uobj.uo_npages == 0);
450 rw_exit(vp->v_uobj.vmobjlock);
451 return (0);
452 }
453
454 void
455 genfs_node_init(struct vnode *vp, const struct genfs_ops *ops)
456 {
457 struct genfs_node *gp = VTOG(vp);
458
459 rw_init(&gp->g_glock);
460 gp->g_op = ops;
461 }
462
463 void
464 genfs_node_destroy(struct vnode *vp)
465 {
466 struct genfs_node *gp = VTOG(vp);
467
468 rw_destroy(&gp->g_glock);
469 }
470
471 void
472 genfs_size(struct vnode *vp, off_t size, off_t *eobp, int flags)
473 {
474 int bsize;
475
476 bsize = 1 << vp->v_mount->mnt_fs_bshift;
477 *eobp = (size + bsize - 1) & ~(bsize - 1);
478 }
479
480 static void
481 filt_genfsdetach(struct knote *kn)
482 {
483 struct vnode *vp = (struct vnode *)kn->kn_hook;
484
485 mutex_enter(vp->v_interlock);
486 SLIST_REMOVE(&vp->v_klist, kn, knote, kn_selnext);
487 mutex_exit(vp->v_interlock);
488 }
489
490 static int
491 filt_genfsread(struct knote *kn, long hint)
492 {
493 struct vnode *vp = (struct vnode *)kn->kn_hook;
494 int rv;
495
496 /*
497 * filesystem is gone, so set the EOF flag and schedule
498 * the knote for deletion.
499 */
500 switch (hint) {
501 case NOTE_REVOKE:
502 KASSERT(mutex_owned(vp->v_interlock));
503 kn->kn_flags |= (EV_EOF | EV_ONESHOT);
504 return (1);
505 case 0:
506 mutex_enter(vp->v_interlock);
507 kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset;
508 rv = (kn->kn_data != 0);
509 mutex_exit(vp->v_interlock);
510 return rv;
511 default:
512 KASSERT(mutex_owned(vp->v_interlock));
513 kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset;
514 return (kn->kn_data != 0);
515 }
516 }
517
518 static int
519 filt_genfswrite(struct knote *kn, long hint)
520 {
521 struct vnode *vp = (struct vnode *)kn->kn_hook;
522
523 /*
524 * filesystem is gone, so set the EOF flag and schedule
525 * the knote for deletion.
526 */
527 switch (hint) {
528 case NOTE_REVOKE:
529 KASSERT(mutex_owned(vp->v_interlock));
530 kn->kn_flags |= (EV_EOF | EV_ONESHOT);
531 return (1);
532 case 0:
533 mutex_enter(vp->v_interlock);
534 kn->kn_data = 0;
535 mutex_exit(vp->v_interlock);
536 return 1;
537 default:
538 KASSERT(mutex_owned(vp->v_interlock));
539 kn->kn_data = 0;
540 return 1;
541 }
542 }
543
544 static int
545 filt_genfsvnode(struct knote *kn, long hint)
546 {
547 struct vnode *vp = (struct vnode *)kn->kn_hook;
548 int fflags;
549
550 switch (hint) {
551 case NOTE_REVOKE:
552 KASSERT(mutex_owned(vp->v_interlock));
553 kn->kn_flags |= EV_EOF;
554 if ((kn->kn_sfflags & hint) != 0)
555 kn->kn_fflags |= hint;
556 return (1);
557 case 0:
558 mutex_enter(vp->v_interlock);
559 fflags = kn->kn_fflags;
560 mutex_exit(vp->v_interlock);
561 break;
562 default:
563 KASSERT(mutex_owned(vp->v_interlock));
564 if ((kn->kn_sfflags & hint) != 0)
565 kn->kn_fflags |= hint;
566 fflags = kn->kn_fflags;
567 break;
568 }
569
570 return (fflags != 0);
571 }
572
573 static const struct filterops genfsread_filtops = {
574 .f_isfd = 1,
575 .f_attach = NULL,
576 .f_detach = filt_genfsdetach,
577 .f_event = filt_genfsread,
578 };
579
580 static const struct filterops genfswrite_filtops = {
581 .f_isfd = 1,
582 .f_attach = NULL,
583 .f_detach = filt_genfsdetach,
584 .f_event = filt_genfswrite,
585 };
586
587 static const struct filterops genfsvnode_filtops = {
588 .f_isfd = 1,
589 .f_attach = NULL,
590 .f_detach = filt_genfsdetach,
591 .f_event = filt_genfsvnode,
592 };
593
594 int
595 genfs_kqfilter(void *v)
596 {
597 struct vop_kqfilter_args /* {
598 struct vnode *a_vp;
599 struct knote *a_kn;
600 } */ *ap = v;
601 struct vnode *vp;
602 struct knote *kn;
603
604 vp = ap->a_vp;
605 kn = ap->a_kn;
606 switch (kn->kn_filter) {
607 case EVFILT_READ:
608 kn->kn_fop = &genfsread_filtops;
609 break;
610 case EVFILT_WRITE:
611 kn->kn_fop = &genfswrite_filtops;
612 break;
613 case EVFILT_VNODE:
614 kn->kn_fop = &genfsvnode_filtops;
615 break;
616 default:
617 return (EINVAL);
618 }
619
620 kn->kn_hook = vp;
621
622 mutex_enter(vp->v_interlock);
623 SLIST_INSERT_HEAD(&vp->v_klist, kn, kn_selnext);
624 mutex_exit(vp->v_interlock);
625
626 return (0);
627 }
628
629 void
630 genfs_node_wrlock(struct vnode *vp)
631 {
632 struct genfs_node *gp = VTOG(vp);
633
634 rw_enter(&gp->g_glock, RW_WRITER);
635 }
636
637 void
638 genfs_node_rdlock(struct vnode *vp)
639 {
640 struct genfs_node *gp = VTOG(vp);
641
642 rw_enter(&gp->g_glock, RW_READER);
643 }
644
645 int
646 genfs_node_rdtrylock(struct vnode *vp)
647 {
648 struct genfs_node *gp = VTOG(vp);
649
650 return rw_tryenter(&gp->g_glock, RW_READER);
651 }
652
653 void
654 genfs_node_unlock(struct vnode *vp)
655 {
656 struct genfs_node *gp = VTOG(vp);
657
658 rw_exit(&gp->g_glock);
659 }
660
661 int
662 genfs_node_wrlocked(struct vnode *vp)
663 {
664 struct genfs_node *gp = VTOG(vp);
665
666 return rw_write_held(&gp->g_glock);
667 }
668
669 static int
670 groupmember(gid_t gid, kauth_cred_t cred)
671 {
672 int ismember;
673 int error = kauth_cred_ismember_gid(cred, gid, &ismember);
674 if (error)
675 return error;
676 if (kauth_cred_getegid(cred) == gid || ismember)
677 return 0;
678 return -1;
679 }
680
681 /*
682 * Common filesystem object access control check routine. Accepts a
683 * vnode, cred, uid, gid, mode, acl, requested access mode.
684 * Returns 0 on success, or an errno on failure.
685 */
686 int
687 genfs_can_access(vnode_t *vp, kauth_cred_t cred, uid_t file_uid, gid_t file_gid,
688 mode_t file_mode, struct acl *acl, accmode_t accmode)
689 {
690 accmode_t dac_granted;
691 int error;
692
693 KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0);
694 KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));
695
696 /*
697 * Look for a normal, non-privileged way to access the file/directory
698 * as requested. If it exists, go with that.
699 */
700
701 dac_granted = 0;
702
703 /* Check the owner. */
704 if (kauth_cred_geteuid(cred) == file_uid) {
705 dac_granted |= VADMIN;
706 if (file_mode & S_IXUSR)
707 dac_granted |= VEXEC;
708 if (file_mode & S_IRUSR)
709 dac_granted |= VREAD;
710 if (file_mode & S_IWUSR)
711 dac_granted |= (VWRITE | VAPPEND);
712
713 goto privchk;
714 }
715
716 /* Otherwise, check the groups (first match) */
717 /* Otherwise, check the groups. */
718 error = groupmember(file_gid, cred);
719 if (error > 0)
720 return error;
721 if (error == 0) {
722 if (file_mode & S_IXGRP)
723 dac_granted |= VEXEC;
724 if (file_mode & S_IRGRP)
725 dac_granted |= VREAD;
726 if (file_mode & S_IWGRP)
727 dac_granted |= (VWRITE | VAPPEND);
728
729 goto privchk;
730 }
731
732 /* Otherwise, check everyone else. */
733 if (file_mode & S_IXOTH)
734 dac_granted |= VEXEC;
735 if (file_mode & S_IROTH)
736 dac_granted |= VREAD;
737 if (file_mode & S_IWOTH)
738 dac_granted |= (VWRITE | VAPPEND);
739
740 privchk:
741 if ((accmode & dac_granted) == accmode)
742 return 0;
743
744 return (accmode & VADMIN) ? EPERM : EACCES;
745 }
746
747 /*
748 * Implement a version of genfs_can_access() that understands POSIX.1e ACL
749 * semantics;
750 * the access ACL has already been prepared for evaluation by the file system
751 * and is passed via 'uid', 'gid', and 'acl'. Return 0 on success, else an
752 * errno value.
753 */
754 int
755 genfs_can_access_acl_posix1e(vnode_t *vp, kauth_cred_t cred, uid_t file_uid,
756 gid_t file_gid, mode_t file_mode, struct acl *acl, accmode_t accmode)
757 {
758 struct acl_entry *acl_other, *acl_mask;
759 accmode_t dac_granted;
760 accmode_t acl_mask_granted;
761 int group_matched, i;
762 int error;
763
764 KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0);
765 KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));
766
767 /*
768 * The owner matches if the effective uid associated with the
769 * credential matches that of the ACL_USER_OBJ entry. While we're
770 * doing the first scan, also cache the location of the ACL_MASK and
771 * ACL_OTHER entries, preventing some future iterations.
772 */
773 acl_mask = acl_other = NULL;
774 for (i = 0; i < acl->acl_cnt; i++) {
775 struct acl_entry *ae = &acl->acl_entry[i];
776 switch (ae->ae_tag) {
777 case ACL_USER_OBJ:
778 if (kauth_cred_geteuid(cred) != file_uid)
779 break;
780 dac_granted = 0;
781 dac_granted |= VADMIN;
782 if (ae->ae_perm & ACL_EXECUTE)
783 dac_granted |= VEXEC;
784 if (ae->ae_perm & ACL_READ)
785 dac_granted |= VREAD;
786 if (ae->ae_perm & ACL_WRITE)
787 dac_granted |= (VWRITE | VAPPEND);
788 goto out;
789
790 case ACL_MASK:
791 acl_mask = ae;
792 break;
793
794 case ACL_OTHER:
795 acl_other = ae;
796 break;
797
798 default:
799 break;
800 }
801 }
802
803 /*
804 * An ACL_OTHER entry should always exist in a valid access ACL. If
805 * it doesn't, then generate a serious failure. For now, this means
806 * a debugging message and EPERM, but in the future should probably
807 * be a panic.
808 */
809 if (acl_other == NULL) {
810 /*
811 * XXX This should never happen
812 */
813 printf("%s: ACL_OTHER missing\n", __func__);
814 return EPERM;
815 }
816
817 /*
818 * Checks against ACL_USER, ACL_GROUP_OBJ, and ACL_GROUP fields are
819 * masked by an ACL_MASK entry, if any. As such, first identify the
820 * ACL_MASK field, then iterate through identifying potential user
821 * matches, then group matches. If there is no ACL_MASK, assume that
822 * the mask allows all requests to succeed.
823 */
824 if (acl_mask != NULL) {
825 acl_mask_granted = 0;
826 if (acl_mask->ae_perm & ACL_EXECUTE)
827 acl_mask_granted |= VEXEC;
828 if (acl_mask->ae_perm & ACL_READ)
829 acl_mask_granted |= VREAD;
830 if (acl_mask->ae_perm & ACL_WRITE)
831 acl_mask_granted |= (VWRITE | VAPPEND);
832 } else
833 acl_mask_granted = VEXEC | VREAD | VWRITE | VAPPEND;
834
835 /*
836 * Check ACL_USER ACL entries. There will either be one or no
837 * matches; if there is one, we accept or rejected based on the
838 * match; otherwise, we continue on to groups.
839 */
840 for (i = 0; i < acl->acl_cnt; i++) {
841 struct acl_entry *ae = &acl->acl_entry[i];
842 switch (ae->ae_tag) {
843 case ACL_USER:
844 if (kauth_cred_geteuid(cred) != ae->ae_id)
845 break;
846 dac_granted = 0;
847 if (ae->ae_perm & ACL_EXECUTE)
848 dac_granted |= VEXEC;
849 if (ae->ae_perm & ACL_READ)
850 dac_granted |= VREAD;
851 if (ae->ae_perm & ACL_WRITE)
852 dac_granted |= (VWRITE | VAPPEND);
853 dac_granted &= acl_mask_granted;
854 goto out;
855 }
856 }
857
858 /*
859 * Group match is best-match, not first-match, so find a "best"
860 * match. Iterate across, testing each potential group match. Make
861 * sure we keep track of whether we found a match or not, so that we
862 * know if we should try again with any available privilege, or if we
863 * should move on to ACL_OTHER.
864 */
865 group_matched = 0;
866 for (i = 0; i < acl->acl_cnt; i++) {
867 struct acl_entry *ae = &acl->acl_entry[i];
868 switch (ae->ae_tag) {
869 case ACL_GROUP_OBJ:
870 error = groupmember(file_gid, cred);
871 if (error > 0)
872 return error;
873 if (error)
874 break;
875 dac_granted = 0;
876 if (ae->ae_perm & ACL_EXECUTE)
877 dac_granted |= VEXEC;
878 if (ae->ae_perm & ACL_READ)
879 dac_granted |= VREAD;
880 if (ae->ae_perm & ACL_WRITE)
881 dac_granted |= (VWRITE | VAPPEND);
882 dac_granted &= acl_mask_granted;
883
884 if ((accmode & dac_granted) == accmode)
885 return 0;
886
887 group_matched = 1;
888 break;
889
890 case ACL_GROUP:
891 error = groupmember(ae->ae_id, cred);
892 if (error > 0)
893 return error;
894 if (error)
895 break;
896 dac_granted = 0;
897 if (ae->ae_perm & ACL_EXECUTE)
898 dac_granted |= VEXEC;
899 if (ae->ae_perm & ACL_READ)
900 dac_granted |= VREAD;
901 if (ae->ae_perm & ACL_WRITE)
902 dac_granted |= (VWRITE | VAPPEND);
903 dac_granted &= acl_mask_granted;
904
905 if ((accmode & dac_granted) == accmode)
906 return 0;
907
908 group_matched = 1;
909 break;
910
911 default:
912 break;
913 }
914 }
915
916 if (group_matched == 1) {
917 /*
918 * There was a match, but it did not grant rights via pure
919 * DAC. Try again, this time with privilege.
920 */
921 for (i = 0; i < acl->acl_cnt; i++) {
922 struct acl_entry *ae = &acl->acl_entry[i];
923 switch (ae->ae_tag) {
924 case ACL_GROUP_OBJ:
925 error = groupmember(file_gid, cred);
926 if (error > 0)
927 return error;
928 if (error)
929 break;
930 dac_granted = 0;
931 if (ae->ae_perm & ACL_EXECUTE)
932 dac_granted |= VEXEC;
933 if (ae->ae_perm & ACL_READ)
934 dac_granted |= VREAD;
935 if (ae->ae_perm & ACL_WRITE)
936 dac_granted |= (VWRITE | VAPPEND);
937 dac_granted &= acl_mask_granted;
938 goto out;
939
940 case ACL_GROUP:
941 error = groupmember(ae->ae_id, cred);
942 if (error > 0)
943 return error;
944 if (error)
945 break;
946 dac_granted = 0;
947 if (ae->ae_perm & ACL_EXECUTE)
948 dac_granted |= VEXEC;
949 if (ae->ae_perm & ACL_READ)
950 dac_granted |= VREAD;
951 if (ae->ae_perm & ACL_WRITE)
952 dac_granted |= (VWRITE | VAPPEND);
953 dac_granted &= acl_mask_granted;
954
955 goto out;
956 default:
957 break;
958 }
959 }
960 /*
961 * Even with privilege, group membership was not sufficient.
962 * Return failure.
963 */
964 dac_granted = 0;
965 goto out;
966 }
967
968 /*
969 * Fall back on ACL_OTHER. ACL_MASK is not applied to ACL_OTHER.
970 */
971 dac_granted = 0;
972 if (acl_other->ae_perm & ACL_EXECUTE)
973 dac_granted |= VEXEC;
974 if (acl_other->ae_perm & ACL_READ)
975 dac_granted |= VREAD;
976 if (acl_other->ae_perm & ACL_WRITE)
977 dac_granted |= (VWRITE | VAPPEND);
978
979 out:
980 if ((accmode & dac_granted) == accmode)
981 return 0;
982 return (accmode & VADMIN) ? EPERM : EACCES;
983 }
984
985 static struct {
986 accmode_t accmode;
987 int mask;
988 } accmode2mask[] = {
989 { VREAD, ACL_READ_DATA },
990 { VWRITE, ACL_WRITE_DATA },
991 { VAPPEND, ACL_APPEND_DATA },
992 { VEXEC, ACL_EXECUTE },
993 { VREAD_NAMED_ATTRS, ACL_READ_NAMED_ATTRS },
994 { VWRITE_NAMED_ATTRS, ACL_WRITE_NAMED_ATTRS },
995 { VDELETE_CHILD, ACL_DELETE_CHILD },
996 { VREAD_ATTRIBUTES, ACL_READ_ATTRIBUTES },
997 { VWRITE_ATTRIBUTES, ACL_WRITE_ATTRIBUTES },
998 { VDELETE, ACL_DELETE },
999 { VREAD_ACL, ACL_READ_ACL },
1000 { VWRITE_ACL, ACL_WRITE_ACL },
1001 { VWRITE_OWNER, ACL_WRITE_OWNER },
1002 { VSYNCHRONIZE, ACL_SYNCHRONIZE },
1003 { 0, 0 },
1004 };
1005
1006 static int
1007 _access_mask_from_accmode(accmode_t accmode)
1008 {
1009 int access_mask = 0, i;
1010
1011 for (i = 0; accmode2mask[i].accmode != 0; i++) {
1012 if (accmode & accmode2mask[i].accmode)
1013 access_mask |= accmode2mask[i].mask;
1014 }
1015
1016 /*
1017 * VAPPEND is just a modifier for VWRITE; if the caller asked
1018 * for 'VAPPEND | VWRITE', we want to check for ACL_APPEND_DATA only.
1019 */
1020 if (access_mask & ACL_APPEND_DATA)
1021 access_mask &= ~ACL_WRITE_DATA;
1022
1023 return (access_mask);
1024 }
1025
1026 /*
1027 * Return 0, iff access is allowed, 1 otherwise.
1028 */
1029 static int
1030 _acl_denies(const struct acl *aclp, int access_mask, kauth_cred_t cred,
1031 int file_uid, int file_gid, int *denied_explicitly)
1032 {
1033 int i, error;
1034 const struct acl_entry *ae;
1035
1036 if (denied_explicitly != NULL)
1037 *denied_explicitly = 0;
1038
1039 KASSERT(aclp->acl_cnt <= ACL_MAX_ENTRIES);
1040
1041 for (i = 0; i < aclp->acl_cnt; i++) {
1042 ae = &(aclp->acl_entry[i]);
1043
1044 if (ae->ae_entry_type != ACL_ENTRY_TYPE_ALLOW &&
1045 ae->ae_entry_type != ACL_ENTRY_TYPE_DENY)
1046 continue;
1047 if (ae->ae_flags & ACL_ENTRY_INHERIT_ONLY)
1048 continue;
1049 switch (ae->ae_tag) {
1050 case ACL_USER_OBJ:
1051 if (kauth_cred_geteuid(cred) != file_uid)
1052 continue;
1053 break;
1054 case ACL_USER:
1055 if (kauth_cred_geteuid(cred) != ae->ae_id)
1056 continue;
1057 break;
1058 case ACL_GROUP_OBJ:
1059 error = groupmember(file_gid, cred);
1060 if (error > 0)
1061 return error;
1062 if (error != 0)
1063 continue;
1064 break;
1065 case ACL_GROUP:
1066 error = groupmember(ae->ae_id, cred);
1067 if (error > 0)
1068 return error;
1069 if (error != 0)
1070 continue;
1071 break;
1072 default:
1073 KASSERT(ae->ae_tag == ACL_EVERYONE);
1074 }
1075
1076 if (ae->ae_entry_type == ACL_ENTRY_TYPE_DENY) {
1077 if (ae->ae_perm & access_mask) {
1078 if (denied_explicitly != NULL)
1079 *denied_explicitly = 1;
1080 return (1);
1081 }
1082 }
1083
1084 access_mask &= ~(ae->ae_perm);
1085 if (access_mask == 0)
1086 return (0);
1087 }
1088
1089 if (access_mask == 0)
1090 return (0);
1091
1092 return (1);
1093 }
1094
1095 int
1096 genfs_can_access_acl_nfs4(vnode_t *vp, kauth_cred_t cred, uid_t file_uid,
1097 gid_t file_gid, mode_t file_mode, struct acl *aclp, accmode_t accmode)
1098 {
1099 int denied, explicitly_denied, access_mask, is_directory,
1100 must_be_owner = 0;
1101 file_mode = 0;
1102
1103 KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND |
1104 VEXPLICIT_DENY | VREAD_NAMED_ATTRS | VWRITE_NAMED_ATTRS |
1105 VDELETE_CHILD | VREAD_ATTRIBUTES | VWRITE_ATTRIBUTES | VDELETE |
1106 VREAD_ACL | VWRITE_ACL | VWRITE_OWNER | VSYNCHRONIZE)) == 0);
1107 KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));
1108
1109 if (accmode & VADMIN)
1110 must_be_owner = 1;
1111
1112 /*
1113 * Ignore VSYNCHRONIZE permission.
1114 */
1115 accmode &= ~VSYNCHRONIZE;
1116
1117 access_mask = _access_mask_from_accmode(accmode);
1118
1119 if (vp && vp->v_type == VDIR)
1120 is_directory = 1;
1121 else
1122 is_directory = 0;
1123
1124 /*
1125 * File owner is always allowed to read and write the ACL
1126 * and basic attributes. This is to prevent a situation
1127 * where user would change ACL in a way that prevents him
1128 * from undoing the change.
1129 */
1130 if (kauth_cred_geteuid(cred) == file_uid)
1131 access_mask &= ~(ACL_READ_ACL | ACL_WRITE_ACL |
1132 ACL_READ_ATTRIBUTES | ACL_WRITE_ATTRIBUTES);
1133
1134 /*
1135 * Ignore append permission for regular files; use write
1136 * permission instead.
1137 */
1138 if (!is_directory && (access_mask & ACL_APPEND_DATA)) {
1139 access_mask &= ~ACL_APPEND_DATA;
1140 access_mask |= ACL_WRITE_DATA;
1141 }
1142
1143 denied = _acl_denies(aclp, access_mask, cred, file_uid, file_gid,
1144 &explicitly_denied);
1145
1146 if (must_be_owner) {
1147 if (kauth_cred_geteuid(cred) != file_uid)
1148 denied = EPERM;
1149 }
1150
1151 /*
1152 * For VEXEC, ensure that at least one execute bit is set for
1153 * non-directories. We have to check the mode here to stay
1154 * consistent with execve(2). See the test in
1155 * exec_check_permissions().
1156 */
1157 __acl_nfs4_sync_mode_from_acl(&file_mode, aclp);
1158 if (!denied && !is_directory && (accmode & VEXEC) &&
1159 (file_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
1160 denied = EACCES;
1161
1162 if (!denied)
1163 return (0);
1164
1165 /*
1166 * Access failed. Iff it was not denied explicitly and
1167 * VEXPLICIT_DENY flag was specified, allow access.
1168 */
1169 if ((accmode & VEXPLICIT_DENY) && explicitly_denied == 0)
1170 return (0);
1171
1172 accmode &= ~VEXPLICIT_DENY;
1173
1174 if (accmode & (VADMIN_PERMS | VDELETE_CHILD | VDELETE))
1175 denied = EPERM;
1176 else
1177 denied = EACCES;
1178
1179 return (denied);
1180 }
1181
1182 /*
1183 * Common routine to check if chmod() is allowed.
1184 *
1185 * Policy:
1186 * - You must own the file, and
1187 * - You must not set the "sticky" bit (meaningless, see chmod(2))
1188 * - You must be a member of the group if you're trying to set the
1189 * SGIDf bit
1190 *
1191 * vp - vnode of the file-system object
1192 * cred - credentials of the invoker
1193 * cur_uid, cur_gid - current uid/gid of the file-system object
1194 * new_mode - new mode for the file-system object
1195 *
1196 * Returns 0 if the change is allowed, or an error value otherwise.
1197 */
1198 int
1199 genfs_can_chmod(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid,
1200 gid_t cur_gid, mode_t new_mode)
1201 {
1202 int error;
1203
1204 /*
1205 * To modify the permissions on a file, must possess VADMIN
1206 * for that file.
1207 */
1208 if ((error = VOP_ACCESSX(vp, VWRITE_ACL, cred)) != 0)
1209 return (error);
1210
1211 /*
1212 * Unprivileged users can't set the sticky bit on files.
1213 */
1214 if ((vp->v_type != VDIR) && (new_mode & S_ISTXT))
1215 return (EFTYPE);
1216
1217 /*
1218 * If the invoker is trying to set the SGID bit on the file,
1219 * check group membership.
1220 */
1221 if (new_mode & S_ISGID) {
1222 int ismember;
1223
1224 error = kauth_cred_ismember_gid(cred, cur_gid,
1225 &ismember);
1226 if (error || !ismember)
1227 return (EPERM);
1228 }
1229
1230 /*
1231 * Deny setting setuid if we are not the file owner.
1232 */
1233 if ((new_mode & S_ISUID) && cur_uid != kauth_cred_geteuid(cred))
1234 return (EPERM);
1235
1236 return (0);
1237 }
1238
1239 /*
1240 * Common routine to check if chown() is allowed.
1241 *
1242 * Policy:
1243 * - You must own the file, and
1244 * - You must not try to change ownership, and
1245 * - You must be member of the new group
1246 *
1247 * vp - vnode
1248 * cred - credentials of the invoker
1249 * cur_uid, cur_gid - current uid/gid of the file-system object
1250 * new_uid, new_gid - target uid/gid of the file-system object
1251 *
1252 * Returns 0 if the change is allowed, or an error value otherwise.
1253 */
1254 int
1255 genfs_can_chown(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid,
1256 gid_t cur_gid, uid_t new_uid, gid_t new_gid)
1257 {
1258 int error, ismember;
1259
1260 /*
1261 * To modify the ownership of a file, must possess VADMIN for that
1262 * file.
1263 */
1264 if ((error = VOP_ACCESSX(vp, VWRITE_OWNER, cred)) != 0)
1265 return (error);
1266
1267 /*
1268 * You can only change ownership of a file if:
1269 * You own the file and...
1270 */
1271 if (kauth_cred_geteuid(cred) == cur_uid) {
1272 /*
1273 * You don't try to change ownership, and...
1274 */
1275 if (new_uid != cur_uid)
1276 return (EPERM);
1277
1278 /*
1279 * You don't try to change group (no-op), or...
1280 */
1281 if (new_gid == cur_gid)
1282 return (0);
1283
1284 /*
1285 * Your effective gid is the new gid, or...
1286 */
1287 if (kauth_cred_getegid(cred) == new_gid)
1288 return (0);
1289
1290 /*
1291 * The new gid is one you're a member of.
1292 */
1293 ismember = 0;
1294 error = kauth_cred_ismember_gid(cred, new_gid,
1295 &ismember);
1296 if (!error && ismember)
1297 return (0);
1298 }
1299
1300 return (EPERM);
1301 }
1302
1303 int
1304 genfs_can_chtimes(vnode_t *vp, kauth_cred_t cred, uid_t owner_uid,
1305 u_int vaflags)
1306 {
1307 int error;
1308 /*
1309 * Grant permission if the caller is the owner of the file, or
1310 * the super-user, or has ACL_WRITE_ATTRIBUTES permission on
1311 * on the file. If the time pointer is null, then write
1312 * permission on the file is also sufficient.
1313 *
1314 * From NFSv4.1, draft 21, 6.2.1.3.1, Discussion of Mask Attributes:
1315 * A user having ACL_WRITE_DATA or ACL_WRITE_ATTRIBUTES
1316 * will be allowed to set the times [..] to the current
1317 * server time.
1318 */
1319 if ((error = VOP_ACCESSX(vp, VWRITE_ATTRIBUTES, cred)) != 0)
1320 return (vaflags & VA_UTIMES_NULL) == 0 ? EPERM : EACCES;
1321
1322 /* Must be owner, or... */
1323 if (kauth_cred_geteuid(cred) == owner_uid)
1324 return (0);
1325
1326 /* set the times to the current time, and... */
1327 if ((vaflags & VA_UTIMES_NULL) == 0)
1328 return (EPERM);
1329
1330 /* have write access. */
1331 error = VOP_ACCESS(vp, VWRITE, cred);
1332 if (error)
1333 return (error);
1334
1335 return (0);
1336 }
1337
1338 /*
1339 * Common routine to check if chflags() is allowed.
1340 *
1341 * Policy:
1342 * - You must own the file, and
1343 * - You must not change system flags, and
1344 * - You must not change flags on character/block devices.
1345 *
1346 * vp - vnode
1347 * cred - credentials of the invoker
1348 * owner_uid - uid of the file-system object
1349 * changing_sysflags - true if the invoker wants to change system flags
1350 */
1351 int
1352 genfs_can_chflags(vnode_t *vp, kauth_cred_t cred,
1353 uid_t owner_uid, bool changing_sysflags)
1354 {
1355
1356 /* The user must own the file. */
1357 if (kauth_cred_geteuid(cred) != owner_uid) {
1358 return EPERM;
1359 }
1360
1361 if (changing_sysflags) {
1362 return EPERM;
1363 }
1364
1365 /*
1366 * Unprivileged users cannot change the flags on devices, even if they
1367 * own them.
1368 */
1369 if (vp->v_type == VCHR || vp->v_type == VBLK) {
1370 return EPERM;
1371 }
1372
1373 return 0;
1374 }
1375
1376 /*
1377 * Common "sticky" policy.
1378 *
1379 * When a directory is "sticky" (as determined by the caller), this
1380 * function may help implementing the following policy:
1381 * - Renaming a file in it is only possible if the user owns the directory
1382 * or the file being renamed.
1383 * - Deleting a file from it is only possible if the user owns the
1384 * directory or the file being deleted.
1385 */
1386 int
1387 genfs_can_sticky(vnode_t *vp, kauth_cred_t cred, uid_t dir_uid, uid_t file_uid)
1388 {
1389 if (kauth_cred_geteuid(cred) != dir_uid &&
1390 kauth_cred_geteuid(cred) != file_uid)
1391 return EPERM;
1392
1393 return 0;
1394 }
1395
1396 int
1397 genfs_can_extattr(vnode_t *vp, kauth_cred_t cred, accmode_t accmode,
1398 int attrnamespace)
1399 {
1400 /*
1401 * Kernel-invoked always succeeds.
1402 */
1403 if (cred == NOCRED)
1404 return 0;
1405
1406 switch (attrnamespace) {
1407 case EXTATTR_NAMESPACE_SYSTEM:
1408 return kauth_authorize_system(cred, KAUTH_SYSTEM_FS_EXTATTR,
1409 0, vp->v_mount, NULL, NULL);
1410 case EXTATTR_NAMESPACE_USER:
1411 return VOP_ACCESS(vp, accmode, cred);
1412 default:
1413 return EPERM;
1414 }
1415 }
1416
1417 int
1418 genfs_access(void *v)
1419 {
1420 struct vop_access_args *ap = v;
1421
1422 KASSERT((ap->a_accmode & ~(VEXEC | VWRITE | VREAD | VADMIN |
1423 VAPPEND)) == 0);
1424
1425 return VOP_ACCESSX(ap->a_vp, ap->a_accmode, ap->a_cred);
1426 }
1427
1428 int
1429 genfs_accessx(void *v)
1430 {
1431 struct vop_accessx_args *ap = v;
1432 int error;
1433 accmode_t accmode = ap->a_accmode;
1434 error = vfs_unixify_accmode(&accmode);
1435 if (error != 0)
1436 return error;
1437
1438 if (accmode == 0)
1439 return 0;
1440
1441 return VOP_ACCESS(ap->a_vp, accmode, ap->a_cred);
1442 }
1443
1444 /*
1445 * genfs_pathconf:
1446 *
1447 * Standard implementation of POSIX pathconf, to get information about limits
1448 * for a filesystem.
1449 * Override per filesystem for the case where the filesystem has smaller
1450 * limits.
1451 */
1452 int
1453 genfs_pathconf(void *v)
1454 {
1455 struct vop_pathconf_args *ap = v;
1456
1457 switch (ap->a_name) {
1458 case _PC_PATH_MAX:
1459 *ap->a_retval = PATH_MAX;
1460 return 0;
1461 case _PC_ACL_EXTENDED:
1462 case _PC_ACL_NFS4:
1463 *ap->a_retval = 0;
1464 return 0;
1465 default:
1466 return EINVAL;
1467 }
1468 }
1469