xref: /illumos-gate/usr/src/uts/common/fs/fd/fdops.c (revision d0fccfcda73f8b52d101bd2b0f7885a766f7e354)
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  * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
26 /*	  All rights reserved.  	*/
27 
28 
29 #include <sys/types.h>
30 #include <sys/param.h>
31 #include <sys/cmn_err.h>
32 #include <sys/debug.h>
33 #include <sys/dirent.h>
34 #include <sys/errno.h>
35 #include <sys/file.h>
36 #include <sys/inline.h>
37 #include <sys/kmem.h>
38 #include <sys/pathname.h>
39 #include <sys/resource.h>
40 #include <sys/statvfs.h>
41 #include <sys/mount.h>
42 #include <sys/sysmacros.h>
43 #include <sys/systm.h>
44 #include <sys/uio.h>
45 #include <sys/vfs.h>
46 #include <sys/vfs_opreg.h>
47 #include <sys/vnode.h>
48 #include <sys/cred.h>
49 #include <sys/mntent.h>
50 #include <sys/mount.h>
51 #include <sys/user.h>
52 #include <sys/t_lock.h>
53 #include <sys/modctl.h>
54 #include <sys/policy.h>
55 #include <fs/fs_subr.h>
56 #include <sys/atomic.h>
57 #include <sys/mkdev.h>
58 
59 #define	round(r)	(((r)+sizeof (int)-1)&(~(sizeof (int)-1)))
60 #define	fdtoi(n)	((n)+100)
61 
62 #define	FDDIRSIZE 14
63 struct fddirect {
64 	short	d_ino;
65 	char	d_name[FDDIRSIZE];
66 };
67 
68 #define	FDROOTINO	2
69 #define	FDSDSIZE	sizeof (struct fddirect)
70 #define	FDNSIZE		10
71 
72 static int		fdfstype = 0;
73 static major_t		fdfsmaj;
74 static minor_t		fdfsmin;
75 static major_t		fdrmaj;
76 static kmutex_t		fd_minor_lock;
77 
78 static int fdget(vnode_t *, char *, vnode_t **);
79 
80 /* ARGSUSED */
81 static int
82 fdopen(vnode_t **vpp, int mode, cred_t *cr, caller_context_t *ct)
83 {
84 	if ((*vpp)->v_type != VDIR) {
85 		mutex_enter(&(*vpp)->v_lock);
86 		(*vpp)->v_flag |= VDUP;
87 		mutex_exit(&(*vpp)->v_lock);
88 	}
89 	return (0);
90 }
91 
92 /* ARGSUSED */
93 static int
94 fdclose(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr,
95 	caller_context_t *ct)
96 {
97 	return (0);
98 }
99 
100 /* ARGSUSED */
101 static int
102 fdread(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct)
103 {
104 	static struct fddirect dotbuf[] = {
105 		{ FDROOTINO, "."  },
106 		{ FDROOTINO, ".." }
107 	};
108 	struct fddirect dirbuf;
109 	int i, n;
110 	int minfd, maxfd, modoff, error = 0;
111 	int nentries;
112 	rctl_qty_t fdno_ctl;
113 	int endoff;
114 
115 	if (vp->v_type != VDIR)
116 		return (ENOSYS);
117 
118 	mutex_enter(&curproc->p_lock);
119 	fdno_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_NOFILE],
120 	    curproc->p_rctls, curproc);
121 	nentries = MIN(P_FINFO(curproc)->fi_nfiles, (int)fdno_ctl);
122 	mutex_exit(&curproc->p_lock);
123 
124 	endoff = (nentries + 2) * FDSDSIZE;
125 
126 	/*
127 	 * Fake up ".", "..", and the /dev/fd directory entries.
128 	 */
129 	if (uiop->uio_loffset < (offset_t)0 ||
130 	    uiop->uio_loffset >= (offset_t)endoff ||
131 	    uiop->uio_resid <= 0)
132 		return (0);
133 	ASSERT(uiop->uio_loffset <= MAXOFF_T);
134 	if (uiop->uio_offset < 2*FDSDSIZE) {
135 		error = uiomove((caddr_t)dotbuf + uiop->uio_offset,
136 		    MIN(uiop->uio_resid, 2*FDSDSIZE - uiop->uio_offset),
137 		    UIO_READ, uiop);
138 		if (uiop->uio_resid <= 0 || error)
139 			return (error);
140 	}
141 	minfd = (uiop->uio_offset - 2*FDSDSIZE)/FDSDSIZE;
142 	maxfd = (uiop->uio_offset + uiop->uio_resid - 1)/FDSDSIZE;
143 	modoff = uiop->uio_offset % FDSDSIZE;
144 
145 	for (i = 0; i < FDDIRSIZE; i++)
146 		dirbuf.d_name[i] = '\0';
147 	for (i = minfd; i < MIN(maxfd, nentries); i++) {
148 		n = i;
149 		dirbuf.d_ino = fdtoi(n);
150 		numtos((ulong_t)n, dirbuf.d_name);
151 		error = uiomove((caddr_t)&dirbuf + modoff,
152 		    MIN(uiop->uio_resid, FDSDSIZE - modoff),
153 		    UIO_READ, uiop);
154 		if (uiop->uio_resid <= 0 || error)
155 			return (error);
156 		modoff = 0;
157 	}
158 
159 	return (error);
160 }
161 
162 /* ARGSUSED */
163 static int
164 fdgetattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
165 	caller_context_t *ct)
166 {
167 	vfs_t *vfsp = vp->v_vfsp;
168 	timestruc_t now;
169 
170 	if (vp->v_type == VDIR) {
171 		vap->va_nlink = 2;
172 		vap->va_size = (u_offset_t)
173 		    ((P_FINFO(curproc)->fi_nfiles + 2) * FDSDSIZE);
174 		vap->va_mode = 0555;
175 		vap->va_nodeid = (ino64_t)FDROOTINO;
176 	} else {
177 		vap->va_nlink = 1;
178 		vap->va_size = (u_offset_t)0;
179 		vap->va_mode = 0666;
180 		vap->va_nodeid = (ino64_t)fdtoi(getminor(vp->v_rdev));
181 	}
182 	vap->va_type = vp->v_type;
183 	vap->va_rdev = vp->v_rdev;
184 	vap->va_blksize = vfsp->vfs_bsize;
185 	vap->va_nblocks = (fsblkcnt64_t)0;
186 	gethrestime(&now);
187 	vap->va_atime = vap->va_mtime = vap->va_ctime = now;
188 	vap->va_uid = 0;
189 	vap->va_gid = 0;
190 	vap->va_fsid = vfsp->vfs_dev;
191 	vap->va_seq = 0;
192 	return (0);
193 }
194 
195 /* ARGSUSED */
196 static int
197 fdaccess(vnode_t *vp, int mode, int flags, cred_t *cr, caller_context_t *ct)
198 {
199 	return (0);
200 }
201 
202 /* ARGSUSED */
203 static int
204 fdlookup(vnode_t *dp, char *comp, vnode_t **vpp, pathname_t *pnp,
205 	int flags, vnode_t *rdir, cred_t *cr, caller_context_t *ct,
206 	int *direntflags, pathname_t *realpnp)
207 {
208 	if (comp[0] == 0 || strcmp(comp, ".") == 0 || strcmp(comp, "..") == 0) {
209 		VN_HOLD(dp);
210 		*vpp = dp;
211 		return (0);
212 	}
213 	return (fdget(dp, comp, vpp));
214 }
215 
216 /* ARGSUSED */
217 static int
218 fdcreate(vnode_t *dvp, char *comp, vattr_t *vap, enum vcexcl excl,
219 	int mode, vnode_t **vpp, cred_t *cr, int flag, caller_context_t *ct,
220 	vsecattr_t *vsecp)
221 {
222 	return (fdget(dvp, comp, vpp));
223 }
224 
225 /* ARGSUSED */
226 static int
227 fdreaddir(vnode_t *vp, uio_t *uiop, cred_t *cr, int *eofp, caller_context_t *ct,
228 	int flags)
229 {
230 	/* bp holds one dirent structure */
231 	u_offset_t bp[DIRENT64_RECLEN(FDNSIZE) / sizeof (u_offset_t)];
232 	struct dirent64 *dirent = (struct dirent64 *)bp;
233 	int reclen, nentries;
234 	rctl_qty_t fdno_ctl;
235 	int  n;
236 	int oresid;
237 	off_t off;
238 
239 	if (uiop->uio_offset < 0 || uiop->uio_resid <= 0 ||
240 	    (uiop->uio_offset % FDSDSIZE) != 0)
241 		return (ENOENT);
242 
243 	ASSERT(uiop->uio_loffset <= MAXOFF_T);
244 	oresid = uiop->uio_resid;
245 	bzero(bp, sizeof (bp));
246 
247 	mutex_enter(&curproc->p_lock);
248 	fdno_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_NOFILE],
249 	    curproc->p_rctls, curproc);
250 	nentries = MIN(P_FINFO(curproc)->fi_nfiles, (int)fdno_ctl);
251 	mutex_exit(&curproc->p_lock);
252 
253 	while (uiop->uio_resid > 0) {
254 		if ((off = uiop->uio_offset) == 0) {	/* "." */
255 			dirent->d_ino = (ino64_t)FDROOTINO;
256 			dirent->d_name[0] = '.';
257 			dirent->d_name[1] = '\0';
258 			reclen = DIRENT64_RECLEN(1);
259 		} else if (off == FDSDSIZE) {		/* ".." */
260 			dirent->d_ino = (ino64_t)FDROOTINO;
261 			dirent->d_name[0] = '.';
262 			dirent->d_name[1] = '.';
263 			dirent->d_name[2] = '\0';
264 			reclen = DIRENT64_RECLEN(2);
265 		} else {
266 			/*
267 			 * Return entries corresponding to the allowable
268 			 * number of file descriptors for this process.
269 			 */
270 			if ((n = (off-2*FDSDSIZE)/FDSDSIZE) >= nentries)
271 				break;
272 			dirent->d_ino = (ino64_t)fdtoi(n);
273 			numtos((ulong_t)n, dirent->d_name);
274 			reclen = DIRENT64_RECLEN(strlen(dirent->d_name));
275 		}
276 		dirent->d_off = (offset_t)(uiop->uio_offset + FDSDSIZE);
277 		dirent->d_reclen = (ushort_t)reclen;
278 
279 		if (reclen > uiop->uio_resid) {
280 			/*
281 			 * Error if no entries have been returned yet.
282 			 */
283 			if (uiop->uio_resid == oresid)
284 				return (EINVAL);
285 			break;
286 		}
287 		/*
288 		 * uiomove() updates both resid and offset by the same
289 		 * amount.  But we want offset to change in increments
290 		 * of FDSDSIZE, which is different from the number of bytes
291 		 * being returned to the user.  So we set uio_offset
292 		 * separately, ignoring what uiomove() does.
293 		 */
294 		if (uiomove((caddr_t)dirent, reclen, UIO_READ, uiop))
295 			return (EFAULT);
296 		uiop->uio_offset = off + FDSDSIZE;
297 	}
298 	if (eofp)
299 		*eofp = ((uiop->uio_offset-2*FDSDSIZE)/FDSDSIZE >= nentries);
300 	return (0);
301 }
302 
303 /* ARGSUSED */
304 static void
305 fdinactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
306 {
307 	mutex_enter(&vp->v_lock);
308 	ASSERT(vp->v_count >= 1);
309 	if (--vp->v_count != 0) {
310 		mutex_exit(&vp->v_lock);
311 		return;
312 	}
313 	mutex_exit(&vp->v_lock);
314 	vn_invalid(vp);
315 	vn_free(vp);
316 }
317 
318 static struct vnodeops *fd_vnodeops;
319 
320 static const fs_operation_def_t fd_vnodeops_template[] = {
321 	VOPNAME_OPEN,		{ .vop_open = fdopen },
322 	VOPNAME_CLOSE,		{ .vop_close = fdclose },
323 	VOPNAME_READ,		{ .vop_read = fdread },
324 	VOPNAME_GETATTR,	{ .vop_getattr = fdgetattr },
325 	VOPNAME_ACCESS,		{ .vop_access = fdaccess },
326 	VOPNAME_LOOKUP,		{ .vop_lookup = fdlookup },
327 	VOPNAME_CREATE,		{ .vop_create = fdcreate },
328 	VOPNAME_READDIR,	{ .vop_readdir = fdreaddir },
329 	VOPNAME_INACTIVE,	{ .vop_inactive = fdinactive },
330 	VOPNAME_FRLOCK,		{ .error = fs_error },
331 	VOPNAME_POLL,		{ .error = fs_error },
332 	VOPNAME_DISPOSE,	{ .error = fs_error },
333 	NULL,			NULL
334 };
335 
336 static int
337 fdget(struct vnode *dvp, char *comp, struct vnode **vpp)
338 {
339 	int n = 0;
340 	struct vnode *vp;
341 
342 	while (*comp) {
343 		if (*comp < '0' || *comp > '9')
344 			return (ENOENT);
345 		n = 10 * n + *comp++ - '0';
346 	}
347 	vp = vn_alloc(KM_SLEEP);
348 	vp->v_type = VCHR;
349 	vp->v_vfsp = dvp->v_vfsp;
350 	vn_setops(vp, fd_vnodeops);
351 	vp->v_data = NULL;
352 	vp->v_flag = VNOMAP;
353 	vp->v_rdev = makedevice(fdrmaj, n);
354 	vn_exists(vp);
355 	*vpp = vp;
356 	return (0);
357 }
358 
359 /*
360  * fdfs is mounted on /dev/fd, however, there are two interesting
361  * possibilities - two threads racing to do the same mount (protected
362  * by vfs locking), and two threads mounting fdfs in different places.
363  */
364 /*ARGSUSED*/
365 static int
366 fdmount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
367 {
368 	struct vnode *vp;
369 
370 	if (secpolicy_fs_mount(cr, mvp, vfsp) != 0)
371 		return (EPERM);
372 	if (mvp->v_type != VDIR)
373 		return (ENOTDIR);
374 
375 	mutex_enter(&mvp->v_lock);
376 	if ((uap->flags & MS_OVERLAY) == 0 &&
377 	    (mvp->v_count > 1 || (mvp->v_flag & VROOT))) {
378 		mutex_exit(&mvp->v_lock);
379 		return (EBUSY);
380 	}
381 	mutex_exit(&mvp->v_lock);
382 
383 	/*
384 	 * Having the resource be anything but "fd" doesn't make sense
385 	 */
386 	vfs_setresource(vfsp, "fd", 0);
387 
388 	vp = vn_alloc(KM_SLEEP);
389 	vp->v_vfsp = vfsp;
390 	vn_setops(vp, fd_vnodeops);
391 	vp->v_type = VDIR;
392 	vp->v_data = NULL;
393 	vp->v_flag |= VROOT;
394 	vfsp->vfs_fstype = fdfstype;
395 	vfsp->vfs_data = (char *)vp;
396 	mutex_enter(&fd_minor_lock);
397 	do {
398 		fdfsmin = (fdfsmin + 1) & L_MAXMIN32;
399 		vfsp->vfs_dev = makedevice(fdfsmaj, fdfsmin);
400 	} while (vfs_devismounted(vfsp->vfs_dev));
401 	mutex_exit(&fd_minor_lock);
402 	vfs_make_fsid(&vfsp->vfs_fsid, vfsp->vfs_dev, fdfstype);
403 	vfsp->vfs_bsize = 1024;
404 	return (0);
405 }
406 
407 /* ARGSUSED */
408 static int
409 fdunmount(vfs_t *vfsp, int flag, cred_t *cr)
410 {
411 	vnode_t *rvp;
412 
413 	if (secpolicy_fs_unmount(cr, vfsp) != 0)
414 		return (EPERM);
415 
416 	/*
417 	 * forced unmount is not supported by this file system
418 	 * and thus, ENOTSUP, is being returned.
419 	 */
420 	if (flag & MS_FORCE)
421 		return (ENOTSUP);
422 
423 	rvp = (vnode_t *)vfsp->vfs_data;
424 	if (rvp->v_count > 1)
425 		return (EBUSY);
426 
427 	VN_RELE(rvp);
428 	return (0);
429 }
430 
431 /* ARGSUSED */
432 static int
433 fdroot(vfs_t *vfsp, vnode_t **vpp)
434 {
435 	vnode_t *vp = (vnode_t *)vfsp->vfs_data;
436 
437 	VN_HOLD(vp);
438 	*vpp = vp;
439 	return (0);
440 }
441 
442 /*
443  * No locking required because I held the root vnode before calling this
444  * function so the vfs won't disappear on me.  To be more explicit:
445  * fdvrootp->v_count will be greater than 1 so fdunmount will just return.
446  */
447 static int
448 fdstatvfs(struct vfs *vfsp, struct statvfs64 *sp)
449 {
450 	dev32_t d32;
451 	rctl_qty_t fdno_ctl;
452 
453 	mutex_enter(&curproc->p_lock);
454 	fdno_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_NOFILE],
455 	    curproc->p_rctls, curproc);
456 	mutex_exit(&curproc->p_lock);
457 
458 	bzero(sp, sizeof (*sp));
459 	sp->f_bsize = 1024;
460 	sp->f_frsize = 1024;
461 	sp->f_blocks = (fsblkcnt64_t)0;
462 	sp->f_bfree = (fsblkcnt64_t)0;
463 	sp->f_bavail = (fsblkcnt64_t)0;
464 	sp->f_files = (fsfilcnt64_t)
465 	    (MIN(P_FINFO(curproc)->fi_nfiles, fdno_ctl + 2));
466 	sp->f_ffree = (fsfilcnt64_t)0;
467 	sp->f_favail = (fsfilcnt64_t)0;
468 	(void) cmpldev(&d32, vfsp->vfs_dev);
469 	sp->f_fsid = d32;
470 	(void) strcpy(sp->f_basetype, vfssw[fdfstype].vsw_name);
471 	sp->f_flag = vf_to_stf(vfsp->vfs_flag);
472 	sp->f_namemax = FDNSIZE;
473 	(void) strcpy(sp->f_fstr, "/dev/fd");
474 	(void) strcpy(&sp->f_fstr[8], "/dev/fd");
475 	return (0);
476 }
477 
478 int
479 fdinit(int fstype, char *name)
480 {
481 	static const fs_operation_def_t fd_vfsops_template[] = {
482 		VFSNAME_MOUNT,		{ .vfs_mount = fdmount },
483 		VFSNAME_UNMOUNT,	{ .vfs_unmount = fdunmount },
484 		VFSNAME_ROOT, 		{ .vfs_root = fdroot },
485 		VFSNAME_STATVFS,	{ .vfs_statvfs = fdstatvfs },
486 		NULL,			NULL
487 	};
488 	int error;
489 
490 	fdfstype = fstype;
491 	ASSERT(fdfstype != 0);
492 
493 	/*
494 	 * Associate VFS ops vector with this fstype.
495 	 */
496 	error = vfs_setfsops(fstype, fd_vfsops_template, NULL);
497 	if (error != 0) {
498 		cmn_err(CE_WARN, "fdinit: bad vnode ops template");
499 		return (error);
500 	}
501 
502 	error = vn_make_ops(name, fd_vnodeops_template, &fd_vnodeops);
503 	if (error != 0) {
504 		(void) vfs_freevfsops_by_type(fstype);
505 		cmn_err(CE_WARN, "fdinit: bad vnode ops template");
506 		return (error);
507 	}
508 
509 	/*
510 	 * Assign unique "device" numbers (reported by stat(2)).
511 	 */
512 	fdfsmaj = getudev();
513 	fdrmaj = getudev();
514 	if (fdfsmaj == (major_t)-1 || fdrmaj == (major_t)-1) {
515 		cmn_err(CE_WARN, "fdinit: can't get unique device numbers");
516 		if (fdfsmaj == (major_t)-1)
517 			fdfsmaj = 0;
518 		if (fdrmaj == (major_t)-1)
519 			fdrmaj = 0;
520 	}
521 	mutex_init(&fd_minor_lock, NULL, MUTEX_DEFAULT, NULL);
522 	return (0);
523 }
524 
525 /*
526  * FDFS Mount options table
527  */
528 static char *rw_cancel[] = { MNTOPT_RO, NULL };
529 
530 static mntopt_t mntopts[] = {
531 /*
532  *	option name		cancel option	default arg	flags
533  */
534 	{ MNTOPT_RW,		rw_cancel,	NULL,		MO_DEFAULT,
535 		(void *)MNTOPT_NOINTR },
536 	{ MNTOPT_IGNORE,	NULL,		NULL,		0,
537 		(void *)0 },
538 };
539 
540 static mntopts_t fdfs_mntopts = {
541 	sizeof (mntopts) / sizeof (mntopt_t),
542 	mntopts
543 };
544 
545 static vfsdef_t vfw = {
546 	VFSDEF_VERSION,
547 	"fd",
548 	fdinit,
549 	VSW_HASPROTO | VSW_ZMOUNT,
550 	&fdfs_mntopts
551 };
552 
553 static struct modlfs modlfs = {
554 	&mod_fsops,
555 	"filesystem for fd",
556 	&vfw
557 };
558 
559 static struct modlinkage modlinkage = {
560 	MODREV_1,
561 	&modlfs,
562 	NULL
563 };
564 
565 int
566 _init(void)
567 {
568 	return (mod_install(&modlinkage));
569 }
570 
571 int
572 _info(struct modinfo *modinfop)
573 {
574 	return (mod_info(&modlinkage, modinfop));
575 }
576