]> CyberLeo.Net >> Repos - FreeBSD/stable/9.git/blob - sbin/tunefs/tunefs.c
Copy head to stable/9 as part of 9.0-RELEASE release cycle.
[FreeBSD/stable/9.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 <unistd.h>
68
69 /* the optimization warning string template */
70 #define OPTWARN "should optimize for %s with minfree %s %d%%"
71
72 struct uufsd disk;
73 #define sblock disk.d_fs
74
75 void usage(void);
76 void printfs(void);
77 int journal_alloc(int64_t size);
78 void journal_clear(void);
79 void sbdirty(void);
80
81 int
82 main(int argc, char *argv[])
83 {
84         char *avalue, *jvalue, *Jvalue, *Lvalue, *lvalue, *Nvalue, *nvalue;
85         char *tvalue;
86         const char *special, *on;
87         const char *name;
88         int active;
89         int Aflag, aflag, eflag, evalue, fflag, fvalue, jflag, Jflag, Lflag;
90         int lflag, mflag, mvalue, Nflag, nflag, oflag, ovalue, pflag, sflag;
91         int tflag;
92         int svalue, Sflag, Svalue;
93         int ch, found_arg, i;
94         const char *chg[2];
95         struct ufs_args args;
96         struct statfs stfs;
97
98         if (argc < 3)
99                 usage();
100         Aflag = aflag = eflag = fflag = jflag = Jflag = Lflag = lflag = 0;
101         mflag = Nflag = nflag = oflag = pflag = sflag = tflag = 0;
102         avalue = jvalue = Jvalue = Lvalue = lvalue = Nvalue = nvalue = NULL;
103         evalue = fvalue = mvalue = ovalue = svalue = Svalue = 0;
104         active = 0;
105         found_arg = 0;          /* At least one arg is required. */
106         while ((ch = getopt(argc, argv, "Aa:e:f:j:J:L:l:m:N:n:o:ps:S:t:"))
107             != -1)
108                 switch (ch) {
109
110                 case 'A':
111                         found_arg = 1;
112                         Aflag++;
113                         break;
114
115                 case 'a':
116                         found_arg = 1;
117                         name = "POSIX.1e ACLs";
118                         avalue = optarg;
119                         if (strcmp(avalue, "enable") &&
120                             strcmp(avalue, "disable")) {
121                                 errx(10, "bad %s (options are %s)",
122                                     name, "`enable' or `disable'");
123                         }
124                         aflag = 1;
125                         break;
126
127                 case 'e':
128                         found_arg = 1;
129                         name = "maximum blocks per file in a cylinder group";
130                         evalue = atoi(optarg);
131                         if (evalue < 1)
132                                 errx(10, "%s must be >= 1 (was %s)",
133                                     name, optarg);
134                         eflag = 1;
135                         break;
136
137                 case 'f':
138                         found_arg = 1;
139                         name = "average file size";
140                         fvalue = atoi(optarg);
141                         if (fvalue < 1)
142                                 errx(10, "%s must be >= 1 (was %s)",
143                                     name, optarg);
144                         fflag = 1;
145                         break;
146
147                 case 'j':
148                         found_arg = 1;
149                         name = "softdep journaled file system";
150                         jvalue = optarg;
151                         if (strcmp(jvalue, "enable") &&
152                             strcmp(jvalue, "disable")) {
153                                 errx(10, "bad %s (options are %s)",
154                                     name, "`enable' or `disable'");
155                         }
156                         jflag = 1;
157                         break;
158
159                 case 'J':
160                         found_arg = 1;
161                         name = "gjournaled file system";
162                         Jvalue = optarg;
163                         if (strcmp(Jvalue, "enable") &&
164                             strcmp(Jvalue, "disable")) {
165                                 errx(10, "bad %s (options are %s)",
166                                     name, "`enable' or `disable'");
167                         }
168                         Jflag = 1;
169                         break;
170
171
172                 case 'L':
173                         found_arg = 1;
174                         name = "volume label";
175                         Lvalue = optarg;
176                         i = -1;
177                         while (isalnum(Lvalue[++i]));
178                         if (Lvalue[i] != '\0') {
179                                 errx(10,
180                                 "bad %s. Valid characters are alphanumerics.",
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 = 1;
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 = 1;
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 = 1;
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 = 1;
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 = 1;
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 = 1;
251                         pflag = 1;
252                         break;
253
254                 case 's':
255                         found_arg = 1;
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 = 1;
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                         Sflag = 1;
272                         break;
273
274                 case 't':
275                         found_arg = 1;
276                         name = "trim";
277                         tvalue = optarg;
278                         if (strcmp(tvalue, "enable") != 0 &&
279                             strcmp(tvalue, "disable") != 0) {
280                                 errx(10, "bad %s (options are %s)",
281                                     name, "`enable' or `disable'");
282                         }
283                         tflag = 1;
284                         break;
285
286                 default:
287                         usage();
288                 }
289         argc -= optind;
290         argv += optind;
291         if (found_arg == 0 || argc != 1)
292                 usage();
293
294         on = special = argv[0];
295         if (ufs_disk_fillout(&disk, special) == -1)
296                 goto err;
297         if (disk.d_name != special) {
298                 if (statfs(special, &stfs) != 0)
299                         warn("Can't stat %s", special);
300                 if (strcmp(special, stfs.f_mntonname) == 0)
301                         active = 1;
302         }
303
304         if (pflag) {
305                 printfs();
306                 exit(0);
307         }
308         if (Lflag) {
309                 name = "volume label";
310                 strlcpy(sblock.fs_volname, Lvalue, MAXVOLLEN);
311         }
312         if (aflag) {
313                 name = "POSIX.1e ACLs";
314                 if (strcmp(avalue, "enable") == 0) {
315                         if (sblock.fs_flags & FS_ACLS) {
316                                 warnx("%s remains unchanged as enabled", name);
317                         } else if (sblock.fs_flags & FS_NFS4ACLS) {
318                                 warnx("%s and NFSv4 ACLs are mutually "
319                                     "exclusive", name);
320                         } else {
321                                 sblock.fs_flags |= FS_ACLS;
322                                 warnx("%s set", name);
323                         }
324                 } else if (strcmp(avalue, "disable") == 0) {
325                         if ((~sblock.fs_flags & FS_ACLS) ==
326                             FS_ACLS) {
327                                 warnx("%s remains unchanged as disabled",
328                                     name);
329                         } else {
330                                 sblock.fs_flags &= ~FS_ACLS;
331                                 warnx("%s cleared", name);
332                         }
333                 }
334         }
335         if (eflag) {
336                 name = "maximum blocks per file in a cylinder group";
337                 if (sblock.fs_maxbpg == evalue)
338                         warnx("%s remains unchanged as %d", name, evalue);
339                 else {
340                         warnx("%s changes from %d to %d",
341                             name, sblock.fs_maxbpg, evalue);
342                         sblock.fs_maxbpg = evalue;
343                 }
344         }
345         if (fflag) {
346                 name = "average file size";
347                 if (sblock.fs_avgfilesize == (unsigned)fvalue) {
348                         warnx("%s remains unchanged as %d", name, fvalue);
349                 }
350                 else {
351                         warnx("%s changes from %d to %d",
352                                         name, sblock.fs_avgfilesize, fvalue);
353                         sblock.fs_avgfilesize = fvalue;
354                 }
355         }
356         if (jflag) {
357                 name = "soft updates journaling";
358                 if (strcmp(jvalue, "enable") == 0) {
359                         if ((sblock.fs_flags & (FS_DOSOFTDEP | FS_SUJ)) ==
360                             (FS_DOSOFTDEP | FS_SUJ)) {
361                                 warnx("%s remains unchanged as enabled", name);
362                         } else if (sblock.fs_clean == 0) {
363                                 warnx("%s cannot be enabled until fsck is run",
364                                     name);
365                         } else if (journal_alloc(Svalue) != 0) {
366                                 warnx("%s can not be enabled", name);
367                         } else {
368                                 sblock.fs_flags |= FS_DOSOFTDEP | FS_SUJ;
369                                 warnx("%s set", name);
370                         }
371                 } else if (strcmp(jvalue, "disable") == 0) {
372                         if ((~sblock.fs_flags & FS_SUJ) == FS_SUJ) {
373                                 warnx("%s remains unchanged as disabled", name);
374                         } else {
375                                 journal_clear();
376                                 sblock.fs_flags &= ~FS_SUJ;
377                                 sblock.fs_sujfree = 0;
378                                 warnx("%s cleared but soft updates still set.",
379                                     name);
380
381                                 warnx("remove .sujournal to reclaim space");
382                         }
383                 }
384         }
385         if (Jflag) {
386                 name = "gjournal";
387                 if (strcmp(Jvalue, "enable") == 0) {
388                         if (sblock.fs_flags & FS_GJOURNAL) {
389                                 warnx("%s remains unchanged as enabled", name);
390                         } else {
391                                 sblock.fs_flags |= FS_GJOURNAL;
392                                 warnx("%s set", name);
393                         }
394                 } else if (strcmp(Jvalue, "disable") == 0) {
395                         if ((~sblock.fs_flags & FS_GJOURNAL) ==
396                             FS_GJOURNAL) {
397                                 warnx("%s remains unchanged as disabled",
398                                     name);
399                         } else {
400                                 sblock.fs_flags &= ~FS_GJOURNAL;
401                                 warnx("%s cleared", name);
402                         }
403                 }
404         }
405         if (lflag) {
406                 name = "multilabel";
407                 if (strcmp(lvalue, "enable") == 0) {
408                         if (sblock.fs_flags & FS_MULTILABEL) {
409                                 warnx("%s remains unchanged as enabled", name);
410                         } else {
411                                 sblock.fs_flags |= FS_MULTILABEL;
412                                 warnx("%s set", name);
413                         }
414                 } else if (strcmp(lvalue, "disable") == 0) {
415                         if ((~sblock.fs_flags & FS_MULTILABEL) ==
416                             FS_MULTILABEL) {
417                                 warnx("%s remains unchanged as disabled",
418                                     name);
419                         } else {
420                                 sblock.fs_flags &= ~FS_MULTILABEL;
421                                 warnx("%s cleared", name);
422                         }
423                 }
424         }
425         if (mflag) {
426                 name = "minimum percentage of free space";
427                 if (sblock.fs_minfree == mvalue)
428                         warnx("%s remains unchanged as %d%%", name, mvalue);
429                 else {
430                         warnx("%s changes from %d%% to %d%%",
431                                     name, sblock.fs_minfree, mvalue);
432                         sblock.fs_minfree = mvalue;
433                         if (mvalue >= MINFREE && sblock.fs_optim == FS_OPTSPACE)
434                                 warnx(OPTWARN, "time", ">=", MINFREE);
435                         if (mvalue < MINFREE && sblock.fs_optim == FS_OPTTIME)
436                                 warnx(OPTWARN, "space", "<", MINFREE);
437                 }
438         }
439         if (Nflag) {
440                 name = "NFSv4 ACLs";
441                 if (strcmp(Nvalue, "enable") == 0) {
442                         if (sblock.fs_flags & FS_NFS4ACLS) {
443                                 warnx("%s remains unchanged as enabled", name);
444                         } else if (sblock.fs_flags & FS_ACLS) {
445                                 warnx("%s and POSIX.1e ACLs are mutually "
446                                     "exclusive", name);
447                         } else {
448                                 sblock.fs_flags |= FS_NFS4ACLS;
449                                 warnx("%s set", name);
450                         }
451                 } else if (strcmp(Nvalue, "disable") == 0) {
452                         if ((~sblock.fs_flags & FS_NFS4ACLS) ==
453                             FS_NFS4ACLS) {
454                                 warnx("%s remains unchanged as disabled",
455                                     name);
456                         } else {
457                                 sblock.fs_flags &= ~FS_NFS4ACLS;
458                                 warnx("%s cleared", name);
459                         }
460                 }
461         }
462         if (nflag) {
463                 name = "soft updates";
464                 if (strcmp(nvalue, "enable") == 0) {
465                         if (sblock.fs_flags & FS_DOSOFTDEP)
466                                 warnx("%s remains unchanged as enabled", name);
467                         else if (sblock.fs_clean == 0) {
468                                 warnx("%s cannot be enabled until fsck is run",
469                                     name);
470                         } else {
471                                 sblock.fs_flags |= FS_DOSOFTDEP;
472                                 warnx("%s set", name);
473                         }
474                 } else if (strcmp(nvalue, "disable") == 0) {
475                         if ((~sblock.fs_flags & FS_DOSOFTDEP) == FS_DOSOFTDEP)
476                                 warnx("%s remains unchanged as disabled", name);
477                         else {
478                                 sblock.fs_flags &= ~FS_DOSOFTDEP;
479                                 warnx("%s cleared", name);
480                         }
481                 }
482         }
483         if (oflag) {
484                 name = "optimization preference";
485                 chg[FS_OPTSPACE] = "space";
486                 chg[FS_OPTTIME] = "time";
487                 if (sblock.fs_optim == ovalue)
488                         warnx("%s remains unchanged as %s", name, chg[ovalue]);
489                 else {
490                         warnx("%s changes from %s to %s",
491                                     name, chg[sblock.fs_optim], chg[ovalue]);
492                         sblock.fs_optim = ovalue;
493                         if (sblock.fs_minfree >= MINFREE &&
494                             ovalue == FS_OPTSPACE)
495                                 warnx(OPTWARN, "time", ">=", MINFREE);
496                         if (sblock.fs_minfree < MINFREE && ovalue == FS_OPTTIME)
497                                 warnx(OPTWARN, "space", "<", MINFREE);
498                 }
499         }
500         if (sflag) {
501                 name = "expected number of files per directory";
502                 if (sblock.fs_avgfpdir == (unsigned)svalue) {
503                         warnx("%s remains unchanged as %d", name, svalue);
504                 }
505                 else {
506                         warnx("%s changes from %d to %d",
507                                         name, sblock.fs_avgfpdir, svalue);
508                         sblock.fs_avgfpdir = svalue;
509                 }
510         }
511         if (tflag) {
512                 name = "issue TRIM to the disk";
513                 if (strcmp(tvalue, "enable") == 0) {
514                         if (sblock.fs_flags & FS_TRIM)
515                                 warnx("%s remains unchanged as enabled", name);
516                         else {
517                                 sblock.fs_flags |= FS_TRIM;
518                                 warnx("%s set", name);
519                         }
520                 } else if (strcmp(tvalue, "disable") == 0) {
521                         if ((~sblock.fs_flags & FS_TRIM) == FS_TRIM)
522                                 warnx("%s remains unchanged as disabled", name);
523                         else {
524                                 sblock.fs_flags &= ~FS_TRIM;
525                                 warnx("%s cleared", name);
526                         }
527                 }
528         }
529
530         if (sbwrite(&disk, Aflag) == -1)
531                 goto err;
532         ufs_disk_close(&disk);
533         if (active) {
534                 bzero(&args, sizeof(args));
535                 if (mount("ufs", on,
536                     stfs.f_flags | MNT_UPDATE | MNT_RELOAD, &args) < 0)
537                         err(9, "%s: reload", special);
538                 warnx("file system reloaded");
539         }
540         exit(0);
541 err:
542         if (disk.d_error != NULL)
543                 errx(11, "%s: %s", special, disk.d_error);
544         else
545                 err(12, "%s", special);
546 }
547
548 void
549 sbdirty(void)
550 {
551         disk.d_fs.fs_flags |= FS_UNCLEAN | FS_NEEDSFSCK;
552         disk.d_fs.fs_clean = 0;
553 }
554
555 int blocks;
556 static char clrbuf[MAXBSIZE];
557
558 static ufs2_daddr_t
559 journal_balloc(void)
560 {
561         ufs2_daddr_t blk;
562         struct cg *cgp;
563         int valid;
564         static int contig = 1;
565
566         cgp = &disk.d_cg;
567         for (;;) {
568                 blk = cgballoc(&disk);
569                 if (blk > 0)
570                         break;
571                 /*
572                  * If we failed to allocate a block from this cg, move to
573                  * the next.
574                  */
575                 if (cgwrite(&disk) < 0) {
576                         warn("Failed to write updated cg");
577                         return (-1);
578                 }
579                 while ((valid = cgread(&disk)) == 1) {
580                         /*
581                          * Try to minimize fragmentation by requiring a minimum
582                          * number of blocks present.
583                          */
584                         if (cgp->cg_cs.cs_nbfree > 256 * 1024)
585                                 break;
586                         if (contig == 0 && cgp->cg_cs.cs_nbfree)
587                                 break;
588                 }
589                 if (valid)
590                         continue;
591                 /*
592                  * Try once through looking only for large contiguous regions
593                  * and again taking any space we can find.
594                  */
595                 if (contig) {
596                         contig = 0;
597                         disk.d_ccg = 0;
598                         warnx("Journal file fragmented.");
599                         continue;
600                 }
601                 warnx("Failed to find sufficient free blocks for the journal");
602                 return -1;
603         }
604         if (bwrite(&disk, fsbtodb(&sblock, blk), clrbuf,
605             sblock.fs_bsize) <= 0) {
606                 warn("Failed to initialize new block");
607                 return -1;
608         }
609         return (blk);
610 }
611
612 /*
613  * Search a directory block for the SUJ_FILE.
614  */
615 static ino_t
616 dir_search(ufs2_daddr_t blk, int bytes)
617 {
618         char block[MAXBSIZE];
619         struct direct *dp;
620         int off;
621
622         if (bread(&disk, fsbtodb(&sblock, blk), block, bytes) <= 0) {
623                 warn("Failed to read dir block");
624                 return (-1);
625         }
626         for (off = 0; off < bytes; off += dp->d_reclen) {
627                 dp = (struct direct *)&block[off];
628                 if (dp->d_reclen == 0)
629                         break;
630                 if (dp->d_ino == 0)
631                         continue;
632                 if (dp->d_namlen != strlen(SUJ_FILE))
633                         continue;
634                 if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0)
635                         continue;
636                 return (dp->d_ino);
637         }
638
639         return (0);
640 }
641
642 /*
643  * Search in the ROOTINO for the SUJ_FILE.  If it exists we can not enable
644  * journaling.
645  */
646 static ino_t
647 journal_findfile(void)
648 {
649         struct ufs1_dinode *dp1;
650         struct ufs2_dinode *dp2;
651         ino_t ino;
652         int mode;
653         void *ip;
654         int i;
655
656         if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
657                 warn("Failed to get root inode");
658                 return (-1);
659         }
660         dp2 = ip;
661         dp1 = ip;
662         if (sblock.fs_magic == FS_UFS1_MAGIC) {
663                 if ((off_t)dp1->di_size >= lblktosize(&sblock, NDADDR)) {
664                         warnx("ROOTINO extends beyond direct blocks.");
665                         return (-1);
666                 }
667                 for (i = 0; i < NDADDR; i++) {
668                         if (dp1->di_db[i] == 0)
669                                 break;
670                         if ((ino = dir_search(dp1->di_db[i],
671                             sblksize(&sblock, (off_t)dp1->di_size, i))) != 0)
672                                 return (ino);
673                 }
674         } else {
675                 if ((off_t)dp1->di_size >= lblktosize(&sblock, NDADDR)) {
676                         warnx("ROOTINO extends beyond direct blocks.");
677                         return (-1);
678                 }
679                 for (i = 0; i < NDADDR; i++) {
680                         if (dp2->di_db[i] == 0)
681                                 break;
682                         if ((ino = dir_search(dp2->di_db[i],
683                             sblksize(&sblock, (off_t)dp2->di_size, i))) != 0)
684                                 return (ino);
685                 }
686         }
687
688         return (0);
689 }
690
691 static void
692 dir_clear_block(char *block, off_t off)
693 {
694         struct direct *dp;
695
696         for (; off < sblock.fs_bsize; off += DIRBLKSIZ) {
697                 dp = (struct direct *)&block[off];
698                 dp->d_ino = 0;
699                 dp->d_reclen = DIRBLKSIZ;
700                 dp->d_type = DT_UNKNOWN;
701         }
702 }
703
704 /*
705  * Insert the journal at inode 'ino' into directory blk 'blk' at the first
706  * free offset of 'off'.  DIRBLKSIZ blocks after off are initialized as
707  * empty.
708  */
709 static int
710 dir_insert(ufs2_daddr_t blk, off_t off, ino_t ino)
711 {
712         struct direct *dp;
713         char block[MAXBSIZE];
714
715         if (bread(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
716                 warn("Failed to read dir block");
717                 return (-1);
718         }
719         bzero(&block[off], sblock.fs_bsize - off);
720         dp = (struct direct *)&block[off];
721         dp->d_ino = ino;
722         dp->d_reclen = DIRBLKSIZ;
723         dp->d_type = DT_REG;
724         dp->d_namlen = strlen(SUJ_FILE);
725         bcopy(SUJ_FILE, &dp->d_name, strlen(SUJ_FILE));
726         dir_clear_block(block, off + DIRBLKSIZ);
727         if (bwrite(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
728                 warn("Failed to write dir block");
729                 return (-1);
730         }
731         return (0);
732 }
733
734 /*
735  * Extend a directory block in 'blk' by copying it to a full size block
736  * and inserting the new journal inode into .sujournal.
737  */
738 static int
739 dir_extend(ufs2_daddr_t blk, ufs2_daddr_t nblk, off_t size, ino_t ino)
740 {
741         char block[MAXBSIZE];
742
743         if (bread(&disk, fsbtodb(&sblock, blk), block,
744             roundup(size, sblock.fs_fsize)) <= 0) {
745                 warn("Failed to read dir block");
746                 return (-1);
747         }
748         dir_clear_block(block, size);
749         if (bwrite(&disk, fsbtodb(&sblock, nblk), block, sblock.fs_bsize)
750             <= 0) {
751                 warn("Failed to write dir block");
752                 return (-1);
753         }
754
755         return (dir_insert(nblk, size, ino));
756 }
757
758 /*
759  * Insert the journal file into the ROOTINO directory.  We always extend the
760  * last frag
761  */
762 static int
763 journal_insertfile(ino_t ino)
764 {
765         struct ufs1_dinode *dp1;
766         struct ufs2_dinode *dp2;
767         void *ip;
768         ufs2_daddr_t nblk;
769         ufs2_daddr_t blk;
770         ufs_lbn_t lbn;
771         int size;
772         int mode;
773         int off;
774
775         if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
776                 warn("Failed to get root inode");
777                 sbdirty();
778                 return (-1);
779         }
780         dp2 = ip;
781         dp1 = ip;
782         blk = 0;
783         size = 0;
784         nblk = journal_balloc();
785         if (nblk <= 0)
786                 return (-1);
787         /*
788          * For simplicity sake we aways extend the ROOTINO into a new
789          * directory block rather than searching for space and inserting
790          * into an existing block.  However, if the rootino has frags
791          * have to free them and extend the block.
792          */
793         if (sblock.fs_magic == FS_UFS1_MAGIC) {
794                 lbn = lblkno(&sblock, dp1->di_size);
795                 off = blkoff(&sblock, dp1->di_size);
796                 blk = dp1->di_db[lbn];
797                 size = sblksize(&sblock, (off_t)dp1->di_size, lbn);
798         } else {
799                 lbn = lblkno(&sblock, dp2->di_size);
800                 off = blkoff(&sblock, dp2->di_size);
801                 blk = dp2->di_db[lbn];
802                 size = sblksize(&sblock, (off_t)dp2->di_size, lbn);
803         }
804         if (off != 0) {
805                 if (dir_extend(blk, nblk, off, ino) == -1)
806                         return (-1);
807         } else {
808                 blk = 0;
809                 if (dir_insert(nblk, 0, ino) == -1)
810                         return (-1);
811         }
812         if (sblock.fs_magic == FS_UFS1_MAGIC) {
813                 dp1->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
814                 dp1->di_db[lbn] = nblk;
815                 dp1->di_size = lblktosize(&sblock, lbn+1);
816         } else {
817                 dp2->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
818                 dp2->di_db[lbn] = nblk;
819                 dp2->di_size = lblktosize(&sblock, lbn+1);
820         }
821         if (putino(&disk) < 0) {
822                 warn("Failed to write root inode");
823                 return (-1);
824         }
825         if (cgwrite(&disk) < 0) {
826                 warn("Failed to write updated cg");
827                 sbdirty();
828                 return (-1);
829         }
830         if (blk) {
831                 if (cgbfree(&disk, blk, size) < 0) {
832                         warn("Failed to write cg");
833                         return (-1);
834                 }
835         }
836
837         return (0);
838 }
839
840 static int
841 indir_fill(ufs2_daddr_t blk, int level, int *resid)
842 {
843         char indirbuf[MAXBSIZE];
844         ufs1_daddr_t *bap1;
845         ufs2_daddr_t *bap2;
846         ufs2_daddr_t nblk;
847         int ncnt;
848         int cnt;
849         int i;
850
851         bzero(indirbuf, sizeof(indirbuf));
852         bap1 = (ufs1_daddr_t *)indirbuf;
853         bap2 = (void *)bap1;
854         cnt = 0;
855         for (i = 0; i < NINDIR(&sblock) && *resid != 0; i++) {
856                 nblk = journal_balloc();
857                 if (nblk <= 0)
858                         return (-1);
859                 cnt++;
860                 if (sblock.fs_magic == FS_UFS1_MAGIC)
861                         *bap1++ = nblk;
862                 else
863                         *bap2++ = nblk;
864                 if (level != 0) {
865                         ncnt = indir_fill(nblk, level - 1, resid);
866                         if (ncnt <= 0)
867                                 return (-1);
868                         cnt += ncnt;
869                 } else 
870                         (*resid)--;
871         }
872         if (bwrite(&disk, fsbtodb(&sblock, blk), indirbuf,
873             sblock.fs_bsize) <= 0) {
874                 warn("Failed to write indirect");
875                 return (-1);
876         }
877         return (cnt);
878 }
879
880 /*
881  * Clear the flag bits so the journal can be removed.
882  */
883 void
884 journal_clear(void)
885 {
886         struct ufs1_dinode *dp1;
887         struct ufs2_dinode *dp2;
888         ino_t ino;
889         int mode;
890         void *ip;
891
892         ino = journal_findfile();
893         if (ino == (ino_t)-1 || ino == 0) {
894                 warnx("Journal file does not exist");
895                 return;
896         }
897         printf("Clearing journal flags from inode %d\n", ino);
898         if (getino(&disk, &ip, ino, &mode) != 0) {
899                 warn("Failed to get journal inode");
900                 return;
901         }
902         dp2 = ip;
903         dp1 = ip;
904         if (sblock.fs_magic == FS_UFS1_MAGIC)
905                 dp1->di_flags = 0;
906         else
907                 dp2->di_flags = 0;
908         if (putino(&disk) < 0) {
909                 warn("Failed to write journal inode");
910                 return;
911         }
912 }
913
914 int
915 journal_alloc(int64_t size)
916 {
917         struct ufs1_dinode *dp1;
918         struct ufs2_dinode *dp2;
919         ufs2_daddr_t blk;
920         void *ip;
921         struct cg *cgp;
922         int resid;
923         ino_t ino;
924         int blks;
925         int mode;
926         int i;
927
928         cgp = &disk.d_cg;
929         ino = 0;
930
931         /*
932          * If the journal file exists we can't allocate it.
933          */
934         ino = journal_findfile();
935         if (ino == (ino_t)-1)
936                 return (-1);
937         if (ino > 0) {
938                 warnx("Journal file %s already exists, please remove.",
939                     SUJ_FILE);
940                 return (-1);
941         }
942         /*
943          * If the user didn't supply a size pick one based on the filesystem
944          * size constrained with hardcoded MIN and MAX values.  We opt for
945          * 1/1024th of the filesystem up to MAX but not exceeding one CG and
946          * not less than the MIN.
947          */
948         if (size == 0) {
949                 size = (sblock.fs_size * sblock.fs_bsize) / 1024;
950                 size = MIN(SUJ_MAX, size);
951                 if (size / sblock.fs_fsize > sblock.fs_fpg)
952                         size = sblock.fs_fpg * sblock.fs_fsize;
953                 size = MAX(SUJ_MIN, size);
954                 /* fsck does not support fragments in journal files. */
955                 size = roundup(size, sblock.fs_bsize);
956         }
957         resid = blocks = size / sblock.fs_bsize;
958         if (sblock.fs_cstotal.cs_nbfree < blocks) {
959                 warn("Insufficient free space for %jd byte journal", size);
960                 return (-1);
961         }
962         /*
963          * Find a cg with enough blocks to satisfy the journal
964          * size.  Presently the journal does not span cgs.
965          */
966         while (cgread(&disk) == 1) {
967                 if (cgp->cg_cs.cs_nifree == 0)
968                         continue;
969                 ino = cgialloc(&disk);
970                 if (ino <= 0)
971                         break;
972                 printf("Using inode %d in cg %d for %jd byte journal\n", 
973                     ino, cgp->cg_cgx, size);
974                 if (getino(&disk, &ip, ino, &mode) != 0) {
975                         warn("Failed to get allocated inode");
976                         sbdirty();
977                         goto out;
978                 }
979                 /*
980                  * We leave fields unrelated to the number of allocated
981                  * blocks and size uninitialized.  This causes legacy
982                  * fsck implementations to clear the inode.
983                  */
984                 dp2 = ip;
985                 dp1 = ip;
986                 if (sblock.fs_magic == FS_UFS1_MAGIC) {
987                         bzero(dp1, sizeof(*dp1));
988                         dp1->di_size = size;
989                         dp1->di_mode = IFREG | IREAD;
990                         dp1->di_nlink = 1;
991                         dp1->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
992                 } else {
993                         bzero(dp2, sizeof(*dp2));
994                         dp2->di_size = size;
995                         dp2->di_mode = IFREG | IREAD;
996                         dp2->di_nlink = 1;
997                         dp2->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
998                 }
999                 for (i = 0; i < NDADDR && resid; i++, resid--) {
1000                         blk = journal_balloc();
1001                         if (blk <= 0)
1002                                 goto out;
1003                         if (sblock.fs_magic == FS_UFS1_MAGIC) {
1004                                 dp1->di_db[i] = blk;
1005                                 dp1->di_blocks++;
1006                         } else {
1007                                 dp2->di_db[i] = blk;
1008                                 dp2->di_blocks++;
1009                         }
1010                 }
1011                 for (i = 0; i < NIADDR && resid; i++) {
1012                         blk = journal_balloc();
1013                         if (blk <= 0)
1014                                 goto out;
1015                         blks = indir_fill(blk, i, &resid) + 1;
1016                         if (blks <= 0) {
1017                                 sbdirty();
1018                                 goto out;
1019                         }
1020                         if (sblock.fs_magic == FS_UFS1_MAGIC) {
1021                                 dp1->di_ib[i] = blk;
1022                                 dp1->di_blocks += blks;
1023                         } else {
1024                                 dp2->di_ib[i] = blk;
1025                                 dp2->di_blocks += blks;
1026                         }
1027                 }
1028                 if (sblock.fs_magic == FS_UFS1_MAGIC)
1029                         dp1->di_blocks *= sblock.fs_bsize / disk.d_bsize;
1030                 else
1031                         dp2->di_blocks *= sblock.fs_bsize / disk.d_bsize;
1032                 if (putino(&disk) < 0) {
1033                         warn("Failed to write inode");
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 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]", 
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 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("optimization preference: (-o)                      %s",
1093               sblock.fs_optim == FS_OPTSPACE ? "space" : "time");
1094         if (sblock.fs_minfree >= MINFREE &&
1095             sblock.fs_optim == FS_OPTSPACE)
1096                 warnx(OPTWARN, "time", ">=", MINFREE);
1097         if (sblock.fs_minfree < MINFREE &&
1098             sblock.fs_optim == FS_OPTTIME)
1099                 warnx(OPTWARN, "space", "<", MINFREE);
1100         warnx("volume label: (-L)                                 %s",
1101                 sblock.fs_volname);
1102 }