blob: cc6c4e2c6948088859b9215c4fd62218bf5629c7 [file] [log] [blame]
/*
* Copyright (c) 1980, 1990 Regents of the University of California. All
* rights reserved.
*
* This code is derived from software contributed to Berkeley by Robert Elz at
* The University of Melbourne.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. 2.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution. 3. All advertising
* materials mentioning features or use of this software must display the
* following acknowledgement: This product includes software developed by the
* University of California, Berkeley and its contributors. 4. Neither the
* name of the University nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
/*
* Disk quota editor.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <paths.h>
#include <stdlib.h>
#include <fcntl.h>
#include <getopt.h>
#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, 0);
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, 0);
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, 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;
}