]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/fsck_ffs/pass2.c
Update fsdb(8) to reflect new structure of fsck_ffs(8).
[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                         fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
221                         if (reply("FIX") == 0)
222                                 continue;
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;
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                         dirp->d_ino = idesc->id_number;
305                         if (reply("FIX") == 1)
306                                 ret |= ALTERED;
307                 }
308                 if (dirp->d_type != DT_DIR) {
309                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
310                         dirp->d_type = DT_DIR;
311                         if (reply("FIX") == 1)
312                                 ret |= ALTERED;
313                 }
314                 goto chk1;
315         }
316         direrror(idesc->id_number, "MISSING '.'");
317         proto.d_ino = idesc->id_number;
318         proto.d_type = DT_DIR;
319         proto.d_namlen = 1;
320         (void)strcpy(proto.d_name, ".");
321         entrysize = DIRSIZ(0, &proto);
322         if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
323                 pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
324                         dirp->d_name);
325         } else if (dirp->d_reclen < entrysize) {
326                 pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
327         } else if (dirp->d_reclen < 2 * entrysize) {
328                 proto.d_reclen = dirp->d_reclen;
329                 memmove(dirp, &proto, (size_t)entrysize);
330                 if (reply("FIX") == 1)
331                         ret |= ALTERED;
332         } else {
333                 n = dirp->d_reclen - entrysize;
334                 proto.d_reclen = entrysize;
335                 memmove(dirp, &proto, (size_t)entrysize);
336                 idesc->id_entryno++;
337                 inoinfo(dirp->d_ino)->ino_linkcnt--;
338                 dirp = (struct direct *)((char *)(dirp) + entrysize);
339                 memset(dirp, 0, (size_t)n);
340                 dirp->d_reclen = n;
341                 if (reply("FIX") == 1)
342                         ret |= ALTERED;
343         }
344 chk1:
345         if (idesc->id_entryno > 1)
346                 goto chk2;
347         inp = getinoinfo(idesc->id_number);
348         proto.d_ino = inp->i_parent;
349         proto.d_type = DT_DIR;
350         proto.d_namlen = 2;
351         (void)strcpy(proto.d_name, "..");
352         entrysize = DIRSIZ(0, &proto);
353         if (idesc->id_entryno == 0) {
354                 n = DIRSIZ(0, dirp);
355                 if (dirp->d_reclen < n + entrysize)
356                         goto chk2;
357                 proto.d_reclen = dirp->d_reclen - n;
358                 dirp->d_reclen = n;
359                 idesc->id_entryno++;
360                 inoinfo(dirp->d_ino)->ino_linkcnt--;
361                 dirp = (struct direct *)((char *)(dirp) + n);
362                 memset(dirp, 0, (size_t)proto.d_reclen);
363                 dirp->d_reclen = proto.d_reclen;
364         }
365         if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
366                 inp->i_dotdot = dirp->d_ino;
367                 if (dirp->d_type != DT_DIR) {
368                         direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
369                         dirp->d_type = DT_DIR;
370                         if (reply("FIX") == 1)
371                                 ret |= ALTERED;
372                 }
373                 goto chk2;
374         }
375         if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
376                 fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
377                 pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
378                         dirp->d_name);
379                 inp->i_dotdot = (ino_t)-1;
380         } else if (dirp->d_reclen < entrysize) {
381                 fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
382                 pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
383                 inp->i_dotdot = (ino_t)-1;
384         } else if (inp->i_parent != 0) {
385                 /*
386                  * We know the parent, so fix now.
387                  */
388                 inp->i_dotdot = inp->i_parent;
389                 fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
390                 proto.d_reclen = dirp->d_reclen;
391                 memmove(dirp, &proto, (size_t)entrysize);
392                 if (reply("FIX") == 1)
393                         ret |= ALTERED;
394         }
395         idesc->id_entryno++;
396         if (dirp->d_ino != 0)
397                 inoinfo(dirp->d_ino)->ino_linkcnt--;
398         return (ret|KEEPON);
399 chk2:
400         if (dirp->d_ino == 0)
401                 return (ret|KEEPON);
402         if (dirp->d_namlen <= 2 &&
403             dirp->d_name[0] == '.' &&
404             idesc->id_entryno >= 2) {
405                 if (dirp->d_namlen == 1) {
406                         direrror(idesc->id_number, "EXTRA '.' ENTRY");
407                         dirp->d_ino = 0;
408                         if (reply("FIX") == 1)
409                                 ret |= ALTERED;
410                         return (KEEPON | ret);
411                 }
412                 if (dirp->d_name[1] == '.') {
413                         direrror(idesc->id_number, "EXTRA '..' ENTRY");
414                         dirp->d_ino = 0;
415                         if (reply("FIX") == 1)
416                                 ret |= ALTERED;
417                         return (KEEPON | ret);
418                 }
419         }
420         idesc->id_entryno++;
421         n = 0;
422         if (dirp->d_ino > maxino) {
423                 fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
424                 n = reply("REMOVE");
425         } else if (((dirp->d_ino == UFS_WINO && dirp->d_type != DT_WHT) ||
426                     (dirp->d_ino != UFS_WINO && dirp->d_type == DT_WHT))) {
427                 fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
428                 dirp->d_ino = UFS_WINO;
429                 dirp->d_type = DT_WHT;
430                 if (reply("FIX") == 1)
431                         ret |= ALTERED;
432         } else {
433 again:
434                 switch (inoinfo(dirp->d_ino)->ino_state) {
435                 case USTATE:
436                         if (idesc->id_entryno <= 2)
437                                 break;
438                         fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
439                         n = reply("REMOVE");
440                         break;
441
442                 case DCLEAR:
443                 case FCLEAR:
444                         if (idesc->id_entryno <= 2)
445                                 break;
446                         if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
447                                 errmsg = "DUP/BAD";
448                         else if (!preen && !usedsoftdep)
449                                 errmsg = "ZERO LENGTH DIRECTORY";
450                         else if (cursnapshot == 0) {
451                                 n = 1;
452                                 break;
453                         } else {
454                                 getpathname(dirname, idesc->id_number,
455                                     dirp->d_ino);
456                                 pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
457                                     dirname, (uintmax_t)dirp->d_ino);
458                                 /*
459                                  * We need to:
460                                  *    setcwd(idesc->id_parent);
461                                  *    rmdir(dirp->d_name);
462                                  */
463                                 cmd.value = idesc->id_number;
464                                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
465                                     &cmd, sizeof cmd) == -1) {
466                                         /* kernel lacks support */
467                                         printf(" (IGNORED)\n");
468                                         n = 1;
469                                         break;
470                                 }
471                                 if (rmdir(dirp->d_name) == -1) {
472                                         printf(" (REMOVAL FAILED: %s)\n",
473                                             strerror(errno));
474                                         n = 1;
475                                         break;
476                                 }
477                                 /* ".." reference to parent is removed */
478                                 inoinfo(idesc->id_number)->ino_linkcnt--;
479                                 printf(" (REMOVED)\n");
480                                 break;
481                         }
482                         fileerror(idesc->id_number, dirp->d_ino, errmsg);
483                         if ((n = reply("REMOVE")) == 1)
484                                 break;
485                         ginode(dirp->d_ino, &ip);
486                         dp = ip.i_dp;
487                         inoinfo(dirp->d_ino)->ino_state =
488                            (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
489                         inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
490                         irelse(&ip);
491                         goto again;
492
493                 case DSTATE:
494                 case DZLINK:
495                         if (inoinfo(idesc->id_number)->ino_state == DFOUND)
496                                 inoinfo(dirp->d_ino)->ino_state = DFOUND;
497                         /* FALLTHROUGH */
498
499                 case DFOUND:
500                         inp = getinoinfo(dirp->d_ino);
501                         if (idesc->id_entryno > 2) {
502                                 if (inp->i_parent == 0)
503                                         inp->i_parent = idesc->id_number;
504                                 else if ((n = fix_extraneous(inp, idesc)) == 1)
505                                         break;
506                         }
507                         /* FALLTHROUGH */
508
509                 case FSTATE:
510                 case FZLINK:
511                         if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
512                                 fileerror(idesc->id_number, dirp->d_ino,
513                                     "BAD TYPE VALUE");
514                                 dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
515                                 if (reply("FIX") == 1)
516                                         ret |= ALTERED;
517                         }
518                         inoinfo(dirp->d_ino)->ino_linkcnt--;
519                         break;
520
521                 default:
522                         errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
523                             inoinfo(dirp->d_ino)->ino_state,
524                             (uintmax_t)dirp->d_ino);
525                 }
526         }
527         if (n == 0)
528                 return (ret|KEEPON);
529         dirp->d_ino = 0;
530         return (ret|KEEPON|ALTERED);
531 }
532
533 static int
534 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
535 {
536         char *cp;
537         struct inode ip;
538         struct inodesc dotdesc;
539         char oldname[MAXPATHLEN + 1];
540         char newname[MAXPATHLEN + 1];
541
542         /*
543          * If we have not yet found "..", look it up now so we know
544          * which inode the directory itself believes is its parent.
545          */
546         if (inp->i_dotdot == 0) {
547                 memset(&dotdesc, 0, sizeof(struct inodesc));
548                 dotdesc.id_type = DATA;
549                 dotdesc.id_number = idesc->id_dirp->d_ino;
550                 dotdesc.id_func = findino;
551                 dotdesc.id_name = strdup("..");
552                 ginode(dotdesc.id_number, &ip);
553                 if ((ckinode(ip.i_dp, &dotdesc) & FOUND))
554                         inp->i_dotdot = dotdesc.id_parent;
555                 irelse(&ip);
556         }
557         /*
558          * We have the previously found old name (inp->i_parent) and the
559          * just found new name (idesc->id_number). We have five cases:
560          * 1)  ".." is missing - can remove either name, choose to delete
561          *     new one and let fsck create ".." pointing to old name.
562          * 2) Both new and old are in same directory, choose to delete
563          *    the new name and let fsck fix ".." if it is wrong.
564          * 3) ".." does not point to the new name, so delete it and let
565          *    fsck fix ".." to point to the old one if it is wrong.
566          * 4) ".." points to the old name only, so delete the new one.
567          * 5) ".." points to the new name only, so delete the old one.
568          *
569          * For cases 1-4 we eliminate the new name;
570          * for case 5 we eliminate the old name.
571          */
572         if (inp->i_dotdot == 0 ||                   /* Case 1 */
573             idesc->id_number == inp->i_parent ||    /* Case 2 */
574             inp->i_dotdot != idesc->id_number ||    /* Case 3 */
575             inp->i_dotdot == inp->i_parent) {       /* Case 4 */
576                 getpathname(newname, idesc->id_number, idesc->id_number);
577                 if (strcmp(newname, "/") != 0)
578                         strcat (newname, "/");
579                 strcat(newname, idesc->id_dirp->d_name);
580                 getpathname(oldname, inp->i_number, inp->i_number);
581                 pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
582                     newname, oldname);
583                 if (cursnapshot != 0) {
584                         /*
585                          * We need to
586                          *    setcwd(idesc->id_number);
587                          *    unlink(idesc->id_dirp->d_name);
588                          */
589                         cmd.value = idesc->id_number;
590                         if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
591                             &cmd, sizeof cmd) == -1) {
592                                 printf(" (IGNORED)\n");
593                                 return (0);
594                         }
595                         cmd.value = (intptr_t)idesc->id_dirp->d_name;
596                         cmd.size = inp->i_number; /* verify same name */
597                         if (sysctlbyname("vfs.ffs.unlink", 0, 0,
598                             &cmd, sizeof cmd) == -1) {
599                                 printf(" (UNLINK FAILED: %s)\n",
600                                     strerror(errno));
601                                 return (0);
602                         }
603                         printf(" (REMOVED)\n");
604                         return (0);
605                 }
606                 if (preen) {
607                         printf(" (REMOVED)\n");
608                         return (1);
609                 }
610                 return (reply("REMOVE"));
611         }
612         /*
613          * None of the first four cases above, so must be case (5).
614          * Eliminate the old name and make the new the name the parent.
615          */
616         getpathname(oldname, inp->i_parent, inp->i_number);
617         getpathname(newname, inp->i_number, inp->i_number);
618         pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
619             newname);
620         if (cursnapshot != 0) {
621                 /*
622                  * We need to
623                  *    setcwd(inp->i_parent);
624                  *    unlink(last component of oldname pathname);
625                  */
626                 cmd.value = inp->i_parent;
627                 if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
628                     &cmd, sizeof cmd) == -1) {
629                         printf(" (IGNORED)\n");
630                         return (0);
631                 }
632                 if ((cp = strchr(oldname, '/')) == NULL) {
633                         printf(" (IGNORED)\n");
634                         return (0);
635                 }
636                 cmd.value = (intptr_t)(cp + 1);
637                 cmd.size = inp->i_number; /* verify same name */
638                 if (sysctlbyname("vfs.ffs.unlink", 0, 0,
639                     &cmd, sizeof cmd) == -1) {
640                         printf(" (UNLINK FAILED: %s)\n",
641                             strerror(errno));
642                         return (0);
643                 }
644                 printf(" (REMOVED)\n");
645                 inp->i_parent = idesc->id_number;  /* reparent to correct dir */
646                 return (0);
647         }
648         if (!preen && !reply("REMOVE"))
649                 return (0);
650         memset(&dotdesc, 0, sizeof(struct inodesc));
651         dotdesc.id_type = DATA;
652         dotdesc.id_number = inp->i_parent; /* directory in which name appears */
653         dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
654         dotdesc.id_func = deleteentry;
655         ginode(dotdesc.id_number, &ip);
656         if ((ckinode(ip.i_dp, &dotdesc) & FOUND) && preen)
657                 printf(" (REMOVED)\n");
658         irelse(&ip);
659         inp->i_parent = idesc->id_number;  /* reparent to correct directory */
660         inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
661         return (0);
662 }
663
664 static int
665 deleteentry(struct inodesc *idesc)
666 {
667         struct direct *dirp = idesc->id_dirp;
668
669         if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
670                 return (KEEPON);
671         dirp->d_ino = 0;
672         return (ALTERED|STOP|FOUND);
673 }
674
675 /*
676  * Routine to sort disk blocks.
677  */
678 static int
679 blksort(const void *arg1, const void *arg2)
680 {
681
682         return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
683                 (*(struct inoinfo * const *)arg2)->i_blks[0]);
684 }