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
load_libzfs(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
main(int argc,char * argv[])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
dispatch(rqstp,transp)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
closedown()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
getzfsquota(uid_t user,char * dataset,struct dqblk * zq)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
getquota(rqstp,transp)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
quotactl(int cmd,char * mountp,uid_t uid,struct dqblk * dqp)409 quotactl(int cmd, char *mountp, uid_t uid, struct dqblk *dqp)
410 {
411 int fd;
412 int status;
413 struct quotctl quota;
414 char mountpoint[256];
415 FILE *fstab;
416 struct mnttab mntp;
417
418 if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
419 /*
420 * Find the mount point of any ufs file system. this is
421 * because the ioctl that implements the quotactl call has
422 * to go to a real file, and not to the block device.
423 */
424 if ((fstab = fopen(MNTTAB, "r")) == NULL) {
425 syslog(LOG_ERR, "can not open %s: %m ", MNTTAB);
426 return (-1);
427 }
428 fd = -1;
429 while ((status = getmntent(fstab, &mntp)) == 0) {
430 if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
431 !(hasmntopt(&mntp, MNTOPT_RQ) ||
432 hasmntopt(&mntp, MNTOPT_QUOTA)))
433 continue;
434 (void) strlcpy(mountpoint, mntp.mnt_mountp,
435 sizeof (mountpoint));
436 strcat(mountpoint, "/quotas");
437 if ((fd = open64(mountpoint, O_RDWR)) >= 0)
438 break;
439 }
440 fclose(fstab);
441 if (fd == -1) {
442 errno = ENOENT;
443 return (-1);
444 }
445 } else {
446 if (mountp == NULL || mountp[0] == '\0') {
447 errno = ENOENT;
448 return (-1);
449 }
450 (void) strlcpy(mountpoint, mountp, sizeof (mountpoint));
451 strcat(mountpoint, "/quotas");
452
453 if ((fd = open64(mountpoint, O_RDONLY)) < 0) {
454 errno = ENOENT;
455 syslog(LOG_ERR, "can not open %s: %m ", mountpoint);
456 return (-1);
457 }
458 }
459 quota.op = cmd;
460 quota.uid = uid;
461 quota.addr = (caddr_t)dqp;
462
463 status = ioctl(fd, Q_QUOTACTL, "a);
464
465 close(fd);
466 return (status);
467 }
468
469 /*
470 * Return the quota information for the given path. Returns NULL if none
471 * was found.
472 */
473
474 struct fsquot *
findfsq(char * dir)475 findfsq(char *dir)
476 {
477 struct stat sb;
478 struct fsquot *fsqp;
479 static time_t lastmtime = 0; /* mount table's previous mtime */
480
481 /*
482 * If we've never looked at the mount table, or it has changed
483 * since the last time, rebuild the list of quota'd file systems
484 * and remember the current mod time for the mount table.
485 */
486
487 if (stat(MNTTAB, &sb) < 0) {
488 syslog(LOG_ERR, "can't stat %s: %m", MNTTAB);
489 return (NULL);
490 }
491 if (lastmtime == 0 || sb.st_mtime != lastmtime) {
492 freefs();
493 setupfs();
494 lastmtime = sb.st_mtime;
495 }
496
497 /*
498 * Try to find the given path in the list of file systems with
499 * quotas.
500 */
501
502 if (fsqlist == NULL)
503 return (NULL);
504 if (stat(dir, &sb) < 0)
505 return (NULL);
506
507 for (fsqp = fsqlist; fsqp != NULL; fsqp = fsqp->fsq_next) {
508 if (sb.st_dev == fsqp->fsq_dev)
509 return (fsqp);
510 }
511
512 return (NULL);
513 }
514
515 static void
setup_zfs(struct mnttab * mp)516 setup_zfs(struct mnttab *mp)
517 {
518 struct fsquot *fsqp;
519 struct stat sb;
520
521 if (stat(mp->mnt_mountp, &sb) < 0)
522 return;
523
524 fsqp = malloc(sizeof (struct fsquot));
525 if (fsqp == NULL) {
526 syslog(LOG_ERR, "out of memory");
527 zexit(1);
528 }
529 fsqp->fsq_dir = strdup(mp->mnt_mountp);
530 fsqp->fsq_devname = strdup(mp->mnt_special);
531 if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
532 syslog(LOG_ERR, "out of memory");
533 zexit(1);
534 }
535
536 fsqp->fsq_fstype = MNTTYPE_ZFS;
537 fsqp->fsq_dev = sb.st_dev;
538 fsqp->fsq_next = fsqlist;
539 fsqlist = fsqp;
540 }
541
542 void
setupfs()543 setupfs()
544 {
545 struct fsquot *fsqp;
546 FILE *mt;
547 struct mnttab m;
548 struct stat sb;
549 char qfilename[MAXPATHLEN];
550
551 mt = fopen(MNTTAB, "r");
552 if (mt == NULL) {
553 syslog(LOG_ERR, "can't read %s: %m", MNTTAB);
554 return;
555 }
556
557 while (getmntent(mt, &m) == 0) {
558 if (strcmp(m.mnt_fstype, MNTTYPE_ZFS) == 0) {
559 setup_zfs(&m);
560 continue;
561 }
562
563 if (strcmp(m.mnt_fstype, MNTTYPE_UFS) != 0)
564 continue;
565 if (!hasquota(m.mnt_mntopts)) {
566 snprintf(qfilename, sizeof (qfilename), "%s/%s",
567 m.mnt_mountp, QFNAME);
568 if (access(qfilename, F_OK) < 0)
569 continue;
570 }
571 if (stat(m.mnt_special, &sb) < 0 ||
572 (sb.st_mode & S_IFMT) != S_IFBLK)
573 continue;
574 fsqp = malloc(sizeof (struct fsquot));
575 if (fsqp == NULL) {
576 syslog(LOG_ERR, "out of memory");
577 zexit(1);
578 }
579 fsqp->fsq_dir = strdup(m.mnt_mountp);
580 fsqp->fsq_devname = strdup(m.mnt_special);
581 if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
582 syslog(LOG_ERR, "out of memory");
583 zexit(1);
584 }
585 fsqp->fsq_fstype = MNTTYPE_UFS;
586 fsqp->fsq_dev = sb.st_rdev;
587 fsqp->fsq_next = fsqlist;
588 fsqlist = fsqp;
589 }
590 (void) fclose(mt);
591 }
592
593 /*
594 * Free the memory used by the current list of quota'd file systems. Nulls
595 * out the list.
596 */
597
598 void
freefs()599 freefs()
600 {
601 register struct fsquot *fsqp;
602
603 while ((fsqp = fsqlist) != NULL) {
604 fsqlist = fsqp->fsq_next;
605 free(fsqp->fsq_dir);
606 free(fsqp->fsq_devname);
607 free(fsqp);
608 }
609 }
610
611 int
getdiskquota(fsqp,uid,dqp)612 getdiskquota(fsqp, uid, dqp)
613 struct fsquot *fsqp;
614 uid_t uid;
615 struct dqblk *dqp;
616 {
617 int fd;
618 char qfilename[MAXPATHLEN];
619
620 snprintf(qfilename, sizeof (qfilename), "%s/%s", fsqp->fsq_dir, QFNAME);
621 if ((fd = open64(qfilename, O_RDONLY)) < 0)
622 return (0);
623 (void) llseek(fd, (offset_t)dqoff(uid), L_SET);
624 if (read(fd, dqp, sizeof (struct dqblk)) != sizeof (struct dqblk)) {
625 close(fd);
626 return (0);
627 }
628 close(fd);
629 if (dqp->dqb_bhardlimit == 0 && dqp->dqb_bsoftlimit == 0 &&
630 dqp->dqb_fhardlimit == 0 && dqp->dqb_fsoftlimit == 0) {
631 return (0);
632 }
633 return (1);
634 }
635
636 /*
637 * Get the client's hostname from the transport handle
638 * If the name is not available then return "(anon)".
639 */
640 struct nd_hostservlist *
getclientsnames(transp)641 getclientsnames(transp)
642 SVCXPRT *transp;
643 {
644 struct netbuf *nbuf;
645 struct netconfig *nconf;
646 static struct nd_hostservlist *serv;
647 static struct nd_hostservlist anon_hsl;
648 static struct nd_hostserv anon_hs;
649 static char anon_hname[] = "(anon)";
650 static char anon_sname[] = "";
651
652 /* Set up anonymous client */
653 anon_hs.h_host = anon_hname;
654 anon_hs.h_serv = anon_sname;
655 anon_hsl.h_cnt = 1;
656 anon_hsl.h_hostservs = &anon_hs;
657
658 if (serv) {
659 netdir_free((char *)serv, ND_HOSTSERVLIST);
660 serv = NULL;
661 }
662 nconf = getnetconfigent(transp->xp_netid);
663 if (nconf == NULL) {
664 syslog(LOG_ERR, "%s: getnetconfigent failed",
665 transp->xp_netid);
666 return (&anon_hsl);
667 }
668
669 nbuf = svc_getrpccaller(transp);
670 if (nbuf == NULL) {
671 freenetconfigent(nconf);
672 return (&anon_hsl);
673 }
674 if (netdir_getbyaddr(nconf, &serv, nbuf)) {
675 freenetconfigent(nconf);
676 return (&anon_hsl);
677 }
678 freenetconfigent(nconf);
679 return (serv);
680 }
681
682 void
log_cant_reply(transp)683 log_cant_reply(transp)
684 SVCXPRT *transp;
685 {
686 int saverrno;
687 struct nd_hostservlist *clnames;
688 register char *name;
689
690 saverrno = errno; /* save error code */
691 clnames = getclientsnames(transp);
692 if (clnames == NULL)
693 return;
694 name = clnames->h_hostservs->h_host;
695
696 errno = saverrno;
697 if (errno == 0)
698 syslog(LOG_ERR, "couldn't send reply to %s", name);
699 else
700 syslog(LOG_ERR, "couldn't send reply to %s: %m", name);
701 }
702
703 char *mntopts[] = { MNTOPT_QUOTA, NULL };
704 #define QUOTA 0
705
706 /*
707 * Return 1 if "quota" appears in the options string
708 */
709 int
hasquota(opts)710 hasquota(opts)
711 char *opts;
712 {
713 char *value;
714
715 if (opts == NULL)
716 return (0);
717 while (*opts != '\0') {
718 if (getsubopt(&opts, mntopts, &value) == QUOTA)
719 return (1);
720 }
721
722 return (0);
723 }
724
725 static void
zexit(int n)726 zexit(int n)
727 {
728 if (g_zfs != NULL)
729 _libzfs_fini(g_zfs);
730 exit(n);
731 }
732