+static int overquota( struct dqblk *);
+
+#ifdef linux
+
+#ifdef HAVE_LINUX_XQM_H
+#include <linux/xqm.h>
+#else
+#ifdef HAVE_XFS_XQM_H
+#include <xfs/xqm.h>
+#define HAVE_LINUX_XQM_H
+#else
+#ifdef HAVE_LINUX_DQBLK_XFS_H
+#include <linux/dqblk_xfs.h>
+#define HAVE_LINUX_XQM_H
+#endif /* HAVE_LINUX_DQBLK_XFS_H */
+#endif /* HAVE_XFS_XQM_H */
+#endif /* HAVE_LINUX_XQM_H */
+
+#include <linux/unistd.h>
+
+static int is_xfs = 0;
+
+static int get_linux_xfs_quota(int, char*, uid_t, struct dqblk *);
+static int get_linux_fs_quota(int, char*, uid_t, struct dqblk *);
+
+/* format supported by current kernel */
+static int kernel_iface = IFACE_UNSET;
+
+/*
+** Check kernel quota version
+** Taken from quota-tools 3.08 by Jan Kara <jack@suse.cz>
+*/
+static void linuxquota_get_api( void )
+{
+#ifndef LINUX_API_VERSION
+ struct stat st;
+
+ if (stat("/proc/sys/fs/quota", &st) == 0) {
+ kernel_iface = IFACE_GENERIC;
+ }
+ else {
+ struct dqstats_v2 v2_stats;
+ struct sigaction sig;
+ struct sigaction 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;
+ sig.sa_flags = 0;
+ sigemptyset(&sig.sa_mask);
+ if (sigaction(SIGSEGV, &sig, &oldsig) < 0) {
+ LOG( log_error, logtype_afpd, "cannot set SEGV signal handler: %s", strerror(errno));
+ goto failure;
+ }
+ if (quotactl(QCMD(Q_V2_GETSTATS, 0), NULL, 0, (void *)&v2_stats) >= 0) {
+ 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_iface = IFACE_VFSV0;
+ }
+ else {
+ kernel_iface = IFACE_VFSOLD;
+ }
+ }
+ else {
+ /* This branch is *not* in quota-tools 3.08
+ ** but without it quota version is not correctly
+ ** identified for the original SuSE 8.0 kernel */
+ unsigned int vers_no;
+ FILE * qf;
+
+ if ((qf = fopen("/proc/fs/quota", "r"))) {
+ if (fscanf(qf, "Version %u", &vers_no) == 1) {
+ if ( (vers_no == (6*10000 + 5*100 + 0)) ||
+ (vers_no == (6*10000 + 5*100 + 1)) ) {
+ kernel_iface = IFACE_VFSV0;
+ }
+ }
+ fclose(qf);
+ }
+ }
+ if (sigaction(SIGSEGV, &oldsig, NULL) < 0) {
+ LOG(log_error, logtype_afpd, "cannot reset signal handler: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+failure:
+ if (kernel_iface == IFACE_UNSET)
+ kernel_iface = IFACE_VFSOLD;
+
+#else /* defined LINUX_API_VERSION */
+ kernel_iface = LINUX_API_VERSION;
+#endif
+}
+
+/****************************************************************************/
+
+static int get_linux_quota(int what, char *path, uid_t euser_id, struct dqblk *dp)
+{
+ int r; /* result */
+
+ if ( is_xfs )
+ r=get_linux_xfs_quota(what, path, euser_id, dp);
+ else
+ r=get_linux_fs_quota(what, path, euser_id, dp);
+
+ return r;
+}
+
+/****************************************************************************
+ Abstract out the XFS Quota Manager quota get call.
+****************************************************************************/
+
+static int get_linux_xfs_quota(int what, char *path, uid_t euser_id, struct dqblk *dqb)
+{
+ int ret = -1;
+#ifdef HAVE_LINUX_XQM_H
+ struct fs_disk_quota D;
+
+ memset (&D, 0, sizeof(D));
+
+ if ((ret = quotactl(QCMD(Q_XGETQUOTA,(what ? GRPQUOTA : USRQUOTA)), path, euser_id, (caddr_t)&D)))
+ return ret;
+
+ dqb->bsize = (uint64_t)512;
+ dqb->dqb_bsoftlimit = (uint64_t)D.d_blk_softlimit;
+ dqb->dqb_bhardlimit = (uint64_t)D.d_blk_hardlimit;
+ dqb->dqb_ihardlimit = (uint64_t)D.d_ino_hardlimit;
+ dqb->dqb_isoftlimit = (uint64_t)D.d_ino_softlimit;
+ dqb->dqb_curinodes = (uint64_t)D.d_icount;
+ dqb->dqb_curblocks = (uint64_t)D.d_bcount;
+#endif
+ return ret;
+}
+
+/*
+** Wrapper for the quotactl(GETQUOTA) call.
+** For API v2 the results are copied back into a v1 structure.
+** Taken from quota-1.4.8 perl module
+*/
+static int get_linux_fs_quota(int what, char *path, uid_t euser_id, struct dqblk *dqb)
+{
+ int ret;
+
+ if (kernel_iface == IFACE_UNSET)
+ linuxquota_get_api();
+
+ if (kernel_iface == IFACE_GENERIC)
+ {
+ struct dqblk_v3 dqb3;
+
+ ret = quotactl(QCMD(Q_V3_GETQUOTA, (what ? GRPQUOTA : USRQUOTA)), path, euser_id, (caddr_t) &dqb3);
+ if (ret == 0)
+ {
+ dqb->dqb_bhardlimit = dqb3.dqb_bhardlimit;
+ dqb->dqb_bsoftlimit = dqb3.dqb_bsoftlimit;
+ dqb->dqb_curblocks = dqb3.dqb_curspace / DEV_QBSIZE;
+ dqb->dqb_ihardlimit = dqb3.dqb_ihardlimit;
+ dqb->dqb_isoftlimit = dqb3.dqb_isoftlimit;
+ dqb->dqb_curinodes = dqb3.dqb_curinodes;
+ dqb->dqb_btime = dqb3.dqb_btime;
+ dqb->dqb_itime = dqb3.dqb_itime;
+ dqb->bsize = DEV_QBSIZE;
+ }
+ }
+ else if (kernel_iface == IFACE_VFSV0)
+ {
+ struct dqblk_v2 dqb2;
+
+ ret = quotactl(QCMD(Q_V2_GETQUOTA, (what ? GRPQUOTA : USRQUOTA)), path, euser_id, (caddr_t) &dqb2);
+ if (ret == 0)
+ {
+ dqb->dqb_bhardlimit = dqb2.dqb_bhardlimit;
+ dqb->dqb_bsoftlimit = dqb2.dqb_bsoftlimit;
+ dqb->dqb_curblocks = dqb2.dqb_curspace / DEV_QBSIZE;
+ dqb->dqb_ihardlimit = dqb2.dqb_ihardlimit;
+ dqb->dqb_isoftlimit = dqb2.dqb_isoftlimit;
+ dqb->dqb_curinodes = dqb2.dqb_curinodes;
+ dqb->dqb_btime = dqb2.dqb_btime;
+ dqb->dqb_itime = dqb2.dqb_itime;
+ dqb->bsize = DEV_QBSIZE;
+ }
+ }
+ else /* if (kernel_iface == IFACE_VFSOLD) */
+ {
+ struct dqblk_v1 dqb1;
+
+ ret = quotactl(QCMD(Q_V1_GETQUOTA, (what ? GRPQUOTA : USRQUOTA)), path, euser_id, (caddr_t) &dqb1);
+ if (ret == 0)
+ {
+ dqb->dqb_bhardlimit = dqb1.dqb_bhardlimit;
+ dqb->dqb_bsoftlimit = dqb1.dqb_bsoftlimit;
+ dqb->dqb_curblocks = dqb1.dqb_curblocks;
+ dqb->dqb_ihardlimit = dqb1.dqb_ihardlimit;
+ dqb->dqb_isoftlimit = dqb1.dqb_isoftlimit;
+ dqb->dqb_curinodes = dqb1.dqb_curinodes;
+ dqb->dqb_btime = dqb1.dqb_btime;
+ dqb->dqb_itime = dqb1.dqb_itime;
+ dqb->bsize = DEV_QBSIZE;
+ }
+ }
+ return ret;
+}
+
+#endif /* linux */