/* * Tool to edit quota limits via editor */ #include "config.h" /* * Disk quota editor. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pot.h" #include "quotaops.h" #include "quotasys.h" #include "quotaio.h" #include "common.h" #define FL_EDIT_PERIOD 1 #define FL_EDIT_TIMES 2 #define FL_REMOTE 4 #define FL_NUMNAMES 8 #define FL_NO_MIXED_PATHS 16 char *progname; static int flags, quotatype; static int fmt = -1; static char *protoname; static char *dirname; static void usage(void) { #if defined(RPC_SETQUOTA) char *rpcflag = "[-rm] "; #else char *rpcflag = ""; #endif errstr(_("Usage:\n\tedquota %1$s[-u] [-F formatname] [-p username] [-f filesystem] username ...\n\ \tedquota %1$s-g [-F formatname] [-p groupname] [-f filesystem] groupname ...\n\ \tedquota %1$s-P [-F formatname] [-p projectname] [-f filesystem] projectname ...\n\ \tedquota [-u|g|-P] [-F formatname] [-f filesystem] -t\n\ \tedquota [-u|g|-P] [-F formatname] [-f filesystem] -T username|groupname|projectname ...\n"), rpcflag); fputs(_("\n\ -u, --user edit user data\n\ -g, --group edit group data\n\ -P, --project edit project data\n\ "), stderr); #if defined(RPC_SETQUOTA) fputs(_("-r, --remote edit remote quota (via RPC)\n\ -m, --no-mixed-pathnames trim leading slashes from NFSv4 mountpoints\n"), stderr); #endif fputs(_("-F, --format=formatname edit quotas of a specific format\n\ -p, --prototype=name copy data from a prototype user/group\n\ --always-resolve always try to resolve name, even if it is\n\ composed only of digits\n\ -f, --filesystem=filesystem edit data only on a specific filesystem\n\ -t, --edit-period edit grace period\n\ -T, --edit-times edit grace time of a user/group\n\ -h, --help display this help text and exit\n\ -V, --version display version information and exit\n\n"), stderr); fprintf(stderr, _("Bugs to: %s\n"), PACKAGE_BUGREPORT); exit(1); } static int parse_options(int argc, char **argv) { int ret; struct option long_opts[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, { "prototype", 1, NULL, 'p' }, { "user", 0, NULL, 'u' }, { "group", 0, NULL, 'g' }, { "project", 0, NULL, 'P' }, { "format", 1, NULL, 'F' }, { "filesystem", 1, NULL, 'f' }, #if defined(RPC_SETQUOTA) { "remote", 0, NULL, 'r' }, { "no-mixed-pathnames", 0, NULL, 'm' }, #endif { "always-resolve", 0, NULL, 256 }, { "edit-period", 0, NULL, 't' }, { "edit-times", 0, NULL, 'T' }, { NULL, 0, NULL, 0 } }; if (argc < 2) usage(); quotatype = USRQUOTA; #if defined(RPC_SETQUOTA) while ((ret = getopt_long(argc, argv, "ugPhrmntTVp:F:f:", long_opts, NULL)) != -1) { #else while ((ret = getopt_long(argc, argv, "ugPhtTVp:F:f:", long_opts, NULL)) != -1) { #endif switch (ret) { case 'p': protoname = optarg; break; case 'g': quotatype = GRPQUOTA; break; case 'P': quotatype = PRJQUOTA; break; #if defined(RPC_SETQUOTA) case 'n': case 'r': flags |= FL_REMOTE; break; case 'm': flags |= FL_NO_MIXED_PATHS; break; #endif case 'u': quotatype = USRQUOTA; break; case 't': flags |= FL_EDIT_PERIOD; break; case 'T': flags |= FL_EDIT_TIMES; break; case 'F': if ((fmt = name2fmt(optarg)) == QF_ERROR) /* Error? */ exit(1); break; case 'f': dirname = optarg; break; case 256: flags |= FL_NUMNAMES; break; case 'V': version(); exit(0); case 'h': default: usage(); } } argc -= optind; if (((flags & FL_EDIT_PERIOD) && argc != 0) || ((flags & FL_EDIT_TIMES) && argc < 1)) usage(); if ((flags & (FL_EDIT_PERIOD | FL_EDIT_TIMES)) && protoname) { errstr(_("Prototype name does not make sense when editing grace period or times.\n")); usage(); } if (flags & FL_REMOTE && (flags & (FL_EDIT_TIMES | FL_EDIT_PERIOD))) { errstr(_("Cannot change grace times over RPC protocol.\n")); usage(); } return optind; } static void copy_prototype(int argc, char **argv, struct quota_handle **handles) { int ret, protoid, id; struct dquot *protoprivs, *curprivs, *pprivs, *cprivs; ret = 0; protoid = name2id(protoname, quotatype, !!(flags & FL_NUMNAMES), NULL); protoprivs = getprivs(protoid, handles, 0); while (argc-- > 0) { id = name2id(*argv, quotatype, !!(flags & FL_NUMNAMES), NULL); curprivs = getprivs(id, handles, !dirname); if (!curprivs) die(1, _("Cannot get quota information for user %s\n"), *argv); argv++; for (pprivs = protoprivs, cprivs = curprivs; pprivs && cprivs; pprivs = pprivs->dq_next, cprivs = cprivs->dq_next) { if (!devcmp_handles(pprivs->dq_h, cprivs->dq_h)) { errstr(_("fsname mismatch\n")); continue; } cprivs->dq_dqb.dqb_bsoftlimit = pprivs->dq_dqb.dqb_bsoftlimit; cprivs->dq_dqb.dqb_bhardlimit = pprivs->dq_dqb.dqb_bhardlimit; cprivs->dq_dqb.dqb_isoftlimit = pprivs->dq_dqb.dqb_isoftlimit; cprivs->dq_dqb.dqb_ihardlimit = pprivs->dq_dqb.dqb_ihardlimit; update_grace_times(cprivs); } if (putprivs(curprivs, COMMIT_LIMITS) == -1) ret = -1; freeprivs(curprivs); } if (dispose_handle_list(handles) == -1) ret = -1; freeprivs(protoprivs); exit(ret ? 1 : 0); } int main(int argc, char **argv) { struct dquot *curprivs; int tmpfd, ret, id; struct quota_handle **handles; char *tmpfil, *tmpdir = NULL; struct stat st; struct timespec mtime; gettexton(); progname = basename(argv[0]); ret = parse_options(argc, argv); argc -= ret; argv += ret; init_kernel_interface(); handles = create_handle_list(dirname ? 1 : 0, dirname ? &dirname : NULL, quotatype, fmt, (flags & FL_NO_MIXED_PATHS) ? 0 : IOI_NFS_MIXED_PATHS, (flags & FL_REMOTE) ? 0 : MS_LOCALONLY); if (!handles[0]) { dispose_handle_list(handles); fputs(_("No filesystems with quota detected.\n"), stderr); return 0; } if (protoname) copy_prototype(argc, argv, handles); umask(077); if (getuid() == geteuid() && getgid() == getegid()) tmpdir = getenv("TMPDIR"); if (!tmpdir) tmpdir = _PATH_TMP; tmpfil = smalloc(strlen(tmpdir) + strlen("/EdP.aXXXXXX") + 1); strcpy(tmpfil, tmpdir); strcat(tmpfil, "/EdP.aXXXXXX"); tmpfd = mkstemp(tmpfil); if (tmpfd < 0) { errstr(_("Cannot create temporary file: %s\n"), strerror(errno)); ret = -1; goto out; } if (fchown(tmpfd, getuid(), getgid()) < 0) { errstr(_("Cannot change owner of temporary file: %s\n"), strerror(errno)); ret = -1; goto out; } ret = 0; if (flags & FL_EDIT_PERIOD) { if (writetimes(handles, tmpfd) < 0) { errstr(_("Cannot write grace times to file.\n")); ret = -1; goto out; } if (stat(tmpfil, &st) < 0) { errstr(_("Cannot stat file with times.\n")); ret = -1; goto out; } mtime = st.st_mtim; if (editprivs(tmpfil) < 0) { errstr(_("Error while editing grace times.\n")); ret = -1; goto out; } close(tmpfd); /* * Reopen the file since editor may have written the * file in a new place. Open in rw mode because we can * reuse the file for editting the next user as well. */ if ((tmpfd = open(tmpfil, O_RDWR)) < 0) die(1, _("Cannot reopen!\n")); if (stat(tmpfil, &st) < 0) { errstr(_("Cannot stat file with times.\n")); ret = -1; goto out; } /* File not modified? */ if (timespec_cmp(&mtime, &st.st_mtim) == 0) goto out; if (readtimes(handles, tmpfd) < 0) { errstr(_("Failed to parse grace times file.\n")); ret = -1; goto out; } } else { for (; argc > 0; argc--, argv++) { id = name2id(*argv, quotatype, !!(flags & FL_NUMNAMES), NULL); curprivs = getprivs(id, handles, !dirname); if (!curprivs) die(1, _("Cannot get quota information for user %s.\n"), *argv); if (flags & FL_EDIT_TIMES) { if (writeindividualtimes(curprivs, tmpfd, *argv, quotatype) < 0) { errstr(_("Cannot write individual grace times to file.\n")); ret = -1; goto next_user; } } else { if (writeprivs(curprivs, tmpfd, *argv, quotatype) < 0) { errstr(_("Cannot write quotas to file.\n")); ret = -1; goto next_user; } } if (stat(tmpfil, &st) < 0) { errstr(_("Cannot stat file with times.\n")); ret = -1; goto out; } mtime = st.st_mtim; if (editprivs(tmpfil) < 0) { errstr(_("Error while editing quotas.\n")); ret = -1; goto next_user; } close(tmpfd); /* * Reopen the file since editor may have written the * file in a new place. Open in rw mode because we can * reuse the file for editting the next user as well. */ if ((tmpfd = open(tmpfil, O_RDWR)) < 0) die(1, _("Cannot reopen!\n")); if (stat(tmpfil, &st) < 0) { errstr(_("Cannot stat file with times.\n")); ret = -1; goto next_user; } /* File not modified? */ if (timespec_cmp(&mtime, &st.st_mtim) == 0) goto next_user; if (flags & FL_EDIT_TIMES) { if (readindividualtimes(curprivs, tmpfd) < 0) { errstr(_("Cannot read individual grace times from file.\n")); ret = -1; goto next_user; } } else { if (readprivs(curprivs, tmpfd) < 0) { errstr(_("Cannot read quotas from file.\n")); ret = -1; goto next_user; } } if (putprivs(curprivs, (flags & FL_EDIT_TIMES) ? COMMIT_TIMES : COMMIT_LIMITS) == -1) ret = -1; next_user: freeprivs(curprivs); } } out: if (dispose_handle_list(handles) == -1) ret = -1; close(tmpfd); unlink(tmpfil); free(tmpfil); return ret ? 1 : 0; }