smbd: dfree - ignore quota if not enforced
[Samba.git] / source3 / smbd / quotas.c
blob20f74b4acebc299e2d51288627375edfe194e69f
1 /*
2 Unix SMB/CIFS implementation.
3 support for quotas
4 Copyright (C) Andrew Tridgell 1992-1998
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 /*
22 * This is one of the most system dependent parts of Samba, and its
23 * done a litle differently. Each system has its own way of doing
24 * things :-(
27 #include "includes.h"
28 #include "smbd/smbd.h"
29 #include "system/filesys.h"
31 #undef DBGC_CLASS
32 #define DBGC_CLASS DBGC_QUOTA
34 #ifndef HAVE_SYS_QUOTAS
36 /* just a quick hack because sysquotas.h is included before linux/quota.h */
37 #ifdef QUOTABLOCK_SIZE
38 #undef QUOTABLOCK_SIZE
39 #endif
41 #ifdef WITH_QUOTAS
43 #if defined(SUNOS5) /* Solaris */
45 #include <fcntl.h>
46 #include <sys/param.h>
47 #include <sys/fs/ufs_quota.h>
48 #include <sys/mnttab.h>
49 #include <sys/mntent.h>
51 /****************************************************************************
52 Allows querying of remote hosts for quotas on NFS mounted shares.
53 Supports normal NFS and AMD mounts.
54 Alan Romeril <a.romeril@ic.ac.uk> July 2K.
55 ****************************************************************************/
57 #include <rpc/rpc.h>
58 #include <rpc/types.h>
59 #include <rpcsvc/rquota.h>
60 #include <rpc/nettype.h>
61 #include <rpc/xdr.h>
63 static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
65 int quotastat;
67 if (!xdr_int(xdrsp, &quotastat)) {
68 DEBUG(6,("nfs_quotas: Status bad or zero\n"));
69 return 0;
71 gqr->status = quotastat;
73 if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
74 DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
75 return 0;
77 if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
78 DEBUG(6,("nfs_quotas: Active bad or zero\n"));
79 return 0;
81 if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
82 DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
83 return 0;
85 if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
86 DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
87 return 0;
89 if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
90 DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
91 return 0;
93 return (1);
96 static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
98 if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
99 return(0);
100 if (!xdr_int(xdrsp, &args->gqa_uid))
101 return(0);
102 return (1);
105 /* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */
106 static bool nfs_quotas(char *nfspath, uid_t euser_id, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
108 uid_t uid = euser_id;
109 struct dqblk D;
110 char *mnttype = nfspath;
111 CLIENT *clnt;
112 struct getquota_rslt gqr;
113 struct getquota_args args;
114 char *cutstr, *pathname, *host, *testpath;
115 int len;
116 static struct timeval timeout = {2,0};
117 enum clnt_stat clnt_stat;
118 bool ret = True;
120 *bsize = *dfree = *dsize = (uint64_t)0;
122 len=strcspn(mnttype, ":");
123 pathname=strstr(mnttype, ":");
124 cutstr = (char *) SMB_MALLOC(len+1);
125 if (!cutstr)
126 return False;
128 memset(cutstr, '\0', len+1);
129 host = strncat(cutstr,mnttype, sizeof(char) * len );
130 DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
131 DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
132 testpath=strchr_m(mnttype, ':');
133 args.gqa_pathp = testpath+1;
134 args.gqa_uid = uid;
136 DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
138 if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
139 ret = False;
140 goto out;
143 clnt->cl_auth = authunix_create_default();
144 DEBUG(9,("nfs_quotas: auth_success\n"));
146 clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
148 if (clnt_stat != RPC_SUCCESS) {
149 DEBUG(9,("nfs_quotas: clnt_call fail\n"));
150 ret = False;
151 goto out;
155 * gqr.status returns 1 if quotas exist, 2 if there is
156 * no quota set, and 3 if no permission to get the quota.
157 * If 3, return something sensible.
160 switch (gqr.status) {
161 case 1:
162 DEBUG(9,("nfs_quotas: Good quota data\n"));
163 D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
164 D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
165 D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
166 break;
168 case 2:
169 case 3:
170 D.dqb_bsoftlimit = 1;
171 D.dqb_curblocks = 1;
172 DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", gqr.status));
173 break;
175 default:
176 DEBUG(9, ("nfs_quotas: Unknown Remote Quota Status \"%i\"\n",
177 gqr.status));
178 ret = false;
179 goto out;
182 DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n",
183 gqr.status,
184 gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
185 gqr.getquota_rslt_u.gqr_rquota.rq_active,
186 gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
187 gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
188 gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
190 *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
191 *dsize = D.dqb_bsoftlimit;
193 if (D.dqb_curblocks > D.dqb_bsoftlimit) {
194 *dfree = 0;
195 *dsize = D.dqb_curblocks;
196 } else
197 *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
199 out:
201 if (clnt) {
202 if (clnt->cl_auth)
203 auth_destroy(clnt->cl_auth);
204 clnt_destroy(clnt);
207 DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
209 SAFE_FREE(cutstr);
210 DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
211 return ret;
214 /****************************************************************************
215 try to get the disk space from disk quotas (SunOS & Solaris2 version)
216 Quota code by Peter Urbanec (amiga@cse.unsw.edu.au).
217 ****************************************************************************/
219 bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
220 uint64_t *dfree, uint64_t *dsize)
222 uid_t euser_id;
223 int ret;
224 struct dqblk D;
225 struct quotctl command;
226 int file;
227 struct mnttab mnt;
228 char *name = NULL;
229 FILE *fd;
230 SMB_STRUCT_STAT sbuf;
231 SMB_DEV_T devno;
232 bool found = false;
234 euser_id = geteuid();
236 if (sys_stat(path, &sbuf, false) == -1) {
237 return false;
240 devno = sbuf.st_ex_dev ;
241 DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
242 path, (unsigned int)devno));
243 if ((fd = fopen(MNTTAB, "r")) == NULL) {
244 return false;
247 while (getmntent(fd, &mnt) == 0) {
248 if (sys_stat(mnt.mnt_mountp, &sbuf, false) == -1) {
249 continue;
252 DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
253 mnt.mnt_mountp, (unsigned int)devno));
255 /* quotas are only on vxfs, UFS or NFS */
256 if ((sbuf.st_ex_dev == devno) && (
257 strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 ||
258 strcmp( mnt.mnt_fstype, "nfs" ) == 0 ||
259 strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) {
260 found = true;
261 name = talloc_asprintf(talloc_tos(),
262 "%s/quotas",
263 mnt.mnt_mountp);
264 break;
268 fclose(fd);
269 if (!found) {
270 return false;
273 if (!name) {
274 return false;
276 become_root();
278 if (strcmp(mnt.mnt_fstype, "nfs") == 0) {
279 bool retval;
280 DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n",
281 mnt.mnt_special));
282 retval = nfs_quotas(mnt.mnt_special,
283 euser_id, bsize, dfree, dsize);
284 unbecome_root();
285 return retval;
288 DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name));
289 if((file=open(name, O_RDONLY,0))<0) {
290 unbecome_root();
291 return false;
293 command.op = Q_GETQUOTA;
294 command.uid = euser_id;
295 command.addr = (caddr_t) &D;
296 ret = ioctl(file, Q_QUOTACTL, &command);
297 close(file);
299 unbecome_root();
301 if (ret < 0) {
302 DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n",
303 strerror(errno) ));
305 return false;
308 /* If softlimit is zero, set it equal to hardlimit.
311 if (D.dqb_bsoftlimit==0) {
312 D.dqb_bsoftlimit = D.dqb_bhardlimit;
315 /* Use softlimit to determine disk space. A user exceeding the quota
316 * is told that there's no space left. Writes might actually work for
317 * a bit if the hardlimit is set higher than softlimit. Effectively
318 * the disk becomes made of rubber latex and begins to expand to
319 * accommodate the user :-)
322 if (D.dqb_bsoftlimit==0)
323 return(False);
324 *bsize = DEV_BSIZE;
325 *dsize = D.dqb_bsoftlimit;
327 if (D.dqb_curblocks > D.dqb_bsoftlimit) {
328 *dfree = 0;
329 *dsize = D.dqb_curblocks;
330 } else {
331 *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
334 DEBUG(5,("disk_quotas for path \"%s\" returning "
335 "bsize %.0f, dfree %.0f, dsize %.0f\n",
336 path,(double)*bsize,(double)*dfree,(double)*dsize));
338 return true;
342 #else /* not Solaris */
344 #if AIX
345 /* AIX quota patch from Ole Holm Nielsen <ohnielse@fysik.dtu.dk> */
346 #include <jfs/quota.h>
347 /* AIX 4.X: Rename members of the dqblk structure (ohnielse@fysik.dtu.dk) */
348 #define dqb_curfiles dqb_curinodes
349 #define dqb_fhardlimit dqb_ihardlimit
350 #define dqb_fsoftlimit dqb_isoftlimit
351 #ifdef _AIXVERSION_530
352 #include <sys/statfs.h>
353 #include <sys/vmount.h>
354 #endif /* AIX 5.3 */
355 #else /* !AIX */
356 #include <sys/quota.h>
357 #include <devnm.h>
358 #endif
361 /****************************************************************************
362 try to get the disk space from disk quotas - default version
363 ****************************************************************************/
365 bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
366 uint64_t *dfree, uint64_t *dsize)
368 int r;
369 struct dqblk D;
370 uid_t euser_id;
371 #if !defined(AIX)
372 char dev_disk[256];
373 SMB_STRUCT_STAT S;
375 /* find the block device file */
377 if ((sys_stat(path, &S, false)<0)
378 || (devnm(S_IFBLK, S.st_ex_dev, dev_disk, 256, 0)<0))
379 return (False);
381 #endif /* !defined(AIX) */
383 euser_id = geteuid();
385 #if defined(AIX)
386 /* AIX has both USER and GROUP quotas:
387 Get the USER quota (ohnielse@fysik.dtu.dk) */
388 #ifdef _AIXVERSION_530
390 struct statfs statbuf;
391 quota64_t user_quota;
392 if (statfs(path,&statbuf) != 0)
393 return False;
394 if(statbuf.f_vfstype == MNT_J2)
396 /* For some reason we need to be root for jfs2 */
397 become_root();
398 r = quotactl(path,QCMD(Q_J2GETQUOTA,USRQUOTA),euser_id,(char *) &user_quota);
399 unbecome_root();
400 /* Copy results to old struct to let the following code work as before */
401 D.dqb_curblocks = user_quota.bused;
402 D.dqb_bsoftlimit = user_quota.bsoft;
403 D.dqb_bhardlimit = user_quota.bhard;
404 D.dqb_curfiles = user_quota.iused;
405 D.dqb_fsoftlimit = user_quota.isoft;
406 D.dqb_fhardlimit = user_quota.ihard;
408 else if(statbuf.f_vfstype == MNT_JFS)
410 #endif /* AIX 5.3 */
411 save_re_uid();
412 if (set_re_uid() != 0)
413 return False;
414 r= quotactl(path,QCMD(Q_GETQUOTA,USRQUOTA),euser_id,(char *) &D);
415 restore_re_uid();
416 #ifdef _AIXVERSION_530
418 else
419 r = 1; /* Fail for other FS-types */
421 #endif /* AIX 5.3 */
422 #else /* !AIX */
423 r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D);
424 #endif /* !AIX */
426 /* Use softlimit to determine disk space, except when it has been exceeded */
427 *bsize = 1024;
429 if (r)
431 if (errno == EDQUOT)
433 *dfree =0;
434 *dsize =D.dqb_curblocks;
435 return (True);
437 else return(False);
440 /* If softlimit is zero, set it equal to hardlimit.
443 if (D.dqb_bsoftlimit==0)
444 D.dqb_bsoftlimit = D.dqb_bhardlimit;
446 if (D.dqb_bsoftlimit==0)
447 return(False);
448 /* Use softlimit to determine disk space, except when it has been exceeded */
449 if ((D.dqb_curblocks>D.dqb_bsoftlimit)
450 ||((D.dqb_curfiles>D.dqb_fsoftlimit) && (D.dqb_fsoftlimit != 0))
452 *dfree = 0;
453 *dsize = D.dqb_curblocks;
455 else {
456 *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
457 *dsize = D.dqb_bsoftlimit;
459 return (True);
462 #endif /* Solaris */
464 #else /* WITH_QUOTAS */
466 bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
467 uint64_t *dfree, uint64_t *dsize)
469 (*bsize) = 512; /* This value should be ignored */
471 /* And just to be sure we set some values that hopefully */
472 /* will be larger that any possible real-world value */
473 (*dfree) = (uint64_t)-1;
474 (*dsize) = (uint64_t)-1;
476 /* As we have select not to use quotas, allways fail */
477 return false;
479 #endif /* WITH_QUOTAS */
481 #else /* HAVE_SYS_QUOTAS */
482 /* wrapper to the new sys_quota interface
483 this file should be removed later
485 bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
486 uint64_t *dfree, uint64_t *dsize)
488 int r;
489 SMB_DISK_QUOTA D;
490 unid_t id;
493 * First of all, check whether user quota is
494 * enforced. If the call fails, assume it is
495 * not enforced.
497 ZERO_STRUCT(D);
498 id.uid = -1;
499 r = SMB_VFS_GET_QUOTA(conn, path, SMB_USER_FS_QUOTA_TYPE, id, &D);
500 if (r == -1 && errno != ENOSYS) {
501 goto try_group_quota;
503 if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
504 goto try_group_quota;
507 ZERO_STRUCT(D);
508 id.uid = geteuid();
510 r = SMB_VFS_GET_QUOTA(conn, path, SMB_USER_QUOTA_TYPE, id, &D);
512 /* Use softlimit to determine disk space, except when it has been exceeded */
513 *bsize = D.bsize;
514 if (r == -1) {
515 if (errno == EDQUOT) {
516 *dfree =0;
517 *dsize =D.curblocks;
518 return (True);
519 } else {
520 goto try_group_quota;
524 /* Use softlimit to determine disk space, except when it has been exceeded */
525 if (
526 (D.softlimit && D.curblocks >= D.softlimit) ||
527 (D.hardlimit && D.curblocks >= D.hardlimit) ||
528 (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
529 (D.ihardlimit && D.curinodes>=D.ihardlimit)
531 *dfree = 0;
532 *dsize = D.curblocks;
533 } else if (D.softlimit==0 && D.hardlimit==0) {
534 goto try_group_quota;
535 } else {
536 if (D.softlimit == 0) {
537 D.softlimit = D.hardlimit;
539 *dfree = D.softlimit - D.curblocks;
540 *dsize = D.softlimit;
543 return True;
545 try_group_quota:
547 * First of all, check whether group quota is
548 * enforced. If the call fails, assume it is
549 * not enforced.
551 ZERO_STRUCT(D);
552 id.gid = -1;
553 r = SMB_VFS_GET_QUOTA(conn, path, SMB_GROUP_FS_QUOTA_TYPE, id, &D);
554 if (r == -1 && errno != ENOSYS) {
555 return false;
557 if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
558 return false;
561 id.gid = getegid();
563 ZERO_STRUCT(D);
564 r = SMB_VFS_GET_QUOTA(conn, path, SMB_GROUP_QUOTA_TYPE, id, &D);
566 /* Use softlimit to determine disk space, except when it has been exceeded */
567 *bsize = D.bsize;
568 if (r == -1) {
569 if (errno == EDQUOT) {
570 *dfree =0;
571 *dsize =D.curblocks;
572 return (True);
573 } else {
574 return False;
578 /* Use softlimit to determine disk space, except when it has been exceeded */
579 if (
580 (D.softlimit && D.curblocks >= D.softlimit) ||
581 (D.hardlimit && D.curblocks >= D.hardlimit) ||
582 (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
583 (D.ihardlimit && D.curinodes>=D.ihardlimit)
585 *dfree = 0;
586 *dsize = D.curblocks;
587 } else if (D.softlimit==0 && D.hardlimit==0) {
588 return False;
589 } else {
590 if (D.softlimit == 0) {
591 D.softlimit = D.hardlimit;
593 *dfree = D.softlimit - D.curblocks;
594 *dsize = D.softlimit;
597 return (True);
599 #endif /* HAVE_SYS_QUOTAS */