1 /*        $NetBSD: pass2.c,v 1.53 2023/07/04 20:40:53 riastradh Exp $ */
2 
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 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)pass2.c     8.9 (Berkeley) 4/28/95";
36 #else
37 __RCSID("$NetBSD: pass2.c,v 1.53 2023/07/04 20:40:53 riastradh Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include <sys/param.h>
42 #include <sys/time.h>
43 
44 #include <ufs/ufs/dinode.h>
45 #include <ufs/ufs/dir.h>
46 #include <ufs/ffs/fs.h>
47 
48 #include <err.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 
53 #include "fsck.h"
54 #include "fsutil.h"
55 #include "extern.h"
56 #include "exitvalues.h"
57 
58 #define MINDIRSIZE  (sizeof (struct dirtemplate))
59 
60 static int blksort(const void *, const void *);
61 static int pass2check(struct inodesc *);
62 
63 void
pass2(void)64 pass2(void)
65 {
66           union dinode *dp;
67           struct inoinfo **inpp, *inp, *pinp;
68           struct inoinfo **inpend;
69           struct inostat *rinfo, *info;
70           struct inodesc curino;
71           union dinode dino;
72           int i, maxblk;
73 #ifndef NO_FFS_EI
74           unsigned ii;
75 #endif
76           char pathbuf[MAXPATHLEN + 1];
77 
78           rinfo = inoinfo(UFS_ROOTINO);
79           switch (rinfo->ino_state) {
80 
81           case USTATE:
82                     pfatal("ROOT INODE UNALLOCATED");
83                     if (reply("ALLOCATE") == 0) {
84                               markclean = 0;
85                               ckfini(1);
86                               exit(FSCK_EXIT_CHECK_FAILED);
87                     }
88                     if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) != UFS_ROOTINO)
89                               errexit("CANNOT ALLOCATE ROOT INODE");
90                     break;
91 
92           case DCLEAR:
93                     pfatal("DUPS/BAD IN ROOT INODE");
94                     if (reply("REALLOCATE")) {
95                               freeino(UFS_ROOTINO);
96                               if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) != UFS_ROOTINO)
97                                         errexit("CANNOT ALLOCATE ROOT INODE");
98                               break;
99                     }
100                     markclean = 0;
101                     if (reply("CONTINUE") == 0) {
102                               ckfini(1);
103                               exit(FSCK_EXIT_CHECK_FAILED);
104                     }
105                     break;
106 
107           case FSTATE:
108           case FCLEAR:
109                     pfatal("ROOT INODE NOT DIRECTORY");
110                     if (reply("REALLOCATE")) {
111                               freeino(UFS_ROOTINO);
112                               if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) != UFS_ROOTINO)
113                                         errexit("CANNOT ALLOCATE ROOT INODE");
114                               break;
115                     }
116                     if (reply("FIX") == 0) {
117                               markclean = 0;
118                               ckfini(1);
119                               exit(FSCK_EXIT_CHECK_FAILED);
120                     }
121                     dp = ginode(UFS_ROOTINO);
122                     DIP_SET(dp, mode,
123                         iswap16((iswap16(DIP(dp, mode)) & ~IFMT) | IFDIR));
124                     inodirty();
125                     break;
126 
127           case DSTATE:
128                     break;
129 
130           default:
131                     errexit("BAD STATE %d FOR ROOT INODE", rinfo->ino_state);
132           }
133           if (newinofmt) {
134                     info = inoinfo(UFS_WINO);
135                     info->ino_state = FSTATE;
136                     info->ino_type = DT_WHT;
137           }
138           /*
139            * Sort the directory list into disk block order.
140            */
141           qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
142           /*
143            * Check the integrity of each directory.
144            */
145           memset(&curino, 0, sizeof(struct inodesc));
146           curino.id_type = DATA;
147           curino.id_func = pass2check;
148           inpend = &inpsort[inplast];
149           for (inpp = inpsort; inpp < inpend; inpp++) {
150                     if (got_siginfo) {
151                               fprintf(stderr,
152                                   "%s: phase 2: dir %ld of %d (%d%%)\n", cdevname(),
153                                   (long)(inpp - inpsort), (int)inplast,
154                                   (int)((inpp - inpsort) * 100 / inplast));
155                               got_siginfo = 0;
156                     }
157 #ifdef PROGRESS
158                     progress_bar(cdevname(), preen ? NULL : "phase 2",
159                                   (inpp - inpsort), inplast);
160 #endif /* PROGRESS */
161                     inp = *inpp;
162                     if (inp->i_isize == 0)
163                               continue;
164                     if (inp->i_isize < MINDIRSIZE) {
165                               direrror(inp->i_number, "DIRECTORY TOO SHORT");
166                               inp->i_isize = roundup(MINDIRSIZE, dirblksiz);
167                               if (reply("FIX") == 1) {
168                                         dp = ginode(inp->i_number);
169                                         DIP_SET(dp, size, iswap64(inp->i_isize));
170                                         inodirty();
171                               } else
172                                         markclean = 0;
173                     } else if ((inp->i_isize & (dirblksiz - 1)) != 0) {
174                               getpathname(pathbuf, sizeof(pathbuf), inp->i_number,
175                                   inp->i_number);
176                               if (usedsoftdep)
177                                         pfatal("%s %s: LENGTH %lld NOT MULTIPLE OF %d",
178                                                   "DIRECTORY", pathbuf,
179                                                   (long long)inp->i_isize, dirblksiz);
180                               else
181                                         pwarn("%s %s: LENGTH %lld NOT MULTIPLE OF %d",
182                                                   "DIRECTORY", pathbuf,
183                                                   (long long)inp->i_isize, dirblksiz);
184                               if (preen)
185                                         printf(" (ADJUSTED)\n");
186                               inp->i_isize = roundup(inp->i_isize, dirblksiz);
187                               if (preen || reply("ADJUST") == 1) {
188                                         dp = ginode(inp->i_number);
189                                         DIP_SET(dp, size, iswap64(inp->i_isize));
190                                         inodirty();
191                               } else
192                                         markclean = 0;
193                     }
194                     memset(&dino, 0, sizeof dino);
195                     dp = &dino;
196                     if (!is_ufs2) {
197                               dp->dp1.di_mode = iswap16(IFDIR);
198                               dp->dp1.di_size = iswap64(inp->i_isize);
199                               maxblk = inp->i_numblks < UFS_NDADDR ? inp->i_numblks :
200                                   UFS_NDADDR;
201                               for (i = 0; i < maxblk; i++)
202                                         dp->dp1.di_db[i] = inp->i_blks[i];
203                               if (inp->i_numblks > UFS_NDADDR) {
204                                         for (i = 0; i < UFS_NIADDR; i++)
205                                                   dp->dp1.di_ib[i] =
206                                                       inp->i_blks[UFS_NDADDR + i];
207                               }
208                     } else {
209                               dp->dp2.di_mode = iswap16(IFDIR);
210                               dp->dp2.di_size = iswap64(inp->i_isize);
211                               maxblk = inp->i_numblks < UFS_NDADDR ? inp->i_numblks :
212                                   UFS_NDADDR;
213                               for (i = 0; i < maxblk; i++)
214                                         dp->dp2.di_db[i] = inp->i_blks[i];
215                               if (inp->i_numblks > UFS_NDADDR) {
216                                         for (i = 0; i < UFS_NIADDR; i++)
217                                                   dp->dp2.di_ib[i] =
218                                                       inp->i_blks[UFS_NDADDR + i];
219                               }
220                     }
221                     curino.id_number = inp->i_number;
222                     curino.id_parent = inp->i_parent;
223                     curino.id_uid = iswap32(DIP(dp, uid));
224                     curino.id_gid = iswap32(DIP(dp, gid));
225                     (void)ckinode(&dino, &curino);
226           }
227 
228 #ifndef NO_FFS_EI
229           /*
230            * Byte swapping in directory entries, if needed, has been done.
231            * Now rescan dirs for pass2check()
232            */
233           if (do_dirswap) {
234                     do_dirswap = 0;
235                     for (inpp = inpsort; inpp < inpend; inpp++) {
236                               inp = *inpp;
237                               if (inp->i_isize == 0)
238                                         continue;
239                               memset(&dino, 0, sizeof dino);
240                               if (!is_ufs2) {
241                                         dino.dp1.di_mode = iswap16(IFDIR);
242                                         dino.dp1.di_size = iswap64(inp->i_isize);
243                                         for (ii = 0; ii < inp->i_numblks; ii++)
244                                                   dino.dp1.di_db[ii] = inp->i_blks[ii];
245                               } else {
246                                         dino.dp2.di_mode = iswap16(IFDIR);
247                                         dino.dp2.di_size = iswap64(inp->i_isize);
248                                         for (ii = 0; ii < inp->i_numblks; ii++)
249                                                   dino.dp2.di_db[ii] = inp->i_blks[ii];
250                               }
251                               curino.id_number = inp->i_number;
252                               curino.id_parent = inp->i_parent;
253                               curino.id_uid = iswap32(DIP(&dino, uid));
254                               curino.id_gid = iswap32(DIP(&dino, gid));
255                               (void)ckinode(&dino, &curino);
256                     }
257           }
258 #endif /* !NO_FFS_EI */
259 
260           /*
261            * Now that the parents of all directories have been found,
262            * make another pass to verify the value of `..'
263            */
264           for (inpp = inpsort; inpp < inpend; inpp++) {
265                     inp = *inpp;
266                     if (inp->i_parent == 0 || inp->i_isize == 0)
267                               continue;
268                     if (inp->i_dotdot == inp->i_parent ||
269                         inp->i_dotdot == (ino_t)-1)
270                               continue;
271                     info = inoinfo(inp->i_parent);
272                     if (inp->i_dotdot == 0) {
273                               inp->i_dotdot = inp->i_parent;
274                               if (debug)
275                                         fileerror(inp->i_parent, inp->i_number,
276                                             "DEFERRED MISSING '..' FIX");
277                               (void)makeentry(inp->i_number, inp->i_parent, "..");
278                               info->ino_linkcnt--;
279                               continue;
280                     }
281                     fileerror(inp->i_parent, inp->i_number,
282                         "BAD INODE NUMBER FOR '..'");
283                     if (reply("FIX") == 0) {
284                               markclean = 0;
285                               continue;
286                     }
287                     inoinfo(inp->i_dotdot)->ino_linkcnt++;
288                     info->ino_linkcnt--;
289                     inp->i_dotdot = inp->i_parent;
290                     (void)changeino(inp->i_number, "..", inp->i_parent);
291           }
292           /*
293            * Create a list of children for each directory.
294            */
295           inpend = &inpsort[inplast];
296           for (inpp = inpsort; inpp < inpend; inpp++) {
297                     inp = *inpp;
298                     info = inoinfo(inp->i_number);
299                     inp->i_child = inp->i_sibling = 0;
300                     if (info->ino_state == DFOUND)
301                               info->ino_state = DSTATE;
302           }
303           for (inpp = inpsort; inpp < inpend; inpp++) {
304                     inp = *inpp;
305                     if (inp->i_parent == 0 ||
306                         inp->i_number == UFS_ROOTINO)
307                               continue;
308                     pinp = getinoinfo(inp->i_parent);
309                     inp->i_sibling = pinp->i_child;
310                     pinp->i_child = inp;
311           }
312           /*
313            * Mark all the directories that can be found from the root.
314            */
315           propagate(UFS_ROOTINO);
316 
317 #ifdef PROGRESS
318           if (!preen)
319                     progress_done();
320 #endif /* PROGRESS */
321 }
322 
323 static int
pass2check(struct inodesc * idesc)324 pass2check(struct inodesc *idesc)
325 {
326           struct direct *dirp = idesc->id_dirp;
327           struct inoinfo *inp;
328           struct inostat *info;
329           int n, entrysize, ret = 0;
330           union dinode *dp;
331           const char *errmsg;
332           struct direct proto, *newdirp;
333           char namebuf[MAXPATHLEN + 1];
334           char pathbuf[MAXPATHLEN + 1];
335 
336           /*
337            * If converting, set directory entry type.
338            */
339           if (!is_ufs2 && doinglevel2 && iswap32(dirp->d_ino) > 0 &&
340               iswap32(dirp->d_ino) < maxino) {
341                     dirp->d_type = inoinfo(iswap32(dirp->d_ino))->ino_type;
342                     ret |= ALTERED;
343           }
344           /*
345            * check for "."
346            */
347           if (idesc->id_entryno != 0)
348                     goto chk1;
349           if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
350                     if (iswap32(dirp->d_ino) != idesc->id_number) {
351                               direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
352                               if (reply("FIX") == 1) {
353                                         dirp->d_ino = iswap32(idesc->id_number);
354                                         ret |= ALTERED;
355                               } else
356                                         markclean = 0;
357                     }
358                     if (newinofmt && dirp->d_type != DT_DIR) {
359                               direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
360                               if (reply("FIX") == 1) {
361                                         dirp->d_type = DT_DIR;
362                                         ret |= ALTERED;
363                               } else
364                                         markclean = 0;
365                     }
366                     goto chk1;
367           }
368           proto.d_ino = iswap32(idesc->id_number);
369           if (newinofmt)
370                     proto.d_type = DT_DIR;
371           else
372                     proto.d_type = 0;
373           proto.d_namlen = 1;
374           (void)strlcpy(proto.d_name, ".", sizeof(proto.d_name));
375 #         if BYTE_ORDER == LITTLE_ENDIAN
376                     if (!newinofmt && !needswap) {
377 #         else
378                     if (!newinofmt && needswap) {
379 #         endif
380                               u_char tmp;
381 
382                               tmp = proto.d_type;
383                               proto.d_type = proto.d_namlen;
384                               proto.d_namlen = tmp;
385                     }
386           entrysize = UFS_DIRSIZ(0, &proto, 0);
387           direrror(idesc->id_number, "MISSING '.'");
388           errmsg = "ADD '.' ENTRY";
389           if (iswap16(dirp->d_reclen) < entrysize + UFS_DIRSIZ(0, dirp, 0)) {
390                     /* Not enough space to add '.', replace first entry with '.' */
391                     if (dirp->d_ino != 0) {
392                               pwarn("\nFIRST ENTRY IN DIRECTORY CONTAINS %s\n",
393                                    dirp->d_name);
394                               errmsg = "REPLACE WITH '.'";
395                     }
396                     if (reply(errmsg) == 0)
397                               goto chk1;
398                     proto.d_reclen = dirp->d_reclen;
399                     memmove(dirp, &proto, (size_t)entrysize);
400                     ret |= ALTERED;
401           } else {
402                     /* Move over first entry and add '.' entry */
403                     if (reply(errmsg) == 0)
404                               goto chk1;
405                     newdirp = (struct direct *)((char *)(dirp) + entrysize);
406                     dirp->d_reclen = iswap16(iswap16(dirp->d_reclen) - entrysize);
407                     memmove(newdirp, dirp, iswap16(dirp->d_reclen));
408                     proto.d_reclen = iswap16(entrysize);
409                     memmove(dirp, &proto, (size_t)entrysize);
410                     idesc->id_entryno++;
411                     inoinfo(idesc->id_number)->ino_linkcnt--;
412                     dirp = newdirp;
413                     ret |= ALTERED;
414           }
415 chk1:
416           if (idesc->id_entryno > 1)
417                     goto chk2;
418           inp = getinoinfo(idesc->id_number);
419           proto.d_ino = iswap32(inp->i_parent);
420           if (newinofmt)
421                     proto.d_type = DT_DIR;
422           else
423                     proto.d_type = 0;
424           proto.d_namlen = 2;
425           (void)strlcpy(proto.d_name, "..", sizeof(proto.d_name));
426 #if BYTE_ORDER == LITTLE_ENDIAN
427           if (!newinofmt && !needswap) {
428 #else
429           if (!newinofmt && needswap) {
430 #endif
431                     u_char tmp;
432 
433                     tmp = proto.d_type;
434                     proto.d_type = proto.d_namlen;
435                     proto.d_namlen = tmp;
436           }
437           entrysize = UFS_DIRSIZ(0, &proto, 0);
438           if (idesc->id_entryno == 0) {
439                     n = UFS_DIRSIZ(0, dirp, 0);
440                     if (iswap16(dirp->d_reclen) < n + entrysize)
441                               goto chk2;
442                     proto.d_reclen = iswap16(iswap16(dirp->d_reclen) - n);
443                     dirp->d_reclen = iswap16(n);
444                     idesc->id_entryno++;
445                     inoinfo(iswap32(dirp->d_ino))->ino_linkcnt--;
446                     dirp = (struct direct *)((char *)(dirp) + n);
447                     memset(dirp, 0, (size_t)iswap16(proto.d_reclen));
448                     dirp->d_reclen = proto.d_reclen;
449           }
450           if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
451                     inp->i_dotdot = iswap32(dirp->d_ino);
452                     if (newinofmt && dirp->d_type != DT_DIR) {
453                               direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
454                               dirp->d_type = DT_DIR;
455                               if (reply("FIX") == 1)
456                                         ret |= ALTERED;
457                               else
458                                         markclean = 0;
459                     }
460                     goto chk2;
461           }
462           fileerror(inp->i_parent != 0 ? inp->i_parent : idesc->id_number,
463               idesc->id_number, "MISSING '..'");
464           errmsg = "ADD '..' ENTRY";
465           if (dirp->d_reclen < entrysize + UFS_DIRSIZ(0, dirp, 0)) {
466                     /* No space to add '..', replace second entry with '..' */
467                     if (dirp->d_ino != 0) {
468                               pfatal("SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
469                                   dirp->d_name);
470                               errmsg = "REPLACE WITH '..'";
471                     }
472                     if (reply(errmsg) == 0) {
473                               inp->i_dotdot = (ino_t)-1;
474                               goto chk2;
475                     }
476                     if (proto.d_ino == 0) {
477                               /* Defer processing until parent known */
478                               idesc->id_entryno++;
479                               if (debug)
480                                         printf("(FIX DEFERRED)\n");
481                     }
482                     inp->i_dotdot = proto.d_ino;
483                     proto.d_reclen = dirp->d_reclen;
484                     memmove(dirp, &proto, (size_t)entrysize);
485                     ret |= ALTERED;
486           } else {
487                     /* Move over second entry and add '..' entry */
488                     if (reply(errmsg) == 0) {
489                               inp->i_dotdot = (ino_t)-1;
490                               goto chk2;
491                     }
492                     if (proto.d_ino == 0) {
493                               /* Defer processing until parent known */
494                               idesc->id_entryno++;
495                               if (debug)
496                                         printf("(FIX DEFERRED)\n");
497                     }
498                     inp->i_dotdot = proto.d_ino;
499                     if (dirp->d_ino == 0) {
500                               proto.d_reclen = dirp->d_reclen;
501                               memmove(dirp, &proto, (size_t)entrysize);
502                     } else {
503                               newdirp = (struct direct *)((char *)(dirp) + entrysize);
504                               dirp->d_reclen -= entrysize;
505                               memmove(newdirp, dirp, dirp->d_reclen);
506                               proto.d_reclen = entrysize;
507                               memmove(dirp, &proto, (size_t)entrysize);
508                               if (dirp->d_ino != 0) {
509                                         idesc->id_entryno++;
510                                         inoinfo(dirp->d_ino)->ino_linkcnt--;
511                               }
512                               dirp = newdirp;
513                     }
514                     ret |= ALTERED;
515           }
516 chk2:
517           if (dirp->d_ino == 0)
518                     return (ret|KEEPON);
519           if (dirp->d_namlen <= 2 &&
520               dirp->d_name[0] == '.' &&
521               idesc->id_entryno >= 2) {
522                     if (dirp->d_namlen == 1) {
523                               direrror(idesc->id_number, "EXTRA '.' ENTRY");
524                               dirp->d_ino = 0;
525                               if (reply("FIX") == 1)
526                                         ret |= ALTERED;
527                               else
528                                         markclean = 0;
529                               return (KEEPON | ret);
530                     }
531                     if (dirp->d_name[1] == '.') {
532                               direrror(idesc->id_number, "EXTRA '..' ENTRY");
533                               dirp->d_ino = 0;
534                               if (reply("FIX") == 1)
535                                         ret |= ALTERED;
536                               else
537                                         markclean = 0;
538                               return (KEEPON | ret);
539                     }
540           }
541           idesc->id_entryno++;
542           n = 0;
543           if (iswap32(dirp->d_ino) > maxino) {
544                     fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
545                     n = reply("REMOVE");
546                     if (n == 0)
547                               markclean = 0;
548           } else if (newinofmt &&
549                        ((iswap32(dirp->d_ino) == UFS_WINO && dirp->d_type != DT_WHT) ||
550                         (iswap32(dirp->d_ino) != UFS_WINO && dirp->d_type == DT_WHT))) {
551                     fileerror(idesc->id_number, iswap32(dirp->d_ino), "BAD WHITEOUT ENTRY");
552                     dirp->d_ino = iswap32(UFS_WINO);
553                     dirp->d_type = DT_WHT;
554                     if (reply("FIX") == 1)
555                               ret |= ALTERED;
556                     else
557                               markclean = 0;
558           } else {
559 again:
560                     info = inoinfo(iswap32(dirp->d_ino));
561                     switch (info->ino_state) {
562                     case USTATE:
563                               if (idesc->id_entryno <= 2)
564                                         break;
565                               fileerror(idesc->id_number, iswap32(dirp->d_ino), "UNALLOCATED");
566                               n = reply("REMOVE");
567                               if (n == 0)
568                                         markclean = 0;
569                               break;
570 
571                     case DCLEAR:
572                     case FCLEAR:
573                               if (idesc->id_entryno <= 2)
574                                         break;
575                               if (info->ino_state == FCLEAR)
576                                         errmsg = "DUP/BAD";
577                               else if (!preen && !usedsoftdep)
578                                         errmsg = "ZERO LENGTH DIRECTORY";
579                               else {
580                                         n = 1;
581                                         break;
582                               }
583                               fileerror(idesc->id_number, iswap32(dirp->d_ino), errmsg);
584                               if ((n = reply("REMOVE")) == 1)
585                                         break;
586                               dp = ginode(iswap32(dirp->d_ino));
587                               info->ino_state =
588                                   (iswap16(DIP(dp, mode)) & IFMT) == IFDIR ? DSTATE : FSTATE;
589                               info->ino_linkcnt = iswap16(DIP(dp, nlink));
590                               goto again;
591 
592                     case DSTATE:
593                     case DFOUND:
594                               inp = getinoinfo(iswap32(dirp->d_ino));
595                               if (inp->i_parent != 0 && idesc->id_entryno > 2) {
596                                         getpathname(pathbuf, sizeof(pathbuf),
597                                             idesc->id_number, idesc->id_number);
598                                         getpathname(namebuf, sizeof(namebuf),
599                                             iswap32(dirp->d_ino), iswap32(dirp->d_ino));
600                                         pwarn("%s %s %s\n", pathbuf,
601                                             "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
602                                             namebuf);
603                                         if (preen)
604                                                   printf(" (IGNORED)\n");
605                                         else if ((n = reply("REMOVE")) == 1)
606                                                   break;
607                               }
608                               if (idesc->id_entryno > 2)
609                                         inp->i_parent = idesc->id_number;
610                               /* fall through */
611 
612                     case FSTATE:
613                               if (newinofmt && dirp->d_type != info->ino_type) {
614                                         fileerror(idesc->id_number, iswap32(dirp->d_ino),
615                                             "BAD TYPE VALUE");
616                                         dirp->d_type = info->ino_type;
617                                         if (reply("FIX") == 1)
618                                                   ret |= ALTERED;
619                                         else
620                                                   markclean = 0;
621                               }
622                               info->ino_linkcnt--;
623                               break;
624 
625                     default:
626                               errexit("BAD STATE %d FOR INODE I=%d",
627                                   info->ino_state, iswap32(dirp->d_ino));
628                     }
629           }
630           if (n == 0)
631                     return (ret|KEEPON);
632           dirp->d_ino = 0;
633           return (ret|KEEPON|ALTERED);
634 }
635 
636 /*
637  * Routine to sort disk blocks.
638  */
639 static int
640 blksort(const void *arg1, const void *arg2)
641 {
642 
643           return ((*(const struct inoinfo *const *)arg1)->i_blks[0] -
644                     (*(const struct inoinfo *const *)arg2)->i_blks[0]);
645 }
646