xref: /illumos-gate/usr/src/cmd/fs.d/nfs/rquotad/rpc.rquotad.c (revision 03270635d68df6a0392fb8f4b7c04acad764648b)
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 2017 Joyent Inc
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <signal.h>
32 #include <syslog.h>
33 #include <string.h>
34 #include <stropts.h>
35 #include <errno.h>
36 #include <sys/netconfig.h>
37 #include <sys/mntent.h>
38 #include <sys/mnttab.h>
39 #include <sys/param.h>
40 #include <sys/time.h>
41 #include <sys/debug.h>
42 #ifdef notdef
43 #include <netconfig.h>
44 #endif
45 #include <sys/stat.h>
46 #include <sys/file.h>
47 #include <sys/fs/ufs_quota.h>
48 #include <netdir.h>
49 #include <rpc/rpc.h>
50 #include <rpcsvc/rquota.h>
51 #include <tiuser.h>
52 #include <unistd.h>
53 #include <dlfcn.h>
54 #include <libzfs.h>
55 
56 #define	QFNAME		"quotas"	/* name of quota file */
57 #define	RPCSVC_CLOSEDOWN 120		/* 2 minutes */
58 
59 struct fsquot {
60 	char *fsq_fstype;
61 	struct fsquot *fsq_next;
62 	char *fsq_dir;
63 	char *fsq_devname;
64 	dev_t fsq_dev;
65 };
66 
67 struct fsquot *fsqlist = NULL;
68 
69 typedef struct authunix_parms *authp;
70 
71 static int request_pending;		/* Request in progress ? */
72 
73 void closedown();
74 void dispatch();
75 struct fsquot *findfsq();
76 void freefs();
77 int  getdiskquota();
78 void getquota();
79 int  hasquota();
80 void log_cant_reply();
81 void setupfs();
82 static void zexit(int) __NORETURN;
83 
84 static libzfs_handle_t *(*_libzfs_init)(void);
85 static void (*_libzfs_fini)(libzfs_handle_t *);
86 static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int);
87 static void (*_zfs_close)(zfs_handle_t *);
88 static int (*_zfs_prop_get_userquota_int)(zfs_handle_t *, const char *,
89     uint64_t *);
90 static libzfs_handle_t *g_zfs = NULL;
91 
92 /*
93  * Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs
94  * packages.  'rquotad' supports zfs as an option.
95  */
96 static void
97 load_libzfs(void)
98 {
99 	void *hdl;
100 
101 	if (g_zfs != NULL)
102 		return;
103 
104 	if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) {
105 		_libzfs_init = (libzfs_handle_t *(*)(void))dlsym(hdl,
106 		    "libzfs_init");
107 		_libzfs_fini = (void (*)())dlsym(hdl, "libzfs_fini");
108 		_zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open");
109 		_zfs_close = (void (*)())dlsym(hdl, "zfs_close");
110 		_zfs_prop_get_userquota_int = (int (*)())
111 		    dlsym(hdl, "zfs_prop_get_userquota_int");
112 
113 		if (_libzfs_init && _libzfs_fini && _zfs_open &&
114 		    _zfs_close && _zfs_prop_get_userquota_int)
115 			g_zfs = _libzfs_init();
116 	}
117 }
118 
119 /*ARGSUSED*/
120 int
121 main(int argc, char *argv[])
122 {
123 	register SVCXPRT *transp;
124 
125 	load_libzfs();
126 
127 	/*
128 	 * If stdin looks like a TLI endpoint, we assume
129 	 * that we were started by a port monitor. If
130 	 * t_getstate fails with TBADF, this is not a
131 	 * TLI endpoint.
132 	 */
133 	if (t_getstate(0) != -1 || t_errno != TBADF) {
134 		char *netid;
135 		struct netconfig *nconf = NULL;
136 
137 		openlog("rquotad", LOG_PID, LOG_DAEMON);
138 
139 		if ((netid = getenv("NLSPROVIDER")) == NULL) {
140 			struct t_info tinfo;
141 
142 			if (t_sync(0) == -1) {
143 				syslog(LOG_ERR, "could not do t_sync");
144 				zexit(1);
145 			}
146 			if (t_getinfo(0, &tinfo) == -1) {
147 				syslog(LOG_ERR, "t_getinfo failed");
148 				zexit(1);
149 			}
150 			if (tinfo.servtype == T_CLTS) {
151 				if (tinfo.addr == INET_ADDRSTRLEN)
152 					netid = "udp";
153 				else
154 					netid = "udp6";
155 			} else {
156 				syslog(LOG_ERR, "wrong transport");
157 				zexit(1);
158 			}
159 		}
160 		if ((nconf = getnetconfigent(netid)) == NULL) {
161 			syslog(LOG_ERR, "cannot get transport info");
162 		}
163 
164 		if ((transp = svc_tli_create(0, nconf, NULL, 0, 0)) == NULL) {
165 			syslog(LOG_ERR, "cannot create server handle");
166 			zexit(1);
167 		}
168 		if (nconf)
169 			freenetconfigent(nconf);
170 
171 		if (!svc_reg(transp, RQUOTAPROG, RQUOTAVERS, dispatch, 0)) {
172 			syslog(LOG_ERR,
173 			    "unable to register (RQUOTAPROG, RQUOTAVERS).");
174 			zexit(1);
175 		}
176 
177 		(void) sigset(SIGALRM, (void(*)(int)) closedown);
178 		(void) alarm(RPCSVC_CLOSEDOWN);
179 
180 		svc_run();
181 		zexit(1);
182 		/* NOTREACHED */
183 	}
184 
185 	/*
186 	 * Started from a shell - fork the daemon.
187 	 */
188 
189 	switch (fork()) {
190 	case 0:		/* child */
191 		break;
192 	case -1:
193 		perror("rquotad: can't fork");
194 		zexit(1);
195 	default:	/* parent */
196 		zexit(0);
197 	}
198 
199 	/*
200 	 * Close existing file descriptors, open "/dev/null" as
201 	 * standard input, output, and error, and detach from
202 	 * controlling terminal.
203 	 */
204 	closefrom(0);
205 	(void) open("/dev/null", O_RDONLY);
206 	(void) open("/dev/null", O_WRONLY);
207 	(void) dup(1);
208 	(void) setsid();
209 
210 	openlog("rquotad", LOG_PID, LOG_DAEMON);
211 
212 	/*
213 	 * Create datagram service
214 	 */
215 	if (svc_create(dispatch, RQUOTAPROG, RQUOTAVERS, "datagram_v") == 0) {
216 		syslog(LOG_ERR, "couldn't register datagram_v service");
217 		zexit(1);
218 	}
219 
220 	/*
221 	 * Start serving
222 	 */
223 	svc_run();
224 	syslog(LOG_ERR, "Error: svc_run shouldn't have returned");
225 	return (1);
226 }
227 
228 void
229 dispatch(rqstp, transp)
230 	register struct svc_req *rqstp;
231 	register SVCXPRT *transp;
232 {
233 
234 	request_pending = 1;
235 
236 	switch (rqstp->rq_proc) {
237 	case NULLPROC:
238 		errno = 0;
239 		if (!svc_sendreply(transp, xdr_void, 0))
240 			log_cant_reply(transp);
241 		break;
242 
243 	case RQUOTAPROC_GETQUOTA:
244 	case RQUOTAPROC_GETACTIVEQUOTA:
245 		getquota(rqstp, transp);
246 		break;
247 
248 	default:
249 		svcerr_noproc(transp);
250 		break;
251 	}
252 
253 	request_pending = 0;
254 }
255 
256 void
257 closedown()
258 {
259 	if (!request_pending) {
260 		int i, openfd;
261 		struct t_info tinfo;
262 
263 		if (!t_getinfo(0, &tinfo) && (tinfo.servtype == T_CLTS))
264 			zexit(0);
265 
266 		for (i = 0, openfd = 0; i < svc_max_pollfd && openfd < 2; i++) {
267 			if (svc_pollfd[i].fd >= 0)
268 				openfd++;
269 		}
270 
271 		if (openfd <= 1)
272 			zexit(0);
273 	}
274 	(void) alarm(RPCSVC_CLOSEDOWN);
275 }
276 
277 static int
278 getzfsquota(uid_t user, char *dataset, struct dqblk *zq)
279 {
280 	zfs_handle_t *zhp = NULL;
281 	char propname[ZFS_MAXPROPLEN];
282 	uint64_t userquota, userused;
283 
284 	if (g_zfs == NULL)
285 		return (1);
286 
287 	if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) {
288 		syslog(LOG_ERR, "can not open zfs dataset %s", dataset);
289 		return (1);
290 	}
291 
292 	(void) snprintf(propname, sizeof (propname), "userquota@%u", user);
293 	if (_zfs_prop_get_userquota_int(zhp, propname, &userquota) != 0) {
294 		_zfs_close(zhp);
295 		return (1);
296 	}
297 
298 	(void) snprintf(propname, sizeof (propname), "userused@%u", user);
299 	if (_zfs_prop_get_userquota_int(zhp, propname, &userused) != 0) {
300 		_zfs_close(zhp);
301 		return (1);
302 	}
303 
304 	zq->dqb_bhardlimit = userquota / DEV_BSIZE;
305 	zq->dqb_bsoftlimit = userquota / DEV_BSIZE;
306 	zq->dqb_curblocks = userused / DEV_BSIZE;
307 	_zfs_close(zhp);
308 	return (0);
309 }
310 
311 void
312 getquota(rqstp, transp)
313 	register struct svc_req *rqstp;
314 	register SVCXPRT *transp;
315 {
316 	struct getquota_args gqa;
317 	struct getquota_rslt gqr;
318 	struct dqblk dqblk;
319 	struct fsquot *fsqp;
320 	struct timeval tv;
321 	bool_t qactive;
322 
323 	gqa.gqa_pathp = NULL;		/* let xdr allocate the storage */
324 	if (!svc_getargs(transp, xdr_getquota_args, (caddr_t)&gqa)) {
325 		svcerr_decode(transp);
326 		return;
327 	}
328 	/*
329 	 * This authentication is really bogus with the current rpc
330 	 * authentication scheme. One day we will have something for real.
331 	 */
332 	CTASSERT(sizeof (authp) <= RQCRED_SIZE);
333 	if (rqstp->rq_cred.oa_flavor != AUTH_UNIX ||
334 	    (((authp) rqstp->rq_clntcred)->aup_uid != 0 &&
335 		((authp) rqstp->rq_clntcred)->aup_uid != (uid_t)gqa.gqa_uid)) {
336 		gqr.status = Q_EPERM;
337 		goto sendreply;
338 	}
339 	fsqp = findfsq(gqa.gqa_pathp);
340 	if (fsqp == NULL) {
341 		gqr.status = Q_NOQUOTA;
342 		goto sendreply;
343 	}
344 
345 	bzero(&dqblk, sizeof (dqblk));
346 	if (strcmp(fsqp->fsq_fstype, MNTTYPE_ZFS) == 0) {
347 		if (getzfsquota(gqa.gqa_uid, fsqp->fsq_devname, &dqblk)) {
348 			gqr.status = Q_NOQUOTA;
349 			goto sendreply;
350 		}
351 		qactive = TRUE;
352 	} else {
353 		if (quotactl(Q_GETQUOTA, fsqp->fsq_dir,
354 		    (uid_t)gqa.gqa_uid, &dqblk) != 0) {
355 			qactive = FALSE;
356 			if ((errno == ENOENT) ||
357 			    (rqstp->rq_proc != RQUOTAPROC_GETQUOTA)) {
358 				gqr.status = Q_NOQUOTA;
359 				goto sendreply;
360 			}
361 
362 			/*
363 			 * If there is no quotas file, don't bother to sync it.
364 			 */
365 			if (errno != ENOENT) {
366 				if (quotactl(Q_ALLSYNC, fsqp->fsq_dir,
367 				    (uid_t)gqa.gqa_uid, &dqblk) < 0 &&
368 				    errno == EINVAL)
369 					syslog(LOG_WARNING,
370 					    "Quotas are not compiled "
371 					    "into this kernel");
372 				if (getdiskquota(fsqp, (uid_t)gqa.gqa_uid,
373 				    &dqblk) == 0) {
374 					gqr.status = Q_NOQUOTA;
375 					goto sendreply;
376 				}
377 			}
378 		} else {
379 			qactive = TRUE;
380 		}
381 		/*
382 		 * We send the remaining time instead of the absolute time
383 		 * because clock skew between machines should be much greater
384 		 * than rpc delay.
385 		 */
386 #define	gqrslt getquota_rslt_u.gqr_rquota
387 
388 		gettimeofday(&tv, NULL);
389 		gqr.gqrslt.rq_btimeleft	= dqblk.dqb_btimelimit - tv.tv_sec;
390 		gqr.gqrslt.rq_ftimeleft	= dqblk.dqb_ftimelimit - tv.tv_sec;
391 	}
392 
393 	gqr.status = Q_OK;
394 	gqr.gqrslt.rq_active	= qactive;
395 	gqr.gqrslt.rq_bsize	= DEV_BSIZE;
396 	gqr.gqrslt.rq_bhardlimit = dqblk.dqb_bhardlimit;
397 	gqr.gqrslt.rq_bsoftlimit = dqblk.dqb_bsoftlimit;
398 	gqr.gqrslt.rq_curblocks = dqblk.dqb_curblocks;
399 	gqr.gqrslt.rq_fhardlimit = dqblk.dqb_fhardlimit;
400 	gqr.gqrslt.rq_fsoftlimit = dqblk.dqb_fsoftlimit;
401 	gqr.gqrslt.rq_curfiles	= dqblk.dqb_curfiles;
402 sendreply:
403 	errno = 0;
404 	if (!svc_sendreply(transp, xdr_getquota_rslt, (caddr_t)&gqr))
405 		log_cant_reply(transp);
406 }
407 
408 int
409 quotactl(cmd, mountp, uid, dqp)
410 	int	cmd;
411 	char	*mountp;
412 	uid_t	uid;
413 	struct dqblk *dqp;
414 {
415 	int 		fd;
416 	int 		status;
417 	struct quotctl 	quota;
418 	char		mountpoint[256];
419 	FILE		*fstab;
420 	struct mnttab	mntp;
421 
422 	if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
423 		/*
424 		 * Find the mount point of any ufs file system. this is
425 		 * because the ioctl that implements the quotactl call has
426 		 * to go to a real file, and not to the block device.
427 		 */
428 		if ((fstab = fopen(MNTTAB, "r")) == NULL) {
429 			syslog(LOG_ERR, "can not open %s: %m ", MNTTAB);
430 			return (-1);
431 		}
432 		fd = -1;
433 		while ((status = getmntent(fstab, &mntp)) == NULL) {
434 			if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
435 				!(hasmntopt(&mntp, MNTOPT_RQ) ||
436 				hasmntopt(&mntp, MNTOPT_QUOTA)))
437 				continue;
438 			(void) strlcpy(mountpoint, mntp.mnt_mountp,
439 			    sizeof (mountpoint));
440 			strcat(mountpoint, "/quotas");
441 			if ((fd = open64(mountpoint, O_RDWR)) >= 0)
442 				break;
443 		}
444 		fclose(fstab);
445 		if (fd == -1) {
446 			errno = ENOENT;
447 			return (-1);
448 		}
449 	} else {
450 		if (mountp == NULL || mountp[0] == '\0') {
451 			errno = ENOENT;
452 			return (-1);
453 		}
454 		(void) strlcpy(mountpoint, mountp, sizeof (mountpoint));
455 		strcat(mountpoint, "/quotas");
456 
457 		if ((fd = open64(mountpoint, O_RDONLY)) < 0) {
458 			errno = ENOENT;
459 			syslog(LOG_ERR, "can not open %s: %m ", mountpoint);
460 			return (-1);
461 		}
462 	}
463 	quota.op = cmd;
464 	quota.uid = uid;
465 	quota.addr = (caddr_t)dqp;
466 
467 	status = ioctl(fd, Q_QUOTACTL, &quota);
468 
469 	close(fd);
470 	return (status);
471 }
472 
473 /*
474  * Return the quota information for the given path.  Returns NULL if none
475  * was found.
476  */
477 
478 struct fsquot *
479 findfsq(char *dir)
480 {
481 	struct stat sb;
482 	struct fsquot *fsqp;
483 	static time_t lastmtime = 0; 	/* mount table's previous mtime */
484 
485 	/*
486 	 * If we've never looked at the mount table, or it has changed
487 	 * since the last time, rebuild the list of quota'd file systems
488 	 * and remember the current mod time for the mount table.
489 	 */
490 
491 	if (stat(MNTTAB, &sb) < 0) {
492 		syslog(LOG_ERR, "can't stat %s: %m", MNTTAB);
493 		return (NULL);
494 	}
495 	if (lastmtime == 0 || sb.st_mtime != lastmtime) {
496 		freefs();
497 		setupfs();
498 		lastmtime = sb.st_mtime;
499 	}
500 
501 	/*
502 	 * Try to find the given path in the list of file systems with
503 	 * quotas.
504 	 */
505 
506 	if (fsqlist == NULL)
507 		return (NULL);
508 	if (stat(dir, &sb) < 0)
509 		return (NULL);
510 
511 	for (fsqp = fsqlist; fsqp != NULL; fsqp = fsqp->fsq_next) {
512 		if (sb.st_dev == fsqp->fsq_dev)
513 			return (fsqp);
514 	}
515 
516 	return (NULL);
517 }
518 
519 static void
520 setup_zfs(struct mnttab *mp)
521 {
522 	struct fsquot *fsqp;
523 	struct stat sb;
524 
525 	if (stat(mp->mnt_mountp, &sb) < 0)
526 		return;
527 
528 	fsqp = malloc(sizeof (struct fsquot));
529 	if (fsqp == NULL) {
530 		syslog(LOG_ERR, "out of memory");
531 		zexit(1);
532 	}
533 	fsqp->fsq_dir = strdup(mp->mnt_mountp);
534 	fsqp->fsq_devname = strdup(mp->mnt_special);
535 	if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
536 		syslog(LOG_ERR, "out of memory");
537 		zexit(1);
538 	}
539 
540 	fsqp->fsq_fstype = MNTTYPE_ZFS;
541 	fsqp->fsq_dev = sb.st_dev;
542 	fsqp->fsq_next = fsqlist;
543 	fsqlist = fsqp;
544 }
545 
546 void
547 setupfs()
548 {
549 	struct fsquot *fsqp;
550 	FILE *mt;
551 	struct mnttab m;
552 	struct stat sb;
553 	char qfilename[MAXPATHLEN];
554 
555 	mt = fopen(MNTTAB, "r");
556 	if (mt == NULL) {
557 		syslog(LOG_ERR, "can't read %s: %m", MNTTAB);
558 		return;
559 	}
560 
561 	while (getmntent(mt, &m) == 0) {
562 		if (strcmp(m.mnt_fstype, MNTTYPE_ZFS) == 0) {
563 			setup_zfs(&m);
564 			continue;
565 		}
566 
567 		if (strcmp(m.mnt_fstype, MNTTYPE_UFS) != 0)
568 			continue;
569 		if (!hasquota(m.mnt_mntopts)) {
570 			snprintf(qfilename, sizeof (qfilename), "%s/%s",
571 			    m.mnt_mountp, QFNAME);
572 			if (access(qfilename, F_OK) < 0)
573 				continue;
574 		}
575 		if (stat(m.mnt_special, &sb) < 0 ||
576 		    (sb.st_mode & S_IFMT) != S_IFBLK)
577 			continue;
578 		fsqp = malloc(sizeof (struct fsquot));
579 		if (fsqp == NULL) {
580 			syslog(LOG_ERR, "out of memory");
581 			zexit(1);
582 		}
583 		fsqp->fsq_dir = strdup(m.mnt_mountp);
584 		fsqp->fsq_devname = strdup(m.mnt_special);
585 		if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
586 			syslog(LOG_ERR, "out of memory");
587 			zexit(1);
588 		}
589 		fsqp->fsq_fstype = MNTTYPE_UFS;
590 		fsqp->fsq_dev = sb.st_rdev;
591 		fsqp->fsq_next = fsqlist;
592 		fsqlist = fsqp;
593 	}
594 	(void) fclose(mt);
595 }
596 
597 /*
598  * Free the memory used by the current list of quota'd file systems.  Nulls
599  * out the list.
600  */
601 
602 void
603 freefs()
604 {
605 	register struct fsquot *fsqp;
606 
607 	while ((fsqp = fsqlist) != NULL) {
608 		fsqlist = fsqp->fsq_next;
609 		free(fsqp->fsq_dir);
610 		free(fsqp->fsq_devname);
611 		free(fsqp);
612 	}
613 }
614 
615 int
616 getdiskquota(fsqp, uid, dqp)
617 	struct fsquot *fsqp;
618 	uid_t uid;
619 	struct dqblk *dqp;
620 {
621 	int fd;
622 	char qfilename[MAXPATHLEN];
623 
624 	snprintf(qfilename, sizeof (qfilename), "%s/%s", fsqp->fsq_dir, QFNAME);
625 	if ((fd = open64(qfilename, O_RDONLY)) < 0)
626 		return (0);
627 	(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
628 	if (read(fd, dqp, sizeof (struct dqblk)) != sizeof (struct dqblk)) {
629 		close(fd);
630 		return (0);
631 	}
632 	close(fd);
633 	if (dqp->dqb_bhardlimit == 0 && dqp->dqb_bsoftlimit == 0 &&
634 	    dqp->dqb_fhardlimit == 0 && dqp->dqb_fsoftlimit == 0) {
635 		return (0);
636 	}
637 	return (1);
638 }
639 
640 /*
641  * Get the client's hostname from the transport handle
642  * If the name is not available then return "(anon)".
643  */
644 struct nd_hostservlist *
645 getclientsnames(transp)
646 	SVCXPRT *transp;
647 {
648 	struct netbuf *nbuf;
649 	struct netconfig *nconf;
650 	static struct nd_hostservlist	*serv;
651 	static struct nd_hostservlist	anon_hsl;
652 	static struct nd_hostserv	anon_hs;
653 	static char anon_hname[] = "(anon)";
654 	static char anon_sname[] = "";
655 
656 	/* Set up anonymous client */
657 	anon_hs.h_host = anon_hname;
658 	anon_hs.h_serv = anon_sname;
659 	anon_hsl.h_cnt = 1;
660 	anon_hsl.h_hostservs = &anon_hs;
661 
662 	if (serv) {
663 		netdir_free((char *)serv, ND_HOSTSERVLIST);
664 		serv = NULL;
665 	}
666 	nconf = getnetconfigent(transp->xp_netid);
667 	if (nconf == NULL) {
668 		syslog(LOG_ERR, "%s: getnetconfigent failed",
669 			transp->xp_netid);
670 		return (&anon_hsl);
671 	}
672 
673 	nbuf = svc_getrpccaller(transp);
674 	if (nbuf == NULL) {
675 		freenetconfigent(nconf);
676 		return (&anon_hsl);
677 	}
678 	if (netdir_getbyaddr(nconf, &serv, nbuf)) {
679 		freenetconfigent(nconf);
680 		return (&anon_hsl);
681 	}
682 	freenetconfigent(nconf);
683 	return (serv);
684 }
685 
686 void
687 log_cant_reply(transp)
688 	SVCXPRT *transp;
689 {
690 	int saverrno;
691 	struct nd_hostservlist *clnames;
692 	register char *name;
693 
694 	saverrno = errno;	/* save error code */
695 	clnames = getclientsnames(transp);
696 	if (clnames == NULL)
697 		return;
698 	name = clnames->h_hostservs->h_host;
699 
700 	errno = saverrno;
701 	if (errno == 0)
702 		syslog(LOG_ERR, "couldn't send reply to %s", name);
703 	else
704 		syslog(LOG_ERR, "couldn't send reply to %s: %m", name);
705 }
706 
707 char *mntopts[] = { MNTOPT_QUOTA, NULL };
708 #define	QUOTA    0
709 
710 /*
711  * Return 1 if "quota" appears in the options string
712  */
713 int
714 hasquota(opts)
715 	char *opts;
716 {
717 	char *value;
718 
719 	if (opts == NULL)
720 		return (0);
721 	while (*opts != '\0') {
722 		if (getsubopt(&opts, mntopts, &value) == QUOTA)
723 			return (1);
724 	}
725 
726 	return (0);
727 }
728 
729 static void
730 zexit(int n)
731 {
732 	if (g_zfs != NULL)
733 		_libzfs_fini(g_zfs);
734 	exit(n);
735 }
736