dir: check afs_dir_Create return code in afs_dir_MakeDir
[openafs.git] / src / dir / dir.c
index 4e13121..0fb6bff 100644 (file)
@@ -13,7 +13,9 @@
 #ifdef KERNEL
 # if !defined(UKERNEL)
 #  include "h/types.h"
-#  include "h/param.h"
+#  if !defined(AFS_LINUX26_ENV)
+#   include "h/param.h"
+#  endif
 #  ifdef       AFS_AUX_ENV
 #   include "h/mmu.h"
 #   include "h/seg.h"
 #    include "h/kernel.h"
 #   endif
 #  endif
-#  if  defined(AFS_SUN56_ENV) || defined(AFS_HPUX_ENV) || defined(AFS_FBSD_ENV) || defined(AFS_DARWIN80_ENV)
+#  if  defined(AFS_SUN5_ENV) || defined(AFS_HPUX_ENV) || defined(AFS_FBSD_ENV) || defined(AFS_DARWIN80_ENV)
 #   include "afs/sysincludes.h"
 #  endif
 #  if !defined(AFS_SGI64_ENV) && !defined(AFS_DARWIN_ENV) && !defined(AFS_OBSD48_ENV) && !defined(AFS_NBSD_ENV)
 #   include "h/user.h"
 #  endif /* AFS_SGI64_ENV */
 #  include "h/uio.h"
-#  ifdef       AFS_OSF_ENV
-#   include <sys/mount.h>
-#   include <sys/vnode.h>
-#   include <ufs/inode.h>
-#  endif
 #  if !defined(AFS_SUN5_ENV) && !defined(AFS_LINUX20_ENV) && !defined(AFS_HPUX110_ENV)
 #   include "h/mbuf.h"
 #  endif
@@ -73,8 +70,6 @@ extern int DNew(struct dcache *adc, int page, struct DirBuffer *);
 # include "dir.h"
 #endif /* KERNEL */
 
-afs_int32 DErrno;
-
 /* Local static prototypes */
 static int FindBlobs(dir_file_t, int);
 static void AddPage(dir_file_t, int);
@@ -102,16 +97,20 @@ afs_dir_Create(dir_file_t dir, char *entry, void *voidfid)
     struct DirBuffer entrybuf, prevbuf, headerbuf;
     struct DirEntry *ep;
     struct DirHeader *dhp;
+    int code;
 
     /* check name quality */
     if (*entry == 0)
        return EINVAL;
 
     /* First check if file already exists. */
-    if (FindItem(dir, entry, &prevbuf, &entrybuf) == 0) {
+    code = FindItem(dir, entry, &prevbuf, &entrybuf);
+    if (code && code != ENOENT) {
+        return code;
+    }
+    if (code == 0) {
        DRelease(&entrybuf, 0);
        DRelease(&prevbuf, 0);
-       printf("Exists ...\n");
        return EEXIST;
     }
 
@@ -179,9 +178,12 @@ afs_dir_Delete(dir_file_t dir, char *entry)
     struct DirBuffer entrybuf, prevbuf;
     struct DirEntry *firstitem;
     unsigned short *previtem;
+    int code;
 
-    if (FindItem(dir, entry, &prevbuf, &entrybuf) != 0)
-       return ENOENT;
+    code = FindItem(dir, entry, &prevbuf, &entrybuf);
+    if (code) {
+        return code;
+    }
 
     firstitem = (struct DirEntry *)entrybuf.data;
     previtem = (unsigned short *)prevbuf.data;
@@ -190,7 +192,9 @@ afs_dir_Delete(dir_file_t dir, char *entry)
     DRelease(&prevbuf, 1);
     index = DVOffset(&entrybuf) / 32;
     nitems = afs_dir_NameBlobs(firstitem->name);
-    DRelease(&entrybuf, 0);
+    /* Clear entire DirEntry and any DirXEntry extensions */
+    memset(firstitem, 0, nitems * sizeof(*firstitem));
+    DRelease(&entrybuf, 1);
     FreeBlobs(dir, index, nitems);
     return 0;
 }
@@ -236,7 +240,6 @@ FindBlobs(dir_file_t dir, int nblobs)
 
            /* read the page in. */
            if (DRead(dir, i, &pagebuf) != 0) {
-               DRelease(&headerbuf, 1);
                break;
            }
            pp = (struct PageHeader *)pagebuf.data;
@@ -323,10 +326,17 @@ FreeBlobs(dir_file_t dir, int firstblob, int nblobs)
     DRelease(&pagehdbuf, 1);
 }
 
-/*
+/*!
  * Format an empty directory properly.  Note that the first 13 entries in a
  * directory header page are allocated, 1 to the page header, 4 to the
  * allocation map and 8 to the hash table.
+ *
+ * \param dir      pointer to the directory object
+ * \param me       fid (vnode+uniq) for new dir
+ * \param parent    fid (vnode+uniq) for parent dir
+ *
+ * \retval 0       success
+ * \retval nonzero  error code
  */
 int
 afs_dir_MakeDir(dir_file_t dir, afs_int32 * me, afs_int32 * parent)
@@ -334,6 +344,7 @@ afs_dir_MakeDir(dir_file_t dir, afs_int32 * me, afs_int32 * parent)
     int i;
     struct DirBuffer buffer;
     struct DirHeader *dhp;
+    int code;
 
     DNew(dir, 0, &buffer);
     dhp = (struct DirHeader *)buffer.data;
@@ -351,8 +362,12 @@ afs_dir_MakeDir(dir_file_t dir, afs_int32 * me, afs_int32 * parent)
     for (i = 0; i < NHASHENT; i++)
        dhp->hashTable[i] = 0;
     DRelease(&buffer, 1);
-    afs_dir_Create(dir, ".", me);
-    afs_dir_Create(dir, "..", parent); /* Virtue is its own .. */
+    code = afs_dir_Create(dir, ".", me);
+    if (code)
+       return code;
+    code = afs_dir_Create(dir, "..", parent);
+    if (code)
+       return code;
     return 0;
 }
 
@@ -364,9 +379,12 @@ afs_dir_Lookup(dir_file_t dir, char *entry, void *voidfid)
     afs_int32 *fid = (afs_int32 *) voidfid;
     struct DirBuffer firstbuf, prevbuf;
     struct DirEntry *firstitem;
+    int code;
 
-    if (FindItem(dir, entry, &prevbuf, &firstbuf) != 0)
-       return ENOENT;
+    code = FindItem(dir, entry, &prevbuf, &firstbuf);
+    if (code) {
+        return code;
+    }
     DRelease(&prevbuf, 0);
     firstitem = (struct DirEntry *)firstbuf.data;
 
@@ -385,9 +403,12 @@ afs_dir_LookupOffset(dir_file_t dir, char *entry, void *voidfid,
     afs_int32 *fid = (afs_int32 *) voidfid;
     struct DirBuffer firstbuf, prevbuf;
     struct DirEntry *firstitem;
+    int code;
 
-    if (FindItem(dir, entry, &prevbuf, &firstbuf) != 0)
-       return ENOENT;
+    code = FindItem(dir, entry, &prevbuf, &firstbuf);
+    if (code) {
+        return code;
+    }
     DRelease(&prevbuf, 0);
     firstitem = (struct DirEntry *)firstbuf.data;
 
@@ -416,6 +437,7 @@ afs_dir_EnumerateDir(dir_file_t dir, int (*proc) (void *, char *name,
     struct DirHeader *dhp;
     struct DirEntry *ep;
     int code = 0;
+    int elements;
 
     if (DRead(dir, 0, &headerbuf) != 0)
        return EIO;
@@ -424,27 +446,31 @@ afs_dir_EnumerateDir(dir_file_t dir, int (*proc) (void *, char *name,
     for (i = 0; i < NHASHENT; i++) {
        /* For each hash chain, enumerate everyone on the list. */
        num = ntohs(dhp->hashTable[i]);
-       while (num != 0) {
+       elements = 0;
+       while (num != 0 && elements < BIGMAXPAGES * EPP) {
+           elements++;
+
            /* Walk down the hash table list. */
-           DErrno = 0;
-           if (afs_dir_GetBlob(dir, num, &entrybuf) != 0) {
-               if (DErrno) {
-                   /* we failed, return why */
-                   DRelease(&headerbuf, 0);
-                   return DErrno;
-               }
+           code = afs_dir_GetVerifiedBlob(dir, num, &entrybuf);
+           if (code)
+               goto out;
+
+           ep = (struct DirEntry *)entrybuf.data;
+           if (!ep) {
+               DRelease(&entrybuf, 0);
                break;
            }
-           ep = (struct DirEntry *)entrybuf.data;
 
            num = ntohs(ep->next);
            code = (*proc) (hook, ep->name, ntohl(ep->fid.vnode),
                            ntohl(ep->fid.vunique));
            DRelease(&entrybuf, 0);
            if (code)
-               break;
+               goto out;
        }
     }
+
+out:
     DRelease(&headerbuf, 0);
     return 0;
 }
@@ -458,6 +484,7 @@ afs_dir_IsEmpty(dir_file_t dir)
     struct DirBuffer headerbuf, entrybuf;
     struct DirHeader *dhp;
     struct DirEntry *ep;
+    int elements;
 
     if (DRead(dir, 0, &headerbuf) != 0)
        return 0;
@@ -466,9 +493,11 @@ afs_dir_IsEmpty(dir_file_t dir)
     for (i = 0; i < NHASHENT; i++) {
        /* For each hash chain, enumerate everyone on the list. */
        num = ntohs(dhp->hashTable[i]);
-       while (num != 0) {
+       elements = 0;
+       while (num != 0 && elements < BIGMAXPAGES * EPP) {
+           elements++;
            /* Walk down the hash table list. */
-           if (afs_dir_GetBlob(dir, num, &entrybuf) != 0);
+           if (afs_dir_GetVerifiedBlob(dir, num, &entrybuf) != 0)
                break;
            ep = (struct DirEntry *)entrybuf.data;
            if (strcmp(ep->name, "..") && strcmp(ep->name, ".")) {
@@ -484,22 +513,93 @@ afs_dir_IsEmpty(dir_file_t dir)
     return 0;
 }
 
-int
-afs_dir_GetBlob(dir_file_t dir, afs_int32 blobno, struct DirBuffer *buffer)
+/* Return a pointer to an entry, given its number. Also return the maximum
+ * size of the entry, which is determined by its position within the directory
+ * page.
+ *
+ * If physerr is supplied by caller, it will be set to:
+ *      0       for logical errors
+ *      errno   for physical errors
+ */
+static int
+GetBlobWithLimit(dir_file_t dir, afs_int32 blobno,
+               struct DirBuffer *buffer, afs_size_t *maxlen, int *physerr)
 {
+    afs_size_t pos;
     int code;
 
+    *maxlen = 0;
     memset(buffer, 0, sizeof(struct DirBuffer));
 
-    code = DRead(dir, blobno >> LEPP, buffer);
+    code = DReadWithErrno(dir, blobno >> LEPP, buffer, physerr);
     if (code)
        return code;
 
-    buffer->data = (void *)(((long)buffer->data) + 32 * (blobno & (EPP - 1)));
+    pos = 32 * (blobno & (EPP - 1));
+
+    *maxlen = AFS_PAGESIZE - pos - 1;
+
+    buffer->data = (void *)(((char *)buffer->data) + pos);
 
     return 0;
 }
 
+/*
+ * Given an entry's number, return a pointer to that entry.
+ * If physerr is supplied by caller, it will be set to:
+ *      0       for logical errors
+ *      errno   for physical errors
+ */
+int
+afs_dir_GetBlobWithErrno(dir_file_t dir, afs_int32 blobno, struct DirBuffer *buffer,
+                       int *physerr)
+{
+    afs_size_t maxlen = 0;
+    return GetBlobWithLimit(dir, blobno, buffer, &maxlen, physerr);
+}
+
+/* Given an entries number, return a pointer to that entry */
+int
+afs_dir_GetBlob(dir_file_t dir, afs_int32 blobno, struct DirBuffer *buffer)
+{
+    afs_size_t maxlen = 0;
+    return GetBlobWithLimit(dir, blobno, buffer, &maxlen, NULL);
+}
+
+/* Return an entry, having verified that the name held within the entry
+ * doesn't overflow off the end of the directory page it is contained
+ * within
+ */
+
+int
+afs_dir_GetVerifiedBlob(dir_file_t file, afs_int32 blobno,
+                       struct DirBuffer *outbuf)
+{
+    struct DirEntry *dir;
+    struct DirBuffer buffer;
+    afs_size_t maxlen;
+    int code;
+    char *cp;
+
+    code = GetBlobWithLimit(file, blobno, &buffer, &maxlen, NULL);
+    if (code)
+       return code;
+
+    dir = (struct DirEntry *)buffer.data;
+
+    /* A blob is only valid if the name within it is NULL terminated before
+     * the end of the blob's containing page */
+    for (cp = dir->name; *cp != '\0' && cp < ((char *)dir) + maxlen; cp++);
+
+    if (*cp != '\0') {
+       DRelease(&buffer, 0);
+       return EIO;
+    }
+
+    *outbuf = buffer;
+    return 0;
+}
+
 int
 afs_dir_DirHash(char *string)
 {
@@ -515,7 +615,7 @@ afs_dir_DirHash(char *string)
     tval = hval & (NHASHENT - 1);
     if (tval == 0)
        return tval;
-    else if (hval >= 1<<31)
+    else if (hval >= 1u<<31)
        tval = NHASHENT - tval;
     return tval;
 }
@@ -535,6 +635,7 @@ FindItem(dir_file_t dir, char *ename, struct DirBuffer *prevbuf,
     struct DirBuffer curr, prev;
     struct DirHeader *dhp;
     struct DirEntry *tp;
+    int elements;
 
     memset(prevbuf, 0, sizeof(struct DirBuffer));
     memset(itembuf, 0, sizeof(struct DirBuffer));
@@ -547,20 +648,23 @@ FindItem(dir_file_t dir, char *ename, struct DirBuffer *prevbuf,
     i = afs_dir_DirHash(ename);
     if (dhp->hashTable[i] == 0) {
        /* no such entry */
-       DRelease(&prev, 0);
-       return ENOENT;
+       code = ENOENT;
+       goto out;
     }
 
-    code = afs_dir_GetBlob(dir, (u_short) ntohs(dhp->hashTable[i]),
-                          &curr);
+    code = afs_dir_GetVerifiedBlob(dir,
+                                  (u_short) ntohs(dhp->hashTable[i]),
+                                  &curr);
     if (code) {
-       DRelease(&prev, 0);
-       return code;
+       goto out;
     }
 
     prev.data = &(dhp->hashTable[i]);
+    elements = 0;
+    /* Detect circular hash chains. Absolute max size of a directory */
+    while (elements < BIGMAXPAGES * EPP) {
+       elements++;
 
-    while (1) {
        /* Look at each entry on the hash chain */
        tp = (struct DirEntry *)curr.data;
        if (!strcmp(ename, tp->name)) {
@@ -569,23 +673,31 @@ FindItem(dir_file_t dir, char *ename, struct DirBuffer *prevbuf,
            *itembuf = curr;
            return 0;
        }
+
        DRelease(&prev, 0);
-       if (tp->next == 0) {
-           /* The end of the line */
-           DRelease(&curr, 0);
-           return ENOENT;
-       }
 
        prev = curr;
        prev.data = &(tp->next);
 
-       code = afs_dir_GetBlob(dir, (u_short) ntohs(tp->next), &curr);
-       if (code) {
-           DRelease(&prev, 0);
-           return code;
+       if (tp->next == 0) {
+           /* The end of the line */
+           code = ENOENT;
+           goto out;
        }
+
+       code = afs_dir_GetVerifiedBlob(dir, (u_short) ntohs(tp->next),
+                                      &curr);
+       if (code)
+           goto out;
     }
-    /* Never reached */
+
+    /* If we've reached here, we've hit our loop limit. Something is weird with
+     * the directory; maybe a circular hash chain? */
+    code = EIO;
+
+out:
+    DRelease(&prev, 0);
+    return code;
 }
 
 static int
@@ -602,6 +714,7 @@ FindFid (void *dir, afs_uint32 vnode, afs_uint32 unique,
     struct DirBuffer curr, header;
     struct DirHeader *dhp;
     struct DirEntry *tp;
+    int elements;
 
     memset(itembuf, 0, sizeof(struct DirBuffer));
 
@@ -612,14 +725,16 @@ FindFid (void *dir, afs_uint32 vnode, afs_uint32 unique,
 
     for (i=0; i<NHASHENT; i++) {
        if (dhp->hashTable[i] != 0) {
-           code = afs_dir_GetBlob(dir, (u_short)ntohs(dhp->hashTable[i]),
-                                  &curr);
+           code = afs_dir_GetVerifiedBlob(dir,
+                                          (u_short)ntohs(dhp->hashTable[i]),
+                                          &curr);
            if (code) {
                DRelease(&header, 0);
                return code;
            }
-
-           while (curr.data != NULL) {
+           elements = 0;
+           while(curr.data != NULL && elements < BIGMAXPAGES * EPP) {
+               elements++;
                tp = (struct DirEntry *)curr.data;
 
                if (vnode == ntohl(tp->fid.vnode)
@@ -635,7 +750,8 @@ FindFid (void *dir, afs_uint32 vnode, afs_uint32 unique,
                if (next == 0)
                    break;
 
-               code = afs_dir_GetBlob(dir, (u_short)ntohs(next), &curr);
+               code = afs_dir_GetVerifiedBlob(dir, (u_short)ntohs(next),
+                                              &curr);
                if (code) {
                    DRelease(&header, 0);
                    return code;
@@ -656,8 +772,10 @@ afs_dir_InverseLookup(void *dir, afs_uint32 vnode, afs_uint32 unique,
     struct DirEntry *entry;
     int code = 0;
 
-    if (FindFid(dir, vnode, unique, &entrybuf) != 0)
-       return ENOENT;
+    code = FindFid(dir, vnode, unique, &entrybuf);
+    if (code) {
+        return code;
+    }
     entry = (struct DirEntry *)entrybuf.data;
 
     if (strlen(entry->name) >= length)
@@ -685,10 +803,13 @@ afs_dir_ChangeFid(dir_file_t dir, char *entry, afs_uint32 *old_fid,
     struct DirEntry *firstitem;
     struct MKFid *fid_old = (struct MKFid *) old_fid;
     struct MKFid *fid_new = (struct MKFid *) new_fid;
+    int code;
 
     /* Find entry. */
-    if (FindItem(dir, entry, &prevbuf, &entrybuf) != 0)
-       return ENOENT;
+    code = FindItem(dir, entry, &prevbuf, &entrybuf);
+    if (code) {
+        return code;
+    }
     firstitem = (struct DirEntry *)entrybuf.data;
     DRelease(&prevbuf, 1);