quotacheck.c revision d1a180b0452ce86577a43be3245d2eacdeec1a34
/*
* 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 2005 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Fix up / report on disc quotas & usage
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/filio.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_fs.h>
#include <sys/fs/ufs_quota.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mnttab.h>
#include <sys/vfstab.h>
#include <pwd.h>
#include <iso/limits_iso.h>
union {
struct fs sblk;
char dummy[MAXBSIZE];
} un;
#define sblock un.sblk
#define ITABSZ 256
struct dinode itab[ITABSZ];
struct dinode *dp;
struct fileusage {
struct fileusage *fu_next;
ulong_t fu_curfiles;
uint64_t fu_curblocks;
uid_t fu_uid;
};
#define FUHASH 997
struct fileusage *fuhead[FUHASH];
struct fileusage *lookup(uid_t);
struct fileusage *adduid(uid_t);
int fi;
ino_t ino;
struct dinode *ginode();
char *mntopt(), *hasvfsopt(), *hasmntopt();
extern int optind;
extern char *optarg;
extern int fsync(int);
static void acct();
static void bread();
static void usage();
static int chkquota();
static int quotactl();
static int preen();
static int waiter();
static int oneof();
int vflag; /* verbose */
int aflag; /* all file systems */
int pflag; /* fsck like parallel check */
int fflag; /* force flag */
#define QFNAME "quotas"
#define CHUNK 50
char **listbuf;
struct dqblk zerodqbuf;
struct fileusage zerofileusage;
int
main(int argc, char **argv)
{
struct mnttab mntp;
struct vfstab vfsbuf;
char **listp;
int listcnt;
int listmax = 0;
char quotafile[MAXPATHLEN];
FILE *mtab, *vfstab;
int errs = 0;
int opt;
if ((listbuf = (char **)malloc(sizeof (char *) * CHUNK)) == NULL) {
fprintf(stderr, "Can't alloc lisbuf array.");
exit(31+1);
}
listmax = CHUNK;
while ((opt = getopt(argc, argv, "vapVf")) != EOF) {
switch (opt) {
case 'v':
vflag++;
break;
case 'a':
aflag++;
break;
case 'p':
pflag++;
break;
case 'V': /* Print command line */
{
char *opt_text;
int opt_count;
(void) fprintf(stdout, "quotacheck -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 'f':
fflag++;
break;
case '?':
usage();
}
}
if (argc <= optind && !aflag) {
usage();
}
if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 &&
errno == EINVAL && vflag)
printf("Warning: Quotas are not compiled into this kernel\n");
sync();
if (aflag) {
/*
* Go through vfstab and make a list of appropriate
* filesystems.
*/
listp = listbuf;
listcnt = 0;
if ((vfstab = fopen(VFSTAB, "r")) == NULL) {
fprintf(stderr, "Can't open ");
perror(VFSTAB);
exit(31+8);
}
while (getvfsent(vfstab, &vfsbuf) == NULL) {
if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 ||
(vfsbuf.vfs_mntopts == 0) ||
hasvfsopt(&vfsbuf, MNTOPT_RO) ||
(!hasvfsopt(&vfsbuf, MNTOPT_RQ) &&
!hasvfsopt(&vfsbuf, MNTOPT_QUOTA)))
continue;
*listp = malloc(strlen(vfsbuf.vfs_special) + 1);
strcpy(*listp, vfsbuf.vfs_special);
listp++;
listcnt++;
/* grow listbuf if needed */
if (listcnt >= listmax) {
listmax += CHUNK;
listbuf = (char **)realloc(listbuf,
sizeof (char *) * listmax);
if (listbuf == NULL) {
fprintf(stderr,
"Can't grow listbuf.\n");
exit(31+1);
}
listp = &listbuf[listcnt];
}
}
fclose(vfstab);
*listp = (char *)0;
listp = listbuf;
} else {
listp = &argv[optind];
listcnt = argc - optind;
}
if (pflag) {
errs = preen(listcnt, listp);
} else {
if ((mtab = fopen(MNTTAB, "r")) == NULL) {
fprintf(stderr, "Can't open ");
perror(MNTTAB);
exit(31+8);
}
while (getmntent(mtab, &mntp) == NULL) {
if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 &&
!hasmntopt(&mntp, MNTOPT_RO) &&
(oneof(mntp.mnt_special, listp, listcnt) ||
oneof(mntp.mnt_mountp, listp, listcnt))) {
(void) snprintf(quotafile, sizeof (quotafile),
"%s/%s", mntp.mnt_mountp, QFNAME);
errs +=
chkquota(mntp.mnt_special,
mntp.mnt_mountp, quotafile);
}
}
fclose(mtab);
}
while (listcnt--) {
if (*listp) {
fprintf(stderr, "Cannot check %s\n", *listp);
errs++;
}
listp++;
}
if (errs > 0)
errs += 31;
return (errs);
}
struct active {
char *rdev;
pid_t pid;
struct active *nxt;
};
int
preen(int listcnt, char **listp)
{
int i, rc, errs;
char **lp, *rdev, *bdev;
extern char *getfullrawname(), *getfullblkname();
struct mnttab mntp, mpref;
struct active *alist, *ap;
FILE *mtab;
char quotafile[MAXPATHLEN];
char name[MAXPATHLEN];
int nactive, serially;
if ((mtab = fopen(MNTTAB, "r")) == NULL) {
fprintf(stderr, "Can't open ");
perror(MNTTAB);
exit(31+8);
}
memset(&mpref, 0, sizeof (struct mnttab));
errs = 0;
for (lp = listp, i = 0; i < listcnt; lp++, i++) {
serially = 0;
rdev = getfullrawname(*lp);
if (rdev == NULL || *rdev == '\0') {
fprintf(stderr, "can't get rawname for `%s'\n", *lp);
serially = 1;
} else if (preen_addev(rdev) != 0) {
fprintf(stderr, "preen_addev error\n");
serially = 1;
}
if (rdev != NULL)
free(rdev);
if (serially) {
rewind(mtab);
mpref.mnt_special = *lp;
if (getmntany(mtab, &mntp, &mpref) == 0 &&
strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 &&
!hasmntopt(&mntp, MNTOPT_RO)) {
errs += (31+chkquota(mntp.mnt_special,
mntp.mnt_mountp, quotafile));
*lp = (char *)0;
}
}
}
nactive = 0;
alist = NULL;
while ((rc = preen_getdev(name)) > 0) {
switch (rc) {
case 1:
bdev = getfullblkname(name);
if (bdev == NULL || *bdev == '\0') {
fprintf(stderr, "can't get blkname for `%s'\n",
name);
if (bdev)
free(bdev);
continue;
}
rewind(mtab);
mpref.mnt_special = bdev;
if (getmntany(mtab, &mntp, &mpref) != 0) {
fprintf(stderr, "`%s' not mounted?\n", name);
preen_releasedev(name);
free(bdev);
continue;
} else if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
hasmntopt(&mntp, MNTOPT_RO) ||
(!oneof(mntp.mnt_special, listp, listcnt) &&
!oneof(mntp.mnt_mountp, listp, listcnt))) {
preen_releasedev(name);
free(bdev);
continue;
}
free(bdev);
ap = (struct active *)malloc(sizeof (struct active));
if (ap == NULL) {
fprintf(stderr, "out of memory\n");
exit(31+8);
}
ap->rdev = (char *)strdup(name);
if (ap->rdev == NULL) {
fprintf(stderr, "out of memory\n");
exit(31+8);
}
ap->nxt = alist;
alist = ap;
switch (ap->pid = fork()) {
case -1:
perror("fork");
exit(31+8);
break;
case 0:
(void) snprintf(quotafile, sizeof (quotafile),
"%s/%s", mntp.mnt_mountp, QFNAME);
exit(31+chkquota(mntp.mnt_special,
mntp.mnt_mountp, quotafile));
break;
default:
nactive++;
break;
}
break;
case 2:
errs += waiter(&alist);
nactive--;
break;
}
}
fclose(mtab);
while (nactive > 0) {
errs += waiter(&alist);
nactive--;
}
return (errs);
}
int
waiter(struct active **alp)
{
pid_t curpid;
int status;
struct active *ap, *lap;
curpid = wait(&status);
if (curpid == -1) {
if (errno == ECHILD)
return (0);
perror("wait");
exit(31+8);
}
for (lap = NULL, ap = *alp; ap != NULL; lap = ap, ap = ap->nxt) {
if (ap->pid == curpid)
break;
}
if (ap == NULL) {
fprintf(stderr, "wait returns unknown pid\n");
exit(31+8);
} else if (lap) {
lap->nxt = ap->nxt;
} else {
*alp = ap->nxt;
}
preen_releasedev(ap->rdev);
free(ap->rdev);
free(ap);
return (WHIBYTE(status));
}
int
chkquota(char *fsdev, char *fsfile, char *qffile)
{
struct fileusage *fup;
dev_t quotadev;
FILE *qf;
uid_t uid;
struct passwd *pw;
int cg, i;
char *rawdisk;
struct stat64 statb;
struct dqblk dqbuf;
extern char *getfullrawname();
if ((rawdisk = getfullrawname(fsdev)) == NULL) {
fprintf(stderr, "malloc failed\n");
return (1);
}
if (*rawdisk == '\0') {
fprintf(stderr, "Could not find character device for %s\n",
fsdev);
return (1);
}
if (vflag)
printf("*** Checking quotas for %s (%s)\n", rawdisk, fsfile);
fi = open64(rawdisk, 0);
if (fi < 0) {
perror(rawdisk);
return (1);
}
qf = fopen64(qffile, "r+");
if (qf == NULL) {
perror(qffile);
close(fi);
return (1);
}
if (fstat64(fileno(qf), &statb) < 0) {
perror(qffile);
fclose(qf);
close(fi);
return (1);
}
quotadev = statb.st_dev;
if (stat64(fsdev, &statb) < 0) {
perror(fsdev);
fclose(qf);
close(fi);
return (1);
}
if (quotadev != statb.st_rdev) {
fprintf(stderr, "%s dev (0x%x) mismatch %s dev (0x%x)\n",
qffile, quotadev, fsdev, statb.st_rdev);
fclose(qf);
close(fi);
return (1);
}
bread((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE);
/*
* Flush filesystem since we are going to read
* disk raw and we want to make sure everything is
* synced to disk before we read it.
*/
if (ioctl(fileno(qf), _FIOFFS, NULL) == -1) {
perror(qffile);
(void) fprintf(stderr, "%s: cannot flush file system.\n",
qffile);
(void) fclose(qf);
return (1);
}
/*
* no need to quotacheck a rw, mounted, and logging file system
*/
if ((fflag == 0) && pflag &&
(FSOKAY == (sblock.fs_state + sblock.fs_time)) &&
(sblock.fs_clean == FSLOG)) {
fclose(qf);
close(fi);
return (0);
}
ino = 0;
for (cg = 0; cg < sblock.fs_ncg; cg++) {
dp = NULL;
for (i = 0; i < sblock.fs_ipg; i++)
acct(ginode());
}
for (uid = 0; uid <= MAXUID && uid >= 0; uid++) {
(void) fread(&dqbuf, sizeof (struct dqblk), 1, qf);
if (feof(qf))
break;
fup = lookup(uid);
if (fup == 0)
fup = &zerofileusage;
if (dqbuf.dqb_bhardlimit == 0 && dqbuf.dqb_bsoftlimit == 0 &&
dqbuf.dqb_fhardlimit == 0 && dqbuf.dqb_fsoftlimit == 0) {
fup->fu_curfiles = 0;
fup->fu_curblocks = 0;
}
if (dqbuf.dqb_curfiles == fup->fu_curfiles &&
dqbuf.dqb_curblocks == fup->fu_curblocks) {
fup->fu_curfiles = 0;
fup->fu_curblocks = 0;
continue;
}
/*
* The maximum number of blocks that can be stored in the
* dqb_curblocks field in the quota record is 2^32 - 1,
* since it must fit into an unsigned 32-bit quantity.
* If this user has more blocks than that, print a message
* to that effect and reduce the count of allocated blocks
* to the maximum value, which is UINT_MAX.
*/
if (fup->fu_curblocks > UINT_MAX) {
if (pflag || aflag)
printf("%s: ", rawdisk);
printf("512-byte blocks allocated to user ");
if ((pw = getpwuid(uid)) && pw->pw_name[0])
printf("%-10s ", pw->pw_name);
else
printf("#%-9d ", uid);
printf(" = %lld\n", fup->fu_curblocks);
printf(
"This exceeds the maximum number of blocks recordable in a quota record.\n");
printf(
"The value will be set to the maximum, which is %lu.\n", UINT_MAX);
fup->fu_curblocks = UINT_MAX;
}
if (vflag) {
if (pflag || aflag)
printf("%s: ", rawdisk);
if ((pw = getpwuid(uid)) && pw->pw_name[0])
printf("%-10s fixed:", pw->pw_name);
else
printf("#%-9d fixed:", uid);
if (dqbuf.dqb_curfiles != fup->fu_curfiles)
printf(" files %lu -> %lu",
dqbuf.dqb_curfiles, fup->fu_curfiles);
if (dqbuf.dqb_curblocks != fup->fu_curblocks)
printf(" blocks %lu -> %llu",
dqbuf.dqb_curblocks, fup->fu_curblocks);
printf("\n");
}
dqbuf.dqb_curfiles = fup->fu_curfiles;
dqbuf.dqb_curblocks = fup->fu_curblocks;
/*
* If quotas are not enabled for the current filesystem
* then just update the quotas file directly.
*/
if ((quotactl(Q_SETQUOTA, fsfile, uid, &dqbuf) < 0) &&
(errno == ESRCH)) {
/* back up, overwrite the entry we just read */
(void) fseeko64(qf, (offset_t)dqoff(uid), 0);
(void) fwrite(&dqbuf, sizeof (struct dqblk), 1, qf);
(void) fflush(qf);
}
fup->fu_curfiles = 0;
fup->fu_curblocks = 0;
}
(void) fflush(qf);
(void) fsync(fileno(qf));
fclose(qf);
close(fi);
return (0);
}
void
acct(struct dinode *ip)
{
struct fileusage *fup;
if (ip == NULL)
return;
ip->di_mode = ip->di_smode;
if (ip->di_suid != UID_LONG) {
ip->di_uid = ip->di_suid;
}
if (ip->di_mode == 0)
return;
fup = adduid(ip->di_uid);
fup->fu_curfiles++;
if ((ip->di_mode & IFMT) == IFCHR || (ip->di_mode & IFMT) == IFBLK)
return;
fup->fu_curblocks += ip->di_blocks;
}
int
oneof(char *target, char **olistp, int on)
{
char **listp = olistp;
int n = on;
while (n--) {
if (*listp && strcmp(target, *listp) == 0) {
*listp = (char *)0;
return (1);
}
listp++;
}
return (0);
}
struct dinode *
ginode()
{
ulong_t iblk;
if (dp == NULL || ++dp >= &itab[ITABSZ]) {
iblk = itod(&sblock, ino);
bread(fsbtodb(&sblock, iblk),
(char *)itab, sizeof (itab));
dp = &itab[(int)ino % (int)INOPB(&sblock)];
}
if (ino++ < UFSROOTINO)
return (NULL);
return (dp);
}
void
bread(diskaddr_t bno, char *buf, int cnt)
{
extern offset_t llseek();
offset_t pos;
pos = (offset_t)bno * DEV_BSIZE;
if (llseek(fi, pos, 0) != pos) {
perror("lseek");
exit(31+1);
}
if (read(fi, buf, cnt) != cnt) {
perror("read");
exit(31+1);
}
}
struct fileusage *
lookup(uid_t uid)
{
struct fileusage *fup;
for (fup = fuhead[uid % FUHASH]; fup != 0; fup = fup->fu_next)
if (fup->fu_uid == uid)
return (fup);
return ((struct fileusage *)0);
}
struct fileusage *
adduid(uid_t uid)
{
struct fileusage *fup, **fhp;
fup = lookup(uid);
if (fup != 0)
return (fup);
fup = (struct fileusage *)calloc(1, sizeof (struct fileusage));
if (fup == 0) {
fprintf(stderr, "out of memory for fileusage structures\n");
exit(31+1);
}
fhp = &fuhead[uid % FUHASH];
fup->fu_next = *fhp;
*fhp = fup;
fup->fu_uid = uid;
return (fup);
}
void
usage()
{
fprintf(stderr, "ufs usage:\n");
fprintf(stderr, "\tquotacheck [-v] [-f] [-p] -a\n");
fprintf(stderr, "\tquotacheck [-v] [-f] [-p] filesys ...\n");
exit(31+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 mntp;
if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
/*
* Find the mount point of any ufs 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");
exit(31+1);
}
fd = -1;
while ((status = getmntent(fstab, &mntp)) == NULL) {
if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
hasmntopt(&mntp, MNTOPT_RO))
continue;
if ((strlcpy(qfile, mntp.mnt_mountp,
sizeof (qfile)) >= sizeof (qfile)) ||
(strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
sizeof (qfile))) {
continue;
}
if ((fd = open64(qfile, O_RDWR)) == -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);
}
if ((fd = open64(qfile, O_RDWR)) < 0) {
fprintf(stderr, "quotactl: ");
perror("open");
exit(31+1);
}
} /* else */
quota.op = cmd;
quota.uid = uid;
quota.addr = addr;
status = ioctl(fd, Q_QUOTACTL, &quota);
if (fd != 0)
close(fd);
return (status);
}
char *
hasvfsopt(struct vfstab *vfs, char *opt)
{
char *f, *opts;
static char *tmpopts;
if (tmpopts == 0) {
tmpopts = (char *)calloc(256, sizeof (char));
if (tmpopts == 0)
return (0);
}
strcpy(tmpopts, vfs->vfs_mntopts);
opts = tmpopts;
f = mntopt(&opts);
for (; *f; f = mntopt(&opts)) {
if (strncmp(opt, f, strlen(opt)) == 0)
return (f - tmpopts + vfs->vfs_mntopts);
}
return (NULL);
}