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