[BACK]Return to edit.c CVS log [TXT][DIR] Up to [Development] / xfs-cmds / xfsprogs / quota

File: [Development] / xfs-cmds / xfsprogs / quota / edit.c (download)

Revision 1.1, Tue Apr 19 15:00:30 2005 UTC (12 years, 6 months ago) by nathans.longdrop.melbourne.sgi.com
Branch: MAIN

Initial version of xfs quota utility.  Knows how to do user/group/project quota, provides missing freespace reporting for realtime on Linux, and for project quotas.  Allows all current and future XFS quota functionality to be exposed without complicating the standard quota tools.
Merge of master-melb:xfs-cmds:22274a by kenmcd.

/*
 * Copyright (c) 2005 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
 */

#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <xfs/input.h>
#include <xfs/command.h>
#include "init.h"
#include "quota.h"

static cmdinfo_t limit_cmd;
static cmdinfo_t restore_cmd;
static cmdinfo_t timer_cmd;
static cmdinfo_t warn_cmd;

static void
limit_help(void)
{
	printf(_(
"\n"
" modify quota limits for the specified user\n"
"\n"
" Example:\n"
" 'limit bsoft=100m bhard=110m tanya\n"
"\n"
" Changes the soft and/or hard block limits, inode limits and/or realtime\n"
" block limits that are currently being used for the specified user, group,\n"
" or project.  The filesystem identified by the current path is modified.\n"
" -d -- set the default values, used the first time a file is created\n"
" -g -- modify group quota limits\n"
" -p -- modify project quota limits\n"
" -u -- modify user quota limits\n"
" The block limit values can be specified with a units suffix - accepted\n"
" units are: k (kilobytes), m (megabytes), g (gigabytes), and t (terabytes).\n"
" The user/group/project can be specified either by name or by number.\n"
"\n"));
}

static void
timer_help(void)
{
	printf(_(
"\n"
" modify quota enforcement timeout for the specified user\n"
"\n"
" Example:\n"
" 'timer -i 3days sam'\n"
" (soft inode limit timer for user 'sam' is changed to 3 days)\n"
"\n"
" Changes the timeout value associated with the block limits, inode limits\n"
" and/or realtime block limits for the specified user, group, or project.\n"
" As soon as a user consumes the amount of space or number of inodes set as\n"
" the soft limit, a timer is started.  If the timer expires and the user is\n"
" still over the soft limit, the soft limit is enforced as the hard limit.\n"
" The default timeout is 7 days.\n"
" -d -- set the default values, used the first time a file is created\n"
" -g -- modify group quota timer\n"
" -p -- modify project quota timer\n"
" -u -- modify user quota timer\n"
" -b -- modify the blocks-used timer\n"
" -i -- modify the inodes-used timer\n"
" -r -- modify the blocks-used timer for the (optional) realtime subvolume\n"
" The timeout value is specified as a number of seconds, by default.\n"
" However, a suffix may be used to alternatively specify minutes (m),\n"
" hours (h), days (d), or weeks (w) - either the full word or the first\n"
" letter of the word can be used.\n"
" The user/group/project can be specified either by name or by number.\n"
"\n"));
}

static void
warn_help(void)
{
	printf(_(
"\n"
" modify the number of quota warnings sent to the specified user\n"
"\n"
" Example:\n"
" 'warn 2 jimmy'\n"
" (tell the quota system that two warnings have been sent to user jimmy)\n"
"\n"
" Changes the warning count associated with the block limits, inode limits\n"
" and/or realtime block limits for the specified user, group, or project.\n"
" When a user has been warned the maximum number of times allowed, the soft\n"
" limit is enforced as the hard limit.  It is intended as an alternative to\n"
" the timeout system, where the system administrator updates a count of the\n"
" number of warnings issued to people, and they are penalised if the warnings\n"
" are ignored.\n"
" -d -- set maximum warning count, which triggers soft limit enforcement\n"
" -g -- set group quota warning count\n"
" -p -- set project quota warning count\n"
" -u -- set user quota warning count\n"
" -b -- set the blocks-used warning count\n"
" -i -- set the inodes-used warning count\n"
" -r -- set the blocks-used warn count for the (optional) realtime subvolume\n"
" The user/group/project can be specified either by name or by number.\n"
"\n"));
}

static void
set_limits(
	__uint32_t	id,
	uint		type,
	uint		mask,
	char		*dev,
	__uint64_t	*bsoft,
	__uint64_t	*bhard,
	__uint64_t	*isoft,
	__uint64_t	*ihard, 
	__uint64_t	*rtbsoft,
	__uint64_t	*rtbhard)
{
	fs_disk_quota_t	d;

	memset(&d, 0, sizeof(d));
	d.d_version = FS_DQUOT_VERSION;
	d.d_id = id;
	d.d_flags = type;
	d.d_fieldmask = mask;
	d.d_blk_hardlimit = *bhard;
	d.d_blk_softlimit = *bsoft;
	d.d_ino_hardlimit = *ihard;
	d.d_ino_softlimit = *isoft;
	d.d_rtb_hardlimit = *rtbhard;
	d.d_rtb_softlimit = *rtbsoft;

	if (xfsquotactl(XFS_SETQLIM, dev, type, id, (void *)&d) < 0)
		fprintf(stderr, _("%s: cannot set limits: %s\n"),
				progname, strerror(errno));
}

static void
set_user_limits(
	char		*name,
	uint		type,
	uint		mask,
	__uint64_t	*bsoft,
	__uint64_t	*bhard,
	__uint64_t	*isoft,
	__uint64_t	*ihard, 
	__uint64_t	*rtbsoft,
	__uint64_t	*rtbhard)
{
	uid_t		uid = uid_from_string(name);

	if (uid == -1)
		fprintf(stderr, _("%s: invalid user name: %s\n"),
				progname, name);
	else
		set_limits(uid, type, mask, fs_path->fs_name,
				bsoft, bhard, isoft, ihard, rtbsoft, rtbhard);
}

static void
set_group_limits(
	char		*name,
	uint		type,
	uint		mask,
	__uint64_t	*bsoft,
	__uint64_t	*bhard,
	__uint64_t	*isoft,
	__uint64_t	*ihard, 
	__uint64_t	*rtbsoft,
	__uint64_t	*rtbhard)
{
	gid_t		gid = gid_from_string(name);

	if (gid == -1)
		fprintf(stderr, _("%s: invalid group name: %s\n"),
				progname, name);
	else
		set_limits(gid, type, mask, fs_path->fs_name,
				bsoft, bhard, isoft, ihard, rtbsoft, rtbhard);
}

static void
set_project_limits(
	char		*name,
	uint		type,
	uint		mask,
	__uint64_t	*bsoft,
	__uint64_t	*bhard,
	__uint64_t	*isoft,
	__uint64_t	*ihard, 
	__uint64_t	*rtbsoft,
	__uint64_t	*rtbhard)
{
	prid_t		prid = prid_from_string(name);

	if (prid == -1)
		fprintf(stderr, _("%s: invalid project name: %s\n"),
				progname, name);
	else
		set_limits(prid, type, mask, fs_path->fs_name,
				bsoft, bhard, isoft, ihard, rtbsoft, rtbhard);
}

static int
extract(
	char		*string,
	const char	*prefix,
	int		length,
	uint		blocksize,
	uint		sectorsize,
	__uint64_t	*value)
{
	__uint64_t	v;
	char		*s = string;

	if (strncmp(string, prefix, length) == 0) {
		s = string + length + 1;
		v = (__uint64_t)cvtnum(blocksize, sectorsize, s);
		*value = v >> 9;	/* syscalls use basic blocks */
		return 1;
	}
	return 0;
}

static int
limit_f(
	int		argc,
	char		**argv)
{
	char		*name;
	__uint64_t	bsoft, bhard, isoft, ihard, rtbsoft, rtbhard;
	int		c, type = 0, mask = 0, flags = 0;
	uint		bsize, ssize, endoptions;

	init_cvtnum(&bsize, &ssize);
	bsoft = bhard = isoft = ihard = rtbsoft = rtbhard = 0;
	while ((c = getopt(argc, argv, "dgpu")) != EOF) {
		switch (c) {
		case 'd':
			flags |= DEFAULTS_FLAG;
			break;
		case 'g':
			type = XFS_GROUP_QUOTA;
			break;
		case 'p':
			type = XFS_PROJ_QUOTA;
			break;
		case 'u':
			type = XFS_USER_QUOTA;
			break;
		default:
			return command_usage(&limit_cmd);
		}
	}

	/*
	 * In the usual case, we need at least 2 more arguments -
	 * one (or more) limits and a user name/id.
	 * For setting defaults (-d) we don't want a user name/id.
	 */
	if (flags & DEFAULTS_FLAG) {
		if (argc < optind + 1)
			return command_usage(&limit_cmd);
		endoptions = 1;
	} else if (argc < optind + 2) {
		return command_usage(&limit_cmd);
	} else {
		endoptions = 2;
	}

	/*
	 * Extract limit values from remaining optional arguments.
	 */
	while (argc > optind + endoptions - 1) {
		char *s = argv[optind++];
		if (extract(s, "bsoft=", 5, bsize, ssize, &bsoft))
			mask |= FS_DQ_BSOFT;
		else if (extract(s, "bhard=", 5, bsize, ssize, &bhard))
			mask |= FS_DQ_BHARD;
		else if (extract(s, "isoft=", 5, bsize, ssize, &isoft))
			mask |= FS_DQ_ISOFT;
		else if (extract(s, "ihard=", 5, bsize, ssize, &ihard))
			mask |= FS_DQ_IHARD;
		else if (extract(s, "rtbsoft=", 7, bsize, ssize, &rtbsoft))
			mask |= FS_DQ_RTBSOFT;
		else if (extract(s, "rtbhard=", 7, bsize, ssize, &rtbhard))
			mask |= FS_DQ_RTBHARD;
		else {
			fprintf(stderr, _("%s: unrecognised argument %s\n"),
				progname, s);
			return 0;
		}
	}
	if (!mask) {
		fprintf(stderr, _("%s: cannot find any valid arguments\n"),
			progname);
		return 0;
	}

	name = (flags & DEFAULTS_FLAG) ? "0" : argv[optind++];

	if (!type)
		type = XFS_USER_QUOTA;

	switch (type) {
	case XFS_USER_QUOTA:
		set_user_limits(name, type, mask,
			&bsoft, &bhard, &isoft, &ihard, &rtbsoft, &rtbhard);
		break;
	case XFS_GROUP_QUOTA:
		set_group_limits(name, type, mask,
			&bsoft, &bhard, &isoft, &ihard, &rtbsoft, &rtbhard);
		break;
	case XFS_PROJ_QUOTA:
		set_project_limits(name, type, mask,
			&bsoft, &bhard, &isoft, &ihard, &rtbsoft, &rtbhard);
		break;
	}
	return 0;
}

/*
 * Iterate through input file, restoring the limits.
 * File format is as follows:
 * fs = <device>
 * <numeric id> bsoft bhard isoft ihard [rtbsoft rtbhard]
 */
static void
restore_file(
	FILE		*fp,
	uint		type)
{
	char		buffer[512];
	char		devbuffer[512];
	char		*dev = NULL;
	uint		mask;
	int		cnt;
	__uint32_t	id;
	__uint64_t	bsoft, bhard, isoft, ihard, rtbsoft, rtbhard;

	while (fgets(buffer, sizeof(buffer), fp) != NULL) {
		if (strncmp("fs = ", buffer, 5) == 0) {
			dev = strncpy(devbuffer, buffer+5, sizeof(devbuffer));
			dev[strlen(dev) - 1] = '\0';
			continue;
		}
		rtbsoft = rtbhard = 0;
		cnt = sscanf(buffer, "%u %llu %llu %llu %llu %llu %llu\n",
				&id,
				&bsoft, &bhard,
				&isoft, &ihard,
				&rtbsoft, &rtbhard);
		if (cnt == 5 || cnt == 7) {
			mask = FS_DQ_ISOFT|FS_DQ_IHARD|FS_DQ_BSOFT|FS_DQ_BHARD;
			if (cnt == 7)
				mask |= FS_DQ_RTBSOFT|FS_DQ_RTBHARD;
			set_limits(id, type, mask, dev, &bsoft, &bhard,
					&isoft, &ihard, &rtbsoft, &rtbhard);
		}
	}
}

static int
restore_f(
	int		argc,
	char		**argv)
{
	FILE		*fp = stdin;
	char		*fname = NULL;
	int		c, type = 0;

	while ((c = getopt(argc, argv, "f:gpu")) != EOF) {
		switch (c) {
		case 'f':
			fname = optarg;
			break;
		case 'g':
			type = XFS_GROUP_QUOTA;
			break;
		case 'p':
			type = XFS_PROJ_QUOTA;
			break;
		case 'u':
			type = XFS_USER_QUOTA;
			break;
		default:
			return command_usage(&restore_cmd);
		}
	}

	if (argc < optind)
		return command_usage(&restore_cmd);

	if (!type)
		type = XFS_USER_QUOTA;

	if (fname) {
		if ((fp = fopen(fname, "r")) == NULL) {
			fprintf(stderr, _("%s: fopen on %s failed: %s\n"),
				progname, fname, strerror(errno));
			return 0;
		}
	}

	restore_file(fp, type);

	if (fname)
		fclose(fp);
	return 0;
}

static void
set_timer(
	__uint32_t	id,
	uint		type,
	uint		mask,
	char		*dev,
	uint		value)
{
	fs_disk_quota_t	d;

	memset(&d, 0, sizeof(d));
	d.d_version = FS_DQUOT_VERSION;
	d.d_id = id;
	d.d_flags = type;
	d.d_fieldmask = mask;
	d.d_itimer = value;
	d.d_btimer = value;
	d.d_rtbtimer = value;

	if (xfsquotactl(XFS_SETQLIM, dev, type, id, (void *)&d) < 0)
		fprintf(stderr, _("%s: cannot set timer: %s\n"),
				progname, strerror(errno));
}

static void
set_user_timer(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	uid_t		uid = uid_from_string(name);

	if (uid == -1)
		fprintf(stderr, _("%s: invalid user name: %s\n"),
				progname, name);
	else
		set_timer(uid, type, mask, fs_path->fs_name, value);
}

static void
set_group_timer(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	gid_t		gid = gid_from_string(name);

	if (gid == -1)
		fprintf(stderr, _("%s: invalid group name: %s\n"),
				progname, name);
	else
		set_timer(gid, type, mask, fs_path->fs_name, value);
}

static void
set_project_timer(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	prid_t		prid = prid_from_string(name);

	if (prid == -1)
		fprintf(stderr, _("%s: invalid project name: %s\n"),
				progname, name);
	else
		set_timer(prid, type, mask, fs_path->fs_name, value);
}

static int
timer_f(
	int		argc,
	char		**argv)
{
	char		*name;
	uint		value;
	int		c, flags = 0, type = 0, mask = 0;

	while ((c = getopt(argc, argv, "bdgipru")) != EOF) {
		switch (c) {
		case 'd':
			flags |= DEFAULTS_FLAG;
			break;
		case 'b':
			mask |= FS_DQ_BTIMER;
			break;
		case 'i':
			mask |= FS_DQ_ITIMER;
			break;
		case 'r':
			mask |= FS_DQ_RTBTIMER;
			break;
		case 'g':
			type = XFS_GROUP_QUOTA;
			break;
		case 'p':
			type = XFS_PROJ_QUOTA;
			break;
		case 'u':
			type = XFS_USER_QUOTA;
			break;
		default:
			return command_usage(&timer_cmd);
		}
	}

	/*
	 * In the usual case, we need at least 2 more arguments -
	 * one (or more) value and a user name/id.
	 * For setting defaults (-d) we don't want a user name/id.
	 */
	if (flags & DEFAULTS_FLAG) {
		if (argc != optind + 1)
			return command_usage(&timer_cmd);
	} else if (argc != optind + 2) {
		return command_usage(&timer_cmd);
	}

	value = cvttime(argv[optind++]);
	name = (flags & DEFAULTS_FLAG) ? "0" : argv[optind++];

	if (!mask)
		mask = FS_DQ_TIMER_MASK;

	if (!type)
		type = XFS_USER_QUOTA;

	switch (type) {
	case XFS_USER_QUOTA:
		set_user_timer(name, type, mask, value);
		break;
	case XFS_GROUP_QUOTA:
		set_group_timer(name, type, mask, value);
		break;
	case XFS_PROJ_QUOTA:
		set_project_timer(name, type, mask, value);
		break;
	}
	return 0;
}

static void
set_warnings(
	__uint32_t	id,
	uint		type,
	uint		mask,
	char		*dev,
	uint		value)
{
	fs_disk_quota_t	d;

	memset(&d, 0, sizeof(d));
	d.d_version = FS_DQUOT_VERSION;
	d.d_id = id;
	d.d_flags = type;
	d.d_fieldmask = mask;
	d.d_iwarns = value;
	d.d_bwarns = value;
	d.d_rtbwarns = value;

	if (xfsquotactl(XFS_SETQLIM, dev, type, id, (void *)&d) < 0)
		fprintf(stderr, _("%s: cannot set warnings: %s\n"),
				progname, strerror(errno));
}

static void
set_user_warnings(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	uid_t		uid = uid_from_string(name);

	if (uid == -1)
		fprintf(stderr, _("%s: invalid user name: %s\n"),
				progname, name);
	else
		set_warnings(uid, type, mask, fs_path->fs_name, value);
}

static void
set_group_warnings(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	gid_t		gid = gid_from_string(name);

	if (gid == -1)
		fprintf(stderr, _("%s: invalid group name: %s\n"),
				progname, name);
	else
		set_warnings(gid, type, mask, fs_path->fs_name, value);
}

static void
set_project_warnings(
	char		*name,
	uint		type,
	uint		mask,
	uint		value)
{
	prid_t		prid = prid_from_string(name);

	if (prid == -1)
		fprintf(stderr, _("%s: invalid project name: %s\n"),
				progname, name);
	else
		set_warnings(prid, type, mask, fs_path->fs_name, value);
}

static int
warn_f(
	int		argc,
	char		**argv)
{
	char		*name;
	uint		value;
	int		c, flags = 0, type = 0, mask = 0;

	while ((c = getopt(argc, argv, "bdgipru")) != EOF) {
		switch (c) {
		case 'd':
			flags |= DEFAULTS_FLAG;
			break;
		case 'b':
			mask |= FS_DQ_BWARNS;
			break;
		case 'i':
			mask |= FS_DQ_IWARNS;
			break;
		case 'r':
			mask |= FS_DQ_RTBWARNS;
			break;
		case 'g':
			type = XFS_GROUP_QUOTA;
			break;
		case 'p':
			type = XFS_PROJ_QUOTA;
			break;
		case 'u':
			type = XFS_USER_QUOTA;
			break;
		default:
			return command_usage(&warn_cmd);
		}
	}

	/*
	 * In the usual case, we need at least 2 more arguments -
	 * one (or more) value and a user name/id.
	 * For setting defaults (-d) we don't want a user name/id.
	 */
	if (flags & DEFAULTS_FLAG) {
		if (argc != optind + 1)
			return command_usage(&warn_cmd);
	} else if (argc != optind + 2) {
		return command_usage(&warn_cmd);
	}

	value = atoi(argv[optind++]);
	name = (flags & DEFAULTS_FLAG) ? "0" : argv[optind++];

	if (!mask)
		mask = FS_DQ_WARNS_MASK;

	if (!type)
		type = XFS_USER_QUOTA;

	switch (type) {
	case XFS_USER_QUOTA:
		set_user_warnings(name, type, mask, value);
		break;
	case XFS_GROUP_QUOTA:
		set_group_warnings(name, type, mask, value);
		break;
	case XFS_PROJ_QUOTA:
		set_project_warnings(name, type, mask, value);
		break;
	}
	return 0;
}

void
edit_init(void)
{
	limit_cmd.name = _("limit");
	limit_cmd.cfunc = limit_f;
	limit_cmd.argmin = 2;
	limit_cmd.argmax = -1;
	limit_cmd.args = \
	_("[-gpu] bsoft|bhard|isoft|ihard|rtbsoft|rtbhard=N -d|id|name");
	limit_cmd.oneline = _("modify quota limits");
	limit_cmd.help = limit_help;

	restore_cmd.name = _("restore");
	restore_cmd.cfunc = restore_f;
	restore_cmd.argmin = 0;
	restore_cmd.argmax = -1;
	restore_cmd.args = _("[-gpu] [-f file]");
	restore_cmd.oneline = _("restore quota limits from a backup file");

	timer_cmd.name = _("timer");
	timer_cmd.cfunc = timer_f;
	timer_cmd.argmin = 2;
	timer_cmd.argmax = -1;
	timer_cmd.args = _("[-bir] [-gpu] value -d|id|name");
	timer_cmd.oneline = _("get/set quota enforcement timeouts");
	timer_cmd.help = timer_help;

	warn_cmd.name = _("warn");
	warn_cmd.cfunc = warn_f;
	warn_cmd.argmin = 2;
	warn_cmd.argmax = -1;
	warn_cmd.args = _("[-bir] [-gpu] value -d|id|name");
	warn_cmd.oneline = _("get/set enforcement warning counter");
	warn_cmd.help = warn_help;

	if (expert) {
		add_command(&limit_cmd);
		add_command(&restore_cmd);
		add_command(&timer_cmd);
		add_command(&warn_cmd);
	}
}