blob: fa28f6eb6d4cb3cdf646960ebee274686274835d [file] [log] [blame]
/*
*
* Interactions of quota with system - filenames, fstab and so on...
*
* Jan Kara <jack@suse.cz> - sponsored by SuSE CR
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <ctype.h>
#include <paths.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <stdint.h>
#include "pot.h"
#include "bylabel.h"
#include "common.h"
#include "quotasys.h"
#include "quotaio.h"
#include "dqblk_v1.h"
#include "dqblk_v2.h"
#include "dqblk_xfs.h"
#include "quotaio_v2.h"
#define min(x,y) (((x) < (y)) ? (x) : (y))
#define QFMT_NAMES 5
#define QOFMT_NAMES 3
static char extensions[MAXQUOTAS + 2][20] = INITQFNAMES;
static char *basenames[] = INITQFBASENAMES;
static char *fmtnames[] = { "vfsold",
"vfsv0",
"vfsv1",
"rpc",
"xfs",
};
static char *ofmtnames[] = { "default",
"csv",
"xml"
};
/*
* Check for various kinds of NFS filesystem
*/
int nfs_fstype(char *type)
{
return !strcmp(type, MNTTYPE_NFS) || !strcmp(type, MNTTYPE_NFS4) ||
!strcmp(type, MNTTYPE_MPFS);
}
/*
* Check whether filesystem has hidden quota files which is handles
* as metadata (and thus always tracks usage).
*/
int meta_qf_fstype(char *type)
{
return !strcmp(type, MNTTYPE_OCFS2);
}
/*
* Convert type of quota to written representation
*/
char *type2name(int type)
{
return extensions[type];
}
/*
* Project quota handling
*/
#define PROJECT_FILE "/etc/projid"
#define MAX_LINE_LEN 1024
static FILE *project_file;
/* Rewind /etc/projid to the beginning */
void setprent(void)
{
if (project_file)
fclose(project_file);
project_file = fopen(PROJECT_FILE, "r");
}
/* Close /etc/projid file */
void endprent(void)
{
if (project_file) {
fclose(project_file);
project_file = NULL;
}
}
/* Get next entry in /etc/projid */
struct fs_project *getprent(void)
{
static struct fs_project p;
static char linebuf[MAX_LINE_LEN];
char *idstart, *idend;
if (!project_file)
return NULL;
while (fgets(linebuf, MAX_LINE_LEN, project_file)) {
/* Line too long? */
if (linebuf[strlen(linebuf) - 1] != '\n')
continue;
/* Skip comments */
if (linebuf[0] == '#')
continue;
idstart = strchr(linebuf, ':');
/* Skip invalid lines... We follow what xfs_quota does */
if (!idstart)
continue;
*idstart = 0;
idstart++;
/*
* Colon can separate name from something else - follow what
* xfs_quota does
*/
idend = strchr(idstart, ':');
if (idend)
*idend = 0;
p.pr_name = linebuf;
p.pr_id = strtoul(idstart, NULL, 10);
return &p;
}
return NULL;
}
static struct fs_project *get_project_by_name(char *name)
{
struct fs_project *p;
setprent();
while ((p = getprent())) {
if (!strcmp(name, p->pr_name))
break;
}
endprent();
return p;
}
static struct fs_project *get_project_by_id(qid_t id)
{
struct fs_project *p;
setprent();
while ((p = getprent())) {
if (p->pr_id == id)
break;
}
endprent();
return p;
}
/*
* Convert name to uid
*/
uid_t user2uid(char *name, int flag, int *err)
{
struct passwd *entry;
uid_t ret;
char *errch;
if (err)
*err = 0;
if (!flag) {
ret = strtoul(name, &errch, 0);
if (!*errch) /* Is name number - we got directly uid? */
return ret;
}
if (!(entry = getpwnam(name))) {
if (!err) {
errstr(_("user %s does not exist.\n"), name);
exit(1);
}
else {
*err = -1;
return 0;
}
}
return entry->pw_uid;
}
/*
* Convert group name to gid
*/
gid_t group2gid(char *name, int flag, int *err)
{
struct group *entry;
gid_t ret;
char *errch;
if (err)
*err = 0;
if (!flag) {
ret = strtoul(name, &errch, 0);
if (!*errch) /* Is name number - we got directly gid? */
return ret;
}
if (!(entry = getgrnam(name))) {
if (!err) {
errstr(_("group %s does not exist.\n"), name);
exit(1);
}
else {
*err = -1;
return 0;
}
}
return entry->gr_gid;
}
/*
* Convert project name to project id
*/
qid_t project2pid(char *name, int flag, int *err)
{
qid_t ret;
char *errch;
struct fs_project *p;
if (err)
*err = 0;
if (!flag) {
ret = strtoul(name, &errch, 0);
if (!*errch) /* Is name number - we got directly pid? */
return ret;
}
p = get_project_by_name(name);
if (!p) {
if (!err) {
errstr(_("project %s does not exist.\n"), name);
exit(1);
}
else {
*err = -1;
return 0;
}
}
return p->pr_id;
}
/*
* Convert name to id
*/
int name2id(char *name, int qtype, int flag, int *err)
{
if (qtype == USRQUOTA)
return user2uid(name, flag, err);
else if (qtype == GRPQUOTA)
return group2gid(name, flag, err);
else
return project2pid(name, flag, err);
}
/*
* Convert uid to name
*/
int uid2user(uid_t id, char *buf)
{
struct passwd *entry;
if (!(entry = getpwuid(id))) {
snprintf(buf, MAXNAMELEN, "#%u", (uint) id);
return 1;
}
else
sstrncpy(buf, entry->pw_name, MAXNAMELEN);
return 0;
}
/*
* Convert gid to name
*/
int gid2group(gid_t id, char *buf)
{
struct group *entry;
if (!(entry = getgrgid(id))) {
snprintf(buf, MAXNAMELEN, "#%u", (uint) id);
return 1;
}
else
sstrncpy(buf, entry->gr_name, MAXNAMELEN);
return 0;
}
/*
* Convert project id to name
*/
int pid2project(qid_t id, char *buf)
{
struct fs_project *p;
if (!(p = get_project_by_id(id))) {
snprintf(buf, MAXNAMELEN, "#%u", (uint) id);
return 1;
}
else
sstrncpy(buf, p->pr_name, MAXNAMELEN);
return 0;
}
/*
* Convert id to user/groupname
*/
int id2name(int id, int qtype, char *buf)
{
if (qtype == USRQUOTA)
return uid2user(id, buf);
else if (qtype == GRPQUOTA)
return gid2group(id, buf);
else
return pid2project(id, buf);
}
/*
* Parse /etc/nsswitch.conf and return type of default passwd handling
*/
int passwd_handling(void)
{
FILE *f;
char buf[1024], *colpos, *spcpos;
int ret = PASSWD_FILES;
if (!(f = fopen("/etc/nsswitch.conf", "r")))
return PASSWD_FILES; /* Can't open nsswitch.conf - fallback on compatible mode */
while (fgets(buf, sizeof(buf), f)) {
if (strncmp(buf, "passwd:", 7)) /* Not passwd entry? */
continue;
for (colpos = buf+7; isspace(*colpos); colpos++);
if (!*colpos) /* Not found any type of handling? */
break;
for (spcpos = colpos; !isspace(*spcpos) && *spcpos; spcpos++);
*spcpos = 0;
if (!strcmp(colpos, "db") || !strcmp(colpos, "nis") || !strcmp(colpos, "nis+"))
ret = PASSWD_DB;
break;
}
fclose(f);
return ret;
}
/*
* Convert quota format name to number
*/
int name2fmt(char *str)
{
int fmt;
for (fmt = 0; fmt < QFMT_NAMES; fmt++)
if (!strcmp(str, fmtnames[fmt]))
return fmt;
errstr(_("Unknown quota format: %s\nSupported formats are:\n\
vfsold - original quota format\n\
vfsv0 - standard quota format\n\
vfsv1 - quota format with 64-bit limits\n\
rpc - use RPC calls\n\
xfs - XFS quota format\n"), str);
return QF_ERROR;
}
/*
* Convert quota format number to name
*/
char *fmt2name(int fmt)
{
return fmtnames[fmt];
}
/*
* Convert output format name to number
*/
int name2ofmt(char *str)
{
int fmt;
for (fmt = 0; fmt < QOFMT_NAMES; fmt++)
if (!strcmp(str, ofmtnames[fmt]))
return fmt;
errstr(_("Unknown output format: %s\nSupported formats are:\n\
default - default\n\
csv - comma-separated values\n\
xml - simple XML\n"), str);
return QOF_ERROR;
}
/*
* Convert output format number to name
*/
char *ofmt2name(int fmt)
{
return ofmtnames[fmt];
}
/*
* Convert kernel to utility quota format number
*/
static int kern2utilfmt(int kernfmt)
{
switch (kernfmt) {
case QFMT_VFS_OLD:
return QF_VFSOLD;
case QFMT_VFS_V0:
return QF_VFSV0;
case QFMT_VFS_V1:
return QF_VFSV1;
case QFMT_OCFS2:
return QF_META;
}
return -1;
}
/*
* Convert utility to kernel quota format number
*/
int util2kernfmt(int fmt)
{
switch (fmt) {
case QF_VFSOLD:
return QFMT_VFS_OLD;
case QF_VFSV0:
return QFMT_VFS_V0;
case QF_VFSV1:
return QFMT_VFS_V1;
}
return -1;
}
/*
* Convert time difference of seconds and current time
*/
void difftime2str(time_t seconds, char *buf)
{
time_t now;
buf[0] = 0;
if (!seconds)
return;
time(&now);
if (seconds <= now) {
strcpy(buf, _("none"));
return;
}
time2str(seconds - now, buf, TF_ROUND);
}
/*
* Round difference of two time_t values into int32_t
*/
int32_t difftime2net(time_t later, time_t sooner)
{
if ((later - sooner) > INT32_MAX)
return INT32_MAX;
if ((later - sooner) < INT32_MIN)
return INT32_MIN;
return (later - sooner);
}
/*
* Convert time to printable form
*/
void time2str(time_t seconds, char *buf, int flags)
{
uint minutes, hours, days;
if (flags & TF_ROUND) {
minutes = (seconds + 30) / 60; /* Rounding */
hours = minutes / 60;
minutes %= 60;
days = hours / 24;
hours %= 24;
if (days >= 2)
snprintf(buf, MAXTIMELEN, _("%ddays"), days);
else
snprintf(buf, MAXTIMELEN, _("%02d:%02d"), hours + days * 24, minutes);
}
else {
minutes = seconds / 60;
seconds %= 60;
hours = minutes / 60;
minutes %= 60;
days = hours / 24;
hours %= 24;
if (seconds || (!minutes && !hours && !days))
snprintf(buf, MAXTIMELEN, _("%useconds"), (uint)(seconds+minutes*60+hours*3600+days*3600*24));
else if (minutes)
snprintf(buf, MAXTIMELEN, _("%uminutes"), (uint)(minutes+hours*60+days*60*24));
else if (hours)
snprintf(buf, MAXTIMELEN, _("%uhours"), (uint)(hours+days*24));
else
snprintf(buf, MAXTIMELEN, _("%udays"), days);
}
}
/*
* Convert number with unit to time in seconds
*/
int str2timeunits(time_t num, char *unit, time_t *res)
{
if (!strcmp(unit, _("second")) || !strcmp(unit, _("seconds")))
*res = num;
else if (!strcmp(unit, _("minute")) || !strcmp(unit, _("minutes")))
*res = num * 60;
else if (!strcmp(unit, _("hour")) || !strcmp(unit, _("hours")))
*res = num * 60 * 60;
else if (!strcmp(unit, _("day")) || !strcmp(unit, _("days")))
*res = num * 24 * 60 * 60;
else
return -1;
return 0;
}
#define DIV_ROUND_UP(x, d) (((x) + d - 1) / d)
/*
* Convert number in quota blocks to some nice short form for printing
*/
void space2str(qsize_t space, char *buf, int format)
{
int i;
char suffix[8] = " MGT";
qsize_t aspace = space >= 0 ? space : -space;
space = qb2kb(space);
if (format) {
for (i = 3; i > 0; i--) {
long long unit = 1LL << (QUOTABLOCK_BITS*i);
if (aspace >= unit * 100) {
int sign = aspace != space ? -1 : 1;
sprintf(buf, "%lld%c", (long long)
DIV_ROUND_UP(aspace, unit) * sign,
suffix[i]);
return;
}
}
sprintf(buf, "%lldK", (long long)space);
return;
}
sprintf(buf, "%lld", (long long)space);
}
/*
* Convert block number with unit from string to quota blocks.
* Return NULL on success, static error message otherwise.
*/
const char *str2space(const char *string, qsize_t *space)
{
char *unit;
long long int number;
int unit_shift;
number = strtoll(string, &unit, 0);
if (number == LLONG_MAX || number == LLONG_MIN)
return _("Integer overflow while parsing space number.");
if (!unit || unit[0] == '\0' || !strcmp(unit, _("K")))
unit_shift = 0;
else if (!strcmp(unit, _("M")))
unit_shift = 10;
else if (!strcmp(unit, _("G")))
unit_shift = 20;
else if (!strcmp(unit, _("T")))
unit_shift = 30;
else
return _("Unknown space binary unit. "
"Valid units are K, M, G, T.");
if (number > (QSIZE_MAX >> unit_shift) ||
number < -(QSIZE_MAX >> unit_shift))
return _("Integer overflow while interpreting space unit.");
*space = number << unit_shift;
return NULL;
}
/*
* Convert number to some nice short form for printing
*/
void number2str(long long num, char *buf, int format)
{
int i;
unsigned long long div;
char suffix[8] = " kmgt";
if (format) {
long long anum = num >= 0 ? num : -num;
int sign = num != anum ? -1 : 1;
for (i = 4, div = 1000000000000LL; i > 0; i--, div /= 1000)
if (anum >= 100*div) {
sprintf(buf, "%lld%c",
DIV_ROUND_UP(anum, div) * sign,
suffix[i]);
return;
}
}
sprintf(buf, "%lld", num);
}
/*
* Convert inode number with unit from string to quota inodes.
* Return NULL on success, static error message otherwise.
*/
const char *str2number(const char *string, qsize_t *inodes)
{
char *unit;
long long int number, multiple;
number = strtoll(string, &unit, 0);
if (number == LLONG_MAX || number == LLONG_MIN)
return _("Integer overflow while parsing number.");
if (!unit || unit[0] == '\0')
multiple = 1;
else if (!strcmp(unit, _("k")))
multiple = 1000;
else if (!strcmp(unit, _("m")))
multiple = 1000000;
else if (!strcmp(unit, _("g")))
multiple = 1000000000;
else if (!strcmp(unit, _("t")))
multiple = 1000000000000ULL;
else
return _("Unknown decimal unit. "
"Valid units are k, m, g, t.");
if (number > QSIZE_MAX / multiple ||
number < -(QSIZE_MAX / multiple))
return _("Integer overflow while interpreting decimal unit.");
*inodes = number * multiple;
return NULL;
}
/*
* Wrappers for mount options processing functions
*/
/*
* Check for XFS filesystem with quota accounting enabled
*/
static int hasxfsquota(const char *dev, struct mntent *mnt, int type, int flags)
{
struct xfs_mem_dqinfo info;
if (flags & MS_XFS_DISABLED)
return QF_XFS;
memset(&info, 0, sizeof(struct xfs_mem_dqinfo));
if (!quotactl(QCMD(Q_XFS_GETQSTAT, type), dev, 0, (void *)&info)) {
#ifdef XFS_ROOTHACK
int sbflags = (info.qs_flags & 0xff00) >> 8;
#endif /* XFS_ROOTHACK */
if (type == USRQUOTA && (info.qs_flags & XFS_QUOTA_UDQ_ACCT))
return QF_XFS;
else if (type == GRPQUOTA && (info.qs_flags & XFS_QUOTA_GDQ_ACCT))
return QF_XFS;
else if (type == PRJQUOTA && (info.qs_flags & XFS_QUOTA_PDQ_ACCT))
return QF_XFS;
#ifdef XFS_ROOTHACK
/*
* Old XFS filesystems (up to XFS 1.2 / Linux 2.5.47) had a
* hack to allow enabling quota on the root filesystem without
* having to specify it at mount time.
*/
else if (strcmp(mnt->mnt_dir, "/"))
return QF_ERROR;
else if (type == USRQUOTA && (sbflags & XFS_QUOTA_UDQ_ACCT))
return QF_XFS;
else if (type == GRPQUOTA && (sbflags & XFS_QUOTA_GDQ_ACCT))
return QF_XFS;
else if (type == PRJQUOTA && (sbflags & XFS_QUOTA_PDQ_ACCT))
return QF_XFS;
#endif /* XFS_ROOTHACK */
}
return QF_ERROR;
}
static int hasvfsmetaquota(const char *dev, struct mntent *mnt, int type, int flags)
{
uint32_t fmt;
if (!quotactl(QCMD(Q_GETFMT, type), dev, 0, (void *)&fmt))
return QF_META;
return QF_ERROR;
}
/* Return pointer to given mount option in mount option string */
char *str_hasmntopt(const char *optstring, const char *opt)
{
const char *p = optstring;
const char *s;
int len = strlen(opt);
do {
s = p;
while (*p && *p != ',' && *p != '=')
p++;
/* Found option? */
if (p - s == len && !strncmp(s, opt, len))
return (char *)s;
/* Skip mount option argument if there's any */
if (*p == '=') {
p++;
while (*p && *p != ',')
p++;
}
/* Skip separating ',' */
if (*p)
p++;
} while (*p);
return NULL;
}
/* Return if given option has nonempty argument */
static char *hasmntoptarg(const char *optstring, const char *opt)
{
char *p = str_hasmntopt(optstring, opt);
if (!p)
return NULL;
p += strlen(opt);
if (*p == '=' && p[1] != ',')
return p+1;
return NULL;
}
/* Copy out mount option argument to a buffer */
static void copy_mntoptarg(char *buf, const char *optarg, int buflen)
{
char *sep = strchr(optarg, ',');
if (!sep)
sstrncpy(buf, optarg, min(buflen, strlen(optarg) + 1));
else
sstrncpy(buf, optarg, min(buflen, sep - optarg + 1));
}
/*
* Check to see if a particular quota is to be enabled (filesystem mounted with proper option)
*/
static int hasquota(const char *dev, struct mntent *mnt, int type, int flags)
{
if (!strcmp(mnt->mnt_type, MNTTYPE_GFS2) ||
!strcmp(mnt->mnt_type, MNTTYPE_XFS))
return hasxfsquota(dev, mnt, type, flags);
if (!strcmp(mnt->mnt_type, MNTTYPE_OCFS2))
return hasvfsmetaquota(dev, mnt, type, flags);
/*
* For ext4 we check whether it has quota in system files and if not,
* we fall back on checking standard quotas. Furthermore we cannot use
* standard GETFMT quotactl because that does not distinguish between
* quota in system file and quota in ordinary file.
*/
if (!strcmp(mnt->mnt_type, MNTTYPE_EXT4)) {
struct if_dqinfo kinfo;
if (quotactl(QCMD(Q_GETINFO, type), dev, 0, (void *)&kinfo) == 0) {
if (kinfo.dqi_flags & DQF_SYS_FILE)
return QF_META;
}
}
/* NFS always has quota or better there is no good way how to detect it */
if (nfs_fstype(mnt->mnt_type))
return QF_RPC;
if ((type == USRQUOTA) && (hasmntopt(mnt, MNTOPT_USRQUOTA) || hasmntoptarg(mnt->mnt_opts, MNTOPT_USRJQUOTA)))
return QF_VFSUNKNOWN;
if ((type == GRPQUOTA) && (hasmntopt(mnt, MNTOPT_GRPQUOTA) || hasmntoptarg(mnt->mnt_opts, MNTOPT_GRPJQUOTA)))
return QF_VFSUNKNOWN;
if ((type == USRQUOTA) && hasmntopt(mnt, MNTOPT_QUOTA))
return QF_VFSUNKNOWN;
return -1;
}
/* Check whether quotafile for given format exists - return its name in namebuf */
static int check_fmtfile_ok(char *name, int type, int fmt, int flags)
{
if (!flags)
return 1;
if (flags & NF_EXIST) {
struct stat st;
if (stat(name, &st) < 0) {
if (errno != ENOENT)
errstr(_("Cannot stat quota file %s: %s\n"), name, strerror(errno));
return 0;
}
}
if (flags & NF_FORMAT) {
int fd, ret = 0;
if ((fd = open(name, O_RDONLY)) >= 0) {
if (is_tree_qfmt(fmt))
ret = quotafile_ops_2.check_file(fd, type, fmt);
else
ret = quotafile_ops_1.check_file(fd, type, fmt);
close(fd);
if (ret <= 0)
return 0;
}
else if (errno != ENOENT && errno != EPERM) {
errstr(_("Cannot open quotafile %s: %s\n"), name, strerror(errno));
return 0;
}
}
return 1;
}
/*
* Get quotafile name for given entry. Return 0 in case format check succeeded,
* otherwise return -1.
* Note that formats without quotafile *must* be detected prior to calling this function
*/
int get_qf_name(struct mount_entry *mnt, int type, int fmt, int flags, char **filename)
{
char *option, *pathname, has_quota_file_definition = 0;
char qfullname[PATH_MAX];
qfullname[0] = 0;
if (type == USRQUOTA && (option = str_hasmntopt(mnt->me_opts, MNTOPT_USRQUOTA))) {
if (*(pathname = option + strlen(MNTOPT_USRQUOTA)) == '=')
has_quota_file_definition = 1;
}
else if (type == USRQUOTA && (option = hasmntoptarg(mnt->me_opts, MNTOPT_USRJQUOTA))) {
pathname = option;
has_quota_file_definition = 1;
sstrncpy(qfullname, mnt->me_dir, sizeof(qfullname));
sstrncat(qfullname, "/", sizeof(qfullname));
}
else if (type == GRPQUOTA && (option = str_hasmntopt(mnt->me_opts, MNTOPT_GRPQUOTA))) {
pathname = option + strlen(MNTOPT_GRPQUOTA);
if (*pathname == '=') {
has_quota_file_definition = 1;
pathname++;
}
}
else if (type == GRPQUOTA && (option = hasmntoptarg(mnt->me_opts, MNTOPT_GRPJQUOTA))) {
pathname = option;
has_quota_file_definition = 1;
sstrncpy(qfullname, mnt->me_dir, sizeof(qfullname));
sstrncat(qfullname, "/", sizeof(qfullname));
}
else if (type == USRQUOTA && (option = str_hasmntopt(mnt->me_opts, MNTOPT_QUOTA))) {
pathname = option + strlen(MNTOPT_QUOTA);
if (*pathname == '=') {
has_quota_file_definition = 1;
pathname++;
}
}
else
return -1;
if (has_quota_file_definition) {
int len = strlen(qfullname);
copy_mntoptarg(qfullname + len, pathname, sizeof(qfullname) - len);
} else {
snprintf(qfullname, PATH_MAX, "%s/%s.%s", mnt->me_dir,
basenames[fmt], extensions[type]);
}
if (check_fmtfile_ok(qfullname, type, fmt, flags)) {
*filename = sstrdup(qfullname);
return 0;
}
return -1;
}
#define START_MNT_POINTS 256 /* The number of mount points we start with... */
/*
* Create NULL terminated list of quotafile handles from given list of mountpoints
* List of zero length means scan all entries in /etc/mtab
*/
struct quota_handle **create_handle_list(int count, char **mntpoints, int type, int fmt,
int ioflags, int mntflags)
{
struct mount_entry *mnt;
int gotmnt = 0;
static int hlist_allocated = 0;
static struct quota_handle **hlist = NULL;
if (!hlist_allocated) {
hlist = smalloc(START_MNT_POINTS * sizeof(struct quota_handle *));
hlist_allocated = START_MNT_POINTS;
}
/* If directories are specified, cache all NFS mountpoints */
if (count && !(mntflags & MS_LOCALONLY))
mntflags |= MS_NFS_ALL;
if (init_mounts_scan(count, mntpoints, mntflags) < 0)
die(2, _("Cannot initialize mountpoint scan.\n"));
while ((mnt = get_next_mount())) {
#ifndef RPC
if (nfs_fstype(mnt->me_type))
continue;
#endif
if (fmt == -1 || count) {
add_entry:
if (gotmnt+1 >= hlist_allocated) {
hlist_allocated += START_MNT_POINTS;
hlist = srealloc(hlist, hlist_allocated * sizeof(struct quota_handle *));
}
if (!(hlist[gotmnt] = init_io(mnt, type, fmt, ioflags)))
continue;
gotmnt++;
}
else {
switch (fmt) {
case QF_RPC:
if (nfs_fstype(mnt->me_type))
goto add_entry;
break;
case QF_XFS:
if (!strcmp(mnt->me_type, MNTTYPE_XFS) ||
!strcmp(mnt->me_type, MNTTYPE_GFS2))
goto add_entry;
break;
default:
if (strcmp(mnt->me_type, MNTTYPE_XFS) &&
strcmp(mnt->me_type, MNTTYPE_GFS2) &&
!nfs_fstype(mnt->me_type))
goto add_entry;
break;
}
}
}
end_mounts_scan();
hlist[gotmnt] = NULL;
if (count && gotmnt != count)
die(1, _("Not all specified mountpoints are using quota.\n"));
return hlist;
}
/*
* Free given list of handles
*/
int dispose_handle_list(struct quota_handle **hlist)
{
int i;
int ret = 0;
for (i = 0; hlist[i]; i++)
if (end_io(hlist[i]) < 0) {
errstr(_("Error while releasing file on %s\n"),
hlist[i]->qh_quotadev);
ret = -1;
}
return ret;
}
/*
* Check whether given device name matches this quota handle
*/
int devcmp_handle(const char *dev, struct quota_handle *h)
{
struct stat sbuf;
if (stat(dev, &sbuf) < 0)
return (strcmp(dev, h->qh_quotadev) == 0);
if (!S_ISBLK(sbuf.st_mode))
return (strcmp(dev, h->qh_quotadev) == 0);
if (sbuf.st_rdev != h->qh_stat.st_rdev)
return 0;
return 1;
}
/*
* Check whether two quota handles are for the same device
*/
int devcmp_handles(struct quota_handle *a, struct quota_handle *b)
{
if (!S_ISBLK(a->qh_stat.st_mode) || !S_ISBLK(b->qh_stat.st_mode))
return (strcmp(a->qh_quotadev, b->qh_quotadev) == 0);
if (a->qh_stat.st_rdev != b->qh_stat.st_rdev)
return 0;
return 1;
}
/*
* Check kernel quota version
*/
int kernel_iface; /* Kernel interface type */
static int kernel_qfmt_num; /* Number of different supported formats */
static int kernel_qfmt[QUOTAFORMATS]; /* Formats supported by kernel */
#ifndef FS_DQSTATS
#define FS_DQSTATS 16
#endif
#ifndef FS_DQ_SYNCS
#define FS_DQ_SYNCS 8
#endif
void init_kernel_interface(void)
{
struct stat st;
struct sigaction sig, oldsig;
/* This signal handling is needed because old kernels send us SIGSEGV as they try to resolve the device */
sig.sa_handler = SIG_IGN;
sig.sa_sigaction = NULL;
if (sigemptyset(&sig.sa_mask) < 0)
die(2, _("Cannot create set for sigaction(): %s\n"), strerror(errno));
sig.sa_flags = 0;
if (sigaction(SIGSEGV, &sig, &oldsig) < 0)
die(2, _("Cannot set signal handler: %s\n"), strerror(errno));
kernel_qfmt_num = 0;
/* Detect new kernel interface; Assume generic interface unless we can prove there is not one... */
if (!stat("/proc/sys/fs/quota", &st) || errno != ENOENT) {
kernel_iface = IFACE_GENERIC;
kernel_qfmt[kernel_qfmt_num++] = QF_META;
kernel_qfmt[kernel_qfmt_num++] = QF_XFS;
kernel_qfmt[kernel_qfmt_num++] = QF_VFSOLD;
kernel_qfmt[kernel_qfmt_num++] = QF_VFSV0;
kernel_qfmt[kernel_qfmt_num++] = QF_VFSV1;
}
else {
struct v2_dqstats v2_stats;
if (!stat("/proc/fs/xfs/stat", &st))
kernel_qfmt[kernel_qfmt_num++] = QF_XFS;
else {
fs_quota_stat_t dummy;
if (!quotactl(QCMD(Q_XGETQSTAT, 0), "/dev/root", 0, (void *)&dummy) ||
(errno != EINVAL && errno != ENOSYS))
kernel_qfmt[kernel_qfmt_num++] = QF_XFS;
}
if (quotactl(QCMD(Q_V2_GETSTATS, 0), NULL, 0, (void *)&v2_stats) >= 0) {
kernel_qfmt[kernel_qfmt_num++] = QF_VFSV0;
kernel_iface = IFACE_VFSV0;
}
else if (errno != ENOSYS && errno != ENOTSUP) {
/* RedHat 7.1 (2.4.2-2) newquota check
* Q_V2_GETSTATS in it's old place, Q_GETQUOTA in the new place
* (they haven't moved Q_GETSTATS to its new value) */
int err_stat = 0;
int err_quota = 0;
char tmp[1024]; /* Just temporary buffer */
if (quotactl(QCMD(Q_V1_GETSTATS, 0), NULL, 0, tmp))
err_stat = errno;
if (quotactl(QCMD(Q_V1_GETQUOTA, 0), "/dev/null", 0, tmp))
err_quota = errno;
/* On a RedHat 2.4.2-2 we expect 0, EINVAL
* On a 2.4.x we expect 0, ENOENT
* On a 2.4.x-ac we wont get here */
if (err_stat == 0 && err_quota == EINVAL) {
kernel_qfmt[kernel_qfmt_num++] = QF_VFSV0;
kernel_iface = IFACE_VFSV0;
}
else {
kernel_qfmt[kernel_qfmt_num++] = QF_VFSOLD;
kernel_iface = IFACE_VFSOLD;
}
}
}
if (sigaction(SIGSEGV, &oldsig, NULL) < 0)
die(2, _("Cannot reset signal handler: %s\n"), strerror(errno));
}
/* Return whether kernel is able to handle given format */
int kern_qfmt_supp(int fmt)
{
int i;
if (fmt == -1)
return kernel_qfmt_num > 0;
for (i = 0; i < kernel_qfmt_num; i++)
if (fmt == kernel_qfmt[i])
return 1;
return 0;
}
/* Check whether old quota is turned on on given device */
static int v1_kern_quota_on(const char *dev, int type)
{
char tmp[1024]; /* Just temporary buffer */
qid_t id = (type == USRQUOTA) ? getuid() : getgid();
if (!quotactl(QCMD(Q_V1_GETQUOTA, type), dev, id, tmp)) /* OK? */
return 1;
return 0;
}
/* Check whether new quota is turned on on given device */
static int v2_kern_quota_on(const char *dev, int type)
{
char tmp[1024]; /* Just temporary buffer */
qid_t id = (type == USRQUOTA) ? getuid() : getgid();
if (!quotactl(QCMD(Q_V2_GETQUOTA, type), dev, id, tmp)) /* OK? */
return 1;
return 0;
}
/*
* Check whether quota is turned on on given device. This quotactl always
* worked for XFS and it works even for VFS quotas for kernel 4.1 and newer.
*
* We return 0 when quota is not turned on, 1 when only accounting is turned
* on, and 2 when both accounting and enforcement is turned on. We return -1
* on error.
*/
int kern_quota_state_xfs(const char *dev, int type)
{
struct xfs_mem_dqinfo info;
if (!quotactl(QCMD(Q_XFS_GETQSTAT, type), dev, 0, (void *)&info)) {
if (type == USRQUOTA) {
return !!(info.qs_flags & XFS_QUOTA_UDQ_ACCT) +
!!(info.qs_flags & XFS_QUOTA_UDQ_ENFD);
}
if (type == GRPQUOTA) {
return !!(info.qs_flags & XFS_QUOTA_GDQ_ACCT) +
!!(info.qs_flags & XFS_QUOTA_GDQ_ENFD);
}
if (type == PRJQUOTA) {
return !!(info.qs_flags & XFS_QUOTA_PDQ_ACCT) +
!!(info.qs_flags & XFS_QUOTA_PDQ_ENFD);
}
return 0;
}
return -1;
}
/*
* Check whether is quota turned on on given device for given type
*/
int kern_quota_on(struct mount_entry *mnt, int type, int fmt)
{
if (mnt->me_qfmt[type] < 0)
return -1;
if (fmt == QF_RPC)
return -1;
if (mnt->me_qfmt[type] == QF_XFS) {
if ((fmt == -1 || fmt == QF_XFS) &&
kern_quota_state_xfs(mnt->me_devname, type) > 0)
return QF_XFS;
return -1;
}
/* No more chances for XFS format to succeed... */
if (fmt == QF_XFS)
return -1;
/* Meta format is always enabled */
if (mnt->me_qfmt[type] == QF_META)
return QF_META;
/* Check whether quota is turned on... */
if (kernel_iface == IFACE_GENERIC) {
int actfmt;
if (quotactl(QCMD(Q_GETFMT, type), mnt->me_devname, 0,
(void *)&actfmt) >= 0) {
actfmt = kern2utilfmt(actfmt);
if (actfmt >= 0)
return actfmt;
}
} else {
if ((fmt == -1 || fmt == QF_VFSV0) &&
v2_kern_quota_on(mnt->me_devname, type))
return QF_VFSV0;
if ((fmt == -1 || fmt == QF_VFSOLD) &&
v1_kern_quota_on(mnt->me_devname, type))
return QF_VFSOLD;
}
return -1;
}
/*
*
* mtab/fstab handling routines
*
*/
struct searched_dir {
int sd_dir; /* Is searched dir mountpoint or in fact device? */
dev_t sd_dev; /* Device mountpoint lies on */
ino_t sd_ino; /* Inode number of mountpoint */
const char *sd_name; /* Name of given dir/device */
};
#define ALLOC_ENTRIES_NUM 16 /* Allocate entries by this number */
static int mnt_entries_cnt; /* Number of cached mountpoint entries */
static struct mount_entry *mnt_entries; /* Cached mounted filesystems */
static int check_dirs_cnt, act_checked; /* Number of dirs to check; Actual checked dir/(mountpoint in case of -a) */
static struct searched_dir *check_dirs; /* Directories to check */
/* Cache mtab/fstab */
static int cache_mnt_table(int flags)
{
FILE *mntf;
struct mntent *mnt;
struct stat st;
struct statfs fsstat;
int allocated = 0, i = 0;
dev_t dev = 0;
char mntpointbuf[PATH_MAX];
int autofsdircnt, autofsdir_allocated;
char **autofsdir;
#ifdef ALT_MTAB
mntf = setmntent(ALT_MTAB, "r");
if (mntf)
goto alloc;
#endif
mntf = setmntent(_PATH_MOUNTED, "r");
if (mntf)
goto alloc;
/* Fallback to fstab when mtab not available */
if (!(mntf = setmntent(_PATH_MNTTAB, "r"))) {
errstr(_("Cannot open any file with mount points.\n"));
return -1;
}
alloc:
/* Prepare table of mount entries */
mnt_entries = smalloc(sizeof(struct mount_entry) * ALLOC_ENTRIES_NUM);
mnt_entries_cnt = 0;
allocated += ALLOC_ENTRIES_NUM;
/* Prepare table of autofs mountpoints */
autofsdir = smalloc(sizeof(char *) * ALLOC_ENTRIES_NUM);
autofsdircnt = 0;
autofsdir_allocated = ALLOC_ENTRIES_NUM;
while ((mnt = getmntent(mntf))) {
const char *devname;
char *opt;
int qfmt[MAXQUOTAS];
if (!(devname = get_device_name(mnt->mnt_fsname))) {
errstr(_("Cannot get device name for %s\n"), mnt->mnt_fsname);
continue;
}
/* Check for mountpoints under autofs and skip them*/
for (i = 0; i < autofsdircnt; i++) {
int slen = strlen(autofsdir[i]);
if (slen <= strlen(mnt->mnt_dir) && !strncmp(autofsdir[i], mnt->mnt_dir, slen))
break;
}
if (i < autofsdircnt) {
free((char *)devname);
continue;
}
if (flags & MS_NO_AUTOFS && !strcmp(mnt->mnt_type, MNTTYPE_AUTOFS)) { /* Autofs dir to remember? */
if (autofsdircnt == autofsdir_allocated) {
autofsdir_allocated += ALLOC_ENTRIES_NUM;
autofsdir = srealloc(autofsdir, autofsdir_allocated * sizeof(char *));
}
autofsdir[autofsdircnt] = smalloc(strlen(mnt->mnt_dir) + 2);
strcpy(autofsdir[autofsdircnt], mnt->mnt_dir);
strcat(autofsdir[autofsdircnt], "/");
autofsdircnt++;
free((char *)devname);
continue;
}
if (flags & MS_LOCALONLY && nfs_fstype(mnt->mnt_type)) {
free((char *)devname);
continue;
}
if (hasmntopt(mnt, MNTOPT_NOQUOTA)) {
free((char *)devname);
continue;
}
if (hasmntopt(mnt, MNTOPT_BIND)) {
free((char *)devname);
continue; /* We just ignore bind mounts... */
}
if ((opt = hasmntoptarg(mnt->mnt_opts, MNTOPT_LOOP))) {
char loopdev[PATH_MAX];
copy_mntoptarg(opt, loopdev, PATH_MAX);
free((char *)devname);
devname = sstrdup(loopdev);
}
/* Further we are not interested in mountpoints without quotas and
we don't want to touch them */
qfmt[USRQUOTA] = hasquota(devname, mnt, USRQUOTA, flags);
qfmt[GRPQUOTA] = hasquota(devname, mnt, GRPQUOTA, flags);
qfmt[PRJQUOTA] = hasquota(devname, mnt, PRJQUOTA, flags);
if (qfmt[USRQUOTA] < 0 && qfmt[GRPQUOTA] < 0 &&
qfmt[PRJQUOTA] < 0) {
free((char *)devname);
continue;
}
if (!realpath(mnt->mnt_dir, mntpointbuf)) {
errstr(_("Cannot resolve mountpoint path %s: %s\n"), mnt->mnt_dir, strerror(errno));
free((char *)devname);
continue;
}
if (statfs(mntpointbuf, &fsstat) != 0) {
errstr(_("Cannot statfs() %s: %s\n"), mntpointbuf, strerror(errno));
free((char *)devname);
continue;
}
/* Do not scan quotas on "magic" automount points */
if (fsstat.f_blocks == 0 && fsstat.f_bfree == 0 && fsstat.f_bavail == 0) {
free((char *)devname);
continue;
}
if (!nfs_fstype(mnt->mnt_type)) {
if (stat(devname, &st) < 0) { /* Can't stat mounted device? */
errstr(_("Cannot stat() mounted device %s: %s\n"), devname, strerror(errno));
free((char *)devname);
continue;
}
if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) {
errstr(_("Device (%s) filesystem is mounted on unsupported device type. Skipping.\n"), devname);
free((char *)devname);
continue;
}
dev = st.st_rdev;
for (i = 0; i < mnt_entries_cnt && mnt_entries[i].me_dev != dev; i++);
}
/* Cope with network filesystems or new mountpoint */
if (nfs_fstype(mnt->mnt_type) || i == mnt_entries_cnt) {
if (stat(mnt->mnt_dir, &st) < 0) { /* Can't stat mountpoint? We have better ignore it... */
errstr(_("Cannot stat() mountpoint %s: %s\n"), mnt->mnt_dir, strerror(errno));
free((char *)devname);
continue;
}
if (nfs_fstype(mnt->mnt_type)) {
/* For network filesystems we must get device from root */
dev = st.st_dev;
if (!(flags & MS_NFS_ALL)) {
for (i = 0; i < mnt_entries_cnt && mnt_entries[i].me_dev != dev; i++);
}
else /* Always behave as if the device was unique */
i = mnt_entries_cnt;
}
}
if (i == mnt_entries_cnt) { /* New mounted device? */
if (allocated == mnt_entries_cnt) {
allocated += ALLOC_ENTRIES_NUM;
mnt_entries = srealloc(mnt_entries, allocated * sizeof(struct mount_entry));
}
mnt_entries[i].me_type = sstrdup(mnt->mnt_type);
mnt_entries[i].me_opts = sstrdup(mnt->mnt_opts);
mnt_entries[i].me_dev = dev;
mnt_entries[i].me_ino = st.st_ino;
mnt_entries[i].me_devname = devname;
mnt_entries[i].me__dir = sstrdup(mntpointbuf);
mnt_entries[i].me_dir = NULL;
memcpy(&mnt_entries[i].me_qfmt, qfmt, sizeof(qfmt));
mnt_entries_cnt++;
}
else
free((char *)devname); /* We don't need it any more */
}
endmntent(mntf);
for (i = 0; i < autofsdircnt; i++)
free(autofsdir[i]);
free(autofsdir);
return 0;
}
/* Find mountpoint of filesystem hosting dir in 'st'; Store it in 'st' */
static const char *find_dir_mntpoint(struct stat *st)
{
int i;
for (i = 0; i < mnt_entries_cnt; i++)
if (mnt_entries[i].me_dev == st->st_dev) {
st->st_ino = mnt_entries[i].me_ino;
return mnt_entries[i].me__dir;
}
return NULL;
}
/* Process and store given paths */
static int process_dirs(int dcnt, char **dirs, int flags)
{
struct stat st;
int i;
char mntpointbuf[PATH_MAX];
check_dirs_cnt = 0;
act_checked = -1;
if (dcnt) {
check_dirs = smalloc(sizeof(struct searched_dir) * dcnt);
for (i = 0; i < dcnt; i++) {
if (!strncmp(dirs[i], "UUID=", 5) || !strncmp(dirs[i], "LABEL=", 6)) {
char *devname = (char *)get_device_name(dirs[i]);
if (!devname) {
errstr(_("Cannot find a device with %s.\nSkipping...\n"), dirs[i]);
continue;
}
if (stat(devname, &st) < 0) {
errstr(_("Cannot stat() a mountpoint with %s: %s\nSkipping...\n"), dirs[i], strerror(errno));
free(devname);
continue;
}
free(devname);
}
else
if (stat(dirs[i], &st) < 0) {
errstr(_("Cannot stat() given mountpoint %s: %s\nSkipping...\n"), dirs[i], strerror(errno));
continue;
}
check_dirs[check_dirs_cnt].sd_dir = S_ISDIR(st.st_mode);
if (S_ISDIR(st.st_mode)) {
const char *realmnt = dirs[i];
/* Return st of mountpoint of dir in st.. */
if (flags & MS_NO_MNTPOINT && !(realmnt = find_dir_mntpoint(&st))) {
if (!(flags & MS_QUIET))
errstr(_("Cannot find a filesystem mountpoint for directory %s\n"), dirs[i]);
continue;
}
check_dirs[check_dirs_cnt].sd_dev = st.st_dev;
check_dirs[check_dirs_cnt].sd_ino = st.st_ino;
if (!realpath(realmnt, mntpointbuf)) {
errstr(_("Cannot resolve path %s: %s\n"), realmnt, strerror(errno));
continue;
}
}
else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) {
int mentry;
check_dirs[check_dirs_cnt].sd_dev = st.st_rdev;
for (mentry = 0; mentry < mnt_entries_cnt && mnt_entries[mentry].me_dev != st.st_rdev; mentry++);
if (mentry == mnt_entries_cnt) {
if (!(flags & MS_QUIET))
errstr(_("Cannot find mountpoint for device %s\n"), dirs[i]);
continue;
}
sstrncpy(mntpointbuf, mnt_entries[mentry].me__dir, PATH_MAX-1);
}
else {
errstr(_("Specified path %s is not directory nor device.\n"), dirs[i]);
continue;
}
check_dirs[check_dirs_cnt].sd_name = sstrdup(mntpointbuf);
check_dirs_cnt++;
}
if (!check_dirs_cnt) {
if (!(flags & MS_QUIET))
errstr(_("No correct mountpoint specified.\n"));
free(check_dirs);
return -1;
}
}
return 0;
}
/*
* Initialize mountpoint scan
*/
int init_mounts_scan(int dcnt, char **dirs, int flags)
{
if (cache_mnt_table(flags) < 0)
return -1;
if (process_dirs(dcnt, dirs, flags) < 0) {
end_mounts_scan();
return -1;
}
return 0;
}
/* Find next usable mountpoint when scanning all mountpoints */
static int find_next_entry_all(int *pos)
{
while (++act_checked < mnt_entries_cnt) {
if (!str_hasmntopt(mnt_entries[act_checked].me_opts, MNTOPT_NOAUTO))
break;
}
if (act_checked >= mnt_entries_cnt)
return 0;
*pos = act_checked;
return 1;
}
/* Find next usable mountpoint when scanning selected mountpoints */
static int find_next_entry_sel(int *pos)
{
int i;
struct searched_dir *sd;
restart:
if (++act_checked == check_dirs_cnt)
return 0;
sd = check_dirs + act_checked;
for (i = 0; i < mnt_entries_cnt; i++) {
if (sd->sd_dir) {
if (sd->sd_dev == mnt_entries[i].me_dev && sd->sd_ino == mnt_entries[i].me_ino)
break;
}
else
if (sd->sd_dev == mnt_entries[i].me_dev)
break;
}
if (i == mnt_entries_cnt) {
errstr(_("Mountpoint (or device) %s not found or has no quota enabled.\n"), sd->sd_name);
goto restart;
}
*pos = i;
return 1;
}
/*
* Return next directory from the list
*/
struct mount_entry *get_next_mount(void)
{
int mntpos;
if (!check_dirs_cnt) { /* Scan all mountpoints? */
if (!find_next_entry_all(&mntpos))
return NULL;
mnt_entries[mntpos].me_dir = mnt_entries[mntpos].me__dir;
}
else {
if (!find_next_entry_sel(&mntpos))
return NULL;
mnt_entries[mntpos].me_dir = check_dirs[act_checked].sd_name;
}
return &mnt_entries[mntpos];
}
/*
* Free all structures allocated for mountpoint scan
*/
void end_mounts_scan(void)
{
int i;
for (i = 0; i < mnt_entries_cnt; i++) {
free(mnt_entries[i].me_type);
free(mnt_entries[i].me_opts);
free((char *)mnt_entries[i].me_devname);
free((char *)mnt_entries[i].me__dir);
}
free(mnt_entries);
mnt_entries = NULL;
mnt_entries_cnt = 0;
if (check_dirs_cnt) {
for (i = 0; i < check_dirs_cnt; i++)
free((char *)check_dirs[i].sd_name);
free(check_dirs);
}
check_dirs = NULL;
check_dirs_cnt = 0;
}