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