blob: a3a4ae382dd2346c1818c9dcd99eed29225d538a [file] [log] [blame]
/*
* QUOTA An implementation of the diskquota system for the LINUX
* operating system. QUOTA is implemented using the BSD systemcall
* interface as the means of communication with the user level.
* Should work for all filesystems because of integration into the
* VFS layer of the operating system.
* This is based on the Melbourne quota system wich uses both user and
* group quota files.
*
* This part does the rpc-communication with the rquotad.
*
* Author: Marco van Wieringen <mvw@planets.elm.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include "config.h"
#include <rpc/rpc.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <stdint.h>
#include "mntopt.h"
#include "rquota.h"
#include "common.h"
#include "quotaio.h"
#include "quotasys.h"
#if defined(RPC)
/* Convert network format of quotas to utils one */
static inline void clinet2utildqblk(struct util_dqblk *u, struct rquota *n)
{
time_t now;
/* Copy the quota */
u->dqb_bhardlimit = toqb(((qsize_t)n->rq_bhardlimit) * n->rq_bsize);
u->dqb_bsoftlimit = toqb(((qsize_t)n->rq_bsoftlimit) * n->rq_bsize);
u->dqb_ihardlimit = n->rq_fhardlimit;
u->dqb_isoftlimit = n->rq_fsoftlimit;
u->dqb_curinodes = n->rq_curfiles;
u->dqb_curspace = ((qsize_t)n->rq_curblocks) * n->rq_bsize;
time(&now);
if (n->rq_btimeleft)
u->dqb_btime = (int32_t)n->rq_btimeleft + now;
else
u->dqb_btime = 0;
if (n->rq_ftimeleft)
u->dqb_itime = (int32_t)n->rq_ftimeleft + now;
else
u->dqb_itime = 0;
}
/* Convert utils format of quotas to network one */
static inline void cliutil2netdqblk(struct sq_dqblk *n, struct util_dqblk *u)
{
time_t now;
time(&now);
n->rq_bhardlimit = u->dqb_bhardlimit;
n->rq_bsoftlimit = u->dqb_bsoftlimit;
n->rq_fhardlimit = u->dqb_ihardlimit;
n->rq_fsoftlimit = u->dqb_isoftlimit;
n->rq_curblocks = toqb(u->dqb_curspace);
n->rq_curfiles = u->dqb_curinodes;
if (u->dqb_btime)
n->rq_btimeleft = difftime2net(u->dqb_btime, now);
else
n->rq_btimeleft = 0;
if (u->dqb_itime)
n->rq_ftimeleft = difftime2net(u->dqb_itime, now);
else
n->rq_ftimeleft = 0;
}
/* Write appropriate error message */
static int rquota_err(int stat)
{
switch (stat) {
case -1:
return -ECONNREFUSED;
case 0:
return -ENOSYS;
case Q_NOQUOTA:
return -ENOENT;
case Q_OK:
return 0;
case Q_EPERM:
return -EPERM;
default:
return -EINVAL;
}
}
static int split_nfs_mount(char *devname, char **host, char **path)
{
char *pathname;
/* NFS server name contained in brackets? */
if (*devname == '[') {
*host = devname + 1;
pathname = strchr(devname, ']');
if (!pathname || pathname[1] != ':')
return 0;
/* Autofs? */
if (pathname[2] == '(')
return 0;
*pathname = 0;
*path = pathname + 2;
return 1;
}
*host = devname;
pathname = strchr(devname, ':');
if (!pathname)
return 0;
/* Autofs? */
if (pathname[1] == '(')
return 0;
*pathname = 0;
*path = pathname + 1;
return 1;
}
/*
* Collect the requested quota information from a remote host.
*/
int rpc_rquota_get(struct dquot *dquot)
{
CLIENT *clnt;
getquota_rslt *result;
union {
getquota_args arg;
ext_getquota_args ext_arg;
} args;
char *fsname_tmp, *host, *pathname;
struct timeval timeout = { 2, 0 };
int rquotaprog_not_registered = 0;
int ret;
/*
* Initialize with NULL.
*/
memset(&dquot->dq_dqb, 0, sizeof(dquot->dq_dqb));
/*
* Convert host:pathname to seperate host and pathname.
*/
fsname_tmp = (char *)smalloc(strlen(dquot->dq_h->qh_quotadev) + 1);
strcpy(fsname_tmp, dquot->dq_h->qh_quotadev);
if (!split_nfs_mount(fsname_tmp, &host, &pathname)) {
free(fsname_tmp);
return -ENOENT;
}
/* For NFSv4, we send the filesystem path without initial /. Server prepends proper
* NFS pseudoroot automatically and uses this for detection of NFSv4 mounts. */
if ((dquot->dq_h->qh_io_flags & IOFL_NFS_MIXED_PATHS) &&
!strcmp(dquot->dq_h->qh_fstype, MNTTYPE_NFS4)) {
while (*pathname == '/')
pathname++;
}
/*
* First try EXT_RQUOTAPROG (Extended (LINUX) RPC quota program)
*/
args.ext_arg.gqa_pathp = pathname;
args.ext_arg.gqa_id = dquot->dq_id;
args.ext_arg.gqa_type = dquot->dq_h->qh_type;
/*
* Create a RPC client.
*/
if ((clnt = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp")) != NULL) {
/*
* Initialize unix authentication
*/
clnt->cl_auth = authunix_create_default();
/*
* Setup protocol timeout.
*/
clnt_control(clnt, CLSET_TIMEOUT, (caddr_t) & timeout);
/*
* Do RPC call and check result.
*/
result = rquotaproc_getquota_2(&args.ext_arg, clnt);
if (result != NULL && result->status == Q_OK)
clinet2utildqblk(&dquot->dq_dqb, &result->getquota_rslt_u.gqr_rquota);
/*
* Destroy unix authentication and RPC client structure.
*/
auth_destroy(clnt->cl_auth);
clnt_destroy(clnt);
}
else {
result = NULL;
if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED)
rquotaprog_not_registered = 1;
}
if (result == NULL || !result->status) {
if (dquot->dq_h->qh_type == USRQUOTA) {
/*
* Try RQUOTAPROG because server doesn't seem to understand EXT_RQUOTAPROG. (NON-LINUX servers.)
*/
args.arg.gqa_pathp = pathname;
args.arg.gqa_uid = dquot->dq_id;
/*
* Create a RPC client.
*/
if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) != NULL) {
/*
* Initialize unix authentication
*/
clnt->cl_auth = authunix_create_default();
/*
* Setup protocol timeout.
*/
clnt_control(clnt, CLSET_TIMEOUT, (caddr_t) & timeout);
/*
* Do RPC call and check result.
*/
result = rquotaproc_getquota_1(&args.arg, clnt);
if (result != NULL && result->status == Q_OK)
clinet2utildqblk(&dquot->dq_dqb,
&result->getquota_rslt_u.gqr_rquota);
/*
* Destroy unix authentication and RPC client structure.
*/
auth_destroy(clnt->cl_auth);
clnt_destroy(clnt);
} else {
result = NULL;
if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED)
rquotaprog_not_registered = 1;
}
}
}
free(fsname_tmp);
if (result)
ret = result->status;
else if (rquotaprog_not_registered)
ret = Q_NOQUOTA;
else
ret = -1;
return rquota_err(ret);
}
/*
* Set the requested quota information on a remote host.
*/
int rpc_rquota_set(int qcmd, struct dquot *dquot)
{
#if defined(RPC_SETQUOTA)
CLIENT *clnt;
setquota_rslt *result;
union {
setquota_args arg;
ext_setquota_args ext_arg;
} args;
char *fsname_tmp, *host, *pathname;
struct timeval timeout = { 2, 0 };
int rquotaprog_not_registered = 0;
int ret;
/* RPC limits values to 32b variables. Prevent value wrapping. */
if (check_dquot_range(dquot) < 0)
return -ERANGE;
/*
* Convert host:pathname to seperate host and pathname.
*/
fsname_tmp = (char *)smalloc(strlen(dquot->dq_h->qh_quotadev) + 1);
strcpy(fsname_tmp, dquot->dq_h->qh_quotadev);
if (!split_nfs_mount(fsname_tmp, &host, &pathname)) {
free(fsname_tmp);
return -ENOENT;
}
/* For NFSv4, we send the filesystem path without initial /. Server prepends proper
* NFS pseudoroot automatically and uses this for detection of NFSv4 mounts. */
if ((dquot->dq_h->qh_io_flags & IOFL_NFS_MIXED_PATHS) &&
!strcmp(dquot->dq_h->qh_fstype, MNTTYPE_NFS4)) {
while (*pathname == '/')
pathname++;
}
/*
* First try EXT_RQUOTAPROG (Extended (LINUX) RPC quota program)
*/
args.ext_arg.sqa_qcmd = qcmd;
args.ext_arg.sqa_pathp = pathname;
args.ext_arg.sqa_id = dquot->dq_id;
args.ext_arg.sqa_type = dquot->dq_h->qh_type;
cliutil2netdqblk(&args.ext_arg.sqa_dqblk, &dquot->dq_dqb);
if ((clnt = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp")) != NULL) {
/*
* Initialize unix authentication
*/
clnt->cl_auth = authunix_create_default();
/*
* Setup protocol timeout.
*/
clnt_control(clnt, CLSET_TIMEOUT, (caddr_t) & timeout);
/*
* Do RPC call and check result.
*/
result = rquotaproc_setquota_2(&args.ext_arg, clnt);
if (result != NULL && result->status == Q_OK)
clinet2utildqblk(&dquot->dq_dqb, &result->setquota_rslt_u.sqr_rquota);
/*
* Destroy unix authentication and RPC client structure.
*/
auth_destroy(clnt->cl_auth);
clnt_destroy(clnt);
}
else {
result = NULL;
if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED)
rquotaprog_not_registered = 1;
}
if (result == NULL || !result->status) {
if (dquot->dq_h->qh_type == USRQUOTA) {
/*
* Try RQUOTAPROG because server doesn't seem to understand EXT_RQUOTAPROG. (NON-LINUX servers.)
*/
args.arg.sqa_qcmd = qcmd;
args.arg.sqa_pathp = pathname;
args.arg.sqa_id = dquot->dq_id;
cliutil2netdqblk(&args.arg.sqa_dqblk, &dquot->dq_dqb);
/*
* Create a RPC client.
*/
if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) != NULL) {
/*
* Initialize unix authentication
*/
clnt->cl_auth = authunix_create_default();
/*
* Setup protocol timeout.
*/
clnt_control(clnt, CLSET_TIMEOUT, (caddr_t) & timeout);
/*
* Do RPC call and check result.
*/
result = rquotaproc_setquota_1(&args.arg, clnt);
if (result != NULL && result->status == Q_OK)
clinet2utildqblk(&dquot->dq_dqb,
&result->setquota_rslt_u.sqr_rquota);
/*
* Destroy unix authentication and RPC client structure.
*/
auth_destroy(clnt->cl_auth);
clnt_destroy(clnt);
} else {
result = NULL;
if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED)
rquotaprog_not_registered = 1;
}
}
}
free(fsname_tmp);
if (result)
ret = result->status;
else if (rquotaprog_not_registered)
ret = Q_NOQUOTA;
else
ret = -1;
return rquota_err(ret);
#endif
return -1;
}
#endif