]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/fsck_ffs/pass2.c
zfs: merge openzfs/zfs@10e36e176
[FreeBSD/FreeBSD.git] / sbin / fsck_ffs / pass2.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1986, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #if 0
33 #ifndef lint
34 static const char sccsid[] = "@(#)pass2.c       8.9 (Berkeley) 4/28/95";
35 #endif /* not lint */
36 #endif
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
39
40 #include <sys/param.h>
41 #include <sys/sysctl.h>
42
43 #include <ufs/ufs/dinode.h>
44 #include <ufs/ufs/dir.h>
45 #include <ufs/ffs/fs.h>
46
47 #include <err.h>
48 #include <errno.h>
49 #include <stdint.h>
50 #include <string.h>
51
52 #include "fsck.h"
53
54 #define MINDIRSIZE      (sizeof (struct dirtemplate))
55
56 static int fix_extraneous(struct inoinfo *, struct inodesc *);
57 static int deleteentry(struct inodesc *);
58 static int blksort(const void *, const void *);
59 static int pass2check(struct inodesc *);
60
61 void
62 pass2(void)
63 {
64         struct inode ip;
65         union dinode *dp;
66         struct inoinfo **inpp, *inp;
67         struct inoinfo **inpend;
68         struct inodesc curino;
69         union dinode dino;
70         int i;
71         char pathbuf[MAXPATHLEN + 1];
72
73         switch (inoinfo(UFS_ROOTINO)->ino_state) {
74
75         case USTATE:
76                 pfatal("ROOT INODE UNALLOCATED");
77                 if (reply("ALLOCATE") == 0) {
78                         ckfini(0);
79                         exit(EEXIT);
80                 }
81                 if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) != UFS_ROOTINO)
82                         errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
83                 break;
84
85         case DCLEAR:
86                 pfatal("DUPS/BAD IN ROOT INODE");
87                 if (reply("REALLOCATE")) {
88                         freedirino(UFS_ROOTINO, UFS_ROOTINO);
89                         if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) !=
90                             UFS_ROOTINO)
91                                 errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
92                         break;
93                 }
94                 if (reply("CONTINUE") == 0) {
95                         ckfini(0);
96                         exit(EEXIT);
97                 }
98                 break;
99
100         case FSTATE:
101         case FCLEAR:
102         case FZLINK:
103                 pfatal("ROOT INODE NOT DIRECTORY");
104                 if (reply("REALLOCATE")) {
105                         freeino(UFS_ROOTINO);
106                         if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) !=
107                             UFS_ROOTINO)
108                                 errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
109                         break;
110                 }
111                 if (reply("FIX") == 0) {
112                         ckfini(0);
113                         exit(EEXIT);
114                 }
115                 ginode(UFS_ROOTINO, &ip);
116                 dp = ip.i_dp;
117                 DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
118                 DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
119                 inodirty(&ip);
120                 irelse(&ip);
121                 break;
122
123         case DSTATE:
124         case DZLINK:
125                 break;
126
127         default:
128                 errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
129                     inoinfo(UFS_ROOTINO)->ino_state);
130         }
131         inoinfo(UFS_ROOTINO)->ino_state = DFOUND;
132         inoinfo(UFS_WINO)->ino_state = FSTATE;
133         inoinfo(UFS_WINO)->ino_type = DT_WHT;
134         /*
135          * Sort the directory list into disk block order.
136          */
137         qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
138         /*
139          * Check the integrity of each directory.
140          */
141         memset(&curino, 0, sizeof(struct inodesc));
142         curino.id_type = DATA;
143         curino.id_func = pass2check;
144         inpend = &inpsort[inplast];
145         for (inpp = inpsort; inpp < inpend; inpp++) {
146                 if (got_siginfo) {
147                         printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
148                             inpp - inpsort, (int)inplast,
149                             (int)((inpp - inpsort) * 100 / inplast));
150                         got_siginfo = 0;
151                 }
152                 if (got_sigalarm) {
153                         setproctitle("%s p2 %d%%", cdevname,
154                             (int)((inpp - inpsort) * 100 / inplast));
155                         got_sigalarm = 0;
156                 }
157                 inp = *inpp;
158                 if (inp->i_isize == 0)
159                         continue;
160                 if (inp->i_isize < MINDIRSIZE) {
161                         direrror(inp->i_number, "DIRECTORY TOO SHORT");
162                         inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
163                         if (reply("FIX") == 1) {
164                                 ginode(inp->i_number, &ip);
165                                 DIP_SET(ip.i_dp, di_size, inp->i_isize);
166                                 inodirty(&ip);
167                                 irelse(&ip);
168                         }
169                 } else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
170                         getpathname(pathbuf, inp->i_number, inp->i_number);
171                         if (usedsoftdep)
172                                 pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
173                                         "DIRECTORY", pathbuf,
174                                         (intmax_t)inp->i_isize, DIRBLKSIZ);
175                         else
176                                 pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
177                                         "DIRECTORY", pathbuf,
178                                         (intmax_t)inp->i_isize, DIRBLKSIZ);
179                         if (preen)
180                                 printf(" (ADJUSTED)\n");
181                         inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
182                         if (preen || reply("ADJUST") == 1) {
183                                 ginode(inp->i_number, &ip);
184                                 DIP_SET(ip.i_dp, di_size,
185                                     roundup(inp->i_isize, DIRBLKSIZ));
186                                 inodirty(&ip);
187                                 irelse(&ip);
188                         }
189                 }
190                 dp = &dino;
191                 memset(dp, 0, sizeof(struct ufs2_dinode));
192                 DIP_SET(dp, di_mode, IFDIR);
193                 DIP_SET(dp, di_size, inp->i_isize);
194                 for (i = 0; i < MIN(inp->i_numblks, UFS_NDADDR); i++)
195                         DIP_SET(dp, di_db[i], inp->i_blks[i]);
196                 if (inp->i_numblks > UFS_NDADDR)
197                         for (i = 0; i < UFS_NIADDR; i++)
198                                 DIP_SET(dp, di_ib[i],
199                                     inp->i_blks[UFS_NDADDR + i]);
200                 curino.id_number = inp->i_number;
201                 curino.id_parent = inp->i_parent;
202                 (void)ckinode(dp, &curino);
203         }
204         /*
205          * Now that the parents of all directories have been found,
206          * make another pass to verify the value of `..'
207          */
208         for (inpp = inpsort; inpp < inpend; inpp++) {
209                 inp = *inpp;
210                 if (inp->i_parent == 0 || inp->i_isize == 0)
211                         continue;
212                 if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
213                     INO_IS_DUNFOUND(inp->i_number)) {
214                         inoinfo(inp->i_number)->ino_state = DFOUND;
215                         check_dirdepth(inp);
216                 }
217                 if (inp->i_dotdot == inp->i_parent ||
218                     inp->i_dotdot == (ino_t)-1)
219                         continue;
220                 if (inp->i_dotdot == 0) {
221                         inp->i_dotdot = inp->i_parent;
222                         if (debug)
223                                 fileerror(inp->i_parent, inp->i_number,
224                                     "DEFERRED MISSING '..' FIX");
225                         (void)makeentry(inp->i_number, inp->i_parent, "..");
226                         inoinfo(inp->i_parent)->ino_linkcnt--;
227                         continue;
228                 }
229                 /*
230                  * Here we have:
231                  *    inp->i_number is directory with bad ".." in it.
232                  *    inp->i_dotdot is current value of "..".
233                  *    inp->i_parent is directory to which ".." should point.
234                  */
235                 getpathname(pathbuf, inp->i_parent, inp->i_number);
236                 printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
237                     (uintmax_t)inp->i_number, pathbuf);
238                 getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
239                 printf("CURRENTLY POINTS TO I=%ju (%s), ",
240                     (uintmax_t)inp->i_dotdot, pathbuf);
241                 getpathname(pathbuf, inp->i_parent, inp->i_parent);
242                 printf("SHOULD POINT TO I=%ju (%s)",
243                     (uintmax_t)inp->i_parent, pathbuf);
244                 if (cursnapshot != 0) {
245                         /*
246                          * We need to:
247                          *    setcwd(inp->i_number);
248                          *    setdotdot(inp->i_dotdot, inp->i_parent);
249                          */
250                         cmd.value = inp->i_number;
251                         if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
252                             &cmd, sizeof cmd) == -1) {
253                                 /* kernel lacks support for these functions */
254                                 printf(" (IGNORED)\n");
255                                 continue;
256                         }
257                         cmd.value = inp->i_dotdot; /* verify same value */
258                         cmd.size = inp->i_parent;  /* new parent */
259                         if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
260                             &cmd, sizeof cmd) == -1) {
261                                 printf(" (FIX FAILED: %s)\n", strerror(errno));
262                                 continue;
263                         }
264                         printf(" (FIXED)\n");
265                         inoinfo(inp->i_parent)->ino_linkcnt--;
266                         inp->i_dotdot = inp->i_parent;
267                         continue;
268                 }
269                 if (preen)
270                         printf(" (FIXED)\n");
271                 else if (reply("FIX") == 0)
272                         continue;
273                 inoinfo(inp->i_dotdot)->ino_linkcnt++;
274                 inoinfo(inp->i_parent)->ino_linkcnt--;
275                 inp->i_dotdot = inp->i_parent;
276                 (void)changeino(inp->i_number, "..", inp->i_parent,
277                     getinoinfo(inp->i_parent)->i_depth  + 1);
278         }
279         /*
280          * Mark all the directories that can be found from the root.
281          */
282         propagate();
283 }
284
285 static int
286 pass2check(struct inodesc *idesc)
287 {
288         struct direct *dirp = idesc->id_dirp;
289         char dirname[MAXPATHLEN + 1];
290         struct inoinfo *inp;
291         int n, entrysize, ret = 0;
292         struct inode ip;
293         union dinode *dp;
294         const char *errmsg;
295         struct direct proto, *newdirp;
296
297         /*
298          * check for "."
299          */
300         if (idesc->id_entryno != 0)
301                 goto chk1;
302         if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
303                 if (dirp->d_ino != idesc->id_number) {
304                         direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
305                         if (reply("FIX") == 1) {
306                                 dirp->d_ino = idesc->id_number;
307                                 ret |= ALTERED;
308                         }
309                 }
310                 if (dirp->d_type != DT_DIR) {
311                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
312                         if (reply("FIX") == 1) {
313                                 dirp->d_type = DT_DIR;
314                                 ret |= ALTERED;
315                         }
316                 }
317                 goto chk1;
318         }
319         proto.d_ino = idesc->id_number;
320         proto.d_type = DT_DIR;
321         proto.d_namlen = 1;
322         (void)strcpy(proto.d_name, ".");
323         entrysize = DIRSIZ(0, &proto);
324         direrror(idesc->id_number, "MISSING '.'");
325         errmsg = "ADD '.' ENTRY";
326         if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
327                 /* Not enough space to add '.', replace first entry with '.' */
328                 if (dirp->d_ino != 0) {
329                         pwarn("\nFIRST ENTRY IN DIRECTORY CONTAINS %s\n",
330                              dirp->d_name);
331                         errmsg = "REPLACE WITH '.'";
332                 }
333                 if (reply(errmsg) == 0)
334                         goto chk1;
335                 proto.d_reclen = dirp->d_reclen;
336                 memmove(dirp, &proto, (size_t)entrysize);
337                 ret |= ALTERED;
338         } else {
339                 /* Move over first entry and add '.' entry */
340                 if (reply(errmsg) == 0)
341                         goto chk1;
342                 newdirp = (struct direct *)((char *)(dirp) + entrysize);
343                 dirp->d_reclen -= entrysize;
344                 memmove(newdirp, dirp, dirp->d_reclen);
345                 proto.d_reclen = entrysize;
346                 memmove(dirp, &proto, (size_t)entrysize);
347                 idesc->id_entryno++;
348                 inoinfo(idesc->id_number)->ino_linkcnt--;
349                 dirp = newdirp;
350                 ret |= ALTERED;
351         }
352 chk1:
353         if (idesc->id_entryno > 1)
354                 goto chk2;
355         inp = getinoinfo(idesc->id_number);
356         proto.d_ino = inp->i_parent;
357         proto.d_type = DT_DIR;
358         proto.d_namlen = 2;
359         (void)strcpy(proto.d_name, "..");
360         entrysize = DIRSIZ(0, &proto);
361         if (idesc->id_entryno == 0) {
362                 n = DIRSIZ(0, dirp);
363                 if (dirp->d_reclen < n + entrysize)
364                         goto chk2;
365                 proto.d_reclen = dirp->d_reclen - n;
366                 dirp->d_reclen = n;
367                 idesc->id_entryno++;
368                 inoinfo(dirp->d_ino)->ino_linkcnt--;
369                 dirp = (struct direct *)((char *)(dirp) + n);
370                 memset(dirp, 0, (size_t)proto.d_reclen);
371                 dirp->d_reclen = proto.d_reclen;
372         }
373         if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
374                 if (dirp->d_ino >= maxino) {
375                         direrror(idesc->id_number, "BAD INODE NUMBER FOR '..'");
376                         /*
377                          * If we know parent set it now, otherwise let it
378                          * point to the root inode and it will get cleaned
379                          * up later if that is not correct.
380                          */
381                         if (inp->i_parent != 0)
382                                 dirp->d_ino = inp->i_parent;
383                         else
384                                 dirp->d_ino = UFS_ROOTINO;
385                         if (reply("FIX") == 1)
386                                 ret |= ALTERED;
387                 }
388                 inp->i_dotdot = dirp->d_ino;
389                 if (dirp->d_type != DT_DIR) {
390                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
391                         dirp->d_type = DT_DIR;
392                         if (reply("FIX") == 1)
393                                 ret |= ALTERED;
394                 }
395                 goto chk2;
396         }
397         fileerror(inp->i_parent != 0 ? inp->i_parent : idesc->id_number,
398             idesc->id_number, "MISSING '..'");
399         errmsg = "ADD '..' ENTRY";
400         if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
401                 /* No space to add '..', replace second entry with '..' */
402                 if (dirp->d_ino != 0) {
403                         pfatal("SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
404                             dirp->d_name);
405                         errmsg = "REPLACE WITH '..'";
406                 }
407                 if (reply(errmsg) == 0) {
408                         inp->i_dotdot = (ino_t)-1;
409                         goto chk2;
410                 }
411                 if (proto.d_ino == 0) {
412                         /* Defer processing until parent known */
413                         idesc->id_entryno++;
414                         if (debug)
415                                 printf("(FIX DEFERRED)\n");
416                 }
417                 inp->i_dotdot = proto.d_ino;
418                 proto.d_reclen = dirp->d_reclen;
419                 memmove(dirp, &proto, (size_t)entrysize);
420                 ret |= ALTERED;
421         } else {
422                 /* Move over second entry and add '..' entry */
423                 if (reply(errmsg) == 0) {
424                         inp->i_dotdot = (ino_t)-1;
425                         goto chk2;
426                 }
427                 if (proto.d_ino == 0) {
428                         /* Defer processing until parent known */
429                         idesc->id_entryno++;
430                         if (debug)
431                                 printf("(FIX DEFERRED)\n");
432                 }
433                 inp->i_dotdot = proto.d_ino;
434                 if (dirp->d_ino == 0) {
435                         proto.d_reclen = dirp->d_reclen;
436                         memmove(dirp, &proto, (size_t)entrysize);
437                 } else {
438                         newdirp = (struct direct *)((char *)(dirp) + entrysize);
439                         dirp->d_reclen -= entrysize;
440                         memmove(newdirp, dirp, dirp->d_reclen);
441                         proto.d_reclen = entrysize;
442                         memmove(dirp, &proto, (size_t)entrysize);
443                         if (dirp->d_ino != 0) {
444                                 idesc->id_entryno++;
445                                 inoinfo(dirp->d_ino)->ino_linkcnt--;
446                         }
447                         dirp = newdirp;
448                 }
449                 ret |= ALTERED;
450         }
451 chk2:
452         if (dirp->d_ino == 0)
453                 return (ret|KEEPON);
454         if (dirp->d_namlen <= 2 &&
455             dirp->d_name[0] == '.' &&
456             idesc->id_entryno >= 2) {
457                 if (dirp->d_namlen == 1) {
458                         direrror(idesc->id_number, "EXTRA '.' ENTRY");
459                         dirp->d_ino = 0;
460                         if (reply("FIX") == 1)
461                                 ret |= ALTERED;
462                         return (KEEPON | ret);
463                 }
464                 if (dirp->d_name[1] == '.') {
465                         direrror(idesc->id_number, "EXTRA '..' ENTRY");
466                         dirp->d_ino = 0;
467                         if (reply("FIX") == 1)
468                                 ret |= ALTERED;
469                         return (KEEPON | ret);
470                 }
471         }
472         idesc->id_entryno++;
473         n = 0;
474         if (dirp->d_ino >= maxino) {
475                 fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
476                 n = reply("REMOVE");
477         } else if (((dirp->d_ino == UFS_WINO && dirp->d_type != DT_WHT) ||
478                     (dirp->d_ino != UFS_WINO && dirp->d_type == DT_WHT))) {
479                 fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
480                 dirp->d_ino = UFS_WINO;
481                 dirp->d_type = DT_WHT;
482                 if (reply("FIX") == 1)
483                         ret |= ALTERED;
484         } else {
485 again:
486                 switch (inoinfo(dirp->d_ino)->ino_state) {
487                 case USTATE:
488                         if (idesc->id_entryno <= 2)
489                                 break;
490                         fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
491                         n = reply("REMOVE");
492                         break;
493
494                 case DCLEAR:
495                 case FCLEAR:
496                         if (idesc->id_entryno <= 2)
497                                 break;
498                         if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
499                                 errmsg = "DUP/BAD";
500                         else if (!preen && !usedsoftdep)
501                                 errmsg = "ZERO LENGTH DIRECTORY";
502                         else if (cursnapshot == 0) {
503                                 n = 1;
504                                 break;
505                         } else {
506                                 getpathname(dirname, idesc->id_number,
507                                     dirp->d_ino);
508                                 pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
509                                     dirname, (uintmax_t)dirp->d_ino);
510                                 /*
511                                  * We need to:
512                                  *    setcwd(idesc->id_parent);
513                                  *    rmdir(dirp->d_name);
514                                  */
515                                 cmd.value = idesc->id_number;
516                                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
517                                     &cmd, sizeof cmd) == -1) {
518                                         /* kernel lacks support */
519                                         printf(" (IGNORED)\n");
520                                         n = 1;
521                                         break;
522                                 }
523                                 if (rmdir(dirp->d_name) == -1) {
524                                         printf(" (REMOVAL FAILED: %s)\n",
525                                             strerror(errno));
526                                         n = 1;
527                                         break;
528                                 }
529                                 /* ".." reference to parent is removed */
530                                 inoinfo(idesc->id_number)->ino_linkcnt--;
531                                 printf(" (REMOVED)\n");
532                                 break;
533                         }
534                         fileerror(idesc->id_number, dirp->d_ino, errmsg);
535                         if ((n = reply("REMOVE")) == 1)
536                                 break;
537                         ginode(dirp->d_ino, &ip);
538                         dp = ip.i_dp;
539                         inoinfo(dirp->d_ino)->ino_state =
540                            (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
541                         inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
542                         irelse(&ip);
543                         goto again;
544
545                 case DSTATE:
546                 case DZLINK:
547                         if (inoinfo(idesc->id_number)->ino_state == DFOUND)
548                                 inoinfo(dirp->d_ino)->ino_state = DFOUND;
549                         /* FALLTHROUGH */
550
551                 case DFOUND:
552                         inp = getinoinfo(dirp->d_ino);
553                         if (idesc->id_entryno > 2) {
554                                 if (inp->i_parent == 0) {
555                                         inp->i_parent = idesc->id_number;
556                                         check_dirdepth(inp);
557                                 } else if ((n = fix_extraneous(inp, idesc))) {
558                                         break;
559                                 }
560                         }
561                         /* FALLTHROUGH */
562
563                 case FSTATE:
564                 case FZLINK:
565                         if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
566                                 fileerror(idesc->id_number, dirp->d_ino,
567                                     "BAD TYPE VALUE");
568                                 dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
569                                 if (reply("FIX") == 1)
570                                         ret |= ALTERED;
571                         }
572                         inoinfo(dirp->d_ino)->ino_linkcnt--;
573                         break;
574
575                 default:
576                         errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
577                             inoinfo(dirp->d_ino)->ino_state,
578                             (uintmax_t)dirp->d_ino);
579                 }
580         }
581         if (n == 0)
582                 return (ret|KEEPON);
583         dirp->d_ino = 0;
584         return (ret|KEEPON|ALTERED);
585 }
586
587 static int
588 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
589 {
590         char *cp;
591         struct inode ip;
592         struct inodesc dotdesc;
593         char oldname[MAXPATHLEN + 1];
594         char newname[MAXPATHLEN + 1];
595
596         /*
597          * If we have not yet found "..", look it up now so we know
598          * which inode the directory itself believes is its parent.
599          */
600         if (inp->i_dotdot == 0) {
601                 memset(&dotdesc, 0, sizeof(struct inodesc));
602                 dotdesc.id_type = DATA;
603                 dotdesc.id_number = idesc->id_dirp->d_ino;
604                 dotdesc.id_func = findino;
605                 dotdesc.id_name = strdup("..");
606                 ginode(dotdesc.id_number, &ip);
607                 if ((ckinode(ip.i_dp, &dotdesc) & FOUND))
608                         inp->i_dotdot = dotdesc.id_parent;
609                 irelse(&ip);
610                 free(dotdesc.id_name);
611         }
612         /*
613          * We have the previously found old name (inp->i_parent) and the
614          * just found new name (idesc->id_number). We have five cases:
615          * 1)  ".." is missing - can remove either name, choose to delete
616          *     new one and let fsck create ".." pointing to old name.
617          * 2) Both new and old are in same directory, choose to delete
618          *    the new name and let fsck fix ".." if it is wrong.
619          * 3) ".." does not point to the new name, so delete it and let
620          *    fsck fix ".." to point to the old one if it is wrong.
621          * 4) ".." points to the old name only, so delete the new one.
622          * 5) ".." points to the new name only, so delete the old one.
623          *
624          * For cases 1-4 we eliminate the new name;
625          * for case 5 we eliminate the old name.
626          */
627         if (inp->i_dotdot == 0 ||                   /* Case 1 */
628             idesc->id_number == inp->i_parent ||    /* Case 2 */
629             inp->i_dotdot != idesc->id_number ||    /* Case 3 */
630             inp->i_dotdot == inp->i_parent) {       /* Case 4 */
631                 getpathname(newname, idesc->id_number, idesc->id_number);
632                 if (strcmp(newname, "/") != 0)
633                         strcat (newname, "/");
634                 strcat(newname, idesc->id_dirp->d_name);
635                 getpathname(oldname, inp->i_number, inp->i_number);
636                 pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
637                     newname, oldname);
638                 if (cursnapshot != 0) {
639                         /*
640                          * We need to
641                          *    setcwd(idesc->id_number);
642                          *    unlink(idesc->id_dirp->d_name);
643                          */
644                         cmd.value = idesc->id_number;
645                         if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
646                             &cmd, sizeof cmd) == -1) {
647                                 printf(" (IGNORED)\n");
648                                 return (0);
649                         }
650                         cmd.value = (intptr_t)idesc->id_dirp->d_name;
651                         cmd.size = inp->i_number; /* verify same name */
652                         if (sysctlbyname("vfs.ffs.unlink", 0, 0,
653                             &cmd, sizeof cmd) == -1) {
654                                 printf(" (UNLINK FAILED: %s)\n",
655                                     strerror(errno));
656                                 return (0);
657                         }
658                         printf(" (REMOVED)\n");
659                         return (0);
660                 }
661                 if (preen) {
662                         printf(" (REMOVED)\n");
663                         return (1);
664                 }
665                 return (reply("REMOVE"));
666         }
667         /*
668          * None of the first four cases above, so must be case (5).
669          * Eliminate the old name and make the new the name the parent.
670          */
671         getpathname(oldname, inp->i_parent, inp->i_number);
672         getpathname(newname, inp->i_number, inp->i_number);
673         pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
674             newname);
675         if (cursnapshot != 0) {
676                 /*
677                  * We need to
678                  *    setcwd(inp->i_parent);
679                  *    unlink(last component of oldname pathname);
680                  */
681                 cmd.value = inp->i_parent;
682                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
683                     &cmd, sizeof cmd) == -1) {
684                         printf(" (IGNORED)\n");
685                         return (0);
686                 }
687                 if ((cp = strchr(oldname, '/')) == NULL) {
688                         printf(" (IGNORED)\n");
689                         return (0);
690                 }
691                 cmd.value = (intptr_t)(cp + 1);
692                 cmd.size = inp->i_number; /* verify same name */
693                 if (sysctlbyname("vfs.ffs.unlink", 0, 0,
694                     &cmd, sizeof cmd) == -1) {
695                         printf(" (UNLINK FAILED: %s)\n",
696                             strerror(errno));
697                         return (0);
698                 }
699                 printf(" (REMOVED)\n");
700                 inp->i_parent = idesc->id_number;  /* reparent to correct dir */
701                 return (0);
702         }
703         if (!preen && !reply("REMOVE"))
704                 return (0);
705         memset(&dotdesc, 0, sizeof(struct inodesc));
706         dotdesc.id_type = DATA;
707         dotdesc.id_number = inp->i_parent; /* directory in which name appears */
708         dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
709         dotdesc.id_func = deleteentry;
710         ginode(dotdesc.id_number, &ip);
711         if ((ckinode(ip.i_dp, &dotdesc) & FOUND) && preen)
712                 printf(" (REMOVED)\n");
713         irelse(&ip);
714         inp->i_parent = idesc->id_number;  /* reparent to correct directory */
715         inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
716         return (0);
717 }
718
719 static int
720 deleteentry(struct inodesc *idesc)
721 {
722         struct direct *dirp = idesc->id_dirp;
723
724         if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
725                 return (KEEPON);
726         dirp->d_ino = 0;
727         return (ALTERED|STOP|FOUND);
728 }
729
730 /*
731  * Routine to sort disk blocks.
732  */
733 static int
734 blksort(const void *arg1, const void *arg2)
735 {
736
737         return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
738                 (*(struct inoinfo * const *)arg2)->i_blks[0]);
739 }