/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Disk quota reporting program. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../nfs/lib/replica.h" #include #include int vflag; int nolocalquota; extern int optind; extern char *optarg; #define QFNAME "quotas" #if DEV_BSIZE < 1024 #define kb(x) ((x) / (1024 / DEV_BSIZE)) #else #define kb(x) ((x) * (DEV_BSIZE / 1024)) #endif #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif static void zexit(int); static int getzfsquota(char *, char *, struct dqblk *); static int getnfsquota(char *, char *, uid_t, struct dqblk *); static void showuid(uid_t); static void showquotas(uid_t, char *); static void warn(struct mnttab *, struct dqblk *); static void heading(uid_t, char *); static void prquota(struct mnttab *, struct dqblk *); static void fmttime(char *, long); static libzfs_handle_t *(*_libzfs_init)(void); static void (*_libzfs_fini)(libzfs_handle_t *); static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int); static void (*_zfs_close)(zfs_handle_t *); static int (*_zfs_prop_get_userquota_int)(zfs_handle_t *, const char *, uint64_t *); static libzfs_handle_t *g_zfs = NULL; /* * Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs * packages. 'quota' utility supports zfs as an option. */ static void load_libzfs(void) { void *hdl; if (g_zfs != NULL) return; if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) { _libzfs_init = (libzfs_handle_t *(*)(void))dlsym(hdl, "libzfs_init"); _libzfs_fini = (void (*)())dlsym(hdl, "libzfs_fini"); _zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open"); _zfs_close = (void (*)())dlsym(hdl, "zfs_close"); _zfs_prop_get_userquota_int = (int (*)()) dlsym(hdl, "zfs_prop_get_userquota_int"); if (_libzfs_init && _libzfs_fini && _zfs_open && _zfs_close && _zfs_prop_get_userquota_int) g_zfs = _libzfs_init(); } } int main(int argc, char *argv[]) { int opt; int i; int status = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * PRIV_FILE_DAC_READ is needed to read the QFNAME file * Clear all other privleges from the limit set, and add * the required privilege to the bracketed set. */ if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_FILE_DAC_READ, NULL) == -1) { (void) fprintf(stderr, gettext("Insufficient privileges, " "quota must be set-uid root or have " "file_dac_read privileges\n")); exit(1); } load_libzfs(); while ((opt = getopt(argc, argv, "vV")) != EOF) { switch (opt) { case 'v': vflag++; break; case 'V': /* Print command line */ { char *opt_text; int opt_count; (void) fprintf(stdout, "quota -F UFS "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case '?': fprintf(stderr, "usage: quota [-v] [username]\n"); zexit(32); } } if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 && errno == EINVAL) { if (vflag) fprintf(stderr, "There are no quotas on this system\n"); nolocalquota++; } if (argc == optind) { showuid(getuid()); zexit(0); } for (i = optind; i < argc; i++) { if (alldigits(argv[i])) { showuid((uid_t)atoi(argv[i])); } else status |= showname(argv[i]); } __priv_relinquish(); return (status); } static void showuid(uid_t uid) { struct passwd *pwd = getpwuid(uid); if (uid == 0) { if (vflag) printf("no disk quota for uid 0\n"); return; } if (pwd == NULL) showquotas(uid, "(no account)"); else showquotas(uid, pwd->pw_name); } int showname(char *name) { struct passwd *pwd = getpwnam(name); if (pwd == NULL) { fprintf(stderr, "quota: %s: unknown user\n", name); return (32); } if (pwd->pw_uid == 0) { if (vflag) printf("no disk quota for %s (uid 0)\n", name); return (0); } showquotas(pwd->pw_uid, name); return (0); } static void showquotas(uid_t uid, char *name) { struct mnttab mnt; FILE *mtab; struct dqblk dqblk; uid_t myuid; struct failed_srv { char *serv_name; struct failed_srv *next; }; struct failed_srv *failed_srv_list = NULL; int rc; char my_zonename[ZONENAME_MAX]; zoneid_t my_zoneid = getzoneid(); myuid = getuid(); if (uid != myuid && myuid != 0) { printf("quota: %s (uid %d): permission denied\n", name, uid); zexit(32); } memset(my_zonename, '\0', ZONENAME_MAX); getzonenamebyid(my_zoneid, my_zonename, ZONENAME_MAX); if (vflag) heading(uid, name); mtab = fopen(MNTTAB, "r"); while (getmntent(mtab, &mnt) == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_ZFS) == 0) { bzero(&dqblk, sizeof (dqblk)); if (getzfsquota(name, mnt.mnt_special, &dqblk)) continue; } else if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0) { if (nolocalquota || (quotactl(Q_GETQUOTA, mnt.mnt_mountp, uid, &dqblk) != 0 && !(vflag && getdiskquota(&mnt, uid, &dqblk)))) continue; } else if (strcmp(mnt.mnt_fstype, MNTTYPE_NFS) == 0) { struct replica *rl; int count; char *mntopt = NULL; /* * Skip checking quotas for file systems mounted * in other zones. Zone names will be passed in * following format from hasmntopt(): * "zone=," */ if ((mntopt = hasmntopt(&mnt, MNTOPT_ZONE)) && (my_zonename[0] != '\0')) { mntopt += strcspn(mntopt, "=") + 1; if (strncmp(mntopt, my_zonename, strcspn(mntopt, ",")) != 0) continue; } if (hasopt(MNTOPT_NOQUOTA, mnt.mnt_mntopts)) continue; /* * Skip quota processing if mounted with public * option. We are not likely to be able to pierce * a fire wall to contact the quota server. */ if (hasopt(MNTOPT_PUBLIC, mnt.mnt_mntopts)) continue; rl = parse_replica(mnt.mnt_special, &count); if (rl == NULL) { if (count < 0) fprintf(stderr, "cannot find hostname " "and/or pathname for %s\n", mnt.mnt_mountp); else fprintf(stderr, "no memory to parse " "mnttab entry for %s\n", mnt.mnt_mountp); continue; } /* * We skip quota reporting on mounts with replicas * for the following reasons: * * (1) Very little point in reporting quotas on * a set of read-only replicas ... how will the * user correct the problem? * * (2) Which replica would we report the quota * for? If we pick the current replica, what * happens when a fail over event occurs? The * next time quota is run, the quota will look * all different, or there won't even be one. * This has the potential to break scripts. * * If we prnt quouta for all replicas, how do * we present the output without breaking scripts? */ if (count > 1) { free_replica(rl, count); continue; } /* * Skip file systems mounted using public fh. * We are not likely to be able to pierce * a fire wall to contact the quota server. */ if (strcmp(rl[0].host, "nfs") == 0 && strncmp(rl[0].path, "//", 2) == 0) { free_replica(rl, count); continue; } /* * Skip getting quotas from failing servers */ if (failed_srv_list != NULL) { struct failed_srv *tmp_list; int found_failed = 0; size_t len = strlen(rl[0].host); tmp_list = failed_srv_list; do { if (strncasecmp(rl[0].host, tmp_list->serv_name, len) == 0) { found_failed = 1; break; } } while ((tmp_list = tmp_list->next) != NULL); if (found_failed) { free_replica(rl, count); continue; } } rc = getnfsquota(rl[0].host, rl[0].path, uid, &dqblk); if (rc != RPC_SUCCESS) { size_t len; struct failed_srv *tmp_srv; /* * Failed to get quota from this server. Add * this server to failed_srv_list and skip * getting quotas for other mounted filesystems * from this server. */ if (rc == RPC_TIMEDOUT || rc == RPC_CANTSEND) { len = strlen(rl[0].host); tmp_srv = (struct failed_srv *)malloc( sizeof (struct failed_srv)); tmp_srv->serv_name = (char *)malloc( len * sizeof (char) + 1); strncpy(tmp_srv->serv_name, rl[0].host, len); tmp_srv->serv_name[len] = '\0'; tmp_srv->next = failed_srv_list; failed_srv_list = tmp_srv; } free_replica(rl, count); continue; } free_replica(rl, count); } else { continue; } if (dqblk.dqb_bsoftlimit == 0 && dqblk.dqb_bhardlimit == 0 && dqblk.dqb_fsoftlimit == 0 && dqblk.dqb_fhardlimit == 0) continue; if (vflag) prquota(&mnt, &dqblk); else warn(&mnt, &dqblk); } /* * Free list of failed servers */ while (failed_srv_list != NULL) { struct failed_srv *tmp_srv = failed_srv_list; failed_srv_list = failed_srv_list->next; free(tmp_srv->serv_name); free(tmp_srv); } fclose(mtab); } static void warn(struct mnttab *mntp, struct dqblk *dqp) { struct timeval tv; time(&(tv.tv_sec)); tv.tv_usec = 0; if (dqp->dqb_bhardlimit && dqp->dqb_curblocks >= dqp->dqb_bhardlimit) { printf("Block limit reached on %s\n", mntp->mnt_mountp); } else if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { if (dqp->dqb_btimelimit == 0) { printf("Over disk quota on %s, remove %luK\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); } else if (dqp->dqb_btimelimit > tv.tv_sec) { char btimeleft[80]; fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); printf("Over disk quota on %s, remove %luK within %s\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1), btimeleft); } else { printf( "Over disk quota on %s, time limit has expired, remove %luK\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); } } if (dqp->dqb_fhardlimit && dqp->dqb_curfiles >= dqp->dqb_fhardlimit) { printf("File count limit reached on %s\n", mntp->mnt_mountp); } else if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { if (dqp->dqb_ftimelimit == 0) { printf("Over file quota on %s, remove %lu file%s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : "")); } else if (dqp->dqb_ftimelimit > tv.tv_sec) { char ftimeleft[80]; fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); printf( "Over file quota on %s, remove %lu file%s within %s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : ""), ftimeleft); } else { printf( "Over file quota on %s, time limit has expired, remove %lu file%s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : "")); } } } static void heading(uid_t uid, char *name) { printf("Disk quotas for %s (uid %ld):\n", name, (long)uid); printf("%-12s %7s%7s%7s%12s%7s%7s%7s%12s\n", "Filesystem", "usage", "quota", "limit", "timeleft", "files", "quota", "limit", "timeleft"); } static void prquota(struct mnttab *mntp, struct dqblk *dqp) { struct timeval tv; char ftimeleft[80], btimeleft[80]; char *cp; time(&(tv.tv_sec)); tv.tv_usec = 0; if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { if (dqp->dqb_btimelimit == 0) { strlcpy(btimeleft, "NOT STARTED", sizeof (btimeleft)); } else if (dqp->dqb_btimelimit > tv.tv_sec) { fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); } else { strlcpy(btimeleft, "EXPIRED", sizeof (btimeleft)); } } else { btimeleft[0] = '\0'; } if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { if (dqp->dqb_ftimelimit == 0) { strlcpy(ftimeleft, "NOT STARTED", sizeof (ftimeleft)); } else if (dqp->dqb_ftimelimit > tv.tv_sec) { fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); } else { strlcpy(ftimeleft, "EXPIRED", sizeof (ftimeleft)); } } else { ftimeleft[0] = '\0'; } if (strlen(mntp->mnt_mountp) > 12) { printf("%s\n", mntp->mnt_mountp); cp = ""; } else { cp = mntp->mnt_mountp; } if (dqp->dqb_curfiles == 0 && dqp->dqb_fsoftlimit == 0 && dqp->dqb_fhardlimit == 0) { printf("%-12.12s %7d %6d %6d %11s %6s %6s %6s %11s\n", cp, kb(dqp->dqb_curblocks), kb(dqp->dqb_bsoftlimit), kb(dqp->dqb_bhardlimit), "-", "-", "-", "-", "-"); } else { printf("%-12.12s %7d %6d %6d %11s %6d %6d %6d %11s\n", cp, kb(dqp->dqb_curblocks), kb(dqp->dqb_bsoftlimit), kb(dqp->dqb_bhardlimit), btimeleft, dqp->dqb_curfiles, dqp->dqb_fsoftlimit, dqp->dqb_fhardlimit, ftimeleft); } } static void fmttime(char *buf, long time) { int i; static struct { int c_secs; /* conversion units in secs */ char *c_str; /* unit string */ } cunits [] = { {60*60*24*28, "months"}, {60*60*24*7, "weeks"}, {60*60*24, "days"}, {60*60, "hours"}, {60, "mins"}, {1, "secs"} }; if (time <= 0) { strlcpy(buf, "EXPIRED", sizeof (*buf)); return; } for (i = 0; i < sizeof (cunits)/sizeof (cunits[0]); i++) { if (time >= cunits[i].c_secs) break; } snprintf(buf, sizeof (*buf), "%.1f %s", (double)time/cunits[i].c_secs, cunits[i].c_str); } int alldigits(char *s) { int c; c = *s++; do { if (!isdigit(c)) return (0); } while (c = *s++); return (1); } int getdiskquota(struct mnttab *mntp, uid_t uid, struct dqblk *dqp) { int fd; dev_t fsdev; struct stat64 statb; char qfilename[MAXPATHLEN]; if (stat64(mntp->mnt_special, &statb) < 0 || (statb.st_mode & S_IFMT) != S_IFBLK) return (0); fsdev = statb.st_rdev; (void) snprintf(qfilename, sizeof (qfilename), "%s/%s", mntp->mnt_mountp, QFNAME); if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev) return (0); (void) __priv_bracket(PRIV_ON); fd = open64(qfilename, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd < 0) return (0); (void) llseek(fd, (offset_t)dqoff(uid), L_SET); switch (read(fd, dqp, sizeof (struct dqblk))) { case 0: /* EOF */ /* * Convert implicit 0 quota (EOF) * into an explicit one (zero'ed dqblk). */ memset((caddr_t)dqp, 0, sizeof (struct dqblk)); break; case sizeof (struct dqblk): /* OK */ break; default: /* ERROR */ close(fd); return (0); } close(fd); return (1); } int quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mnt; if ((mountp == NULL) && (cmd == Q_ALLSYNC)) { /* * Find the mount point of any mounted file system. This is * because the ioctl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "%s: ", MNTTAB); perror("open"); zexit(32); } fd = -1; while ((status = getmntent(fstab, &mnt)) == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0 || hasopt(MNTOPT_RO, mnt.mnt_mntopts)) continue; if ((strlcpy(qfile, mnt.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { continue; } (void) __priv_bracket(PRIV_ON); fd = open64(qfile, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd != -1) break; } fclose(fstab); if (fd == -1) { errno = ENOENT; return (-1); } } else { if (mountp == NULL || mountp[0] == '\0') { errno = ENOENT; return (-1); } if ((strlcpy(qfile, mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { errno = ENOENT; return (-1); } (void) __priv_bracket(PRIV_ON); fd = open64(qfile, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd < 0) return (-1); } /* else */ quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); if (fd != 0) close(fd); return (status); } /* * Return 1 if opt appears in optlist */ int hasopt(char *opt, char *optlist) { char *value; char *opts[2]; opts[0] = opt; opts[1] = NULL; if (optlist == NULL) return (0); while (*optlist != '\0') { if (getsubopt(&optlist, opts, &value) == 0) return (1); } return (0); } /* * If there are no quotas available, then getnfsquota() returns * RPC_SYSTEMERROR to caller. */ static int getnfsquota(char *hostp, char *path, uid_t uid, struct dqblk *dqp) { struct getquota_args gq_args; struct getquota_rslt gq_rslt; struct rquota *rquota; extern char *strchr(); int rpc_err; gq_args.gqa_pathp = path; gq_args.gqa_uid = uid; rpc_err = callaurpc(hostp, RQUOTAPROG, RQUOTAVERS, (vflag? RQUOTAPROC_GETQUOTA: RQUOTAPROC_GETACTIVEQUOTA), xdr_getquota_args, &gq_args, xdr_getquota_rslt, &gq_rslt); if (rpc_err != RPC_SUCCESS) { return (rpc_err); } switch (gq_rslt.status) { case Q_OK: { struct timeval tv; u_longlong_t limit; rquota = &gq_rslt.getquota_rslt_u.gqr_rquota; if (!vflag && rquota->rq_active == FALSE) { return (RPC_SYSTEMERROR); } gettimeofday(&tv, NULL); limit = (u_longlong_t)(rquota->rq_bhardlimit) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_bhardlimit = limit; limit = (u_longlong_t)(rquota->rq_bsoftlimit) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_bsoftlimit = limit; limit = (u_longlong_t)(rquota->rq_curblocks) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_curblocks = limit; dqp->dqb_fhardlimit = rquota->rq_fhardlimit; dqp->dqb_fsoftlimit = rquota->rq_fsoftlimit; dqp->dqb_curfiles = rquota->rq_curfiles; dqp->dqb_btimelimit = tv.tv_sec + rquota->rq_btimeleft; dqp->dqb_ftimelimit = tv.tv_sec + rquota->rq_ftimeleft; return (RPC_SUCCESS); } case Q_NOQUOTA: return (RPC_SYSTEMERROR); case Q_EPERM: fprintf(stderr, "quota permission error, host: %s\n", hostp); return (RPC_AUTHERROR); default: fprintf(stderr, "bad rpc result, host: %s\n", hostp); return (RPC_CANTDECODEARGS); } /* NOTREACHED */ } int callaurpc(char *host, int prognum, int versnum, int procnum, xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) { static enum clnt_stat clnt_stat; struct timeval tottimeout = {20, 0}; static CLIENT *cl = NULL; static int oldprognum, oldversnum; static char oldhost[MAXHOSTNAMELEN+1]; /* * Cache the client handle in case there are lots * of entries in the /etc/mnttab for the same * server. If the server returns an error, don't * make further calls. */ if (cl == NULL || oldprognum != prognum || oldversnum != versnum || strcmp(oldhost, host) != 0) { if (cl) { clnt_destroy(cl); cl = NULL; } cl = clnt_create_timed(host, prognum, versnum, "udp", &tottimeout); if (cl == NULL) return ((int)RPC_TIMEDOUT); if ((cl->cl_auth = authunix_create_default()) == NULL) { clnt_destroy(cl); return (RPC_CANTSEND); } oldprognum = prognum; oldversnum = versnum; (void) strlcpy(oldhost, host, sizeof (oldhost)); clnt_stat = RPC_SUCCESS; } if (clnt_stat != RPC_SUCCESS) return ((int)clnt_stat); /* don't bother retrying */ clnt_stat = clnt_call(cl, procnum, inproc, in, outproc, out, tottimeout); return ((int)clnt_stat); } static int getzfsquota(char *user, char *dataset, struct dqblk *zq) { zfs_handle_t *zhp = NULL; char propname[ZFS_MAXPROPLEN]; uint64_t userquota, userused; if (g_zfs == NULL) return (1); if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) return (1); (void) snprintf(propname, sizeof (propname), "userquota@%s", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userquota) != 0) { _zfs_close(zhp); return (1); } (void) snprintf(propname, sizeof (propname), "userused@%s", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userused) != 0) { _zfs_close(zhp); return (1); } zq->dqb_bhardlimit = userquota / DEV_BSIZE; zq->dqb_bsoftlimit = userquota / DEV_BSIZE; zq->dqb_curblocks = userused / DEV_BSIZE; _zfs_close(zhp); return (0); } static void zexit(int n) { if (g_zfs != NULL) _libzfs_fini(g_zfs); exit(n); }