rpc.rquotad.c revision be9616725c937762eb53ade3627d807070648ad3
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
#include <string.h>
#include <stropts.h>
#include <errno.h>
#include <sys/netconfig.h>
#include <sys/mntent.h>
#include <sys/mnttab.h>
#include <sys/param.h>
#include <sys/time.h>
#ifdef notdef
#include <netconfig.h>
#endif
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/fs/ufs_quota.h>
#include <netdir.h>
#include <rpc/rpc.h>
#include <rpcsvc/rquota.h>
#include <tiuser.h>
#include <unistd.h>
#include <dlfcn.h>
#include <libzfs.h>
#define QFNAME "quotas" /* name of quota file */
#define RPCSVC_CLOSEDOWN 120 /* 2 minutes */
struct fsquot {
char *fsq_fstype;
struct fsquot *fsq_next;
char *fsq_dir;
char *fsq_devname;
dev_t fsq_dev;
};
struct fsquot *fsqlist = NULL;
typedef struct authunix_parms *authp;
static int request_pending; /* Request in progress ? */
void closedown();
void dispatch();
struct fsquot *findfsq();
void freefs();
int getdiskquota();
void getquota();
int hasquota();
void log_cant_reply();
void setupfs();
static void zexit();
static libzfs_handle_t *(*_libzfs_init)(void);
static void (*_libzfs_fini)(libzfs_handle_t *);
static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int);
static void (*_zfs_close)(zfs_handle_t *);
static int (*_zfs_prop_get_userquota_int)(zfs_handle_t *, const char *,
uint64_t *);
static libzfs_handle_t *g_zfs = NULL;
/*
* Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs
* packages. 'rquotad' supports zfs as an option.
*/
static void
load_libzfs(void)
{
void *hdl;
if (g_zfs != NULL)
return;
if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) {
_libzfs_init = (libzfs_handle_t *(*)(void))dlsym(hdl,
"libzfs_init");
_libzfs_fini = (void (*)())dlsym(hdl, "libzfs_fini");
_zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open");
_zfs_close = (void (*)())dlsym(hdl, "zfs_close");
_zfs_prop_get_userquota_int = (int (*)())
dlsym(hdl, "zfs_prop_get_userquota_int");
if (_libzfs_init && _libzfs_fini && _zfs_open &&
_zfs_close && _zfs_prop_get_userquota_int)
g_zfs = _libzfs_init();
}
}
/*ARGSUSED*/
int
main(int argc, char *argv[])
{
register SVCXPRT *transp;
load_libzfs();
/*
* If stdin looks like a TLI endpoint, we assume
* that we were started by a port monitor. If
* t_getstate fails with TBADF, this is not a
* TLI endpoint.
*/
if (t_getstate(0) != -1 || t_errno != TBADF) {
char *netid;
struct netconfig *nconf = NULL;
openlog("rquotad", LOG_PID, LOG_DAEMON);
if ((netid = getenv("NLSPROVIDER")) == NULL) {
struct t_info tinfo;
if (t_sync(0) == -1) {
syslog(LOG_ERR, "could not do t_sync");
zexit(1);
}
if (t_getinfo(0, &tinfo) == -1) {
syslog(LOG_ERR, "t_getinfo failed");
zexit(1);
}
if (tinfo.servtype == T_CLTS) {
if (tinfo.addr == INET_ADDRSTRLEN)
netid = "udp";
else
netid = "udp6";
} else {
syslog(LOG_ERR, "wrong transport");
zexit(1);
}
}
if ((nconf = getnetconfigent(netid)) == NULL) {
syslog(LOG_ERR, "cannot get transport info");
}
if ((transp = svc_tli_create(0, nconf, NULL, 0, 0)) == NULL) {
syslog(LOG_ERR, "cannot create server handle");
zexit(1);
}
if (nconf)
freenetconfigent(nconf);
if (!svc_reg(transp, RQUOTAPROG, RQUOTAVERS, dispatch, 0)) {
syslog(LOG_ERR,
"unable to register (RQUOTAPROG, RQUOTAVERS).");
zexit(1);
}
(void) sigset(SIGALRM, (void(*)(int)) closedown);
(void) alarm(RPCSVC_CLOSEDOWN);
svc_run();
zexit(1);
/* NOTREACHED */
}
/*
* Started from a shell - fork the daemon.
*/
switch (fork()) {
case 0: /* child */
break;
case -1:
perror("rquotad: can't fork");
zexit(1);
default: /* parent */
zexit(0);
}
/*
* Close existing file descriptors, open "/dev/null" as
* standard input, output, and error, and detach from
* controlling terminal.
*/
closefrom(0);
(void) open("/dev/null", O_RDONLY);
(void) open("/dev/null", O_WRONLY);
(void) dup(1);
(void) setsid();
openlog("rquotad", LOG_PID, LOG_DAEMON);
/*
* Create datagram service
*/
if (svc_create(dispatch, RQUOTAPROG, RQUOTAVERS, "datagram_v") == 0) {
syslog(LOG_ERR, "couldn't register datagram_v service");
zexit(1);
}
/*
* Start serving
*/
svc_run();
syslog(LOG_ERR, "Error: svc_run shouldn't have returned");
return (1);
}
void
dispatch(rqstp, transp)
register struct svc_req *rqstp;
register SVCXPRT *transp;
{
request_pending = 1;
switch (rqstp->rq_proc) {
case NULLPROC:
errno = 0;
if (!svc_sendreply(transp, xdr_void, 0))
log_cant_reply(transp);
break;
case RQUOTAPROC_GETQUOTA:
case RQUOTAPROC_GETACTIVEQUOTA:
getquota(rqstp, transp);
break;
default:
svcerr_noproc(transp);
break;
}
request_pending = 0;
}
void
closedown()
{
if (!request_pending) {
int i, openfd;
struct t_info tinfo;
if (!t_getinfo(0, &tinfo) && (tinfo.servtype == T_CLTS))
zexit(0);
for (i = 0, openfd = 0; i < svc_max_pollfd && openfd < 2; i++) {
if (svc_pollfd[i].fd >= 0)
openfd++;
}
if (openfd <= 1)
zexit(0);
}
(void) alarm(RPCSVC_CLOSEDOWN);
}
static int
getzfsquota(uid_t user, char *dataset, struct dqblk *zq)
{
zfs_handle_t *zhp = NULL;
char propname[ZFS_MAXPROPLEN];
uint64_t userquota, userused;
if (g_zfs == NULL)
return (1);
if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) {
syslog(LOG_ERR, "can not open zfs dataset %s", dataset);
return (1);
}
(void) snprintf(propname, sizeof (propname), "userquota@%u", user);
if (_zfs_prop_get_userquota_int(zhp, propname, &userquota) != 0) {
_zfs_close(zhp);
return (1);
}
(void) snprintf(propname, sizeof (propname), "userused@%u", user);
if (_zfs_prop_get_userquota_int(zhp, propname, &userused) != 0) {
_zfs_close(zhp);
return (1);
}
zq->dqb_bhardlimit = userquota / DEV_BSIZE;
zq->dqb_bsoftlimit = userquota / DEV_BSIZE;
zq->dqb_curblocks = userused / DEV_BSIZE;
_zfs_close(zhp);
return (0);
}
void
getquota(rqstp, transp)
register struct svc_req *rqstp;
register SVCXPRT *transp;
{
struct getquota_args gqa;
struct getquota_rslt gqr;
struct dqblk dqblk;
struct fsquot *fsqp;
struct timeval tv;
bool_t qactive;
gqa.gqa_pathp = NULL; /* let xdr allocate the storage */
if (!svc_getargs(transp, xdr_getquota_args, (caddr_t)&gqa)) {
svcerr_decode(transp);
return;
}
/*
* This authentication is really bogus with the current rpc
* authentication scheme. One day we will have something for real.
*/
if (rqstp->rq_cred.oa_flavor != AUTH_UNIX ||
(((authp) rqstp->rq_clntcred)->aup_uid != 0 &&
((authp) rqstp->rq_clntcred)->aup_uid != (uid_t)gqa.gqa_uid)) {
gqr.status = Q_EPERM;
goto sendreply;
}
fsqp = findfsq(gqa.gqa_pathp);
if (fsqp == NULL) {
gqr.status = Q_NOQUOTA;
goto sendreply;
}
bzero(&dqblk, sizeof (dqblk));
if (strcmp(fsqp->fsq_fstype, MNTTYPE_ZFS) == 0) {
if (getzfsquota(gqa.gqa_uid, fsqp->fsq_devname, &dqblk)) {
gqr.status = Q_NOQUOTA;
goto sendreply;
}
qactive = TRUE;
} else {
if (quotactl(Q_GETQUOTA, fsqp->fsq_dir,
(uid_t)gqa.gqa_uid, &dqblk) != 0) {
qactive = FALSE;
if ((errno == ENOENT) ||
(rqstp->rq_proc != RQUOTAPROC_GETQUOTA)) {
gqr.status = Q_NOQUOTA;
goto sendreply;
}
/*
* If there is no quotas file, don't bother to sync it.
*/
if (errno != ENOENT) {
if (quotactl(Q_ALLSYNC, fsqp->fsq_dir,
(uid_t)gqa.gqa_uid, &dqblk) < 0 &&
errno == EINVAL)
syslog(LOG_WARNING,
"Quotas are not compiled "
"into this kernel");
if (getdiskquota(fsqp, (uid_t)gqa.gqa_uid,
&dqblk) == 0) {
gqr.status = Q_NOQUOTA;
goto sendreply;
}
}
} else {
qactive = TRUE;
}
/*
* We send the remaining time instead of the absolute time
* because clock skew between machines should be much greater
* than rpc delay.
*/
#define gqrslt getquota_rslt_u.gqr_rquota
gettimeofday(&tv, NULL);
gqr.gqrslt.rq_btimeleft = dqblk.dqb_btimelimit - tv.tv_sec;
gqr.gqrslt.rq_ftimeleft = dqblk.dqb_ftimelimit - tv.tv_sec;
}
gqr.status = Q_OK;
gqr.gqrslt.rq_active = qactive;
gqr.gqrslt.rq_bsize = DEV_BSIZE;
gqr.gqrslt.rq_bhardlimit = dqblk.dqb_bhardlimit;
gqr.gqrslt.rq_bsoftlimit = dqblk.dqb_bsoftlimit;
gqr.gqrslt.rq_curblocks = dqblk.dqb_curblocks;
gqr.gqrslt.rq_fhardlimit = dqblk.dqb_fhardlimit;
gqr.gqrslt.rq_fsoftlimit = dqblk.dqb_fsoftlimit;
gqr.gqrslt.rq_curfiles = dqblk.dqb_curfiles;
sendreply:
errno = 0;
if (!svc_sendreply(transp, xdr_getquota_rslt, (caddr_t)&gqr))
log_cant_reply(transp);
}
int
quotactl(cmd, mountp, uid, dqp)
int cmd;
char *mountp;
uid_t uid;
struct dqblk *dqp;
{
int fd;
int status;
struct quotctl quota;
char mountpoint[256];
FILE *fstab;
struct mnttab mntp;
if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
/*
* Find the mount point of any ufs file system. this is
* because the ioctl that implements the quotactl call has
* to go to a real file, and not to the block device.
*/
if ((fstab = fopen(MNTTAB, "r")) == NULL) {
syslog(LOG_ERR, "can not open %s: %m ", MNTTAB);
return (-1);
}
fd = -1;
while ((status = getmntent(fstab, &mntp)) == NULL) {
if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
!(hasmntopt(&mntp, MNTOPT_RQ) ||
hasmntopt(&mntp, MNTOPT_QUOTA)))
continue;
(void) strlcpy(mountpoint, mntp.mnt_mountp,
sizeof (mountpoint));
strcat(mountpoint, "/quotas");
if ((fd = open64(mountpoint, O_RDWR)) >= 0)
break;
}
fclose(fstab);
if (fd == -1) {
errno = ENOENT;
return (-1);
}
} else {
if (mountp == NULL || mountp[0] == '\0') {
errno = ENOENT;
return (-1);
}
(void) strlcpy(mountpoint, mountp, sizeof (mountpoint));
strcat(mountpoint, "/quotas");
if ((fd = open64(mountpoint, O_RDONLY)) < 0) {
errno = ENOENT;
syslog(LOG_ERR, "can not open %s: %m ", mountpoint);
return (-1);
}
}
quota.op = cmd;
quota.uid = uid;
quota.addr = (caddr_t)dqp;
status = ioctl(fd, Q_QUOTACTL, &quota);
close(fd);
return (status);
}
/*
* Reduce unneeded slashes in the given path. e.g. "//the//dir/"
*/
static void
reduce_slashes(char *path)
{
char *p, *q = path;
for (p = path; *p != '\0'; p++) {
if (p[0] == '/' && p[1] == '/')
continue;
*q++ = *p;
}
if ((q - path > 1) && q[-1] == '/')
q--;
*q = '\0';
}
/*
* Return the quota information for the given path. Returns NULL if none
* was found.
*/
struct fsquot *
findfsq(dir)
char *dir;
{
struct stat sb;
register struct fsquot *fsqp;
static time_t lastmtime = 0; /* mount table's previous mtime */
/*
* If we've never looked at the mount table, or it has changed
* since the last time, rebuild the list of quota'd file systems
* and remember the current mod time for the mount table.
*/
if (stat(MNTTAB, &sb) < 0) {
syslog(LOG_ERR, "can't stat %s: %m", MNTTAB);
return (NULL);
}
if (lastmtime == 0 || sb.st_mtime != lastmtime) {
freefs();
setupfs();
lastmtime = sb.st_mtime;
}
/*
* Try to find the given path in the list of file systems with
* quotas.
*/
if (fsqlist == NULL)
return (NULL);
if (stat(dir, &sb) < 0)
return (NULL);
reduce_slashes(dir);
for (fsqp = fsqlist; fsqp != NULL; fsqp = fsqp->fsq_next) {
if (strcmp(fsqp->fsq_fstype, MNTTYPE_ZFS) == 0) {
if (strcmp(fsqp->fsq_dir, dir) == 0)
return (fsqp);
} else if (sb.st_dev == fsqp->fsq_dev) {
return (fsqp);
}
}
return (NULL);
}
static void
setup_zfs(struct mnttab *mp)
{
struct fsquot *fsqp;
fsqp = malloc(sizeof (struct fsquot));
if (fsqp == NULL) {
syslog(LOG_ERR, "out of memory");
zexit(1);
}
fsqp->fsq_dir = strdup(mp->mnt_mountp);
fsqp->fsq_devname = strdup(mp->mnt_special);
if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
syslog(LOG_ERR, "out of memory");
zexit(1);
}
fsqp->fsq_fstype = MNTTYPE_ZFS;
fsqp->fsq_next = fsqlist;
fsqlist = fsqp;
}
void
setupfs()
{
struct fsquot *fsqp;
FILE *mt;
struct mnttab m;
struct stat sb;
char qfilename[MAXPATHLEN];
mt = fopen(MNTTAB, "r");
if (mt == NULL) {
syslog(LOG_ERR, "can't read %s: %m", MNTTAB);
return;
}
while (getmntent(mt, &m) == 0) {
if (strcmp(m.mnt_fstype, MNTTYPE_ZFS) == 0) {
setup_zfs(&m);
continue;
}
if (strcmp(m.mnt_fstype, MNTTYPE_UFS) != 0)
continue;
if (!hasquota(m.mnt_mntopts)) {
snprintf(qfilename, sizeof (qfilename), "%s/%s",
m.mnt_mountp, QFNAME);
if (access(qfilename, F_OK) < 0)
continue;
}
if (stat(m.mnt_special, &sb) < 0 ||
(sb.st_mode & S_IFMT) != S_IFBLK)
continue;
fsqp = malloc(sizeof (struct fsquot));
if (fsqp == NULL) {
syslog(LOG_ERR, "out of memory");
zexit(1);
}
fsqp->fsq_dir = strdup(m.mnt_mountp);
fsqp->fsq_devname = strdup(m.mnt_special);
if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
syslog(LOG_ERR, "out of memory");
zexit(1);
}
fsqp->fsq_fstype = MNTTYPE_UFS;
fsqp->fsq_dev = sb.st_rdev;
fsqp->fsq_next = fsqlist;
fsqlist = fsqp;
}
(void) fclose(mt);
}
/*
* Free the memory used by the current list of quota'd file systems. Nulls
* out the list.
*/
void
freefs()
{
register struct fsquot *fsqp;
while ((fsqp = fsqlist) != NULL) {
fsqlist = fsqp->fsq_next;
free(fsqp->fsq_dir);
free(fsqp->fsq_devname);
free(fsqp);
}
}
int
getdiskquota(fsqp, uid, dqp)
struct fsquot *fsqp;
uid_t uid;
struct dqblk *dqp;
{
int fd;
char qfilename[MAXPATHLEN];
snprintf(qfilename, sizeof (qfilename), "%s/%s", fsqp->fsq_dir, QFNAME);
if ((fd = open64(qfilename, O_RDONLY)) < 0)
return (0);
(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
if (read(fd, dqp, sizeof (struct dqblk)) != sizeof (struct dqblk)) {
close(fd);
return (0);
}
close(fd);
if (dqp->dqb_bhardlimit == 0 && dqp->dqb_bsoftlimit == 0 &&
dqp->dqb_fhardlimit == 0 && dqp->dqb_fsoftlimit == 0) {
return (0);
}
return (1);
}
/*
* Get the client's hostname from the transport handle
* If the name is not available then return "(anon)".
*/
struct nd_hostservlist *
getclientsnames(transp)
SVCXPRT *transp;
{
struct netbuf *nbuf;
struct netconfig *nconf;
static struct nd_hostservlist *serv;
static struct nd_hostservlist anon_hsl;
static struct nd_hostserv anon_hs;
static char anon_hname[] = "(anon)";
static char anon_sname[] = "";
/* Set up anonymous client */
anon_hs.h_host = anon_hname;
anon_hs.h_serv = anon_sname;
anon_hsl.h_cnt = 1;
anon_hsl.h_hostservs = &anon_hs;
if (serv) {
netdir_free((char *)serv, ND_HOSTSERVLIST);
serv = NULL;
}
nconf = getnetconfigent(transp->xp_netid);
if (nconf == NULL) {
syslog(LOG_ERR, "%s: getnetconfigent failed",
transp->xp_netid);
return (&anon_hsl);
}
nbuf = svc_getrpccaller(transp);
if (nbuf == NULL) {
freenetconfigent(nconf);
return (&anon_hsl);
}
if (netdir_getbyaddr(nconf, &serv, nbuf)) {
freenetconfigent(nconf);
return (&anon_hsl);
}
freenetconfigent(nconf);
return (serv);
}
void
log_cant_reply(transp)
SVCXPRT *transp;
{
int saverrno;
struct nd_hostservlist *clnames;
register char *name;
saverrno = errno; /* save error code */
clnames = getclientsnames(transp);
if (clnames == NULL)
return;
name = clnames->h_hostservs->h_host;
errno = saverrno;
if (errno == 0)
syslog(LOG_ERR, "couldn't send reply to %s", name);
else
syslog(LOG_ERR, "couldn't send reply to %s: %m", name);
}
char *mntopts[] = { MNTOPT_QUOTA, NULL };
#define QUOTA 0
/*
* Return 1 if "quota" appears in the options string
*/
int
hasquota(opts)
char *opts;
{
char *value;
if (opts == NULL)
return (0);
while (*opts != '\0') {
if (getsubopt(&opts, mntopts, &value) == QUOTA)
return (1);
}
return (0);
}
static void
zexit(int n)
{
if (g_zfs != NULL)
_libzfs_fini(g_zfs);
exit(n);
}