blob: df893c679d14dc591aed8743acc7e2dbcd95d044 [file] [log] [blame]
/*
*
* Generic IO operations on quotafiles
*
* Jan Kara <jack@suse.cz> - sponsored by SuSE CR
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <endian.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_rpc.h"
#include "dqblk_xfs.h"
/* Header in all newer quotafiles */
struct disk_dqheader {
u_int32_t dqh_magic;
u_int32_t dqh_version;
} __attribute__ ((packed));
/*
* Detect quota format and initialize quota IO
*/
struct quota_handle *init_io(struct mount_entry *mnt, int type, int fmt, int flags)
{
char *qfname = NULL;
int fd = -1, kernfmt;
struct quota_handle *h = smalloc(sizeof(struct quota_handle));
int nameflag;
if (!me_hasquota(mnt, type))
goto out_handle;
if (stat(mnt->me_devname, &h->qh_stat) < 0)
memset(&h->qh_stat, 0, sizeof(struct stat));
h->qh_io_flags = 0;
if (flags & IOI_READONLY)
h->qh_io_flags |= IOFL_RO;
if (flags & IOI_NFS_MIXED_PATHS)
h->qh_io_flags |= IOFL_NFS_MIXED_PATHS;
h->qh_type = type;
sstrncpy(h->qh_quotadev, mnt->me_devname, sizeof(h->qh_quotadev));
sstrncpy(h->qh_fstype, mnt->me_type, MAX_FSTYPE_LEN);
sstrncpy(h->qh_dir, mnt->me_dir, PATH_MAX);
if (nfs_fstype(mnt->me_type)) { /* NFS filesystem? */
if (fmt != -1 && fmt != QF_RPC) { /* User wanted some other format? */
errstr(_("Only RPC quota format is allowed on NFS filesystem.\n"));
goto out_handle;
}
#ifdef RPC
h->qh_fd = -1;
h->qh_fmt = QF_RPC;
h->qh_ops = &quotafile_ops_rpc;
memset(&h->qh_info, 0, sizeof(h->qh_info));
h->qh_ops->init_io(h);
return h;
#else
errstr(_("RPC quota format not compiled.\n"));
goto out_handle;
#endif
} else if (fmt == QF_RPC) {
errstr(_("RPC quota format specified for non-NFS filesystem.\n"));
goto out_handle;
}
if (!strcmp(mnt->me_type, MNTTYPE_XFS) || /* XFS filesystem? */
!strcmp(mnt->me_type, MNTTYPE_GFS2)) { /* XFS filesystem? */
if (fmt != -1 && fmt != QF_XFS) { /* User wanted some other format? */
errstr(_("Only XFS quota format is allowed on XFS filesystem.\n"));
goto out_handle;
}
h->qh_fd = -1;
h->qh_fmt = QF_XFS;
h->qh_ops = &quotafile_ops_xfs;
memset(&h->qh_info, 0, sizeof(h->qh_info));
h->qh_ops->init_io(h);
return h;
}
else if (fmt == QF_XFS) {
errstr(_("XFS quota allowed only on XFS filesystem.\n"));
goto out_handle;
}
if (kern_qfmt_supp(fmt)) { /* Quota compiled and desired format available? */
/* Quota turned on? */
kernfmt = kern_quota_on(mnt, type, fmt);
if (kernfmt >= 0) {
h->qh_io_flags |= IOFL_QUOTAON;
fmt = kernfmt; /* Default is kernel used format */
}
}
if (meta_qf_fstype(mnt->me_type) || mnt->me_qfmt[type] == QF_META) {
if (!QIO_ENABLED(h)) {
errstr(_("Quota not supported by the filesystem.\n"));
goto out_handle;
}
h->qh_fd = -1;
h->qh_fmt = fmt;
goto set_ops;
}
nameflag = (!QIO_ENABLED(h) || flags & IOI_INITSCAN) ? NF_FORMAT : 0;
if (fmt == -1) {
/* Let's try any VFSv0 quota format... */
if (get_qf_name(mnt, type, QF_VFSV0, nameflag, &qfname) >= 0)
fmt = QF_VFSV0;
/* And then VFSv1 quota format... */
else if (get_qf_name(mnt, type, QF_VFSV1, nameflag, &qfname) >= 0)
fmt = QF_VFSV1;
/* And then old quota format... */
else if (get_qf_name(mnt, type, QF_VFSOLD, nameflag, &qfname) >= 0)
fmt = QF_VFSOLD;
else { /* Don't know... */
errstr(_("Cannot find any quota file to work on.\n"));
goto out_handle;
}
} else {
if (get_qf_name(mnt, type, fmt, nameflag, &qfname) < 0) {
errstr(_("Quota file not found or has wrong format.\n"));
goto out_handle;
}
}
if (!QIO_ENABLED(h) || flags & IOI_INITSCAN) { /* Need to open file? */
if (QIO_ENABLED(h)) { /* Kernel uses same file? */
unsigned int cmd =
(kernel_iface == IFACE_GENERIC) ? Q_SYNC : Q_6_5_SYNC;
if (quotactl(QCMD(cmd, h->qh_type), h->qh_quotadev,
0, NULL) < 0) {
die(4, _("Cannot sync quotas on device %s: %s\n"),
h->qh_quotadev, strerror(errno));
}
}
/* We still need to open file for operations like 'repquota' */
if ((fd = open(qfname, QIO_RO(h) ? O_RDONLY : O_RDWR)) < 0) {
errstr(_("Cannot open quotafile %s: %s\n"),
qfname, strerror(errno));
goto out_handle;
}
flock(fd, QIO_RO(h) ? LOCK_SH : LOCK_EX);
/* Init handle */
h->qh_fd = fd;
h->qh_fmt = fmt;
} else {
h->qh_fd = -1;
h->qh_fmt = fmt;
}
free(qfname); /* We don't need it anymore */
qfname = NULL;
set_ops:
if (fmt == QF_VFSOLD)
h->qh_ops = &quotafile_ops_1;
else if (is_tree_qfmt(fmt))
h->qh_ops = &quotafile_ops_2;
else if (fmt == QF_META)
h->qh_ops = &quotafile_ops_meta;
memset(&h->qh_info, 0, sizeof(h->qh_info));
if (h->qh_ops->init_io && h->qh_ops->init_io(h) < 0) {
errstr(_("Cannot initialize quota on %s: %s\n"), h->qh_quotadev, strerror(errno));
goto out_lock;
}
return h;
out_lock:
if (fd != -1)
flock(fd, LOCK_UN);
out_handle:
if (qfname)
free(qfname);
free(h);
return NULL;
}
/*
* Create new quotafile of specified format on given filesystem
*/
struct quota_handle *new_io(struct mount_entry *mnt, int type, int fmt)
{
char *qfname;
int fd;
struct quota_handle *h;
char namebuf[PATH_MAX];
if (fmt == -1)
fmt = QF_VFSV0;
else if (fmt == QF_RPC || fmt == QF_XFS) {
errstr(_("Creation of %s quota format is not supported.\n"),
fmt2name(fmt));
return NULL;
}
/*
* For filesystems which never have quotas in quota files or for
* filesystems which have quotas already stored in system files we
* refuse to create anything.
*/
if (meta_qf_fstype(mnt->me_type) || mnt->me_qfmt[type] == QF_META) {
errstr(_("Quota on %s is stored in system files and must"
" be manipulated by fs tools.\n"), mnt->me_dir);
return NULL;
}
if (get_qf_name(mnt, type, fmt, 0, &qfname) < 0)
return NULL;
sstrncpy(namebuf, qfname, PATH_MAX);
sstrncat(namebuf, ".new", PATH_MAX);
free(qfname);
if ((fd = open(namebuf, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0) {
errstr(_("Cannot create new quotafile %s: %s\n"),
namebuf, strerror(errno));
return NULL;
}
h = smalloc(sizeof(struct quota_handle));
h->qh_fd = fd;
h->qh_io_flags = 0;
sstrncpy(h->qh_quotadev, mnt->me_devname, sizeof(h->qh_quotadev));
sstrncpy(h->qh_fstype, mnt->me_type, MAX_FSTYPE_LEN);
sstrncpy(h->qh_dir, mnt->me_dir, PATH_MAX);
h->qh_type = type;
h->qh_fmt = fmt;
memset(&h->qh_info, 0, sizeof(h->qh_info));
if (fmt == QF_VFSOLD)
h->qh_ops = &quotafile_ops_1;
else
h->qh_ops = &quotafile_ops_2;
flock(fd, LOCK_EX);
if (h->qh_ops->new_io && h->qh_ops->new_io(h) < 0) {
flock(fd, LOCK_UN);
free(h);
goto out_fd;
}
return h;
out_fd:
close(fd);
return NULL;
}
/*
* Close quotafile and release handle
*/
int end_io(struct quota_handle *h)
{
if (h->qh_io_flags & IOFL_INFODIRTY) {
if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0)
return -1;
h->qh_io_flags &= ~IOFL_INFODIRTY;
}
if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0)
return -1;
if (h->qh_fd != -1) {
flock(h->qh_fd, LOCK_UN);
close(h->qh_fd);
}
free(h);
return 0;
}
/*
* Create empty quota structure
*/
struct dquot *get_empty_dquot(void)
{
struct dquot *dquot = smalloc(sizeof(struct dquot));
memset(dquot, 0, sizeof(*dquot));
dquot->dq_id = -1;
return dquot;
}
/*
* Check whether values in current dquot can be stored on disk
*/
int check_dquot_range(struct dquot *dquot)
{
struct util_dqinfo *info = &dquot->dq_h->qh_info;
if (dquot->dq_dqb.dqb_bhardlimit > info->dqi_max_b_limit ||
dquot->dq_dqb.dqb_bsoftlimit > info->dqi_max_b_limit ||
dquot->dq_dqb.dqb_ihardlimit > info->dqi_max_i_limit ||
dquot->dq_dqb.dqb_isoftlimit > info->dqi_max_i_limit) {
errstr(_("Trying to set quota limits out of range "
"supported by quota format on %s.\n"), dquot->dq_h->qh_quotadev);
return -1;
}
if (dquot->dq_dqb.dqb_curinodes > info->dqi_max_i_usage ||
dquot->dq_dqb.dqb_curspace > info->dqi_max_b_usage) {
errstr(_("Trying to set quota usage out of range "
"supported by quota format on %s.\n"), dquot->dq_h->qh_quotadev);
return -1;
}
return 0;
}