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