]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libutil/quotafile.c
MFV: zlib 1.3
[FreeBSD/FreeBSD.git] / lib / libutil / quotafile.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2008 Dag-Erling Smørgrav
5  * Copyright (c) 2008 Marshall Kirk McKusick
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in this position and unchanged.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 #include <sys/types.h>
32 #include <sys/endian.h>
33 #include <sys/mount.h>
34 #include <sys/stat.h>
35
36 #include <ufs/ufs/quota.h>
37
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <fstab.h>
41 #include <grp.h>
42 #include <pwd.h>
43 #include <libutil.h>
44 #include <stdint.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49
50 struct quotafile {
51         int fd;                         /* -1 means using quotactl for access */
52         int accmode;                    /* access mode */
53         int wordsize;                   /* 32-bit or 64-bit limits */
54         int quotatype;                  /* USRQUOTA or GRPQUOTA */
55         dev_t dev;                      /* device */
56         char fsname[MAXPATHLEN + 1];    /* mount point of filesystem */
57         char qfname[MAXPATHLEN + 1];    /* quota file if not using quotactl */
58 };
59
60 static const char *qfextension[] = INITQFNAMES;
61
62 /*
63  * Check to see if a particular quota is to be enabled.
64  */
65 static int
66 hasquota(struct fstab *fs, int type, char *qfnamep, int qfbufsize)
67 {
68         char *opt;
69         char *cp;
70         struct statfs sfb;
71         char buf[BUFSIZ];
72         static char initname, usrname[100], grpname[100];
73
74         /*
75          * 1) we only need one of these
76          * 2) fstab may specify a different filename
77          */
78         if (!initname) {
79                 (void)snprintf(usrname, sizeof(usrname), "%s%s",
80                     qfextension[USRQUOTA], QUOTAFILENAME);
81                 (void)snprintf(grpname, sizeof(grpname), "%s%s",
82                     qfextension[GRPQUOTA], QUOTAFILENAME);
83                 initname = 1;
84         }
85         strcpy(buf, fs->fs_mntops);
86         for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
87                 if ((cp = strchr(opt, '=')))
88                         *cp++ = '\0';
89                 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
90                         break;
91                 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
92                         break;
93         }
94         if (!opt)
95                 return (0);
96         /*
97          * Ensure that the filesystem is mounted.
98          */
99         if (statfs(fs->fs_file, &sfb) != 0 ||
100             strcmp(fs->fs_file, sfb.f_mntonname)) {
101                 return (0);
102         }
103         if (cp) {
104                 strlcpy(qfnamep, cp, qfbufsize);
105         } else {
106                 (void)snprintf(qfnamep, qfbufsize, "%s/%s.%s", fs->fs_file,
107                     QUOTAFILENAME, qfextension[type]);
108         }
109         return (1);
110 }
111
112 struct quotafile *
113 quota_open(struct fstab *fs, int quotatype, int openflags)
114 {
115         struct quotafile *qf;
116         struct dqhdr64 dqh;
117         struct group *grp;
118         struct stat st;
119         int qcmd, serrno = 0;
120         int ufs;
121
122         if ((qf = calloc(1, sizeof(*qf))) == NULL)
123                 return (NULL);
124         qf->fd = -1;
125         qf->quotatype = quotatype;
126         strlcpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
127         if (stat(qf->fsname, &st) != 0)
128                 goto error;
129         qf->dev = st.st_dev;
130         qcmd = QCMD(Q_GETQUOTASIZE, quotatype);
131         ufs = strcmp(fs->fs_vfstype, "ufs") == 0;
132         /*
133          * On UFS, hasquota() fills in qf->qfname. But we only care about
134          * this for UFS.  So we need to call hasquota() for UFS, first.
135          */
136         if (ufs) {
137                 serrno = hasquota(fs, quotatype, qf->qfname,
138                     sizeof(qf->qfname));
139         }
140         if (quotactl(qf->fsname, qcmd, 0, &qf->wordsize) == 0)
141                 return (qf);
142         if (!ufs) {
143                 errno = 0;
144                 goto error;
145         } else if (serrno == 0) {
146                 errno = EOPNOTSUPP;
147                 goto error;
148         }
149         qf->accmode = openflags & O_ACCMODE;
150         if ((qf->fd = open(qf->qfname, qf->accmode|O_CLOEXEC)) < 0 &&
151             (openflags & O_CREAT) != O_CREAT)
152                 goto error;
153         /* File open worked, so process it */
154         if (qf->fd != -1) {
155                 qf->wordsize = 32;
156                 switch (read(qf->fd, &dqh, sizeof(dqh))) {
157                 case -1:
158                         goto error;
159                 case sizeof(dqh):
160                         if (strcmp(dqh.dqh_magic, Q_DQHDR64_MAGIC) != 0) {
161                                 /* no magic, assume 32 bits */
162                                 qf->wordsize = 32;
163                                 return (qf);
164                         }
165                         if (be32toh(dqh.dqh_version) != Q_DQHDR64_VERSION ||
166                             be32toh(dqh.dqh_hdrlen) != sizeof(struct dqhdr64) ||
167                             be32toh(dqh.dqh_reclen) != sizeof(struct dqblk64)) {
168                                 /* correct magic, wrong version / lengths */
169                                 errno = EINVAL;
170                                 goto error;
171                         }
172                         qf->wordsize = 64;
173                         return (qf);
174                 default:
175                         qf->wordsize = 32;
176                         return (qf);
177                 }
178                 /* not reached */
179         }
180         /* open failed, but O_CREAT was specified, so create a new file */
181         if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0)) <
182             0)
183                 goto error;
184         qf->wordsize = 64;
185         memset(&dqh, 0, sizeof(dqh));
186         memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
187         dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
188         dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
189         dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
190         if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
191                 /* it was one we created ourselves */
192                 unlink(qf->qfname);
193                 goto error;
194         }
195         grp = getgrnam(QUOTAGROUP);
196         fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
197         fchmod(qf->fd, 0640);
198         return (qf);
199 error:
200         serrno = errno;
201         /* did we have an open file? */
202         if (qf->fd != -1)
203                 close(qf->fd);
204         free(qf);
205         errno = serrno;
206         return (NULL);
207 }
208
209 void
210 quota_close(struct quotafile *qf)
211 {
212
213         if (qf->fd != -1)
214                 close(qf->fd);
215         free(qf);
216 }
217
218 int
219 quota_on(struct quotafile *qf)
220 {
221         int qcmd;
222
223         qcmd = QCMD(Q_QUOTAON, qf->quotatype);
224         return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
225 }
226
227 int
228 quota_off(struct quotafile *qf)
229 {
230
231         return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
232 }
233
234 const char *
235 quota_fsname(const struct quotafile *qf)
236 {
237
238         return (qf->fsname);
239 }
240
241 const char *
242 quota_qfname(const struct quotafile *qf)
243 {
244
245         return (qf->qfname);
246 }
247
248 int
249 quota_check_path(const struct quotafile *qf, const char *path)
250 {
251         struct stat st;
252
253         if (stat(path, &st) == -1)
254                 return (-1);
255         return (st.st_dev == qf->dev);
256 }
257
258 int
259 quota_maxid(struct quotafile *qf)
260 {
261         struct stat st;
262         int maxid;
263
264         if (stat(qf->qfname, &st) < 0)
265                 return (0);
266         switch (qf->wordsize) {
267         case 32:
268                 maxid = st.st_size / sizeof(struct dqblk32) - 1;
269                 break;
270         case 64:
271                 maxid = st.st_size / sizeof(struct dqblk64) - 2;
272                 break;
273         default:
274                 maxid = 0;
275                 break;
276         }
277         return (maxid > 0 ? maxid : 0);
278 }
279
280 static int
281 quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
282 {
283         struct dqblk32 dqb32;
284         off_t off;
285
286         off = id * sizeof(struct dqblk32);
287         if (lseek(qf->fd, off, SEEK_SET) == -1)
288                 return (-1);
289         switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
290         case 0:
291                 memset(dqb, 0, sizeof(*dqb));
292                 return (0);
293         case sizeof(dqb32):
294                 dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
295                 dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
296                 dqb->dqb_curblocks = dqb32.dqb_curblocks;
297                 dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
298                 dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
299                 dqb->dqb_curinodes = dqb32.dqb_curinodes;
300                 dqb->dqb_btime = dqb32.dqb_btime;
301                 dqb->dqb_itime = dqb32.dqb_itime;
302                 return (0);
303         default:
304                 return (-1);
305         }
306 }
307
308 static int
309 quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
310 {
311         struct dqblk64 dqb64;
312         off_t off;
313
314         off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
315         if (lseek(qf->fd, off, SEEK_SET) == -1)
316                 return (-1);
317         switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
318         case 0:
319                 memset(dqb, 0, sizeof(*dqb));
320                 return (0);
321         case sizeof(dqb64):
322                 dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
323                 dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
324                 dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
325                 dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
326                 dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
327                 dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
328                 dqb->dqb_btime = be64toh(dqb64.dqb_btime);
329                 dqb->dqb_itime = be64toh(dqb64.dqb_itime);
330                 return (0);
331         default:
332                 return (-1);
333         }
334 }
335
336 int
337 quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
338 {
339         int qcmd;
340
341         if (qf->fd == -1) {
342                 qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
343                 return (quotactl(qf->fsname, qcmd, id, dqb));
344         }
345         switch (qf->wordsize) {
346         case 32:
347                 return (quota_read32(qf, dqb, id));
348         case 64:
349                 return (quota_read64(qf, dqb, id));
350         default:
351                 errno = EINVAL;
352                 return (-1);
353         }
354         /* not reached */
355 }
356
357 #define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
358
359 static int
360 quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
361 {
362         struct dqblk32 dqb32;
363         off_t off;
364
365         dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
366         dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
367         dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
368         dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
369         dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
370         dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
371         dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
372         dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
373
374         off = id * sizeof(struct dqblk32);
375         if (lseek(qf->fd, off, SEEK_SET) == -1)
376                 return (-1);
377         if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
378                 return (0);
379         return (-1);
380 }
381
382 static int
383 quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
384 {
385         struct dqblk64 dqb64;
386         off_t off;
387
388         dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
389         dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
390         dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
391         dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
392         dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
393         dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
394         dqb64.dqb_btime = htobe64(dqb->dqb_btime);
395         dqb64.dqb_itime = htobe64(dqb->dqb_itime);
396
397         off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
398         if (lseek(qf->fd, off, SEEK_SET) == -1)
399                 return (-1);
400         if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
401                 return (0);
402         return (-1);
403 }
404
405 int
406 quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
407 {
408         struct dqblk dqbuf;
409         int qcmd;
410
411         if (qf->fd == -1) {
412                 qcmd = QCMD(Q_SETUSE, qf->quotatype);
413                 return (quotactl(qf->fsname, qcmd, id, dqb));
414         }
415         /*
416          * Have to do read-modify-write of quota in file.
417          */
418         if ((qf->accmode & O_RDWR) != O_RDWR) {
419                 errno = EBADF;
420                 return (-1);
421         }
422         if (quota_read(qf, &dqbuf, id) != 0)
423                 return (-1);
424         /*
425          * Reset time limit if have a soft limit and were
426          * previously under it, but are now over it.
427          */
428         if (dqbuf.dqb_bsoftlimit && id != 0 &&
429             dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
430             dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
431                 dqbuf.dqb_btime = 0;
432         if (dqbuf.dqb_isoftlimit && id != 0 &&
433             dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
434             dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
435                 dqbuf.dqb_itime = 0;
436         dqbuf.dqb_curinodes = dqb->dqb_curinodes;
437         dqbuf.dqb_curblocks = dqb->dqb_curblocks;
438         /*
439          * Write it back.
440          */
441         switch (qf->wordsize) {
442         case 32:
443                 return (quota_write32(qf, &dqbuf, id));
444         case 64:
445                 return (quota_write64(qf, &dqbuf, id));
446         default:
447                 errno = EINVAL;
448                 return (-1);
449         }
450         /* not reached */
451 }
452
453 int
454 quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
455 {
456         struct dqblk dqbuf;
457         int qcmd;
458
459         if (qf->fd == -1) {
460                 qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
461                 return (quotactl(qf->fsname, qcmd, id, dqb));
462         }
463         /*
464          * Have to do read-modify-write of quota in file.
465          */
466         if ((qf->accmode & O_RDWR) != O_RDWR) {
467                 errno = EBADF;
468                 return (-1);
469         }
470         if (quota_read(qf, &dqbuf, id) != 0)
471                 return (-1);
472         /*
473          * Reset time limit if have a soft limit and were
474          * previously under it, but are now over it
475          * or if there previously was no soft limit, but
476          * now have one and are over it.
477          */
478         if (dqbuf.dqb_bsoftlimit && id != 0 &&
479             dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
480             dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
481                 dqb->dqb_btime = 0;
482         if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
483             dqb->dqb_bsoftlimit > 0 &&
484             dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
485                 dqb->dqb_btime = 0;
486         if (dqbuf.dqb_isoftlimit && id != 0 &&
487             dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
488             dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
489                 dqb->dqb_itime = 0;
490         if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
491             dqb->dqb_isoftlimit > 0 &&
492             dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
493                 dqb->dqb_itime = 0;
494         dqb->dqb_curinodes = dqbuf.dqb_curinodes;
495         dqb->dqb_curblocks = dqbuf.dqb_curblocks;
496         /*
497          * Write it back.
498          */
499         switch (qf->wordsize) {
500         case 32:
501                 return (quota_write32(qf, dqb, id));
502         case 64:
503                 return (quota_write64(qf, dqb, id));
504         default:
505                 errno = EINVAL;
506                 return (-1);
507         }
508         /* not reached */
509 }
510
511 /*
512  * Convert a quota file from one format to another.
513  */
514 int
515 quota_convert(struct quotafile *qf, int wordsize)
516 {
517         struct quotafile *newqf;
518         struct dqhdr64 dqh;
519         struct dqblk dqblk;
520         struct group *grp;
521         int serrno, maxid, id, fd;
522
523         /*
524          * Quotas must not be active and quotafile must be open
525          * for reading and writing.
526          */
527         if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
528                 errno = EBADF;
529                 return (-1);
530         }
531         if ((wordsize != 32 && wordsize != 64) ||
532              wordsize == qf->wordsize) {
533                 errno = EINVAL;
534                 return (-1);
535         }
536         maxid = quota_maxid(qf);
537         if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
538                 errno = ENOMEM;
539                 return (-1);
540         }
541         *newqf = *qf;
542         snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
543             qf->wordsize);
544         if (rename(qf->qfname, newqf->qfname) < 0) {
545                 free(newqf);
546                 return (-1);
547         }
548         if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,
549             0)) < 0) {
550                 serrno = errno;
551                 goto error;
552         }
553         newqf->wordsize = wordsize;
554         if (wordsize == 64) {
555                 memset(&dqh, 0, sizeof(dqh));
556                 memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
557                 dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
558                 dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
559                 dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
560                 if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
561                         serrno = errno;
562                         goto error;
563                 }
564         }
565         grp = getgrnam(QUOTAGROUP);
566         fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
567         fchmod(newqf->fd, 0640);
568         for (id = 0; id <= maxid; id++) {
569                 if ((quota_read(qf, &dqblk, id)) < 0)
570                         break;
571                 switch (newqf->wordsize) {
572                 case 32:
573                         if ((quota_write32(newqf, &dqblk, id)) < 0)
574                                 break;
575                         continue;
576                 case 64:
577                         if ((quota_write64(newqf, &dqblk, id)) < 0)
578                                 break;
579                         continue;
580                 default:
581                         errno = EINVAL;
582                         break;
583                 }
584         }
585         if (id < maxid) {
586                 serrno = errno;
587                 goto error;
588         }
589         /*
590          * Update the passed in quotafile to reference the new file
591          * of the converted format size.
592          */
593         fd = qf->fd;
594         qf->fd = newqf->fd;
595         newqf->fd = fd;
596         qf->wordsize = newqf->wordsize;
597         quota_close(newqf);
598         return (0);
599 error:
600         /* put back the original file */
601         (void) rename(newqf->qfname, qf->qfname);
602         quota_close(newqf);
603         errno = serrno;
604         return (-1);
605 }