/*
**  Linux quotactl wrapper - support both quota API v1 and v2
*/

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>

#include "myconfig.h"

/* API v1 command definitions */
#define Q1_GETQUOTA  0x0300
#define Q1_SETQUOTA  0x0400
/* API v2 command definitions */
#define Q2_GETQUOTA  0x0D00
#define Q2_SETQUOTA  0x0E00
#define Q2_GETSTATS  0x1100

/*
** Copy of struct declarations in the v2 quota.h header file
** (with structure names changed to avoid conflicts with v2 headers).
** This is required to be able to compile with v1 kernel headers.
*/

struct dqstats_v2 {
  u_int32_t lookups;
  u_int32_t drops;
  u_int32_t reads;
  u_int32_t writes;
  u_int32_t cache_hits;
  u_int32_t allocated_dquots;
  u_int32_t free_dquots;
  u_int32_t syncs;
  u_int32_t version;
};

struct dqblk_v2 {
  unsigned int dqb_ihardlimit;
  unsigned int dqb_isoftlimit;
  unsigned int dqb_curinodes;
  unsigned int dqb_bhardlimit;
  unsigned int dqb_bsoftlimit;
  qsize_t dqb_curspace;
  __kernel_time_t dqb_btime;
  __kernel_time_t dqb_itime;
};


/* this variable holds the API version number.
** 0 if not initialized
** 3 if determination failed (ignored)
*/
static int linux_api = 0;

/*
**  Check kernel quota version
**  Derived from quota-tools 3.01 by Jan Kara <jack@suse.cz>
*/

#define KERN_KNOWN_QUOTA_VERSION (6*10000 + 5*100 + 0)

static void linuxquota_get_api( void )
{
  struct dqstats_v2 stats;

  if (quotactl(QCMD(Q2_GETSTATS, 0), NULL, 0, (void *)&stats) == 0)
  {
    if (stats.version == KERN_KNOWN_QUOTA_VERSION)
      linux_api = 2;
    else
      linux_api = 3;
  }
  else
  {
    if (errno == EINVAL || errno == EFAULT || errno == EPERM)
      linux_api = 1;
    else
      linux_api = 3;
  }
}

/*
** Wrapper for the quotactl(GETQUOTA) call.
** For API v2 the results are copied back into a v1 structure.
*/
int linuxquota_query( const char * dev, int uid, int isgrp, struct dqblk * dqb )
{
  struct dqblk_v2 dqb2;
  int ret;

  if (linux_api == 0)
    linuxquota_get_api();

  if (linux_api == 2)
  {
    ret = quotactl(QCMD(Q2_GETQUOTA, (isgrp ? GRPQUOTA : USRQUOTA)), dev, uid, (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;
    }
  }
  else /* if (linux_api = 1) */
  {
    ret = quotactl(QCMD(Q1_GETQUOTA, (isgrp ? GRPQUOTA : USRQUOTA)), dev, uid, (caddr_t) dqb);
  }
  return ret;
}

/*
** Wrapper for the quotactl(GETQUOTA) call.
** For API v2 the parameters are copied into a v2 structure.
*/
int linuxquota_setqlim( const char * dev, int uid, int isgrp, struct dqblk * dqb )
{
  struct dqblk_v2 dqb2;
  int ret;

  if (linux_api == 0)
    linuxquota_get_api();

  if (linux_api == 2)
  {
    dqb2.dqb_bhardlimit = dqb->dqb_bhardlimit;
    dqb2.dqb_bsoftlimit = dqb->dqb_bsoftlimit;
    dqb2.dqb_curspace   = 0;
    dqb2.dqb_ihardlimit = dqb->dqb_ihardlimit;
    dqb2.dqb_isoftlimit = dqb->dqb_isoftlimit;
    dqb2.dqb_curinodes  = 0;
    dqb2.dqb_btime      = dqb->dqb_btime;
    dqb2.dqb_itime      = dqb->dqb_itime;

    ret = quotactl (QCMD(Q_SETQLIM, (isgrp ? GRPQUOTA : USRQUOTA)), dev, uid, (caddr_t) &dqb2);
  }
  else /* if (linux_api = 1) */
  {
    dqb->QS_BCUR  = 0;
    dqb->QS_FCUR  = 0;
    ret = quotactl (QCMD(Q_SETQLIM, (isgrp ? GRPQUOTA : USRQUOTA)), dev, uid, (caddr_t) dqb);
  }

  return ret;
}

#if 0
main()
{
  linuxquota_get_api();
  printf("API=%d\n", linux_api);
}
#endif