]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/tunefs/tunefs.c
sys/netpfil/pf: fix non-INET module build
[FreeBSD/FreeBSD.git] / sbin / tunefs / tunefs.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1983, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
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  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 /*
33  * tunefs: change layout parameters to an existing file system.
34  */
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #include <sys/disklabel.h>
38 #include <sys/stat.h>
39
40 #include <ufs/ufs/extattr.h>
41 #include <ufs/ufs/quota.h>
42 #include <ufs/ufs/ufsmount.h>
43 #include <ufs/ufs/dinode.h>
44 #include <ufs/ffs/fs.h>
45 #include <ufs/ufs/dir.h>
46
47 #include <ctype.h>
48 #include <err.h>
49 #include <fcntl.h>
50 #include <fstab.h>
51 #include <libufs.h>
52 #include <mntopts.h>
53 #include <paths.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <stdint.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60
61 /* the optimization warning string template */
62 #define OPTWARN "should optimize for %s with minfree %s %d%%"
63
64 static int blocks;
65 static char clrbuf[MAXBSIZE];
66 static struct uufsd disk;
67 #define sblock disk.d_fs
68
69 static void usage(void) __dead2;
70 static void printfs(void);
71 static int journal_alloc(int64_t size);
72 static void journal_clear(void);
73 static void sbdirty(void);
74
75 int
76 main(int argc, char *argv[])
77 {
78         const char *avalue, *jvalue, *Jvalue, *Lvalue, *lvalue, *Nvalue, *nvalue;
79         const char *tvalue;
80         const char *special;
81         const char *name;
82         char *diskname;
83         int Aflag, aflag, eflag, evalue, fflag, fvalue, jflag, Jflag, kflag;
84         int kvalue, Lflag, lflag, mflag, mvalue, Nflag, nflag, oflag, ovalue;
85         int pflag, sflag, svalue, Svalue, tflag;
86         int ch, found_arg, i;
87         const char *chg[2];
88
89         if (argc < 3)
90                 usage();
91         Aflag = aflag = eflag = fflag = jflag = Jflag = kflag = Lflag = 0;
92         lflag = mflag = Nflag = nflag = oflag = pflag = sflag = tflag = 0;
93         avalue = jvalue = Jvalue = Lvalue = lvalue = Nvalue = nvalue = NULL;
94         evalue = fvalue = mvalue = ovalue = svalue = Svalue = 0;
95         found_arg = 0;          /* At least one arg is required. */
96         while ((ch = getopt(argc, argv, "Aa:e:f:j:J:k:L:l:m:N:n:o:ps:S:t:"))
97             != -1)
98                 switch (ch) {
99
100                 case 'A':
101                         found_arg++;
102                         Aflag++;
103                         break;
104
105                 case 'a':
106                         found_arg++;
107                         name = "POSIX.1e ACLs";
108                         avalue = optarg;
109                         if (strcmp(avalue, "enable") &&
110                             strcmp(avalue, "disable")) {
111                                 errx(10, "bad %s (options are %s)",
112                                     name, "`enable' or `disable'");
113                         }
114                         aflag = 1;
115                         break;
116
117                 case 'e':
118                         found_arg++;
119                         name = "maximum blocks per file in a cylinder group";
120                         evalue = atoi(optarg);
121                         if (evalue < 1)
122                                 errx(10, "%s must be >= 1 (was %s)",
123                                     name, optarg);
124                         eflag = 1;
125                         break;
126
127                 case 'f':
128                         found_arg++;
129                         name = "average file size";
130                         fvalue = atoi(optarg);
131                         if (fvalue < 1)
132                                 errx(10, "%s must be >= 1 (was %s)",
133                                     name, optarg);
134                         fflag = 1;
135                         break;
136
137                 case 'j':
138                         found_arg++;
139                         name = "softdep journaled file system";
140                         jvalue = optarg;
141                         if (strcmp(jvalue, "enable") &&
142                             strcmp(jvalue, "disable")) {
143                                 errx(10, "bad %s (options are %s)",
144                                     name, "`enable' or `disable'");
145                         }
146                         jflag = 1;
147                         break;
148
149                 case 'J':
150                         found_arg++;
151                         name = "gjournaled file system";
152                         Jvalue = optarg;
153                         if (strcmp(Jvalue, "enable") &&
154                             strcmp(Jvalue, "disable")) {
155                                 errx(10, "bad %s (options are %s)",
156                                     name, "`enable' or `disable'");
157                         }
158                         Jflag = 1;
159                         break;
160
161                 case 'k':
162                         found_arg++;
163                         name = "space to hold for metadata blocks";
164                         kvalue = atoi(optarg);
165                         if (kvalue < 0)
166                                 errx(10, "bad %s (%s)", name, optarg);
167                         kflag = 1;
168                         break;
169
170                 case 'L':
171                         found_arg++;
172                         name = "volume label";
173                         Lvalue = optarg;
174                         i = -1;
175                         while (isalnum(Lvalue[++i]) || Lvalue[i] == '_' ||
176                             Lvalue[i] == '-')
177                                 ;
178                         if (Lvalue[i] != '\0') {
179                                 errx(10, "bad %s. Valid characters are "
180                                     "alphanumerics, dashes, and underscores.",
181                                     name);
182                         }
183                         if (strlen(Lvalue) >= MAXVOLLEN) {
184                                 errx(10, "bad %s. Length is longer than %d.",
185                                     name, MAXVOLLEN - 1);
186                         }
187                         Lflag = 1;
188                         break;
189
190                 case 'l':
191                         found_arg++;
192                         name = "multilabel MAC file system";
193                         lvalue = optarg;
194                         if (strcmp(lvalue, "enable") &&
195                             strcmp(lvalue, "disable")) {
196                                 errx(10, "bad %s (options are %s)",
197                                     name, "`enable' or `disable'");
198                         }
199                         lflag = 1;
200                         break;
201
202                 case 'm':
203                         found_arg++;
204                         name = "minimum percentage of free space";
205                         mvalue = atoi(optarg);
206                         if (mvalue < 0 || mvalue > 99)
207                                 errx(10, "bad %s (%s)", name, optarg);
208                         mflag = 1;
209                         break;
210
211                 case 'N':
212                         found_arg++;
213                         name = "NFSv4 ACLs";
214                         Nvalue = optarg;
215                         if (strcmp(Nvalue, "enable") &&
216                             strcmp(Nvalue, "disable")) {
217                                 errx(10, "bad %s (options are %s)",
218                                     name, "`enable' or `disable'");
219                         }
220                         Nflag = 1;
221                         break;
222
223                 case 'n':
224                         found_arg++;
225                         name = "soft updates";
226                         nvalue = optarg;
227                         if (strcmp(nvalue, "enable") != 0 &&
228                             strcmp(nvalue, "disable") != 0) {
229                                 errx(10, "bad %s (options are %s)",
230                                     name, "`enable' or `disable'");
231                         }
232                         nflag = 1;
233                         break;
234
235                 case 'o':
236                         found_arg++;
237                         name = "optimization preference";
238                         if (strcmp(optarg, "space") == 0)
239                                 ovalue = FS_OPTSPACE;
240                         else if (strcmp(optarg, "time") == 0)
241                                 ovalue = FS_OPTTIME;
242                         else
243                                 errx(10,
244                                     "bad %s (options are `space' or `time')",
245                                     name);
246                         oflag = 1;
247                         break;
248
249                 case 'p':
250                         found_arg++;
251                         pflag = 1;
252                         break;
253
254                 case 's':
255                         found_arg++;
256                         name = "expected number of files per directory";
257                         svalue = atoi(optarg);
258                         if (svalue < 1)
259                                 errx(10, "%s must be >= 1 (was %s)",
260                                     name, optarg);
261                         sflag = 1;
262                         break;
263
264                 case 'S':
265                         found_arg++;
266                         name = "Softdep Journal Size";
267                         Svalue = atoi(optarg);
268                         if (Svalue < SUJ_MIN)
269                                 errx(10, "%s must be >= %d (was %s)",
270                                     name, SUJ_MIN, optarg);
271                         break;
272
273                 case 't':
274                         found_arg++;
275                         name = "trim";
276                         tvalue = optarg;
277                         if (strcmp(tvalue, "enable") != 0 &&
278                             strcmp(tvalue, "disable") != 0) {
279                                 errx(10, "bad %s (options are %s)",
280                                     name, "`enable' or `disable'");
281                         }
282                         tflag = 1;
283                         break;
284
285                 default:
286                         usage();
287                 }
288         argc -= optind;
289         argv += optind;
290         if (found_arg == 0 || argc != 1)
291                 usage();
292
293         special = argv[0];
294         if (ufs_disk_fillout(&disk, special) == -1)
295                 goto err;
296         /*
297          * Check for unclean filesystem.
298          */
299         if ((sblock.fs_clean == 0 ||
300             (sblock.fs_flags & (FS_UNCLEAN | FS_NEEDSFSCK)) != 0) &&
301             (found_arg > 1 || !pflag))
302                 errx(1, "%s is not clean - run fsck.\n", special);
303         if (pflag) {
304                 printfs();
305                 exit(0);
306         }
307         if (Lflag) {
308                 name = "volume label";
309                 strncpy(sblock.fs_volname, Lvalue, MAXVOLLEN);
310         }
311         if (aflag) {
312                 name = "POSIX.1e ACLs";
313                 if (strcmp(avalue, "enable") == 0) {
314                         if (sblock.fs_flags & FS_ACLS) {
315                                 warnx("%s remains unchanged as enabled", name);
316                         } else if (sblock.fs_flags & FS_NFS4ACLS) {
317                                 warnx("%s and NFSv4 ACLs are mutually "
318                                     "exclusive", name);
319                         } else {
320                                 sblock.fs_flags |= FS_ACLS;
321                                 warnx("%s set", name);
322                         }
323                 } else if (strcmp(avalue, "disable") == 0) {
324                         if ((~sblock.fs_flags & FS_ACLS) ==
325                             FS_ACLS) {
326                                 warnx("%s remains unchanged as disabled",
327                                     name);
328                         } else {
329                                 sblock.fs_flags &= ~FS_ACLS;
330                                 warnx("%s cleared", name);
331                         }
332                 }
333         }
334         if (eflag) {
335                 name = "maximum blocks per file in a cylinder group";
336                 if (sblock.fs_maxbpg == evalue)
337                         warnx("%s remains unchanged as %d", name, evalue);
338                 else {
339                         warnx("%s changes from %d to %d",
340                             name, sblock.fs_maxbpg, evalue);
341                         sblock.fs_maxbpg = evalue;
342                 }
343         }
344         if (fflag) {
345                 name = "average file size";
346                 if (sblock.fs_avgfilesize == (unsigned)fvalue) {
347                         warnx("%s remains unchanged as %d", name, fvalue);
348                 }
349                 else {
350                         warnx("%s changes from %d to %d",
351                                         name, sblock.fs_avgfilesize, fvalue);
352                         sblock.fs_avgfilesize = fvalue;
353                 }
354         }
355         if (jflag) {
356                 name = "soft updates journaling";
357                 if (strcmp(jvalue, "enable") == 0) {
358                         if ((sblock.fs_flags & (FS_DOSOFTDEP | FS_SUJ)) ==
359                             (FS_DOSOFTDEP | FS_SUJ)) {
360                                 warnx("%s remains unchanged as enabled", name);
361                         } else if (sblock.fs_clean == 0) {
362                                 warnx("%s cannot be enabled until fsck is run",
363                                     name);
364                         } else if (journal_alloc(Svalue) != 0) {
365                                 warnx("%s cannot be enabled", name);
366                         } else {
367                                 sblock.fs_flags |= FS_DOSOFTDEP | FS_SUJ;
368                                 warnx("%s set", name);
369                         }
370                 } else if (strcmp(jvalue, "disable") == 0) {
371                         if ((~sblock.fs_flags & FS_SUJ) == FS_SUJ) {
372                                 warnx("%s remains unchanged as disabled", name);
373                         } else {
374                                 journal_clear();
375                                 sblock.fs_flags &= ~FS_SUJ;
376                                 sblock.fs_sujfree = 0;
377                                 warnx("%s cleared but soft updates still set.",
378                                     name);
379
380                                 warnx("remove .sujournal to reclaim space");
381                         }
382                 }
383         }
384         if (Jflag) {
385                 name = "gjournal";
386                 if (strcmp(Jvalue, "enable") == 0) {
387                         if (sblock.fs_flags & FS_GJOURNAL) {
388                                 warnx("%s remains unchanged as enabled", name);
389                         } else {
390                                 sblock.fs_flags |= FS_GJOURNAL;
391                                 warnx("%s set", name);
392                         }
393                 } else if (strcmp(Jvalue, "disable") == 0) {
394                         if ((~sblock.fs_flags & FS_GJOURNAL) ==
395                             FS_GJOURNAL) {
396                                 warnx("%s remains unchanged as disabled",
397                                     name);
398                         } else {
399                                 sblock.fs_flags &= ~FS_GJOURNAL;
400                                 warnx("%s cleared", name);
401                         }
402                 }
403         }
404         if (kflag) {
405                 name = "space to hold for metadata blocks";
406                 if (sblock.fs_metaspace == kvalue)
407                         warnx("%s remains unchanged as %d", name, kvalue);
408                 else {
409                         kvalue = blknum(&sblock, kvalue);
410                         if (kvalue > sblock.fs_fpg / 2) {
411                                 kvalue = blknum(&sblock, sblock.fs_fpg / 2);
412                                 warnx("%s cannot exceed half the file system "
413                                     "space", name);
414                         }
415                         warnx("%s changes from %jd to %d",
416                                     name, sblock.fs_metaspace, kvalue);
417                         sblock.fs_metaspace = kvalue;
418                 }
419         }
420         if (lflag) {
421                 name = "multilabel";
422                 if (strcmp(lvalue, "enable") == 0) {
423                         if (sblock.fs_flags & FS_MULTILABEL) {
424                                 warnx("%s remains unchanged as enabled", name);
425                         } else {
426                                 sblock.fs_flags |= FS_MULTILABEL;
427                                 warnx("%s set", name);
428                         }
429                 } else if (strcmp(lvalue, "disable") == 0) {
430                         if ((~sblock.fs_flags & FS_MULTILABEL) ==
431                             FS_MULTILABEL) {
432                                 warnx("%s remains unchanged as disabled",
433                                     name);
434                         } else {
435                                 sblock.fs_flags &= ~FS_MULTILABEL;
436                                 warnx("%s cleared", name);
437                         }
438                 }
439         }
440         if (mflag) {
441                 name = "minimum percentage of free space";
442                 if (sblock.fs_minfree == mvalue)
443                         warnx("%s remains unchanged as %d%%", name, mvalue);
444                 else {
445                         warnx("%s changes from %d%% to %d%%",
446                                     name, sblock.fs_minfree, mvalue);
447                         sblock.fs_minfree = mvalue;
448                         if (mvalue >= MINFREE && sblock.fs_optim == FS_OPTSPACE)
449                                 warnx(OPTWARN, "time", ">=", MINFREE);
450                         if (mvalue < MINFREE && sblock.fs_optim == FS_OPTTIME)
451                                 warnx(OPTWARN, "space", "<", MINFREE);
452                 }
453         }
454         if (Nflag) {
455                 name = "NFSv4 ACLs";
456                 if (strcmp(Nvalue, "enable") == 0) {
457                         if (sblock.fs_flags & FS_NFS4ACLS) {
458                                 warnx("%s remains unchanged as enabled", name);
459                         } else if (sblock.fs_flags & FS_ACLS) {
460                                 warnx("%s and POSIX.1e ACLs are mutually "
461                                     "exclusive", name);
462                         } else {
463                                 sblock.fs_flags |= FS_NFS4ACLS;
464                                 warnx("%s set", name);
465                         }
466                 } else if (strcmp(Nvalue, "disable") == 0) {
467                         if ((~sblock.fs_flags & FS_NFS4ACLS) ==
468                             FS_NFS4ACLS) {
469                                 warnx("%s remains unchanged as disabled",
470                                     name);
471                         } else {
472                                 sblock.fs_flags &= ~FS_NFS4ACLS;
473                                 warnx("%s cleared", name);
474                         }
475                 }
476         }
477         if (nflag) {
478                 name = "soft updates";
479                 if (strcmp(nvalue, "enable") == 0) {
480                         if (sblock.fs_flags & FS_DOSOFTDEP)
481                                 warnx("%s remains unchanged as enabled", name);
482                         else if (sblock.fs_clean == 0) {
483                                 warnx("%s cannot be enabled until fsck is run",
484                                     name);
485                         } else {
486                                 sblock.fs_flags |= FS_DOSOFTDEP;
487                                 warnx("%s set", name);
488                         }
489                 } else if (strcmp(nvalue, "disable") == 0) {
490                         if ((~sblock.fs_flags & FS_DOSOFTDEP) == FS_DOSOFTDEP)
491                                 warnx("%s remains unchanged as disabled", name);
492                         else {
493                                 sblock.fs_flags &= ~FS_DOSOFTDEP;
494                                 warnx("%s cleared", name);
495                         }
496                 }
497         }
498         if (oflag) {
499                 name = "optimization preference";
500                 chg[FS_OPTSPACE] = "space";
501                 chg[FS_OPTTIME] = "time";
502                 if (sblock.fs_optim == ovalue)
503                         warnx("%s remains unchanged as %s", name, chg[ovalue]);
504                 else {
505                         warnx("%s changes from %s to %s",
506                                     name, chg[sblock.fs_optim], chg[ovalue]);
507                         sblock.fs_optim = ovalue;
508                         if (sblock.fs_minfree >= MINFREE &&
509                             ovalue == FS_OPTSPACE)
510                                 warnx(OPTWARN, "time", ">=", MINFREE);
511                         if (sblock.fs_minfree < MINFREE && ovalue == FS_OPTTIME)
512                                 warnx(OPTWARN, "space", "<", MINFREE);
513                 }
514         }
515         if (sflag) {
516                 name = "expected number of files per directory";
517                 if (sblock.fs_avgfpdir == (unsigned)svalue) {
518                         warnx("%s remains unchanged as %d", name, svalue);
519                 }
520                 else {
521                         warnx("%s changes from %d to %d",
522                                         name, sblock.fs_avgfpdir, svalue);
523                         sblock.fs_avgfpdir = svalue;
524                 }
525         }
526         if (tflag) {
527                 name = "issue TRIM to the disk";
528                 if (strcmp(tvalue, "enable") == 0) {
529                         if (sblock.fs_flags & FS_TRIM)
530                                 warnx("%s remains unchanged as enabled", name);
531                         else {
532                                 sblock.fs_flags |= FS_TRIM;
533                                 warnx("%s set", name);
534                         }
535                 } else if (strcmp(tvalue, "disable") == 0) {
536                         if ((~sblock.fs_flags & FS_TRIM) == FS_TRIM)
537                                 warnx("%s remains unchanged as disabled", name);
538                         else {
539                                 sblock.fs_flags &= ~FS_TRIM;
540                                 warnx("%s cleared", name);
541                         }
542                 }
543         }
544
545         if (sbwrite(&disk, Aflag) == -1)
546                 goto err;
547         diskname = strdup(disk.d_name);
548         ufs_disk_close(&disk);
549         chkdoreload(getmntpoint(diskname), warnx);
550         exit(0);
551 err:
552         if (disk.d_error != NULL)
553                 errx(11, "%s: %s", special, disk.d_error);
554         else
555                 err(12, "%s", special);
556 }
557
558 static void
559 sbdirty(void)
560 {
561         disk.d_fs.fs_flags |= FS_UNCLEAN | FS_NEEDSFSCK;
562         disk.d_fs.fs_clean = 0;
563 }
564
565 static ufs2_daddr_t
566 journal_balloc(void)
567 {
568         ufs2_daddr_t blk;
569         struct cg *cgp;
570         int valid;
571         static int contig = 1;
572
573         cgp = &disk.d_cg;
574         for (;;) {
575                 blk = cgballoc(&disk);
576                 if (blk > 0)
577                         break;
578                 /*
579                  * If we failed to allocate a block from this cg, move to
580                  * the next.
581                  */
582                 if (cgwrite(&disk) < 0) {
583                         warn("Failed to write updated cg");
584                         return (-1);
585                 }
586                 while ((valid = cgread(&disk)) == 1) {
587                         /*
588                          * Try to minimize fragmentation by requiring a minimum
589                          * number of blocks present.
590                          */
591                         if (cgp->cg_cs.cs_nbfree > 256 * 1024)
592                                 break;
593                         if (contig == 0 && cgp->cg_cs.cs_nbfree)
594                                 break;
595                 }
596                 if (valid)
597                         continue;
598                 /*
599                  * Try once through looking only for large contiguous regions
600                  * and again taking any space we can find.
601                  */
602                 if (contig) {
603                         contig = 0;
604                         disk.d_ccg = 0;
605                         warnx("Journal file fragmented.");
606                         continue;
607                 }
608                 warnx("Failed to find sufficient free blocks for the journal");
609                 return -1;
610         }
611         if (bwrite(&disk, fsbtodb(&sblock, blk), clrbuf,
612             sblock.fs_bsize) <= 0) {
613                 warn("Failed to initialize new block");
614                 return -1;
615         }
616         return (blk);
617 }
618
619 /*
620  * Search a directory block for the SUJ_FILE.
621  */
622 static ino_t
623 dir_search(ufs2_daddr_t blk, int bytes)
624 {
625         char block[MAXBSIZE];
626         struct direct *dp;
627         int off;
628
629         if (bread(&disk, fsbtodb(&sblock, blk), block, bytes) <= 0) {
630                 warn("Failed to read dir block");
631                 return (-1);
632         }
633         for (off = 0; off < bytes; off += dp->d_reclen) {
634                 dp = (struct direct *)&block[off];
635                 if (dp->d_reclen == 0)
636                         break;
637                 if (dp->d_ino == 0)
638                         continue;
639                 if (dp->d_namlen != strlen(SUJ_FILE))
640                         continue;
641                 if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0)
642                         continue;
643                 return (dp->d_ino);
644         }
645
646         return (0);
647 }
648
649 /*
650  * Search in the UFS_ROOTINO for the SUJ_FILE.  If it exists we can not enable
651  * journaling.
652  */
653 static ino_t
654 journal_findfile(void)
655 {
656         union dinodep dp;
657         ino_t ino;
658         int i;
659
660         if (getinode(&disk, &dp, UFS_ROOTINO) != 0) {
661                 warn("Failed to get root inode: %s", disk.d_error);
662                 return (-1);
663         }
664         if (sblock.fs_magic == FS_UFS1_MAGIC) {
665                 if ((off_t)dp.dp1->di_size >= lblktosize(&sblock, UFS_NDADDR)) {
666                         warnx("UFS_ROOTINO extends beyond direct blocks.");
667                         return (-1);
668                 }
669                 for (i = 0; i < UFS_NDADDR; i++) {
670                         if (dp.dp1->di_db[i] == 0)
671                                 break;
672                         if ((ino = dir_search(dp.dp1->di_db[i],
673                             sblksize(&sblock, (off_t)dp.dp1->di_size, i))) != 0)
674                                 return (ino);
675                 }
676         } else {
677                 if ((off_t)dp.dp2->di_size >= lblktosize(&sblock, UFS_NDADDR)) {
678                         warnx("UFS_ROOTINO extends beyond direct blocks.");
679                         return (-1);
680                 }
681                 for (i = 0; i < UFS_NDADDR; i++) {
682                         if (dp.dp2->di_db[i] == 0)
683                                 break;
684                         if ((ino = dir_search(dp.dp2->di_db[i],
685                             sblksize(&sblock, (off_t)dp.dp2->di_size, i))) != 0)
686                                 return (ino);
687                 }
688         }
689
690         return (0);
691 }
692
693 static void
694 dir_clear_block(const char *block, off_t off)
695 {
696         struct direct *dp;
697
698         for (; off < sblock.fs_bsize; off += DIRBLKSIZ) {
699                 dp = (struct direct *)&block[off];
700                 dp->d_ino = 0;
701                 dp->d_reclen = DIRBLKSIZ;
702                 dp->d_type = DT_UNKNOWN;
703         }
704 }
705
706 /*
707  * Insert the journal at inode 'ino' into directory blk 'blk' at the first
708  * free offset of 'off'.  DIRBLKSIZ blocks after off are initialized as
709  * empty.
710  */
711 static int
712 dir_insert(ufs2_daddr_t blk, off_t off, ino_t ino)
713 {
714         struct direct *dp;
715         char block[MAXBSIZE];
716
717         if (bread(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
718                 warn("Failed to read dir block");
719                 return (-1);
720         }
721         bzero(&block[off], sblock.fs_bsize - off);
722         dp = (struct direct *)&block[off];
723         dp->d_ino = ino;
724         dp->d_reclen = DIRBLKSIZ;
725         dp->d_type = DT_REG;
726         dp->d_namlen = strlen(SUJ_FILE);
727         bcopy(SUJ_FILE, &dp->d_name, strlen(SUJ_FILE));
728         dir_clear_block(block, off + DIRBLKSIZ);
729         if (bwrite(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
730                 warn("Failed to write dir block");
731                 return (-1);
732         }
733         return (0);
734 }
735
736 /*
737  * Extend a directory block in 'blk' by copying it to a full size block
738  * and inserting the new journal inode into .sujournal.
739  */
740 static int
741 dir_extend(ufs2_daddr_t blk, ufs2_daddr_t nblk, off_t size, ino_t ino)
742 {
743         char block[MAXBSIZE];
744
745         if (bread(&disk, fsbtodb(&sblock, blk), block,
746             roundup(size, sblock.fs_fsize)) <= 0) {
747                 warn("Failed to read dir block");
748                 return (-1);
749         }
750         dir_clear_block(block, size);
751         if (bwrite(&disk, fsbtodb(&sblock, nblk), block, sblock.fs_bsize)
752             <= 0) {
753                 warn("Failed to write dir block");
754                 return (-1);
755         }
756
757         return (dir_insert(nblk, size, ino));
758 }
759
760 /*
761  * Insert the journal file into the UFS_ROOTINO directory.  We always extend the
762  * last frag
763  */
764 static int
765 journal_insertfile(ino_t ino)
766 {
767         union dinodep dp;
768         ufs2_daddr_t nblk;
769         ufs2_daddr_t blk;
770         ufs_lbn_t lbn;
771         int size;
772         int off;
773
774         if (getinode(&disk, &dp, UFS_ROOTINO) != 0) {
775                 warn("Failed to get root inode: %s", disk.d_error);
776                 sbdirty();
777                 return (-1);
778         }
779         blk = 0;
780         size = 0;
781         nblk = journal_balloc();
782         if (nblk <= 0)
783                 return (-1);
784         /*
785          * For simplicity sake we aways extend the UFS_ROOTINO into a new
786          * directory block rather than searching for space and inserting
787          * into an existing block.  However, if the rootino has frags
788          * have to free them and extend the block.
789          */
790         if (sblock.fs_magic == FS_UFS1_MAGIC) {
791                 lbn = lblkno(&sblock, dp.dp1->di_size);
792                 off = blkoff(&sblock, dp.dp1->di_size);
793                 blk = dp.dp1->di_db[lbn];
794                 size = sblksize(&sblock, (off_t)dp.dp1->di_size, lbn);
795         } else {
796                 lbn = lblkno(&sblock, dp.dp2->di_size);
797                 off = blkoff(&sblock, dp.dp2->di_size);
798                 blk = dp.dp2->di_db[lbn];
799                 size = sblksize(&sblock, (off_t)dp.dp2->di_size, lbn);
800         }
801         if (off != 0) {
802                 if (dir_extend(blk, nblk, off, ino) == -1)
803                         return (-1);
804         } else {
805                 blk = 0;
806                 if (dir_insert(nblk, 0, ino) == -1)
807                         return (-1);
808         }
809         if (sblock.fs_magic == FS_UFS1_MAGIC) {
810                 dp.dp1->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
811                 dp.dp1->di_db[lbn] = nblk;
812                 dp.dp1->di_size = lblktosize(&sblock, lbn+1);
813         } else {
814                 dp.dp2->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
815                 dp.dp2->di_db[lbn] = nblk;
816                 dp.dp2->di_size = lblktosize(&sblock, lbn+1);
817         }
818         if (putinode(&disk) < 0) {
819                 warn("Failed to write root inode: %s", disk.d_error);
820                 return (-1);
821         }
822         if (cgwrite(&disk) < 0) {
823                 warn("Failed to write updated cg");
824                 sbdirty();
825                 return (-1);
826         }
827         if (blk) {
828                 if (cgbfree(&disk, blk, size) < 0) {
829                         warn("Failed to write cg");
830                         return (-1);
831                 }
832         }
833
834         return (0);
835 }
836
837 static int
838 indir_fill(ufs2_daddr_t blk, int level, int *resid)
839 {
840         char indirbuf[MAXBSIZE];
841         ufs1_daddr_t *bap1;
842         ufs2_daddr_t *bap2;
843         ufs2_daddr_t nblk;
844         int ncnt;
845         int cnt;
846         int i;
847
848         bzero(indirbuf, sizeof(indirbuf));
849         bap1 = (ufs1_daddr_t *)indirbuf;
850         bap2 = (void *)bap1;
851         cnt = 0;
852         for (i = 0; i < NINDIR(&sblock) && *resid != 0; i++) {
853                 nblk = journal_balloc();
854                 if (nblk <= 0)
855                         return (-1);
856                 cnt++;
857                 if (sblock.fs_magic == FS_UFS1_MAGIC)
858                         *bap1++ = nblk;
859                 else
860                         *bap2++ = nblk;
861                 if (level != 0) {
862                         ncnt = indir_fill(nblk, level - 1, resid);
863                         if (ncnt <= 0)
864                                 return (-1);
865                         cnt += ncnt;
866                 } else 
867                         (*resid)--;
868         }
869         if (bwrite(&disk, fsbtodb(&sblock, blk), indirbuf,
870             sblock.fs_bsize) <= 0) {
871                 warn("Failed to write indirect");
872                 return (-1);
873         }
874         return (cnt);
875 }
876
877 /*
878  * Clear the flag bits so the journal can be removed.
879  */
880 static void
881 journal_clear(void)
882 {
883         union dinodep dp;
884         ino_t ino;
885
886         ino = journal_findfile();
887         if (ino == (ino_t)-1 || ino == 0) {
888                 warnx("Journal file does not exist");
889                 return;
890         }
891         printf("Clearing journal flags from inode %ju\n", (uintmax_t)ino);
892         if (getinode(&disk, &dp, ino) != 0) {
893                 warn("Failed to get journal inode: %s", disk.d_error);
894                 return;
895         }
896         if (sblock.fs_magic == FS_UFS1_MAGIC)
897                 dp.dp1->di_flags = 0;
898         else
899                 dp.dp2->di_flags = 0;
900         if (putinode(&disk) < 0) {
901                 warn("Failed to write journal inode: %s", disk.d_error);
902                 return;
903         }
904 }
905
906 static int
907 journal_alloc(int64_t size)
908 {
909         union dinodep dp;
910         ufs2_daddr_t blk;
911         struct cg *cgp;
912         int resid;
913         ino_t ino;
914         int blks;
915         time_t utime;
916         int i;
917
918         cgp = &disk.d_cg;
919         ino = 0;
920
921         /*
922          * If the journal file exists we can't allocate it.
923          */
924         ino = journal_findfile();
925         if (ino == (ino_t)-1) {
926                 warnx("journal_findfile() failed.");
927                 return (-1);
928         }
929         if (ino > 0) {
930                 warnx("Journal file %s already exists, please remove.",
931                     SUJ_FILE);
932                 return (-1);
933         }
934         /*
935          * If the user didn't supply a size pick one based on the filesystem
936          * size constrained with hardcoded MIN and MAX values.  We opt for
937          * 1/1024th of the filesystem up to MAX but not exceeding one CG and
938          * not less than the MIN.
939          */
940         if (size == 0) {
941                 size = (sblock.fs_size * sblock.fs_bsize) / 1024;
942                 if (size / sblock.fs_fsize > sblock.fs_fpg)
943                         size = sblock.fs_fpg * sblock.fs_fsize;
944                 size = MAX(SUJ_MIN, size);
945         }
946         /* fsck does not support fragments in journal files. */
947         size = roundup(size, sblock.fs_bsize);
948         resid = blocks = size / sblock.fs_bsize;
949         if (sblock.fs_cstotal.cs_nbfree < blocks) {
950                 warn("Insufficient free space for %jd byte journal", size);
951                 return (-1);
952         }
953         /*
954          * Find a cg with enough blocks to satisfy the journal
955          * size.  Presently the journal does not span cgs.
956          */
957         while (cgread(&disk) == 1) {
958                 if (cgp->cg_cs.cs_nifree == 0)
959                         continue;
960                 ino = cgialloc(&disk);
961                 if (ino <= 0)
962                         break;
963                 printf("Using inode %ju in cg %d for %jd byte journal\n",
964                     (uintmax_t)ino, cgp->cg_cgx, size);
965                 if (getinode(&disk, &dp, ino) != 0) {
966                         warn("Failed to get allocated inode: %s", disk.d_error);
967                         sbdirty();
968                         goto out;
969                 }
970                 /*
971                  * We leave fields unrelated to the number of allocated
972                  * blocks and size uninitialized.  This causes legacy
973                  * fsck implementations to clear the inode.
974                  */
975                 time(&utime);
976                 if (sblock.fs_magic == FS_UFS1_MAGIC) {
977                         bzero(dp.dp1, sizeof(*dp.dp1));
978                         dp.dp1->di_size = size;
979                         dp.dp1->di_mode = IFREG | IREAD;
980                         dp.dp1->di_nlink = 1;
981                         dp.dp1->di_flags =
982                             SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
983                         dp.dp1->di_atime = utime;
984                         dp.dp1->di_mtime = utime;
985                         dp.dp1->di_ctime = utime;
986                 } else {
987                         bzero(dp.dp2, sizeof(*dp.dp2));
988                         dp.dp2->di_size = size;
989                         dp.dp2->di_mode = IFREG | IREAD;
990                         dp.dp2->di_nlink = 1;
991                         dp.dp2->di_flags =
992                             SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
993                         dp.dp2->di_atime = utime;
994                         dp.dp2->di_mtime = utime;
995                         dp.dp2->di_ctime = utime;
996                         dp.dp2->di_birthtime = utime;
997                 }
998                 for (i = 0; i < UFS_NDADDR && resid; i++, resid--) {
999                         blk = journal_balloc();
1000                         if (blk <= 0)
1001                                 goto out;
1002                         if (sblock.fs_magic == FS_UFS1_MAGIC) {
1003                                 dp.dp1->di_db[i] = blk;
1004                                 dp.dp1->di_blocks++;
1005                         } else {
1006                                 dp.dp2->di_db[i] = blk;
1007                                 dp.dp2->di_blocks++;
1008                         }
1009                 }
1010                 for (i = 0; i < UFS_NIADDR && resid; i++) {
1011                         blk = journal_balloc();
1012                         if (blk <= 0)
1013                                 goto out;
1014                         blks = indir_fill(blk, i, &resid) + 1;
1015                         if (blks <= 0) {
1016                                 sbdirty();
1017                                 goto out;
1018                         }
1019                         if (sblock.fs_magic == FS_UFS1_MAGIC) {
1020                                 dp.dp1->di_ib[i] = blk;
1021                                 dp.dp1->di_blocks += blks;
1022                         } else {
1023                                 dp.dp2->di_ib[i] = blk;
1024                                 dp.dp2->di_blocks += blks;
1025                         }
1026                 }
1027                 if (sblock.fs_magic == FS_UFS1_MAGIC)
1028                         dp.dp1->di_blocks *= sblock.fs_bsize / disk.d_bsize;
1029                 else
1030                         dp.dp2->di_blocks *= sblock.fs_bsize / disk.d_bsize;
1031                 if (putinode(&disk) < 0) {
1032                         warn("Failed to write allocated inode: %s",
1033                             disk.d_error);
1034                         sbdirty();
1035                         return (-1);
1036                 }
1037                 if (cgwrite(&disk) < 0) {
1038                         warn("Failed to write updated cg");
1039                         sbdirty();
1040                         return (-1);
1041                 }
1042                 if (journal_insertfile(ino) < 0) {
1043                         sbdirty();
1044                         return (-1);
1045                 }
1046                 sblock.fs_sujfree = 0;
1047                 return (0);
1048         }
1049         warnx("Insufficient free space for the journal.");
1050 out:
1051         return (-1);
1052 }
1053
1054 static void
1055 usage(void)
1056 {
1057         fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
1058 "usage: tunefs [-A] [-a enable | disable] [-e maxbpg] [-f avgfilesize]",
1059 "              [-J enable | disable] [-j enable | disable] [-k metaspace]",
1060 "              [-L volname] [-l enable | disable] [-m minfree]",
1061 "              [-N enable | disable] [-n enable | disable]",
1062 "              [-o space | time] [-p] [-s avgfpdir] [-t enable | disable]",
1063 "              special | filesystem");
1064         exit(2);
1065 }
1066
1067 static void
1068 printfs(void)
1069 {
1070         warnx("POSIX.1e ACLs: (-a)                                %s",
1071                 (sblock.fs_flags & FS_ACLS)? "enabled" : "disabled");
1072         warnx("NFSv4 ACLs: (-N)                                   %s",
1073                 (sblock.fs_flags & FS_NFS4ACLS)? "enabled" : "disabled");
1074         warnx("MAC multilabel: (-l)                               %s",
1075                 (sblock.fs_flags & FS_MULTILABEL)? "enabled" : "disabled");
1076         warnx("soft updates: (-n)                                 %s", 
1077                 (sblock.fs_flags & FS_DOSOFTDEP)? "enabled" : "disabled");
1078         warnx("soft update journaling: (-j)                       %s", 
1079                 (sblock.fs_flags & FS_SUJ)? "enabled" : "disabled");
1080         warnx("gjournal: (-J)                                     %s",
1081                 (sblock.fs_flags & FS_GJOURNAL)? "enabled" : "disabled");
1082         warnx("trim: (-t)                                         %s", 
1083                 (sblock.fs_flags & FS_TRIM)? "enabled" : "disabled");
1084         warnx("maximum blocks per file in a cylinder group: (-e)  %d",
1085               sblock.fs_maxbpg);
1086         warnx("average file size: (-f)                            %d",
1087               sblock.fs_avgfilesize);
1088         warnx("average number of files in a directory: (-s)       %d",
1089               sblock.fs_avgfpdir);
1090         warnx("minimum percentage of free space: (-m)             %d%%",
1091               sblock.fs_minfree);
1092         warnx("space to hold for metadata blocks: (-k)            %jd",
1093               sblock.fs_metaspace);
1094         warnx("optimization preference: (-o)                      %s",
1095               sblock.fs_optim == FS_OPTSPACE ? "space" : "time");
1096         if (sblock.fs_minfree >= MINFREE &&
1097             sblock.fs_optim == FS_OPTSPACE)
1098                 warnx(OPTWARN, "time", ">=", MINFREE);
1099         if (sblock.fs_minfree < MINFREE &&
1100             sblock.fs_optim == FS_OPTTIME)
1101                 warnx(OPTWARN, "space", "<", MINFREE);
1102         warnx("volume label: (-L)                                 %s",
1103                 sblock.fs_volname);
1104 }