Index: head/sys/boot/zfs/zfsimpl.c =================================================================== --- head/sys/boot/zfs/zfsimpl.c +++ head/sys/boot/zfs/zfsimpl.c @@ -2264,6 +2264,61 @@ return (0); } +static int +zfs_dnode_readlink(const spa_t *spa, dnode_phys_t *dn, char *path, size_t psize) +{ + int rc = 0; + + if (dn->dn_bonustype == DMU_OT_SA) { + sa_hdr_phys_t *sahdrp = NULL; + size_t size = 0; + void *buf = NULL; + int hdrsize; + char *p; + + if (dn->dn_bonuslen != 0) + sahdrp = (sa_hdr_phys_t *)DN_BONUS(dn); + else { + blkptr_t *bp; + + if ((dn->dn_flags & DNODE_FLAG_SPILL_BLKPTR) == 0) + return (EIO); + bp = &dn->dn_spill; + + size = BP_GET_LSIZE(bp); + buf = zfs_alloc(size); + rc = zio_read(spa, bp, buf); + if (rc != 0) { + zfs_free(buf, size); + return (rc); + } + sahdrp = buf; + } + hdrsize = SA_HDR_SIZE(sahdrp); + p = (char *)((uintptr_t)sahdrp + hdrsize + SA_SYMLINK_OFFSET); + memcpy(path, p, psize); + if (buf != NULL) + zfs_free(buf, size); + return (0); + } + /* + * Second test is purely to silence bogus compiler + * warning about accessing past the end of dn_bonus. + */ + if (psize + sizeof(znode_phys_t) <= dn->dn_bonuslen && + sizeof(znode_phys_t) <= sizeof(dn->dn_bonus)) { + memcpy(path, &dn->dn_bonus[sizeof(znode_phys_t)], psize); + } else { + rc = dnode_read(spa, dn, 0, path, psize); + } + return (rc); +} + +struct obj_list { + uint64_t objnum; + STAILQ_ENTRY(obj_list) entry; +}; + /* * Lookup a file and return its dnode. */ @@ -2271,7 +2326,7 @@ zfs_lookup(const struct zfsmount *mount, const char *upath, dnode_phys_t *dnode) { int rc; - uint64_t objnum, rootnum, parentnum; + uint64_t objnum; const spa_t *spa; dnode_phys_t dn; const char *p, *q; @@ -2279,6 +2334,8 @@ char path[1024]; int symlinks_followed = 0; struct stat sb; + struct obj_list *entry; + STAILQ_HEAD(, obj_list) on_cache = STAILQ_HEAD_INITIALIZER(on_cache); spa = mount->spa; if (mount->objset.os_type != DMU_OST_ZFS) { @@ -2287,87 +2344,119 @@ return (EIO); } + if ((entry = malloc(sizeof(struct obj_list))) == NULL) + return (ENOMEM); + /* * Get the root directory dnode. */ rc = objset_get_dnode(spa, &mount->objset, MASTER_NODE_OBJ, &dn); - if (rc) + if (rc) { + free(entry); return (rc); + } - rc = zap_lookup(spa, &dn, ZFS_ROOT_OBJ, sizeof (rootnum), 1, &rootnum); - if (rc) + rc = zap_lookup(spa, &dn, ZFS_ROOT_OBJ, sizeof (objnum), 1, &objnum); + if (rc) { + free(entry); return (rc); + } + entry->objnum = objnum; + STAILQ_INSERT_HEAD(&on_cache, entry, entry); - rc = objset_get_dnode(spa, &mount->objset, rootnum, &dn); - if (rc) - return (rc); + rc = objset_get_dnode(spa, &mount->objset, objnum, &dn); + if (rc != 0) + goto done; - objnum = rootnum; p = upath; while (p && *p) { + rc = objset_get_dnode(spa, &mount->objset, objnum, &dn); + if (rc != 0) + goto done; + while (*p == '/') p++; - if (!*p) + if (*p == '\0') break; - q = strchr(p, '/'); - if (q) { - memcpy(element, p, q - p); - element[q - p] = 0; - p = q; - } else { - strcpy(element, p); - p = NULL; - } + q = p; + while (*q != '\0' && *q != '/') + q++; - rc = zfs_dnode_stat(spa, &dn, &sb); - if (rc) - return (rc); - if (!S_ISDIR(sb.st_mode)) - return (ENOTDIR); + /* skip dot */ + if (p + 1 == q && p[0] == '.') { + p++; + continue; + } + /* double dot */ + if (p + 2 == q && p[0] == '.' && p[1] == '.') { + p += 2; + if (STAILQ_FIRST(&on_cache) == + STAILQ_LAST(&on_cache, obj_list, entry)) { + rc = ENOENT; + goto done; + } + entry = STAILQ_FIRST(&on_cache); + STAILQ_REMOVE_HEAD(&on_cache, entry); + free(entry); + objnum = (STAILQ_FIRST(&on_cache))->objnum; + continue; + } + if (q - p + 1 > sizeof(element)) { + rc = ENAMETOOLONG; + goto done; + } + memcpy(element, p, q - p); + element[q - p] = 0; + p = q; + + if ((rc = zfs_dnode_stat(spa, &dn, &sb)) != 0) + goto done; + if (!S_ISDIR(sb.st_mode)) { + rc = ENOTDIR; + goto done; + } - parentnum = objnum; rc = zap_lookup(spa, &dn, element, sizeof (objnum), 1, &objnum); if (rc) - return (rc); + goto done; objnum = ZFS_DIRENT_OBJ(objnum); + if ((entry = malloc(sizeof(struct obj_list))) == NULL) { + rc = ENOMEM; + goto done; + } + entry->objnum = objnum; + STAILQ_INSERT_HEAD(&on_cache, entry, entry); rc = objset_get_dnode(spa, &mount->objset, objnum, &dn); if (rc) - return (rc); + goto done; /* * Check for symlink. */ rc = zfs_dnode_stat(spa, &dn, &sb); if (rc) - return (rc); + goto done; if (S_ISLNK(sb.st_mode)) { - if (symlinks_followed > 10) - return (EMLINK); + if (symlinks_followed > 10) { + rc = EMLINK; + goto done; + } symlinks_followed++; /* * Read the link value and copy the tail of our * current path onto the end. */ - if (p) - strcpy(&path[sb.st_size], p); - else - path[sb.st_size] = 0; - /* - * Second test is purely to silence bogus compiler - * warning about accessing past the end of dn_bonus. - */ - if (sb.st_size + sizeof(znode_phys_t) <= - dn.dn_bonuslen && sizeof(znode_phys_t) <= - sizeof(dn.dn_bonus)) { - memcpy(path, &dn.dn_bonus[sizeof(znode_phys_t)], - sb.st_size); - } else { - rc = dnode_read(spa, &dn, 0, path, sb.st_size); - if (rc) - return (rc); + if (sb.st_size + strlen(p) + 1 > sizeof(path)) { + rc = ENAMETOOLONG; + goto done; } + strcpy(&path[sb.st_size], p); + + rc = zfs_dnode_readlink(spa, &dn, path, sb.st_size); + if (rc != 0) + goto done; /* * Restart with the new path, starting either at @@ -2375,14 +2464,25 @@ * not the link is relative. */ p = path; - if (*p == '/') - objnum = rootnum; - else - objnum = parentnum; - objset_get_dnode(spa, &mount->objset, objnum, &dn); + if (*p == '/') { + while (STAILQ_FIRST(&on_cache) != + STAILQ_LAST(&on_cache, obj_list, entry)) { + entry = STAILQ_FIRST(&on_cache); + STAILQ_REMOVE_HEAD(&on_cache, entry); + free(entry); + } + } else { + entry = STAILQ_FIRST(&on_cache); + STAILQ_REMOVE_HEAD(&on_cache, entry); + free(entry); + } + objnum = (STAILQ_FIRST(&on_cache))->objnum; } } *dnode = dn; - return (0); +done: + STAILQ_FOREACH(entry, &on_cache, entry) + free(entry); + return (rc); } Index: head/sys/cddl/boot/zfs/zfsimpl.h =================================================================== --- head/sys/cddl/boot/zfs/zfsimpl.h +++ head/sys/cddl/boot/zfs/zfsimpl.h @@ -1080,6 +1080,7 @@ #define SA_UID_OFFSET 24 #define SA_GID_OFFSET 32 #define SA_PARENT_OFFSET 40 +#define SA_SYMLINK_OFFSET 160 /* * Intent log header - this on disk structure holds fields to manage