]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/fsck_ffs/pass2.c
Improve handling of missing '.' and '..' in UFS directories.
[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                         freeino(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                 if (inp->i_dotdot == inp->i_parent ||
216                     inp->i_dotdot == (ino_t)-1)
217                         continue;
218                 if (inp->i_dotdot == 0) {
219                         inp->i_dotdot = inp->i_parent;
220                         if (debug)
221                                 fileerror(inp->i_parent, inp->i_number,
222                                     "DEFERRED MISSING '..' FIX");
223                         (void)makeentry(inp->i_number, inp->i_parent, "..");
224                         inoinfo(inp->i_parent)->ino_linkcnt--;
225                         continue;
226                 }
227                 /*
228                  * Here we have:
229                  *    inp->i_number is directory with bad ".." in it.
230                  *    inp->i_dotdot is current value of "..".
231                  *    inp->i_parent is directory to which ".." should point.
232                  */
233                 getpathname(pathbuf, inp->i_parent, inp->i_number);
234                 printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
235                     (uintmax_t)inp->i_number, pathbuf);
236                 getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
237                 printf("CURRENTLY POINTS TO I=%ju (%s), ",
238                     (uintmax_t)inp->i_dotdot, pathbuf);
239                 getpathname(pathbuf, inp->i_parent, inp->i_parent);
240                 printf("SHOULD POINT TO I=%ju (%s)",
241                     (uintmax_t)inp->i_parent, pathbuf);
242                 if (cursnapshot != 0) {
243                         /*
244                          * We need to:
245                          *    setcwd(inp->i_number);
246                          *    setdotdot(inp->i_dotdot, inp->i_parent);
247                          */
248                         cmd.value = inp->i_number;
249                         if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
250                             &cmd, sizeof cmd) == -1) {
251                                 /* kernel lacks support for these functions */
252                                 printf(" (IGNORED)\n");
253                                 continue;
254                         }
255                         cmd.value = inp->i_dotdot; /* verify same value */
256                         cmd.size = inp->i_parent;  /* new parent */
257                         if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
258                             &cmd, sizeof cmd) == -1) {
259                                 printf(" (FIX FAILED: %s)\n", strerror(errno));
260                                 continue;
261                         }
262                         printf(" (FIXED)\n");
263                         inoinfo(inp->i_parent)->ino_linkcnt--;
264                         inp->i_dotdot = inp->i_parent;
265                         continue;
266                 }
267                 if (preen)
268                         printf(" (FIXED)\n");
269                 else if (reply("FIX") == 0)
270                         continue;
271                 inoinfo(inp->i_dotdot)->ino_linkcnt++;
272                 inoinfo(inp->i_parent)->ino_linkcnt--;
273                 inp->i_dotdot = inp->i_parent;
274                 (void)changeino(inp->i_number, "..", inp->i_parent);
275         }
276         /*
277          * Mark all the directories that can be found from the root.
278          */
279         propagate();
280 }
281
282 static int
283 pass2check(struct inodesc *idesc)
284 {
285         struct direct *dirp = idesc->id_dirp;
286         char dirname[MAXPATHLEN + 1];
287         struct inoinfo *inp;
288         int n, entrysize, ret = 0;
289         struct inode ip;
290         union dinode *dp;
291         const char *errmsg;
292         struct direct proto, *newdirp;
293
294         /*
295          * check for "."
296          */
297         if (dirp->d_ino > maxino)
298                 goto chk2;
299         if (idesc->id_entryno != 0)
300                 goto chk1;
301         if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
302                 if (dirp->d_ino != idesc->id_number) {
303                         direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
304                         if (reply("FIX") == 1) {
305                                 dirp->d_ino = idesc->id_number;
306                                 ret |= ALTERED;
307                         }
308                 }
309                 if (dirp->d_type != DT_DIR) {
310                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
311                         if (reply("FIX") == 1) {
312                                 dirp->d_type = DT_DIR;
313                                 ret |= ALTERED;
314                         }
315                 }
316                 goto chk1;
317         }
318         proto.d_ino = idesc->id_number;
319         proto.d_type = DT_DIR;
320         proto.d_namlen = 1;
321         (void)strcpy(proto.d_name, ".");
322         entrysize = DIRSIZ(0, &proto);
323         direrror(idesc->id_number, "MISSING '.'");
324         errmsg = "ADD '.' ENTRY";
325         if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
326                 /* Not enough space to add '.', replace first entry with '.' */
327                 if (dirp->d_ino != 0) {
328                         pwarn("\nFIRST ENTRY IN DIRECTORY CONTAINS %s\n",
329                              dirp->d_name);
330                         errmsg = "REPLACE WITH '.'";
331                 }
332                 if (reply(errmsg) == 0)
333                         goto chk1;
334                 proto.d_reclen = dirp->d_reclen;
335                 memmove(dirp, &proto, (size_t)entrysize);
336                 ret |= ALTERED;
337         } else {
338                 /* Move over first entry and add '.' entry */
339                 if (reply(errmsg) == 0)
340                         goto chk1;
341                 newdirp = (struct direct *)((char *)(dirp) + entrysize);
342                 dirp->d_reclen -= entrysize;
343                 memmove(newdirp, dirp, dirp->d_reclen);
344                 proto.d_reclen = entrysize;
345                 memmove(dirp, &proto, (size_t)entrysize);
346                 idesc->id_entryno++;
347                 inoinfo(idesc->id_number)->ino_linkcnt--;
348                 dirp = newdirp;
349                 ret |= ALTERED;
350         }
351 chk1:
352         if (idesc->id_entryno > 1)
353                 goto chk2;
354         inp = getinoinfo(idesc->id_number);
355         proto.d_ino = inp->i_parent;
356         proto.d_type = DT_DIR;
357         proto.d_namlen = 2;
358         (void)strcpy(proto.d_name, "..");
359         entrysize = DIRSIZ(0, &proto);
360         if (idesc->id_entryno == 0) {
361                 n = DIRSIZ(0, dirp);
362                 if (dirp->d_reclen < n + entrysize)
363                         goto chk2;
364                 proto.d_reclen = dirp->d_reclen - n;
365                 dirp->d_reclen = n;
366                 idesc->id_entryno++;
367                 inoinfo(dirp->d_ino)->ino_linkcnt--;
368                 dirp = (struct direct *)((char *)(dirp) + n);
369                 memset(dirp, 0, (size_t)proto.d_reclen);
370                 dirp->d_reclen = proto.d_reclen;
371         }
372         if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
373                 inp->i_dotdot = dirp->d_ino;
374                 if (dirp->d_type != DT_DIR) {
375                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
376                         dirp->d_type = DT_DIR;
377                         if (reply("FIX") == 1)
378                                 ret |= ALTERED;
379                 }
380                 goto chk2;
381         }
382         fileerror(inp->i_parent != 0 ? inp->i_parent : idesc->id_number,
383             idesc->id_number, "MISSING '..'");
384         errmsg = "ADD '..' ENTRY";
385         if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
386                 /* No space to add '..', replace second entry with '..' */
387                 if (dirp->d_ino != 0) {
388                         pfatal("SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
389                             dirp->d_name);
390                         errmsg = "REPLACE WITH '..'";
391                 }
392                 if (reply(errmsg) == 0) {
393                         inp->i_dotdot = (ino_t)-1;
394                         goto chk2;
395                 }
396                 if (proto.d_ino == 0) {
397                         /* Defer processing until parent known */
398                         idesc->id_entryno++;
399                         if (debug)
400                                 printf("(FIX DEFERRED)\n");
401                 }
402                 inp->i_dotdot = proto.d_ino;
403                 proto.d_reclen = dirp->d_reclen;
404                 memmove(dirp, &proto, (size_t)entrysize);
405                 ret |= ALTERED;
406         } else {
407                 /* Move over second entry and add '..' entry */
408                 if (reply(errmsg) == 0) {
409                         inp->i_dotdot = (ino_t)-1;
410                         goto chk2;
411                 }
412                 if (proto.d_ino == 0) {
413                         /* Defer processing until parent known */
414                         idesc->id_entryno++;
415                         if (debug)
416                                 printf("(FIX DEFERRED)\n");
417                 }
418                 inp->i_dotdot = proto.d_ino;
419                 if (dirp->d_ino == 0) {
420                         proto.d_reclen = dirp->d_reclen;
421                         memmove(dirp, &proto, (size_t)entrysize);
422                 } else {
423                         newdirp = (struct direct *)((char *)(dirp) + entrysize);
424                         dirp->d_reclen -= entrysize;
425                         memmove(newdirp, dirp, dirp->d_reclen);
426                         proto.d_reclen = entrysize;
427                         memmove(dirp, &proto, (size_t)entrysize);
428                         if (dirp->d_ino != 0) {
429                                 idesc->id_entryno++;
430                                 inoinfo(dirp->d_ino)->ino_linkcnt--;
431                         }
432                         dirp = newdirp;
433                 }
434                 ret |= ALTERED;
435         }
436 chk2:
437         if (dirp->d_ino == 0)
438                 return (ret|KEEPON);
439         if (dirp->d_namlen <= 2 &&
440             dirp->d_name[0] == '.' &&
441             idesc->id_entryno >= 2) {
442                 if (dirp->d_namlen == 1) {
443                         direrror(idesc->id_number, "EXTRA '.' ENTRY");
444                         dirp->d_ino = 0;
445                         if (reply("FIX") == 1)
446                                 ret |= ALTERED;
447                         return (KEEPON | ret);
448                 }
449                 if (dirp->d_name[1] == '.') {
450                         direrror(idesc->id_number, "EXTRA '..' ENTRY");
451                         dirp->d_ino = 0;
452                         if (reply("FIX") == 1)
453                                 ret |= ALTERED;
454                         return (KEEPON | ret);
455                 }
456         }
457         idesc->id_entryno++;
458         n = 0;
459         if (dirp->d_ino > maxino) {
460                 fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
461                 n = reply("REMOVE");
462         } else if (((dirp->d_ino == UFS_WINO && dirp->d_type != DT_WHT) ||
463                     (dirp->d_ino != UFS_WINO && dirp->d_type == DT_WHT))) {
464                 fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
465                 dirp->d_ino = UFS_WINO;
466                 dirp->d_type = DT_WHT;
467                 if (reply("FIX") == 1)
468                         ret |= ALTERED;
469         } else {
470 again:
471                 switch (inoinfo(dirp->d_ino)->ino_state) {
472                 case USTATE:
473                         if (idesc->id_entryno <= 2)
474                                 break;
475                         fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
476                         n = reply("REMOVE");
477                         break;
478
479                 case DCLEAR:
480                 case FCLEAR:
481                         if (idesc->id_entryno <= 2)
482                                 break;
483                         if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
484                                 errmsg = "DUP/BAD";
485                         else if (!preen && !usedsoftdep)
486                                 errmsg = "ZERO LENGTH DIRECTORY";
487                         else if (cursnapshot == 0) {
488                                 n = 1;
489                                 break;
490                         } else {
491                                 getpathname(dirname, idesc->id_number,
492                                     dirp->d_ino);
493                                 pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
494                                     dirname, (uintmax_t)dirp->d_ino);
495                                 /*
496                                  * We need to:
497                                  *    setcwd(idesc->id_parent);
498                                  *    rmdir(dirp->d_name);
499                                  */
500                                 cmd.value = idesc->id_number;
501                                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
502                                     &cmd, sizeof cmd) == -1) {
503                                         /* kernel lacks support */
504                                         printf(" (IGNORED)\n");
505                                         n = 1;
506                                         break;
507                                 }
508                                 if (rmdir(dirp->d_name) == -1) {
509                                         printf(" (REMOVAL FAILED: %s)\n",
510                                             strerror(errno));
511                                         n = 1;
512                                         break;
513                                 }
514                                 /* ".." reference to parent is removed */
515                                 inoinfo(idesc->id_number)->ino_linkcnt--;
516                                 printf(" (REMOVED)\n");
517                                 break;
518                         }
519                         fileerror(idesc->id_number, dirp->d_ino, errmsg);
520                         if ((n = reply("REMOVE")) == 1)
521                                 break;
522                         ginode(dirp->d_ino, &ip);
523                         dp = ip.i_dp;
524                         inoinfo(dirp->d_ino)->ino_state =
525                            (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
526                         inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
527                         irelse(&ip);
528                         goto again;
529
530                 case DSTATE:
531                 case DZLINK:
532                         if (inoinfo(idesc->id_number)->ino_state == DFOUND)
533                                 inoinfo(dirp->d_ino)->ino_state = DFOUND;
534                         /* FALLTHROUGH */
535
536                 case DFOUND:
537                         inp = getinoinfo(dirp->d_ino);
538                         if (idesc->id_entryno > 2) {
539                                 if (inp->i_parent == 0)
540                                         inp->i_parent = idesc->id_number;
541                                 else if ((n = fix_extraneous(inp, idesc)) == 1)
542                                         break;
543                         }
544                         /* FALLTHROUGH */
545
546                 case FSTATE:
547                 case FZLINK:
548                         if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
549                                 fileerror(idesc->id_number, dirp->d_ino,
550                                     "BAD TYPE VALUE");
551                                 dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
552                                 if (reply("FIX") == 1)
553                                         ret |= ALTERED;
554                         }
555                         inoinfo(dirp->d_ino)->ino_linkcnt--;
556                         break;
557
558                 default:
559                         errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
560                             inoinfo(dirp->d_ino)->ino_state,
561                             (uintmax_t)dirp->d_ino);
562                 }
563         }
564         if (n == 0)
565                 return (ret|KEEPON);
566         dirp->d_ino = 0;
567         return (ret|KEEPON|ALTERED);
568 }
569
570 static int
571 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
572 {
573         char *cp;
574         struct inode ip;
575         struct inodesc dotdesc;
576         char oldname[MAXPATHLEN + 1];
577         char newname[MAXPATHLEN + 1];
578
579         /*
580          * If we have not yet found "..", look it up now so we know
581          * which inode the directory itself believes is its parent.
582          */
583         if (inp->i_dotdot == 0) {
584                 memset(&dotdesc, 0, sizeof(struct inodesc));
585                 dotdesc.id_type = DATA;
586                 dotdesc.id_number = idesc->id_dirp->d_ino;
587                 dotdesc.id_func = findino;
588                 dotdesc.id_name = strdup("..");
589                 ginode(dotdesc.id_number, &ip);
590                 if ((ckinode(ip.i_dp, &dotdesc) & FOUND))
591                         inp->i_dotdot = dotdesc.id_parent;
592                 irelse(&ip);
593         }
594         /*
595          * We have the previously found old name (inp->i_parent) and the
596          * just found new name (idesc->id_number). We have five cases:
597          * 1)  ".." is missing - can remove either name, choose to delete
598          *     new one and let fsck create ".." pointing to old name.
599          * 2) Both new and old are in same directory, choose to delete
600          *    the new name and let fsck fix ".." if it is wrong.
601          * 3) ".." does not point to the new name, so delete it and let
602          *    fsck fix ".." to point to the old one if it is wrong.
603          * 4) ".." points to the old name only, so delete the new one.
604          * 5) ".." points to the new name only, so delete the old one.
605          *
606          * For cases 1-4 we eliminate the new name;
607          * for case 5 we eliminate the old name.
608          */
609         if (inp->i_dotdot == 0 ||                   /* Case 1 */
610             idesc->id_number == inp->i_parent ||    /* Case 2 */
611             inp->i_dotdot != idesc->id_number ||    /* Case 3 */
612             inp->i_dotdot == inp->i_parent) {       /* Case 4 */
613                 getpathname(newname, idesc->id_number, idesc->id_number);
614                 if (strcmp(newname, "/") != 0)
615                         strcat (newname, "/");
616                 strcat(newname, idesc->id_dirp->d_name);
617                 getpathname(oldname, inp->i_number, inp->i_number);
618                 pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
619                     newname, oldname);
620                 if (cursnapshot != 0) {
621                         /*
622                          * We need to
623                          *    setcwd(idesc->id_number);
624                          *    unlink(idesc->id_dirp->d_name);
625                          */
626                         cmd.value = idesc->id_number;
627                         if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
628                             &cmd, sizeof cmd) == -1) {
629                                 printf(" (IGNORED)\n");
630                                 return (0);
631                         }
632                         cmd.value = (intptr_t)idesc->id_dirp->d_name;
633                         cmd.size = inp->i_number; /* verify same name */
634                         if (sysctlbyname("vfs.ffs.unlink", 0, 0,
635                             &cmd, sizeof cmd) == -1) {
636                                 printf(" (UNLINK FAILED: %s)\n",
637                                     strerror(errno));
638                                 return (0);
639                         }
640                         printf(" (REMOVED)\n");
641                         return (0);
642                 }
643                 if (preen) {
644                         printf(" (REMOVED)\n");
645                         return (1);
646                 }
647                 return (reply("REMOVE"));
648         }
649         /*
650          * None of the first four cases above, so must be case (5).
651          * Eliminate the old name and make the new the name the parent.
652          */
653         getpathname(oldname, inp->i_parent, inp->i_number);
654         getpathname(newname, inp->i_number, inp->i_number);
655         pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
656             newname);
657         if (cursnapshot != 0) {
658                 /*
659                  * We need to
660                  *    setcwd(inp->i_parent);
661                  *    unlink(last component of oldname pathname);
662                  */
663                 cmd.value = inp->i_parent;
664                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
665                     &cmd, sizeof cmd) == -1) {
666                         printf(" (IGNORED)\n");
667                         return (0);
668                 }
669                 if ((cp = strchr(oldname, '/')) == NULL) {
670                         printf(" (IGNORED)\n");
671                         return (0);
672                 }
673                 cmd.value = (intptr_t)(cp + 1);
674                 cmd.size = inp->i_number; /* verify same name */
675                 if (sysctlbyname("vfs.ffs.unlink", 0, 0,
676                     &cmd, sizeof cmd) == -1) {
677                         printf(" (UNLINK FAILED: %s)\n",
678                             strerror(errno));
679                         return (0);
680                 }
681                 printf(" (REMOVED)\n");
682                 inp->i_parent = idesc->id_number;  /* reparent to correct dir */
683                 return (0);
684         }
685         if (!preen && !reply("REMOVE"))
686                 return (0);
687         memset(&dotdesc, 0, sizeof(struct inodesc));
688         dotdesc.id_type = DATA;
689         dotdesc.id_number = inp->i_parent; /* directory in which name appears */
690         dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
691         dotdesc.id_func = deleteentry;
692         ginode(dotdesc.id_number, &ip);
693         if ((ckinode(ip.i_dp, &dotdesc) & FOUND) && preen)
694                 printf(" (REMOVED)\n");
695         irelse(&ip);
696         inp->i_parent = idesc->id_number;  /* reparent to correct directory */
697         inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
698         return (0);
699 }
700
701 static int
702 deleteentry(struct inodesc *idesc)
703 {
704         struct direct *dirp = idesc->id_dirp;
705
706         if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
707                 return (KEEPON);
708         dirp->d_ino = 0;
709         return (ALTERED|STOP|FOUND);
710 }
711
712 /*
713  * Routine to sort disk blocks.
714  */
715 static int
716 blksort(const void *arg1, const void *arg2)
717 {
718
719         return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
720                 (*(struct inoinfo * const *)arg2)->i_blks[0]);
721 }