summaryrefslogtreecommitdiff
path: root/ufs-fsck/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'ufs-fsck/dir.c')
-rw-r--r--ufs-fsck/dir.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/ufs-fsck/dir.c b/ufs-fsck/dir.c
new file mode 100644
index 00000000..85757b16
--- /dev/null
+++ b/ufs-fsck/dir.c
@@ -0,0 +1,567 @@
+/* Directory management subroutines
+ Copyright (C) 1994,96,99,2002 Free Software Foundation, Inc.
+ Written by Michael I. Bushnell.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "fsck.h"
+
+/* This routine is used in pass 1 to initialize DIRARRAY and DIRSORTED.
+ Copy information from DP (for number NUMBER) into a newly allocated
+ dirinfo structure and add it to the arrays. */
+void
+record_directory (struct dinode *dp, ino_t number)
+{
+ u_int blks;
+ struct dirinfo *dnp;
+
+ blks = howmany (dp->di_size, sblock->fs_bsize);
+ if (blks > NDADDR)
+ blks = NDADDR + NIADDR;
+ blks *= sizeof (daddr_t);
+ dnp = malloc (sizeof (struct dirinfo) + blks);
+
+ dnp->i_number = number;
+ dnp->i_parent = dnp->i_dotdot = 0;
+ dnp->i_isize = dp->di_size;
+ dnp->i_numblks = blks;
+ bcopy (dp->di_db, dnp->i_blks, blks);
+
+ if (dirarrayused == dirarraysize)
+ {
+ if (dirarraysize == 0)
+ {
+ dirarraysize = 100;
+ dirarray = malloc (dirarraysize * sizeof (struct dirinfo *));
+ dirsorted = malloc (dirarraysize * sizeof (struct dirinfo *));
+ }
+ else
+ {
+ dirarraysize *= 2;
+ dirarray = realloc (dirarray,
+ dirarraysize * sizeof (struct dirinfo *));
+ dirsorted = realloc (dirsorted,
+ dirarraysize * sizeof (struct dirinfo *));
+ }
+ }
+ dirarray[dirarrayused] = dnp;
+ dirsorted[dirarrayused] = dnp;
+ dirarrayused++;
+}
+
+/* Return the cached dirinfo structure for directory INO. */
+struct dirinfo *
+lookup_directory (ino_t ino)
+{
+ int i;
+
+ for (i = 0; i < dirarrayused; i++)
+ if (dirarray[i]->i_number == ino)
+ return dirarray[i];
+
+ errexit ("Cannot find cached directory I=%Ld\n", ino);
+}
+
+/* Check to see if DIR is really a readable directory; if it
+ isn't, then bail with an appropriate message and return 0;
+ else return 1. MSG identifies the action contemplated */
+static int
+validdir (ino_t dir, char *action)
+{
+ switch (inodestate[dir])
+ {
+ case DIRECTORY:
+ case DIRECTORY|DIR_REF:
+ return 1;
+
+ case UNALLOC:
+ warning (1, "CANNOT %s I=%Ld; NOT ALLOCATED", action, dir);
+ return 0;
+
+ case BADDIR:
+ warning (1, "CANNOT %s I=%Ld; BAD BLOCKS", action, dir);
+ return 0;
+
+ case REG:
+ warning (1, "CANNOT %s I=%Ld; NOT DIRECTORY", action, dir);
+ return 0;
+
+ default:
+ errexit ("ILLEGAL STATE");
+ }
+}
+
+/* Search directory DIR for name NAME. If NAME is found, then
+ set *INO to the inode of the entry; otherwise clear INO. Returns 1 if all
+ was normal, or 0 if there was some error doing the search. */
+int
+searchdir (ino_t dir, char *name, ino_t *ino)
+{
+ struct dinode dino;
+ int len;
+
+ /* Scan through one directory block and see if it
+ contains NAME. */
+ void
+ check1block (void *buf)
+ {
+ struct directory_entry *dp;
+
+ for (dp = buf; (void *)dp - buf < DIRBLKSIZ;
+ dp = (struct directory_entry *) ((void *)dp + dp->d_reclen))
+ {
+ if (dp->d_reclen == 0
+ || dp->d_reclen + (void *)dp - buf > DIRBLKSIZ)
+ return;
+
+ if (dp->d_ino == 0 || dp->d_ino > maxino)
+ continue;
+
+ if (DIRECT_NAMLEN (dp) == len && strcmp (dp->d_name, name) == 0)
+ {
+ *ino = dp->d_ino;
+ return;
+ }
+ }
+ }
+
+ /* Read part of a directory and look to see if it contains
+ NAME. Return 1 if we should keep looking at more
+ blocks. */
+ int
+ checkdirblock (daddr_t bno, int nfrags, off_t offset)
+ {
+ void *buf = alloca (nfrags * sblock->fs_fsize);
+ void *bufp;
+
+ readblock (fsbtodb (sblock, bno), buf, nfrags * sblock->fs_fsize);
+ for (bufp = buf;
+ bufp - buf < nfrags * sblock->fs_fsize
+ && offset + (bufp - buf) + DIRBLKSIZ <= dino.di_size;
+ bufp += DIRBLKSIZ)
+ {
+ check1block (bufp);
+ if (*ino)
+ return 0;
+ }
+ return 1;
+ }
+
+ *ino = 0;
+
+ if (!validdir (dir, "READ"))
+ return 0;
+
+ getinode (dir, &dino);
+
+ len = strlen (name);
+ datablocks_iterate (&dino, checkdirblock);
+
+ return 1;
+}
+
+/* Change the existing entry in DIR for name NAME to be
+ inode INO. Return 1 if the entry was found and
+ replaced, else return 0. */
+int
+changeino (ino_t dir, char *name, ino_t ino)
+{
+ struct dinode dino;
+ int len;
+ int madechange;
+
+ /* Scan through a directory block looking for NAME;
+ if we find it then change the inode pointer to point
+ at INO and return 1; if we don't find it then return 0. */
+ int
+ check1block (void *buf)
+ {
+ struct directory_entry *dp;
+
+ for (dp = buf; (void *)dp - buf < DIRBLKSIZ;
+ dp = (struct directory_entry *) ((void *)dp + dp->d_reclen))
+ {
+ if (dp->d_reclen == 0
+ || dp->d_reclen + (void *)dp - buf > DIRBLKSIZ)
+ return 0;
+
+ if (dp->d_ino == 0 || dp->d_ino > maxino)
+ continue;
+
+ if (DIRECT_NAMLEN (dp) == len && strcmp (dp->d_name, name) == 0)
+ {
+ dp->d_ino = ino;
+ madechange = 1;
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ /* Read part of a directory and look to see if it
+ contains NAME. Return 1 if we should keep looking
+ at more blocks. */
+ int
+ checkdirblock (daddr_t bno, int nfrags, off_t offset)
+ {
+ void *buf = alloca (nfrags * sblock->fs_fsize);
+ void *bufp;
+
+ readblock (fsbtodb (sblock, bno), buf, nfrags * sblock->fs_fsize);
+ for (bufp = buf;
+ bufp - buf < nfrags * sblock->fs_fsize
+ && offset + (bufp - buf) + DIRBLKSIZ <= dino.di_size;
+ bufp += DIRBLKSIZ)
+ {
+ if (check1block (bufp))
+ {
+ writeblock (fsbtodb (sblock, bno), buf,
+ nfrags * sblock->fs_fsize);
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ if (!validdir (dir, "REWRITE"))
+ return 0;
+
+ getinode (dir, &dino);
+ len = strlen (name);
+ madechange = 0;
+ datablocks_iterate (&dino, checkdirblock);
+ return madechange;
+}
+
+/* Attempt to expand the size of a directory. Return
+ 1 if we succeeded. */
+static int
+expanddir (struct dinode *dp)
+{
+ daddr_t lastbn, newblk;
+ char *cp, buf[sblock->fs_bsize];
+
+ lastbn = lblkno (sblock, dp->di_size);
+ if (blkoff (sblock, dp->di_size) && lastbn >= NDADDR - 1)
+ return 0;
+ else if (!blkoff (sblock, dp->di_size) && lastbn >= NDADDR)
+ return 0;
+ else if (blkoff (sblock, dp->di_size) && !dp->di_db[lastbn])
+ return 0;
+ else if (!blkoff (sblock, dp->di_size) && dp->di_db[lastbn])
+ return 0;
+
+ newblk = allocblk (sblock->fs_frag);
+ if (!newblk)
+ return 0;
+
+ if (blkoff (sblock, dp->di_size))
+ dp->di_db[lastbn + 1] = dp->di_db[lastbn];
+ dp->di_db[lastbn] = newblk;
+ dp->di_size += sblock->fs_bsize;
+ dp->di_blocks += sblock->fs_bsize / DEV_BSIZE;
+
+ for (cp = buf; cp < buf + sblock->fs_bsize; cp += DIRBLKSIZ)
+ {
+ struct directory_entry *dir = (struct directory_entry *) cp;
+ dir->d_ino = 0;
+ dir->d_reclen = DIRBLKSIZ;
+ }
+
+ writeblock (fsbtodb (sblock, newblk), buf, sblock->fs_bsize);
+ return 1;
+}
+
+/* Add a new link into directory DIR with name NAME and target
+ INO. Return 1 if we succeeded and 0 if we failed. It is
+ an error to call this routine if NAME is already present
+ in DIR. */
+int
+makeentry (ino_t dir, ino_t ino, char *name)
+{
+ int len;
+ struct dinode dino;
+ int needed;
+ int madeentry;
+
+ /* Read a directory block and see if it contains room for the
+ new entry. If so, add it and return 1; otherwise return 0. */
+ int
+ check1block (void *buf)
+ {
+ struct directory_entry *dp;
+
+ for (dp = buf; (void *)dp - buf < DIRBLKSIZ;
+ dp = (struct directory_entry *) ((void *)dp + dp->d_reclen))
+ {
+ if (dp->d_reclen == 0
+ || dp->d_reclen + (void *)dp - buf > DIRBLKSIZ)
+ return 0;
+ if (dp->d_ino
+ && dp->d_reclen - DIRSIZ (DIRECT_NAMLEN (dp)) >= needed)
+ {
+ struct directory_entry *newdp;
+ newdp = (struct directory_entry *)
+ ((void *)dp + DIRSIZ (DIRECT_NAMLEN (dp)));
+
+ newdp->d_reclen = dp->d_reclen - DIRSIZ (DIRECT_NAMLEN (dp));
+ DIRECT_NAMLEN (newdp) = len;
+ newdp->d_ino = ino;
+ if (direct_symlink_extension)
+ newdp->d_type = typemap[ino];
+ bcopy (name, newdp->d_name, len + 1);
+
+ dp->d_reclen -= newdp->d_reclen;
+ madeentry = 1;
+ return 1;
+ }
+ else if (!dp->d_ino && dp->d_reclen >= needed)
+ {
+ DIRECT_NAMLEN (dp) = len;
+ dp->d_ino = ino;
+ if (direct_symlink_extension)
+ dp->d_type = typemap[ino];
+ bcopy (name, dp->d_name, len + 1);
+ madeentry = 1;
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ /* Read part of a directory and look to see if it
+ contains NAME. Return 1 if we should keep looking
+ at more blocks. */
+ int
+ checkdirblock (daddr_t bno, int nfrags, off_t offset)
+ {
+ void *buf = alloca (nfrags * sblock->fs_fsize);
+ void *bufp;
+
+ readblock (fsbtodb (sblock, bno), buf, nfrags * sblock->fs_fsize);
+ for (bufp = buf;
+ bufp - buf < nfrags * sblock->fs_fsize
+ && offset + (bufp - buf) + DIRBLKSIZ <= dino.di_size;
+ bufp += DIRBLKSIZ)
+ {
+ if (check1block (bufp))
+ {
+ writeblock (fsbtodb (sblock, bno), buf,
+ nfrags * sblock->fs_fsize);
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ if (!validdir (dir, "MODIFY"))
+ return 0;
+
+ getinode (dir, &dino);
+ len = strlen (name);
+ needed = DIRSIZ (len);
+ madeentry = 0;
+ datablocks_iterate (&dino, checkdirblock);
+ if (!madeentry)
+ {
+ /* Attempt to expand the directory. */
+ problem (0, "NO SPACE LEFT IN DIR INO=%Ld", dir);
+ if (preen || reply ("EXPAND"))
+ {
+ if (expanddir (&dino))
+ {
+ write_inode (ino, &dino);
+ datablocks_iterate (&dino, checkdirblock);
+ pfix ("EXPANDED");
+ }
+ else
+ {
+ pfail (0);
+ warning (1, "CANNOT EXPAND DIRECTORY");
+ }
+ }
+ }
+ return madeentry;
+}
+
+/* Create a directory node whose parent is to be PARENT, whose inode
+ is REQUEST, and whose mode is to be MODE. If REQUEST is zero, then
+ allocate any inode. Initialze the contents of the
+ directory. Return the inode of the new directory. */
+ino_t
+allocdir (ino_t parent, ino_t request, mode_t mode)
+{
+ ino_t ino;
+
+ mode |= IFDIR;
+
+ ino = allocino (request, mode);
+ if (!ino)
+ return 0;
+ if (!makeentry (ino, ino, "."))
+ goto bad;
+ if (!makeentry (ino, parent, ".."))
+ goto bad;
+
+ linkfound[ino]++;
+ linkfound[parent]++;
+ return ino;
+
+ bad:
+ freeino (ino);
+ return 0;
+}
+
+/* Link node INO into lost+found. If PARENT is positive then INO is
+ a directory, and PARENT is the number of `..' as found in INO.
+ If PARENT is zero then INO is a directory without any .. entry.
+ If the node could be linked, return 1; else return 0. */
+int
+linkup (ino_t ino, ino_t parent)
+{
+ int search_failed;
+ struct dinode lfdino;
+ char *tempname;
+ ino_t foo;
+
+ if (lfdir == 0)
+ {
+ if (!searchdir (ROOTINO, lfname, &lfdir))
+ {
+ warning (1, "FAILURE SEARCHING FOR `%s'", lfname);
+ return 0;
+ }
+ if (lfdir == 0)
+ {
+ problem (0, "NO `%s' DIRECTORY", lfname);
+ if (preen || reply ("CREATE"))
+ {
+ lfdir = allocdir (ROOTINO, 0, lfmode);
+ if (lfdir != 0)
+ {
+ if (! makeentry (ROOTINO, lfdir, lfname))
+ {
+ freeino (lfdir);
+ linkfound[ROOTINO]--;
+ lfdir = 0;
+ }
+ }
+ }
+ if (lfdir)
+ pfix ("CREATED");
+ else
+ {
+ pfail (0);
+ warning (1, "SORRY, CANNOT CREATE `%s' DIRECTORY", lfname);
+ return 0;
+ }
+ }
+ }
+
+ getinode (lfdir, &lfdino);
+ if ((lfdino.di_model & IFMT) != IFDIR)
+ {
+ ino_t oldlfdir;
+
+ problem (1, "`%s' IS NOT A DIRECTORY", lfname);
+ if (! reply ("REALLOCATE"))
+ return 0;
+
+ oldlfdir = lfdir;
+
+ lfdir = allocdir (ROOTINO, 0, lfmode);
+ if (!lfdir)
+ {
+ warning (1, "SORRY, CANNOT CREATE `%s' DIRECTORY", lfname);
+ return 0;
+ }
+ if (!changeino (ROOTINO, lfname, lfdir))
+ {
+ warning (1, "SORRY, CANNOT CREATE `%s' DIRECTORY", lfname);
+ return 0;
+ }
+
+ /* One less link to the old one */
+ linkfound[oldlfdir]--;
+
+ getinode (lfdir, &lfdino);
+ }
+
+ if (inodestate[lfdir] != DIRECTORY && inodestate[lfdir] != (DIRECTORY|DIR_REF))
+ {
+ warning (1, "SORRY. `%s' DIRECTORY NOT ALLOCATED", lfname);
+ return 0;
+ }
+
+ asprintf (&tempname, "#%Ld", ino);
+ search_failed = !searchdir (lfdir, tempname, &foo);
+ while (foo)
+ {
+ char *newname;
+ asprintf (&newname, "%sa", tempname);
+ free (tempname);
+ tempname = newname;
+ search_failed = !searchdir (lfdir, tempname, &foo);
+ }
+ if (search_failed)
+ {
+ warning (1, "FAILURE SEARCHING FOR `%s' IN `%s'", tempname, lfname);
+ free (tempname);
+ return 0;
+ }
+ if (!makeentry (lfdir, ino, tempname))
+ {
+ free (tempname);
+ warning (1, "SORRY, NO SPACE IN `%s' DIRECTORY", lfname);
+ return 0;
+ }
+ free (tempname);
+ linkfound[ino]++;
+
+ if (parent != -1)
+ {
+ /* Reset `..' in ino */
+ if (parent)
+ {
+ if (!changeino (ino, "..", lfdir))
+ {
+ warning (1, "CANNOT ADJUST `..' LINK I=%Ld", ino);
+ return 0;
+ }
+ /* Forget about link to old parent */
+ linkfound[parent]--;
+ }
+ else if (!makeentry (ino, lfdir, ".."))
+ {
+ warning (1, "CANNOT CREAT `..' LINK I=%Ld", ino);
+ return 0;
+ }
+
+ /* Account for link to lost+found; update inode directly
+ here to avoid confusing warning later. */
+ linkfound[lfdir]++;
+ linkcount[lfdir]++;
+ lfdino.di_nlink++;
+ write_inode (lfdir, &lfdino);
+
+ if (parent)
+ warning (0, "DIR I=%Ld CONNECTED; PARENT WAS I=%Ld", ino, parent);
+ else
+ warning (0, "DIR I=%Ld CONNECTED", ino);
+ }
+ return 1;
+}