/* quotactl.cc: code for manipulating disk quotas

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include "cygtls.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "ntdll.h"
#include "tls_pbuf.h"
#include <sys/mount.h>
#include <sys/quota.h>

#define PGQI_SIZE (sizeof (FILE_GET_QUOTA_INFORMATION) + SECURITY_MAX_SID_SIZE)
#define PFQI_SIZE (sizeof (FILE_QUOTA_INFORMATION) + SECURITY_MAX_SID_SIZE)

/* Modelled after the Linux quotactl function. */
extern "C" int
quotactl (int cmd, const char *special, int id, caddr_t addr)
{
  ACCESS_MASK access = FILE_READ_DATA;
  PSID sid = NO_SID;
  path_conv pc;
  tmp_pathbuf tp;
  UNICODE_STRING path;
  OBJECT_ATTRIBUTES attr;
  NTSTATUS status;
  HANDLE fh;
  IO_STATUS_BLOCK io;
  FILE_FS_CONTROL_INFORMATION ffci;
  int ret = 0;

  uint32_t subcmd = (uint32_t) cmd >> SUBCMDSHIFT;
  uint32_t type = (uint32_t) cmd & SUBCMDMASK;

  if (type != USRQUOTA && type != GRPQUOTA)
    {
      set_errno (EINVAL);
      return -1;
    }
  switch (subcmd)
    {
    case Q_SYNC:
      if (!special)
	return 0;
      access |= FILE_WRITE_DATA;
      break;
    case Q_QUOTAON:
      if (id < QFMT_VFS_OLD || id > QFMT_VFS_V1)
	{
	  set_errno (EINVAL);
	  return -1;
	}
      fallthrough;
    case Q_QUOTAOFF:
    case Q_SETINFO:
      access |= FILE_WRITE_DATA;
      break;
    case Q_GETFMT:
    case Q_GETINFO:
      break;
    case Q_SETQUOTA:
      access |= FILE_WRITE_DATA;
      fallthrough;
    case Q_GETQUOTA:
      /* Windows feature: Default limits.  Get or set them with id == -1. */
      if (id != -1)
	{
	  if (type == USRQUOTA)
	    sid = sidfromuid (id, NULL);
	  else
	    sid = sidfromgid (id, NULL);
	  if (sid == NO_SID)
	    {
	      set_errno (EINVAL);
	      return -1;
	    }
	}
      break;
    default:
      set_errno (EINVAL);
      return -1;
    }
  /* Check path */
  pc.check (special, PC_SYM_FOLLOW, stat_suffixes);
  if (pc.error)
    {
      set_errno (pc.error);
      return -1;
    }
  if (!pc.exists ())
    {
      set_errno (ENOENT);
      return -1;
    }
  if (!S_ISBLK (pc.dev.mode ()))
    {
      set_errno (ENOTBLK);
      return -1;
    }
  pc.get_object_attr (attr, sec_none_nih);
  /* For the following functions to work, we must attach the virtual path to
     the quota file to the device path.

     FIXME: Note that this is NTFS-specific.  Adding ReFS in another step. */
  tp.u_get (&path);
  RtlCopyUnicodeString (&path, attr.ObjectName);
  RtlAppendUnicodeToString (&path, L"\\$Extend\\$Quota:$Q:$INDEX_ALLOCATION");
  attr.ObjectName = &path;

  /* Open filesystem */
  status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_VALID_FLAGS, 0);
  if (NT_SUCCESS (status))
    switch (subcmd)
      {
      case Q_SYNC:
	/* No sync, just report success. */
	status = STATUS_SUCCESS;
	break;
      case Q_QUOTAON:
      case Q_QUOTAOFF:
	/* Ignore filename in addr. */
	status = NtQueryVolumeInformationFile (fh, &io, &ffci, sizeof ffci,
					       FileFsControlInformation);
	if (!NT_SUCCESS (status))
	  break;
	ffci.FileSystemControlFlags &= ~FILE_VC_QUOTA_ENFORCE
				       & ~FILE_VC_QUOTA_TRACK
				       & ~FILE_VC_QUOTAS_INCOMPLETE
				       & ~FILE_VC_QUOTAS_REBUILDING;
	if (subcmd == Q_QUOTAON)
	  ffci.FileSystemControlFlags |= FILE_VC_QUOTA_ENFORCE;
	status = NtSetVolumeInformationFile (fh, &io, &ffci, sizeof ffci,
					     FileFsControlInformation);
	break;
      case Q_GETFMT:
	__try
	  {
	    uint32_t *retval = (uint32_t *) addr;

	    /* Always fake the latest format. */
	    *retval = QFMT_VFS_V1;
	  }
	__except (EFAULT)
	  {
	    ret = -1;
	    break;
	  }
	__endtry
	status = STATUS_SUCCESS;
	break;
      case Q_GETINFO:
	__try
	  {
	    struct dqinfo *dqi = (struct dqinfo *) addr;

	    dqi->dqi_bgrace = dqi->dqi_igrace = UINT64_MAX;
	    dqi->dqi_flags = 0;
	    dqi->dqi_valid = IIF_BGRACE | IIF_IGRACE;
	  }
	__except (EFAULT)
	  {
	    ret = -1;
	    break;
	  }
	__endtry
	status = STATUS_SUCCESS;
	break;
      case Q_SETINFO:
	/* No settings possible, just report success. */
	status = STATUS_SUCCESS;
	break;
      case Q_GETQUOTA:
	/* Windows feature: Default limits.  Get or set them with id == -1. */
	if (id == -1)
	  {
	    status = NtQueryVolumeInformationFile (fh, &io, &ffci, sizeof ffci,
						   FileFsControlInformation);
	    if (!NT_SUCCESS (status))
	      break;
	    __try
	      {
		struct dqblk *dq = (struct dqblk *) addr;

		dq->dqb_bhardlimit = (uint64_t) ffci.DefaultQuotaLimit.QuadPart;
		if (dq->dqb_bhardlimit != UINT64_MAX)
		  dq->dqb_bhardlimit /= BLOCK_SIZE;
		dq->dqb_bsoftlimit =
				(uint64_t) ffci.DefaultQuotaThreshold.QuadPart;
		if (dq->dqb_bsoftlimit != UINT64_MAX)
		  dq->dqb_bsoftlimit /= BLOCK_SIZE;
		dq->dqb_curspace = 0;
		dq->dqb_ihardlimit = UINT64_MAX;
		dq->dqb_isoftlimit = UINT64_MAX;
		dq->dqb_curinodes = 0;
		dq->dqb_btime = UINT64_MAX;
		dq->dqb_itime = UINT64_MAX;
		dq->dqb_valid = QIF_BLIMITS;
	      }
	    __except (EFAULT)
	      {
		ret = -1;
		break;
	      }
	    __endtry
	  }
	else
	  {
	    PFILE_GET_QUOTA_INFORMATION pgqi = (PFILE_GET_QUOTA_INFORMATION)
					       alloca (PGQI_SIZE);
	    PFILE_QUOTA_INFORMATION pfqi = (PFILE_QUOTA_INFORMATION)
					   alloca (PFQI_SIZE);

	    pgqi->NextEntryOffset = 0;
	    pgqi->SidLength = RtlLengthSid (sid);
	    RtlCopySid (RtlLengthSid (sid), &pgqi->Sid, sid);
	    status = NtQueryQuotaInformationFile (fh, &io, pfqi, PFQI_SIZE,
						  TRUE, pgqi, PGQI_SIZE,
						  NULL, TRUE);
	    if (!NT_SUCCESS (status))
	      break;
	    __try
	      {
		struct dqblk *dq = (struct dqblk *) addr;

		dq->dqb_bhardlimit = (uint64_t) pfqi->QuotaLimit.QuadPart;
		if (dq->dqb_bhardlimit != UINT64_MAX)
		  dq->dqb_bhardlimit /= BLOCK_SIZE;
		dq->dqb_bsoftlimit = (uint64_t) pfqi->QuotaThreshold.QuadPart;
		if (dq->dqb_bsoftlimit != UINT64_MAX)
		  dq->dqb_bsoftlimit /= BLOCK_SIZE;
		dq->dqb_curspace = (uint64_t) pfqi->QuotaUsed.QuadPart;
		if (dq->dqb_curspace != UINT64_MAX)
		  dq->dqb_curspace /= BLOCK_SIZE;
		dq->dqb_ihardlimit = UINT64_MAX;
		dq->dqb_isoftlimit = UINT64_MAX;
		dq->dqb_curinodes = 0;
		dq->dqb_btime = UINT64_MAX;
		dq->dqb_itime = UINT64_MAX;
		dq->dqb_valid = QIF_BLIMITS | QIF_SPACE;
	      }
	    __except (EFAULT)
	      {
		ret = -1;
		break;
	      }
	    __endtry
	  }
	break;
      case Q_SETQUOTA:
	/* Windows feature: Default limits.  Get or set them with id == -1. */
	if (id == -1)
	  {
	    status = NtQueryVolumeInformationFile (fh, &io, &ffci, sizeof ffci,
						   FileFsControlInformation);
	    if (!NT_SUCCESS (status))
	      break;
	    __try
	      {
		struct dqblk *dq = (struct dqblk *) addr;

		if (!(dq->dqb_valid & QIF_BLIMITS))
		  break;
		ffci.DefaultQuotaLimit.QuadPart = dq->dqb_bhardlimit;
		if (ffci.DefaultQuotaLimit.QuadPart != -1)
		  ffci.DefaultQuotaLimit.QuadPart *= BLOCK_SIZE;
		ffci.DefaultQuotaThreshold.QuadPart = dq->dqb_bsoftlimit;
		if (ffci.DefaultQuotaThreshold.QuadPart != -1)
		  ffci.DefaultQuotaThreshold.QuadPart *= BLOCK_SIZE;
	      }
	    __except (EFAULT)
	      {
	        ret = -1;
		break;
	      }
	    __endtry
	    status = NtSetVolumeInformationFile (fh, &io, &ffci, sizeof ffci,
						 FileFsControlInformation);
	  }
	else
	  {
	    PFILE_GET_QUOTA_INFORMATION pgqi = (PFILE_GET_QUOTA_INFORMATION)
					       alloca (PGQI_SIZE);
	    PFILE_QUOTA_INFORMATION pfqi = (PFILE_QUOTA_INFORMATION)
					   alloca (PFQI_SIZE);

	    pgqi->NextEntryOffset = 0;
	    pgqi->SidLength = RtlLengthSid (sid);
	    RtlCopySid (RtlLengthSid (sid), &pgqi->Sid, sid);
	    status = NtQueryQuotaInformationFile (fh, &io, pfqi, PFQI_SIZE,
						  TRUE, pgqi, PGQI_SIZE,
						  NULL, TRUE);
	    if (!NT_SUCCESS (status))
	      break;
	    __try
	      {
		struct dqblk *dq = (struct dqblk *) addr;

		if (!(dq->dqb_valid & QIF_BLIMITS))
		  break;
		pfqi->QuotaLimit.QuadPart = dq->dqb_bhardlimit;
		if (pfqi->QuotaLimit.QuadPart != -1)
		  pfqi->QuotaLimit.QuadPart *= BLOCK_SIZE;
		pfqi->QuotaThreshold.QuadPart = dq->dqb_bsoftlimit;
		if (pfqi->QuotaThreshold.QuadPart != -1)
		  pfqi->QuotaThreshold.QuadPart *= BLOCK_SIZE;
	      }
	    __except (EFAULT)
	      {
		ret = -1;
		break;
	      }
	    __endtry
	    status = NtSetQuotaInformationFile (fh, &io, pfqi, PFQI_SIZE);
	  }
	break;
      }
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      ret = -1;
    }
  return ret;
}
