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