1 /*	$NetBSD: dtfs_vnops.c,v 1.10 2013/10/19 17:45:00 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 2006  Antti Kantee.  All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/poll.h>
30 
31 #include <assert.h>
32 #include <errno.h>
33 #include <puffs.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <util.h>
39 
40 #include "dtfs.h"
41 
42 int
dtfs_node_lookup(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn)43 dtfs_node_lookup(struct puffs_usermount *pu, void *opc,
44 	struct puffs_newinfo *pni, const struct puffs_cn *pcn)
45 {
46 	struct puffs_node *pn_dir = opc;
47 	struct dtfs_file *df = DTFS_CTOF(opc);
48 	struct dtfs_dirent *dfd;
49 	extern int straightflush;
50 	int rv;
51 
52 	/* parent dir? */
53 	if (PCNISDOTDOT(pcn)) {
54 		if (df->df_dotdot == NULL)
55 			return ENOENT;
56 
57 		assert(df->df_dotdot->pn_va.va_type == VDIR);
58 		puffs_newinfo_setcookie(pni, df->df_dotdot);
59 		puffs_newinfo_setvtype(pni, df->df_dotdot->pn_va.va_type);
60 
61 		return 0;
62 	}
63 
64 	dfd = dtfs_dirgetbyname(df, pcn->pcn_name, pcn->pcn_namelen);
65 	if (dfd) {
66 		if ((pcn->pcn_flags & NAMEI_ISLASTCN) &&
67 		    (pcn->pcn_nameiop == NAMEI_DELETE)) {
68 			rv = puffs_access(VDIR, pn_dir->pn_va.va_mode,
69 			    pn_dir->pn_va.va_uid, pn_dir->pn_va.va_gid,
70 			    PUFFS_VWRITE, pcn->pcn_cred);
71 			if (rv)
72 				return rv;
73 		}
74 		puffs_newinfo_setcookie(pni, dfd->dfd_node);
75 		puffs_newinfo_setvtype(pni, dfd->dfd_node->pn_va.va_type);
76 		puffs_newinfo_setsize(pni, dfd->dfd_node->pn_va.va_size);
77 		puffs_newinfo_setrdev(pni, dfd->dfd_node->pn_va.va_rdev);
78 
79 		if (straightflush)
80 			puffs_flush_pagecache_node(pu, dfd->dfd_node);
81 
82 		return 0;
83 	}
84 
85 	if ((pcn->pcn_flags & NAMEI_ISLASTCN)
86 	    && (pcn->pcn_nameiop == NAMEI_CREATE ||
87 	        pcn->pcn_nameiop == NAMEI_RENAME)) {
88 		rv = puffs_access(VDIR, pn_dir->pn_va.va_mode,
89 		    pn_dir->pn_va.va_uid, pn_dir->pn_va.va_gid,
90 		    PUFFS_VWRITE, pcn->pcn_cred);
91 		if (rv)
92 			return rv;
93 	}
94 
95 	return ENOENT;
96 }
97 
98 int
dtfs_node_access(struct puffs_usermount * pu,void * opc,int acc_mode,const struct puffs_cred * pcr)99 dtfs_node_access(struct puffs_usermount *pu, void *opc, int acc_mode,
100 	const struct puffs_cred *pcr)
101 {
102 	struct puffs_node *pn = opc;
103 
104 	return puffs_access(pn->pn_va.va_type, pn->pn_va.va_mode,
105 	    pn->pn_va.va_uid, pn->pn_va.va_gid, acc_mode, pcr);
106 }
107 
108 int
dtfs_node_setattr(struct puffs_usermount * pu,void * opc,const struct vattr * va,const struct puffs_cred * pcr)109 dtfs_node_setattr(struct puffs_usermount *pu, void *opc,
110 	const struct vattr *va, const struct puffs_cred *pcr)
111 {
112 	struct puffs_node *pn = opc;
113 	int rv;
114 
115 	/* check permissions */
116 	if (va->va_flags != PUFFS_VNOVAL)
117 		return EOPNOTSUPP;
118 
119 	if (va->va_uid != PUFFS_VNOVAL || va->va_gid != PUFFS_VNOVAL) {
120 		rv = puffs_access_chown(pn->pn_va.va_uid, pn->pn_va.va_gid,
121 		    va->va_uid, va->va_gid, pcr);
122 		if (rv)
123 			return rv;
124 	}
125 
126 	if (va->va_mode != PUFFS_VNOVAL) {
127 		rv = puffs_access_chmod(pn->pn_va.va_uid, pn->pn_va.va_gid,
128 		    pn->pn_va.va_type, va->va_mode, pcr);
129 		if (rv)
130 			return rv;
131 	}
132 
133 	if ((va->va_atime.tv_sec != PUFFS_VNOVAL
134 	      && va->va_atime.tv_nsec != PUFFS_VNOVAL)
135 	    || (va->va_mtime.tv_sec != PUFFS_VNOVAL
136 	      && va->va_mtime.tv_nsec != PUFFS_VNOVAL)) {
137 		rv = puffs_access_times(pn->pn_va.va_uid, pn->pn_va.va_gid,
138 		    pn->pn_va.va_mode, va->va_vaflags & VA_UTIMES_NULL, pcr);
139 		if (rv)
140 			return rv;
141 	}
142 
143 	if (va->va_size != PUFFS_VNOVAL) {
144 		switch (pn->pn_va.va_type) {
145 		case VREG:
146 			dtfs_setsize(pn, va->va_size);
147 			pn->pn_va.va_bytes = va->va_size;
148 			break;
149 		case VBLK:
150 		case VCHR:
151 		case VFIFO:
152 			break;
153 		case VDIR:
154 			return EISDIR;
155 		default:
156 			return EOPNOTSUPP;
157 		}
158 	}
159 
160 	puffs_setvattr(&pn->pn_va, va);
161 
162 	return 0;
163 }
164 
165 /* create a new node in the parent directory specified by opc */
166 int
dtfs_node_create(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn,const struct vattr * va)167 dtfs_node_create(struct puffs_usermount *pu, void *opc,
168 	struct puffs_newinfo *pni, const struct puffs_cn *pcn,
169 	const struct vattr *va)
170 {
171 	struct puffs_node *pn_parent = opc;
172 	struct puffs_node *pn_new;
173 
174 	if (!(va->va_type == VREG || va->va_type == VSOCK))
175 		return ENODEV;
176 
177 	pn_new = dtfs_genfile(pn_parent, pcn, va->va_type);
178 	puffs_setvattr(&pn_new->pn_va, va);
179 
180 	puffs_newinfo_setcookie(pni, pn_new);
181 
182 	return 0;
183 }
184 
185 int
dtfs_node_remove(struct puffs_usermount * pu,void * opc,void * targ,const struct puffs_cn * pcn)186 dtfs_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
187 	const struct puffs_cn *pcn)
188 {
189 	struct puffs_node *pn_parent = opc;
190 	struct puffs_node *pn = targ;
191 
192 	if (pn->pn_va.va_type == VDIR)
193 		return EPERM;
194 
195 	dtfs_nukenode(targ, pn_parent, pcn->pcn_name, pcn->pcn_namelen);
196 
197 	if (pn->pn_va.va_nlink == 0)
198 		puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
199 
200 	return 0;
201 }
202 
203 int
dtfs_node_mkdir(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn,const struct vattr * va)204 dtfs_node_mkdir(struct puffs_usermount *pu, void *opc,
205 	struct puffs_newinfo *pni, const struct puffs_cn *pcn,
206 	const struct vattr *va)
207 {
208 	struct puffs_node *pn_parent = opc;
209 	struct puffs_node *pn_new;
210 
211 	pn_new = dtfs_genfile(pn_parent, pcn, VDIR);
212 	puffs_setvattr(&pn_new->pn_va, va);
213 
214 	puffs_newinfo_setcookie(pni, pn_new);
215 
216 	return 0;
217 }
218 
219 int
dtfs_node_rmdir(struct puffs_usermount * pu,void * opc,void * targ,const struct puffs_cn * pcn)220 dtfs_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
221 	const struct puffs_cn *pcn)
222 {
223 	struct puffs_node *pn_parent = opc;
224 	struct dtfs_file *df = DTFS_CTOF(targ);
225 
226 	if (!LIST_EMPTY(&df->df_dirents))
227 		return ENOTEMPTY;
228 
229 	dtfs_nukenode(targ, pn_parent, pcn->pcn_name, pcn->pcn_namelen);
230 	puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
231 
232 	return 0;
233 }
234 
235 int
dtfs_node_readdir(struct puffs_usermount * pu,void * opc,struct dirent * dent,off_t * readoff,size_t * reslen,const struct puffs_cred * pcr,int * eofflag,off_t * cookies,size_t * ncookies)236 dtfs_node_readdir(struct puffs_usermount *pu, void *opc,
237 	struct dirent *dent, off_t *readoff, size_t *reslen,
238 	const struct puffs_cred *pcr,
239 	int *eofflag, off_t *cookies, size_t *ncookies)
240 {
241 	struct puffs_node *pn = opc;
242 	struct puffs_node *pn_nth;
243 	struct dtfs_dirent *dfd_nth;
244 
245 	if (pn->pn_va.va_type != VDIR)
246 		return ENOTDIR;
247 
248 	dtfs_updatetimes(pn, 1, 0, 0);
249 
250 	*ncookies = 0;
251  again:
252 	if (*readoff == DENT_DOT || *readoff == DENT_DOTDOT) {
253 		puffs_gendotdent(&dent, pn->pn_va.va_fileid, *readoff, reslen);
254 		(*readoff)++;
255 		PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff);
256 		goto again;
257 	}
258 
259 	for (;;) {
260 		dfd_nth = dtfs_dirgetnth(pn->pn_data, DENT_ADJ(*readoff));
261 		if (!dfd_nth) {
262 			*eofflag = 1;
263 			break;
264 		}
265 		pn_nth = dfd_nth->dfd_node;
266 
267 		if (!puffs_nextdent(&dent, dfd_nth->dfd_name,
268 		    pn_nth->pn_va.va_fileid,
269 		    puffs_vtype2dt(pn_nth->pn_va.va_type),
270 		    reslen))
271 			break;
272 
273 		(*readoff)++;
274 		PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff);
275 	}
276 
277 	return 0;
278 }
279 
280 int
dtfs_node_poll(struct puffs_usermount * pu,void * opc,int * events)281 dtfs_node_poll(struct puffs_usermount *pu, void *opc, int *events)
282 {
283 	struct dtfs_mount *dtm = puffs_getspecific(pu);
284 	struct dtfs_poll dp;
285 	struct itimerval it;
286 
287 	memset(&it, 0, sizeof(struct itimerval));
288 	it.it_value.tv_sec = 4;
289 	if (setitimer(ITIMER_REAL, &it, NULL) == -1)
290 		return errno;
291 
292 	dp.dp_pcc = puffs_cc_getcc(pu);
293 	LIST_INSERT_HEAD(&dtm->dtm_pollent, &dp, dp_entries);
294 	puffs_cc_yield(dp.dp_pcc);
295 
296 	*events = *events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM);
297 	return 0;
298 }
299 
300 int
dtfs_node_mmap(struct puffs_usermount * pu,void * opc,vm_prot_t prot,const struct puffs_cred * pcr)301 dtfs_node_mmap(struct puffs_usermount *pu, void *opc, vm_prot_t prot,
302 	const struct puffs_cred *pcr)
303 {
304 	struct dtfs_mount *dtm = puffs_getspecific(pu);
305 
306 	if ((dtm->dtm_allowprot & prot) != prot)
307 		return EACCES;
308 
309 	return 0;
310 }
311 
312 int
dtfs_node_rename(struct puffs_usermount * pu,void * opc,void * src,const struct puffs_cn * pcn_src,void * targ_dir,void * targ,const struct puffs_cn * pcn_targ)313 dtfs_node_rename(struct puffs_usermount *pu, void *opc, void *src,
314 	const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
315 	const struct puffs_cn *pcn_targ)
316 {
317 	struct dtfs_dirent *dfd_src;
318 	struct dtfs_file *df_targ;
319 	struct puffs_node *pn_sdir = opc;
320 	struct puffs_node *pn_sfile = src;
321 	struct puffs_node *pn_tdir = targ_dir;
322 	struct puffs_node *pn_tfile = targ;
323 
324 	/* check that we don't do the old amigados trick */
325 	if (pn_sfile->pn_va.va_type == VDIR) {
326 		if (dtfs_isunder(pn_tdir, pn_sfile))
327 			return EINVAL;
328 
329 		if ((pcn_src->pcn_namelen == 1 && pcn_src->pcn_name[0]=='.') ||
330 		    opc == src ||
331 		    PCNISDOTDOT(pcn_src) ||
332 		    PCNISDOTDOT(pcn_targ)) {
333 			return EINVAL;
334 		}
335 	}
336 
337 	dfd_src = dtfs_dirgetbyname(DTFS_PTOF(pn_sdir),
338 	    pcn_src->pcn_name, pcn_src->pcn_namelen);
339 
340 	/* does it still exist, or did someone race us here? */
341 	if (dfd_src == NULL) {
342 		return ENOENT;
343 	}
344 
345 	/* if there's a target file, nuke it for atomic replacement */
346 	if (pn_tfile) {
347 		if (pn_tfile->pn_va.va_type == VDIR) {
348 			df_targ = DTFS_CTOF(pn_tfile);
349 			if (!LIST_EMPTY(&df_targ->df_dirents))
350 				return ENOTEMPTY;
351 		}
352 		dtfs_nukenode(pn_tfile, pn_tdir,
353 		    pcn_targ->pcn_name, pcn_targ->pcn_namelen);
354 	}
355 
356 	/* out with the old */
357 	dtfs_removedent(pn_sdir, dfd_src);
358 	/* and in with the new */
359 	dtfs_adddent(pn_tdir, dfd_src);
360 
361 	/* update name */
362 	free(dfd_src->dfd_name);
363 	dfd_src->dfd_name = estrndup(pcn_targ->pcn_name,pcn_targ->pcn_namelen);
364 	dfd_src->dfd_namelen = strlen(dfd_src->dfd_name);
365 
366 	dtfs_updatetimes(src, 0, 1, 0);
367 
368 	return 0;
369 }
370 
371 int
dtfs_node_link(struct puffs_usermount * pu,void * opc,void * targ,const struct puffs_cn * pcn)372 dtfs_node_link(struct puffs_usermount *pu, void *opc, void *targ,
373 	const struct puffs_cn *pcn)
374 {
375 	struct puffs_node *pn_dir = opc;
376 	struct dtfs_dirent *dfd;
377 
378 	dfd = emalloc(sizeof(struct dtfs_dirent));
379 	dfd->dfd_node = targ;
380 	dfd->dfd_name = estrndup(pcn->pcn_name, pcn->pcn_namelen);
381 	dfd->dfd_namelen = strlen(dfd->dfd_name);
382 	dtfs_adddent(pn_dir, dfd);
383 
384 	dtfs_updatetimes(targ, 0, 1, 0);
385 
386 	return 0;
387 }
388 
389 int
dtfs_node_symlink(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn_src,const struct vattr * va,const char * link_target)390 dtfs_node_symlink(struct puffs_usermount *pu, void *opc,
391 	struct puffs_newinfo *pni, const struct puffs_cn *pcn_src,
392 	const struct vattr *va, const char *link_target)
393 {
394 	struct puffs_node *pn_parent = opc;
395 	struct puffs_node *pn_new;
396 	struct dtfs_file *df_new;
397 
398 	if (va->va_type != VLNK)
399 		return ENODEV;
400 
401 	pn_new = dtfs_genfile(pn_parent, pcn_src, VLNK);
402 	puffs_setvattr(&pn_new->pn_va, va);
403 	df_new = DTFS_PTOF(pn_new);
404 	df_new->df_linktarget = estrdup(link_target);
405 	pn_new->pn_va.va_size = strlen(df_new->df_linktarget);
406 
407 	puffs_newinfo_setcookie(pni, pn_new);
408 
409 	return 0;
410 }
411 
412 int
dtfs_node_readlink(struct puffs_usermount * pu,void * opc,const struct puffs_cred * cred,char * linkname,size_t * linklen)413 dtfs_node_readlink(struct puffs_usermount *pu, void *opc,
414 	const struct puffs_cred *cred, char *linkname, size_t *linklen)
415 {
416 	struct dtfs_file *df = DTFS_CTOF(opc);
417 	struct puffs_node *pn = opc;
418 
419 	assert(pn->pn_va.va_type == VLNK);
420 	strlcpy(linkname, df->df_linktarget, *linklen);
421 	*linklen = strlen(linkname);
422 
423 	return 0;
424 }
425 
426 int
dtfs_node_mknod(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn,const struct vattr * va)427 dtfs_node_mknod(struct puffs_usermount *pu, void *opc,
428 	struct puffs_newinfo *pni, const struct puffs_cn *pcn,
429 	const struct vattr *va)
430 {
431 	struct puffs_node *pn_parent = opc;
432 	struct puffs_node *pn_new;
433 
434 	if (!(va->va_type == VBLK || va->va_type == VCHR
435 	    || va->va_type == VFIFO))
436 		return EINVAL;
437 
438 	pn_new = dtfs_genfile(pn_parent, pcn, va->va_type);
439 	puffs_setvattr(&pn_new->pn_va, va);
440 
441 	puffs_newinfo_setcookie(pni, pn_new);
442 
443 	return 0;
444 }
445 
446 #define BLOCKOFF(a,b) ((a) & ((b)-1))
447 #define BLOCKLEFT(a,b) ((b) - BLOCKOFF(a,b))
448 
449 /*
450  * Read operation, used both for VOP_READ and VOP_GETPAGES
451  */
452 int
dtfs_node_read(struct puffs_usermount * pu,void * opc,uint8_t * buf,off_t offset,size_t * resid,const struct puffs_cred * pcr,int ioflag)453 dtfs_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
454 	off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag)
455 {
456 	struct puffs_node *pn = opc;
457 	struct dtfs_file *df = DTFS_CTOF(opc);
458 	quad_t xfer, origxfer;
459 	uint8_t *src, *dest;
460 	size_t copylen;
461 
462 	if (pn->pn_va.va_type != VREG)
463 		return EISDIR;
464 
465 	xfer = MIN(*resid, df->df_datalen - offset);
466 	if (xfer < 0)
467 		return EINVAL;
468 
469 	dest = buf;
470 	origxfer = xfer;
471 	while (xfer > 0) {
472 		copylen = MIN(xfer, BLOCKLEFT(offset, DTFS_BLOCKSIZE));
473 		src = df->df_blocks[BLOCKNUM(offset, DTFS_BLOCKSHIFT)]
474 		    + BLOCKOFF(offset, DTFS_BLOCKSIZE);
475 		memcpy(dest, src, copylen);
476 		offset += copylen;
477 		dest += copylen;
478 		xfer -= copylen;
479 	}
480 	*resid -= origxfer;
481 
482 	dtfs_updatetimes(pn, 1, 0, 0);
483 
484 	return 0;
485 }
486 
487 /*
488  * write operation on the wing
489  */
490 int
dtfs_node_write(struct puffs_usermount * pu,void * opc,uint8_t * buf,off_t offset,size_t * resid,const struct puffs_cred * pcr,int ioflag)491 dtfs_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
492 	off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag)
493 {
494 	struct puffs_node *pn = opc;
495 	struct dtfs_file *df = DTFS_CTOF(opc);
496 	uint8_t *src, *dest;
497 	size_t copylen;
498 
499 	if (pn->pn_va.va_type != VREG)
500 		return EISDIR;
501 
502 	if (ioflag & PUFFS_IO_APPEND)
503 		offset = pn->pn_va.va_size;
504 
505 	if (*resid + offset > pn->pn_va.va_size)
506 		dtfs_setsize(pn, *resid + offset);
507 
508 	src = buf;
509 	while (*resid > 0) {
510 		int i;
511 		copylen = MIN(*resid, BLOCKLEFT(offset, DTFS_BLOCKSIZE));
512 		i = BLOCKNUM(offset, DTFS_BLOCKSHIFT);
513 		dest = df->df_blocks[i]
514 		    + BLOCKOFF(offset, DTFS_BLOCKSIZE);
515 		memcpy(dest, src, copylen);
516 		offset += copylen;
517 		dest += copylen;
518 		*resid -= copylen;
519 	}
520 
521 	dtfs_updatetimes(pn, 0, 1, 1);
522 
523 	return 0;
524 }
525 
526 int
dtfs_node_pathconf(struct puffs_usermount * pu,puffs_cookie_t opc,int name,register_t * retval)527 dtfs_node_pathconf(struct puffs_usermount *pu, puffs_cookie_t opc,
528 	int name, register_t *retval)
529 {
530 
531 	switch (name) {
532 	case _PC_LINK_MAX:
533 		*retval = LINK_MAX;
534 		return 0;
535 	case _PC_NAME_MAX:
536 		*retval = NAME_MAX;
537 		return 0;
538 	case _PC_PATH_MAX:
539 		*retval = PATH_MAX;
540 		return 0;
541 	case _PC_PIPE_BUF:
542 		*retval = PIPE_BUF;
543 		return 0;
544 	case _PC_CHOWN_RESTRICTED:
545 		*retval = 1;
546 		return 0;
547 	case _PC_NO_TRUNC:
548 		*retval = 1;
549 		return 0;
550 	case _PC_SYNC_IO:
551 		*retval = 1;
552 		return 0;
553 	case _PC_FILESIZEBITS:
554 		*retval = 43; /* this one goes to 11 */
555 		return 0;
556 	case _PC_SYMLINK_MAX:
557 		*retval = MAXPATHLEN;
558 		return 0;
559 	case _PC_2_SYMLINKS:
560 		*retval = 1;
561 		return 0;
562 	default:
563 		return EINVAL;
564 	}
565 }
566 
567 int
dtfs_node_inactive(struct puffs_usermount * pu,puffs_cookie_t opc)568 dtfs_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
569 {
570 	struct puffs_node *pn = opc;
571 
572 	if (pn->pn_va.va_nlink == 0)
573 		puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1);
574 	return 0;
575 }
576 
577 int
dtfs_node_reclaim(struct puffs_usermount * pu,void * opc)578 dtfs_node_reclaim(struct puffs_usermount *pu, void *opc)
579 {
580 	struct puffs_node *pn = opc;
581 
582 	if (pn->pn_va.va_nlink == 0)
583 		dtfs_freenode(pn);
584 
585 	return 0;
586 }
587