Mercurial > illumos > illumos-gate
changeset 10308:a0c54044a2bd
6839110 ls hangs when run on smbfs mounted directory containing many files
6699861 Directory access denied is not reported correctly
author | Gordon Ross <Gordon.Ross@Sun.COM> |
---|---|
date | Fri, 14 Aug 2009 12:16:18 -0400 |
parents | 05a4e6d289cc |
children | 1b8c848f3840 |
files | usr/src/uts/common/fs/smbclnt/smbfs/smbfs_node.h usr/src/uts/common/fs/smbclnt/smbfs/smbfs_smb.c usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr.h usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr2.c usr/src/uts/common/fs/smbclnt/smbfs/smbfs_vnops.c |
diffstat | 5 files changed, 253 insertions(+), 230 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_node.h Fri Aug 14 02:34:40 2009 -0700 +++ b/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_node.h Fri Aug 14 12:16:18 2009 -0400 @@ -217,12 +217,11 @@ uint32_t n_flag; smbmntinfo_t *n_mount; ino64_t n_ino; - /* Lock for the next 8 is r_lkserlock */ + /* Lock for the next 7 is r_lkserlock */ enum vtype n_ovtype; /* vnode type opened */ int n_dirrefs; struct smbfs_fctx *n_dirseq; /* ff context */ - long n_dirofs; /* last ff offset */ - long n_direof; /* End of dir. offset. */ + int n_dirofs; /* last ff offset */ int n_vcgenid; /* gereration no. (reconnect) */ int n_fidrefs; uint16_t n_fid; /* file handle */
--- a/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_smb.c Fri Aug 14 02:34:40 2009 -0700 +++ b/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_smb.c Fri Aug 14 12:16:18 2009 -0400 @@ -560,18 +560,6 @@ md_get_mem(mdp, fsa->fsa_tname, nlen, MB_MSYSTEM); } - /* - * If fs_name isn't NTFS they probably require resume keys. - * This is another example of the client trying to fix a server - * bug. This code uses the logic created by PR-3983209. See - * long block comment in smbfs_smb_findnextLM2. - */ - if (strcmp(fsa->fsa_tname, "NTFS")) { - SMB_SS_LOCK(ssp); - ssp->ss_flags |= SMBS_RESUMEKEYS; - SMB_SS_UNLOCK(ssp); - } - smb_t2_done(t2p); return (0); } @@ -2304,14 +2292,13 @@ struct smb_vc *vcp = SSTOVC(ctx->f_ssp); struct mbchain *mbp; struct mdchain *mdp; - uint16_t tw, flags; + uint16_t ecnt, eos, lno, flags; int len, error; if (ctx->f_t2) { smb_t2_done(ctx->f_t2); ctx->f_t2 = NULL; } - ctx->f_flags &= ~SMBFS_RDD_GOTRNAME; flags = FIND2_RETURN_RESUME_KEYS | FIND2_CLOSE_ON_EOS; if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) { flags |= FIND2_CLOSE_AFTER_REQUEST; @@ -2330,7 +2317,6 @@ mb_put_uint16le(mbp, flags); mb_put_uint16le(mbp, ctx->f_infolevel); mb_put_uint32le(mbp, 0); - /* mb_put_uint8(mbp, SMB_DT_ASCII); specs? hah! */ len = ctx->f_wclen; error = smbfs_fullpath(mbp, vcp, ctx->f_dnp, ctx->f_wildcard, &len, '\\'); @@ -2347,11 +2333,10 @@ mb_put_uint16le(mbp, ctx->f_Sid); mb_put_uint16le(mbp, ctx->f_limit); mb_put_uint16le(mbp, ctx->f_infolevel); - if (ctx->f_ssp->ss_flags & SMBS_RESUMEKEYS) { - mb_put_uint32le(mbp, ctx->f_rkey); - } else - mb_put_uint32le(mbp, 0); + /* Send whatever resume key we received... */ + mb_put_uint32le(mbp, ctx->f_rkey); mb_put_uint16le(mbp, flags); + /* ... and the resume name if we have one. */ if (ctx->f_rname) { /* resume file name */ mb_put_mem(mbp, ctx->f_rname, ctx->f_rnamelen, @@ -2361,67 +2346,66 @@ if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) mb_put_uint8(mbp, 0); /* 1st byte NULL Unicode char */ mb_put_uint8(mbp, 0); -#if 0 - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 200 * 1000 * 1000; /* 200ms */ - if (vcp->vc_flags & SMBC_WIN95) { - /* - * some implementations suggests to sleep here - * for 200ms, due to the bug in the Win95. - * I've didn't notice any problem, but put code - * for it. - */ - msleep(&flags, 0, PVFS, "fix95", &ts); - } -#endif } t2p->t2_maxpcount = 5 * 2; - t2p->t2_maxdcount = vcp->vc_txmax; + t2p->t2_maxdcount = 0xF000; /* 64K less some overhead */ error = smb_t2_request(t2p); if (error) return (error); + + /* + * This is the "resume name" we just sent. + * We want the new one (if any) that may be + * found in the response we just received and + * will now begin parsing. Free the old one + * now so we'll know if we found a new one. + */ + if (ctx->f_rname) { + kmem_free(ctx->f_rname, ctx->f_rnamelen); + ctx->f_rname = NULL; + ctx->f_rnamelen = 0; + } + mdp = &t2p->t2_rparam; if (ctx->f_flags & SMBFS_RDD_FINDFIRST) { if ((error = md_get_uint16le(mdp, &ctx->f_Sid)) != 0) - return (error); + goto nodata; ctx->f_flags &= ~SMBFS_RDD_FINDFIRST; } - if ((error = md_get_uint16le(mdp, &tw)) != 0) - return (error); - ctx->f_ecnt = tw; /* search count - # entries returned */ - if ((error = md_get_uint16le(mdp, &tw)) != 0) - return (error); + md_get_uint16le(mdp, &ecnt); /* entry count */ + md_get_uint16le(mdp, &eos); /* end of search */ + md_get_uint16le(mdp, NULL); /* EA err. off. */ + error = md_get_uint16le(mdp, &lno); /* last name off. */ + if (error != 0) + goto nodata; + /* - * tw now is the "end of search" flag. against an XP server tw + * The "end of search" flag from an XP server sometimes * comes back zero when the prior find_next returned exactly * the number of entries requested. in which case we'd try again - * but the search has in fact been closed so an EBADF results. our - * circumvention is to check here for a zero search count. + * but the search has in fact been closed so an EBADF results. + * our circumvention is to check here for a zero entry count. */ - if (tw || ctx->f_ecnt == 0) + ctx->f_ecnt = ecnt; + if (eos || ctx->f_ecnt == 0) ctx->f_flags |= SMBFS_RDD_EOF | SMBFS_RDD_NOCLOSE; - if ((error = md_get_uint16le(mdp, &tw)) != 0) - return (error); - if ((error = md_get_uint16le(mdp, &tw)) != 0) - return (error); if (ctx->f_ecnt == 0) return (ENOENT); - ctx->f_rnameofs = tw; - mdp = &t2p->t2_rdata; - if (mdp->md_top == NULL) { - SMBVDEBUG("ecnt = %d, but data is NULL\n", ctx->f_ecnt); - return (ENOENT); - } -#ifdef APPLE - if (mdp->md_top->m_len == 0) { - printf("bug: ecnt = %d, but m_len = 0 and m_next = %p " - "(please report)\n", ctx->f_ecnt, mbp->mb_top->m_next); - return (ENOENT); - } -#endif + + /* Last Name Off (LNO) is the entry with the resume name. */ + ctx->f_rnameofs = lno; ctx->f_eofs = 0; return (0); + +nodata: + /* + * Failed parsing the FindFirst or FindNext response. + * Force this directory listing closed, otherwise the + * calling process may hang in an infinite loop. + */ + ctx->f_ecnt = 0; /* Force closed. */ + ctx->f_flags |= SMBFS_RDD_EOF; + return (EIO); } static int @@ -2478,11 +2462,9 @@ uint16_t date, time, wattr; uint32_t size, next, dattr, resumekey = 0; uint64_t llongint; - int error, svtz, cnt, fxsz, nmlen, recsz, otw; + int error, svtz, cnt, fxsz, nmlen, recsz; struct timespec ts; -again: - otw = 0; /* nothing sent Over The Wire (yet) */ if (ctx->f_ecnt == 0) { if (ctx->f_flags & SMBFS_RDD_EOF) return (ENOENT); @@ -2493,7 +2475,6 @@ return (error); ctx->f_attr.fa_reqtime = ts; ctx->f_otws++; - otw = 1; } t2p = ctx->f_t2; mdp = &t2p->t2_rdata; @@ -2515,7 +2496,9 @@ md_get_uint32le(mdp, NULL); /* allocation size */ md_get_uint16le(mdp, &wattr); ctx->f_attr.fa_attr = wattr; - md_get_uint8(mdp, &tb); + error = md_get_uint8(mdp, &tb); + if (error) + goto nodata; size = nmlen = tb; fxsz = 23; recsz = next = 24 + nmlen; /* docs misses zero byte @end */ @@ -2537,7 +2520,9 @@ /* freebsd bug: fa_attr endian bug */ md_get_uint32le(mdp, &dattr); /* extended file attributes */ ctx->f_attr.fa_attr = dattr; - md_get_uint32le(mdp, &size); /* name len */ + error = md_get_uint32le(mdp, &size); /* name len */ + if (error) + goto nodata; fxsz = 64; /* size ofinfo up to filename */ if (ctx->f_infolevel == SMB_FIND_BOTH_DIRECTORY_INFO) { /* @@ -2545,7 +2530,9 @@ * a reserved byte, and ShortName(8.3 means 24 bytes, * as Leach defined it to always be Unicode) */ - md_get_mem(mdp, NULL, 30, MB_MSYSTEM); + error = md_get_mem(mdp, NULL, 30, MB_MSYSTEM); + if (error) + goto nodata; fxsz += 30; } recsz = next ? next : fxsz + size; @@ -2565,15 +2552,16 @@ error = md_get_mem(mdp, cp, nmlen, MB_MSYSTEM); if (error) - return (error); + goto nodata; if (next) { + /* How much data to skip? */ cnt = next - nmlen - fxsz; + if (cnt < 0) { + SMBVDEBUG("out of sync\n"); + goto nodata; + } if (cnt > 0) md_get_mem(mdp, NULL, cnt, MB_MSYSTEM); - else if (cnt < 0) { - SMBVDEBUG("out of sync\n"); - return (EBADRPC); - } } /* Don't count any trailing null in the name. */ if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) { @@ -2584,63 +2572,27 @@ nmlen--; } if (nmlen == 0) - return (EBADRPC); + goto nodata; /* - * Ref radar 3983209. On a find-next we expect a server will - * 1) if the continue bit is set, use the server's idea of current loc, - * 2) else if the resume key is non-zero, use that location, - * 3) else if the resume name is set, use that location, - * 4) else use the server's idea of current location. - * - * Current NetApps don't do that. If we send no continue bit, a zero - * resume key, and a resume name, the NetApp ignores the resume name - * and acts on the (zero) resume key, sending back the start of the - * directory again. Panther doesn't expose the netapp bug; Panther used - * the continue bit, but that was changed for 2866172. Win2000 as a - * client also relies upon the resume name, but they request a very - * large number of files, so the bug would be seen only with very - * large directories. - * - * Our fix is to notice if the second OTW op (the first find-next) - * returns, in the first filename, the same filename we got back - * at the start of the first OTW (the find-first). In that case - * we've detected the server bug and set SMBS_RESUMEKEYS, causing us - * to send non-zero resume keys henceforth. + * On a find-next we expect that the server will: + * 1) if the continue bit is set, use the server's offset, + * 2) else if the resume key is non-zero, use that offset, + * 3) else if the resume name is set, use that offset, + * 4) else use the server's idea of current offset. * - * Caveat: if there's a netapp so old it doesn't negotiate NTLM 0.12 - * then we get no resume keys so f_rkey stays zero and this "fix" - * changes nothing. - * - * Note due to a similar problem (4051871) we also set SMBS_RESUMEKEYS - * for FAT volumes, at mount time. + * We always set the resume key flag. If the server returns + * a resume key then we should always send it back to them. */ - if (otw && !(ctx->f_ssp->ss_flags & SMBS_RESUMEKEYS)) { - if (ctx->f_otws == 1) { - ctx->f_firstnmlen = nmlen; - ctx->f_firstnm = kmem_alloc(nmlen, KM_SLEEP); - bcopy(ctx->f_name, ctx->f_firstnm, nmlen); - } else if (ctx->f_otws == 2 && nmlen == ctx->f_firstnmlen && - !(bcmp(ctx->f_name, ctx->f_firstnm, nmlen) == 0)) { - struct smb_share *ssp = ctx->f_ssp; - SMBERROR( - "server resume_name bug seen; using resume keys\n"); - SMB_SS_LOCK(ssp); - ssp->ss_flags |= SMBS_RESUMEKEYS; - SMB_SS_UNLOCK(ssp); - ctx->f_ecnt = 0; - goto again; /* must redo last otw op! */ - } - } ctx->f_rkey = resumekey; next = ctx->f_eofs + recsz; if (ctx->f_rnameofs && - (ctx->f_flags & SMBFS_RDD_GOTRNAME) == 0 && - (ctx->f_rnameofs >= ctx->f_eofs && - ctx->f_rnameofs < (int)next)) { + ctx->f_rnameofs >= ctx->f_eofs && + ctx->f_rnameofs < (int)next) { /* - * Server needs a resume filename. + * This entry is the "resume name". + * Save it for the next request. */ if (ctx->f_rnamelen != nmlen) { if (ctx->f_rname) @@ -2649,7 +2601,6 @@ ctx->f_rnamelen = nmlen; } bcopy(ctx->f_name, ctx->f_rname, nmlen); - ctx->f_flags |= SMBFS_RDD_GOTRNAME; } ctx->f_nmlen = nmlen; ctx->f_eofs = next; @@ -2658,6 +2609,18 @@ smbfs_fname_tolocal(ctx); return (0); + +nodata: + /* + * Something bad has happened and we ran out of data + * before we could parse all f_ecnt entries expected. + * Force this directory listing closed, otherwise the + * calling process may hang in an infinite loop. + */ + SMBVDEBUG("ran out of data\n"); + ctx->f_ecnt = 0; /* Force closed. */ + ctx->f_flags |= SMBFS_RDD_EOF; + return (EIO); } static int @@ -2715,22 +2678,9 @@ /* * Note: "limit" (maxcount) needs to fit in a short! - * - * smb_lookup always uses 1, which is OK (no wildcards). - * Otherwise, this is smbfs_readdir, and we want to force - * limit to be in the range 3 to 1000. The low limit (3) - * is so we can always give the caller one "real" entry - * (something other than "." or "..") The high limit is - * just tuning. WinNT used 512, Win2k 1366. We use 1000. - * - * XXX: Move the [skip . ..] gunk to our caller (readdir). */ - if ((ctx->f_flags & SMBFS_RDD_FINDSINGLE) == 0) { - if (limit < 3) - limit = 3; - if (limit > 1000) - limit = 1000; - } + if (limit > 0xffff) + limit = 0xffff; ctx->f_scred = scrp; for (;;) {
--- a/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr.h Fri Aug 14 02:34:40 2009 -0700 +++ b/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr.h Fri Aug 14 12:16:18 2009 -0400 @@ -93,7 +93,6 @@ #define SMBFS_RDD_FINDSINGLE 0x04 /* note SMBFS_RDD_USESEARCH 0x08 replaced by smbfs_fctx_type */ #define SMBFS_RDD_NOCLOSE 0x10 -#define SMBFS_RDD_GOTRNAME 0x1000 /* * Search context supplied by server
--- a/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr2.c Fri Aug 14 02:34:40 2009 -0700 +++ b/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr2.c Fri Aug 14 12:16:18 2009 -0400 @@ -392,7 +392,6 @@ np->r_vnode = vp; np->n_mount = mi; np->r_hashq = rhtp; - np->n_direof = -1; np->n_fid = SMB_FID_UNUSED; np->n_uid = UID_NOBODY; np->n_gid = GID_NOBODY;
--- a/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_vnops.c Fri Aug 14 02:34:40 2009 -0700 +++ b/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_vnops.c Fri Aug 14 12:16:18 2009 -0400 @@ -66,6 +66,17 @@ #include <fs/fs_subr.h> /* + * We assign directory offsets like the NFS client, where the + * offset increments by _one_ after each directory entry. + * Further, the entries "." and ".." are always at offsets + * zero and one (respectively) and the "real" entries from + * the server appear at offsets starting with two. This + * macro is used to initialize the n_dirofs field after + * setting n_dirseq with a _findopen call. + */ +#define FIRST_DIROFS 2 + +/* * These characters are illegal in NTFS file names. * ref: http://support.microsoft.com/kb/147438 * @@ -285,9 +296,18 @@ } /* - * Directory open is easy. + * Directory open. See smbfs_readvdir() */ if (vp->v_type == VDIR) { + if (np->n_dirseq == NULL) { + /* first open */ + error = smbfs_smb_findopen(np, "*", 1, + SMB_FA_SYSTEM | SMB_FA_HIDDEN | SMB_FA_DIR, + &scred, &np->n_dirseq); + if (error != 0) + goto out; + } + np->n_dirofs = FIRST_DIROFS; np->n_dirrefs++; goto have_fid; } @@ -307,18 +327,16 @@ np->n_vcgenid == ssp->ss_vcgenid) { int upgrade = 0; - /* BEGIN CSTYLED */ if ((flag & FWRITE) && !(np->n_rights & (SA_RIGHT_FILE_WRITE_DATA | - GENERIC_RIGHT_ALL_ACCESS | - GENERIC_RIGHT_WRITE_ACCESS))) + GENERIC_RIGHT_ALL_ACCESS | + GENERIC_RIGHT_WRITE_ACCESS))) upgrade = 1; if ((flag & FREAD) && !(np->n_rights & (SA_RIGHT_FILE_READ_DATA | - GENERIC_RIGHT_ALL_ACCESS | - GENERIC_RIGHT_READ_ACCESS))) + GENERIC_RIGHT_ALL_ACCESS | + GENERIC_RIGHT_READ_ACCESS))) upgrade = 1; - /* END CSTYLED */ if (!upgrade) { /* * the existing open is good enough @@ -509,6 +527,7 @@ goto out; if ((fctx = np->n_dirseq) != NULL) { np->n_dirseq = NULL; + np->n_dirofs = 0; error = smbfs_smb_findclose(fctx, &scred); } } else { @@ -2573,49 +2592,69 @@ smbfs_readvdir(vnode_t *vp, uio_t *uio, cred_t *cr, int *eofp, caller_context_t *ct) { - size_t dbufsiz; - struct dirent64 *dp; + /* + * Note: "limit" tells the SMB-level FindFirst/FindNext + * functions how many directory entries to request in + * each OtW call. It needs to be large enough so that + * we don't make lots of tiny OtW requests, but there's + * no point making it larger than the maximum number of + * OtW entries that would fit in a maximum sized trans2 + * response (64k / 48). Beyond that, it's just tuning. + * WinNT used 512, Win2k used 1366. We use 1000. + */ + static const int limit = 1000; + /* Largest possible dirent size. */ + static const size_t dbufsiz = DIRENT64_RECLEN(SMB_MAXFNAMELEN); struct smb_cred scred; vnode_t *newvp; struct smbnode *np = VTOSMB(vp); - int nmlen, reclen, error = 0; - long offset, limit; struct smbfs_fctx *ctx; + struct dirent64 *dp; + ssize_t save_resid; + offset_t save_offset; /* 64 bits */ + int offset; /* yes, 32 bits */ + int nmlen, error; + ushort_t reclen; ASSERT(curproc->p_zone == VTOSMI(vp)->smi_zone); /* Make sure we serialize for n_dirseq use. */ ASSERT(smbfs_rw_lock_held(&np->r_lkserlock, RW_WRITER)); - /* Min size is DIRENT64_RECLEN(256) rounded up. */ - if (uio->uio_resid < 512 || uio->uio_offset < 0) + /* + * Make sure smbfs_open filled in n_dirseq + */ + if (np->n_dirseq == NULL) + return (EBADF); + + /* Check for overflow of (32-bit) directory offset. */ + if (uio->uio_loffset < 0 || uio->uio_loffset > INT32_MAX || + (uio->uio_loffset + uio->uio_resid) > INT32_MAX) return (EINVAL); + /* Require space for at least one dirent. */ + if (uio->uio_resid < dbufsiz) + return (EINVAL); + +#ifdef USE_DNLC /* * This dnlc_purge_vp ensures that name cache for this dir will be * current - it'll only have the items for which the smbfs_nget * MAKEENTRY happened. */ -#ifdef NOT_YET if (smbfs_fastlookup) dnlc_purge_vp(vp); #endif SMBVDEBUG("dirname='%s'\n", np->n_rpath); smb_credinit(&scred, cr); - dbufsiz = DIRENT64_RECLEN(SMB_MAXFNAMELEN); dp = kmem_alloc(dbufsiz, KM_SLEEP); - offset = uio->uio_offset; /* NB: "cookie" */ - limit = uio->uio_resid / DIRENT64_RECLEN(1); - SMBVDEBUG("offset=0x%ld, limit=0x%ld\n", offset, limit); - - if (offset == 0) { - /* Don't know EOF until findclose */ - np->n_direof = -1; - } else if (offset == np->n_direof) { - /* Arrived at end of directory. */ - goto out; - } + save_resid = uio->uio_resid; + save_offset = uio->uio_loffset; + offset = uio->uio_offset; + SMBVDEBUG("in: offset=%d, resid=%d\n", + (int)uio->uio_offset, (int)uio->uio_resid); + error = 0; /* * Generate the "." and ".." entries here so we can @@ -2623,20 +2662,24 @@ * (2) deal with getting their I numbers which the * findnext below does only for normal names. */ - while (limit && offset < 2) { - limit--; + while (offset < FIRST_DIROFS) { + /* + * Tricky bit filling in the first two: + * offset 0 is ".", offset 1 is ".." + * so strlen of these is offset+1. + */ reclen = DIRENT64_RECLEN(offset + 1); + if (uio->uio_resid < reclen) + goto out; bzero(dp, reclen); - /*LINTED*/ dp->d_reclen = reclen; - /* Tricky: offset 0 is ".", offset 1 is ".." */ dp->d_name[0] = '.'; dp->d_name[1] = '.'; dp->d_name[offset + 1] = '\0'; /* * Want the real I-numbers for the "." and ".." * entries. For these two names, we know that - * smbfslookup can do this all locally. + * smbfslookup can get the nodes efficiently. */ error = smbfslookup(vp, dp->d_name, &newvp, cr, 1, ct); if (error) { @@ -2645,21 +2688,29 @@ dp->d_ino = VTOSMB(newvp)->n_ino; VN_RELE(newvp); } - dp->d_off = offset + 1; /* see d_off below */ - error = uiomove(dp, dp->d_reclen, UIO_READ, uio); + /* + * Note: d_off is the offset that a user-level program + * should seek to for reading the NEXT directory entry. + * See libc: readdir, telldir, seekdir + */ + dp->d_off = offset + 1; + error = uiomove(dp, reclen, UIO_READ, uio); if (error) goto out; + /* + * Note: uiomove updates uio->uio_offset, + * but we want it to be our "cookie" value, + * which just counts dirents ignoring size. + */ uio->uio_offset = ++offset; } - if (limit == 0) - goto out; - if (offset != np->n_dirofs || np->n_dirseq == NULL) { - SMBVDEBUG("Reopening search %ld:%ld\n", offset, np->n_dirofs); - if (np->n_dirseq) { - (void) smbfs_smb_findclose(np->n_dirseq, &scred); - np->n_dirseq = NULL; - } - np->n_dirofs = 2; + + /* + * If there was a backward seek, we have to reopen. + */ + if (offset < np->n_dirofs) { + SMBVDEBUG("Reopening search %d:%d\n", + offset, np->n_dirofs); error = smbfs_smb_findopen(np, "*", 1, SMB_FA_SYSTEM | SMB_FA_HIDDEN | SMB_FA_DIR, &scred, &ctx); @@ -2667,72 +2718,97 @@ SMBVDEBUG("can not open search, error = %d", error); goto out; } + /* free the old one */ + (void) smbfs_smb_findclose(np->n_dirseq, &scred); + /* save the new one */ np->n_dirseq = ctx; - } else + np->n_dirofs = FIRST_DIROFS; + } else { ctx = np->n_dirseq; - while (np->n_dirofs < offset) { - if (smbfs_smb_findnext(ctx, offset - np->n_dirofs++, - &scred) != 0) { - (void) smbfs_smb_findclose(np->n_dirseq, &scred); - np->n_dirseq = NULL; - np->n_direof = np->n_dirofs; - np->n_dirofs = 0; - *eofp = 1; - error = 0; - goto out; - } } - error = 0; - for (; limit; limit--) { + + /* + * Skip entries before the requested offset. + */ + while (np->n_dirofs < offset) { error = smbfs_smb_findnext(ctx, limit, &scred); - if (error) { - if (error == EBADRPC) - error = ENOENT; - (void) smbfs_smb_findclose(np->n_dirseq, &scred); - np->n_dirseq = NULL; - np->n_direof = np->n_dirofs; - np->n_dirofs = 0; - *eofp = 1; - error = 0; - break; - } + if (error != 0) + goto out; np->n_dirofs++; + } + + /* + * While there's room in the caller's buffer: + * get a directory entry from SMB, + * convert to a dirent, copyout. + * We stop when there is no longer room for a + * maximum sized dirent because we must decide + * before we know anything about the next entry. + */ + while (uio->uio_resid >= dbufsiz) { + error = smbfs_smb_findnext(ctx, limit, &scred); + if (error != 0) + goto out; + np->n_dirofs++; + /* Sanity check the name length. */ nmlen = ctx->f_nmlen; if (nmlen > SMB_MAXFNAMELEN) { nmlen = SMB_MAXFNAMELEN; SMBVDEBUG("Truncating name: %s\n", ctx->f_name); } - reclen = DIRENT64_RECLEN(nmlen); - if (uio->uio_resid < reclen) - break; - bzero(dp, reclen); - /*LINTED*/ - dp->d_reclen = reclen; - dp->d_ino = ctx->f_attr.fa_ino; - /* - * Note: d_off is the offset that a user-level program - * should seek to for reading the _next_ directory entry. - * See libc: readdir, telldir, seekdir - */ - dp->d_off = offset + 1; - bcopy(ctx->f_name, dp->d_name, nmlen); - dp->d_name[nmlen] = '\0'; #ifdef NOT_YET if (smbfs_fastlookup) { - if (smbfs_nget(vp, ctx->f_name, - ctx->f_nmlen, &ctx->f_attr, &newvp) == 0) + if (smbfs_nget(vp, ctx->f_name, nmlen, + &ctx->f_attr, &newvp) == 0) VN_RELE(newvp); } #endif /* NOT_YET */ - error = uiomove(dp, dp->d_reclen, UIO_READ, uio); + + reclen = DIRENT64_RECLEN(nmlen); + bzero(dp, reclen); + dp->d_reclen = reclen; + bcopy(ctx->f_name, dp->d_name, nmlen); + dp->d_name[nmlen] = '\0'; + dp->d_ino = ctx->f_attr.fa_ino; + dp->d_off = offset + 1; /* See d_off comment above */ + error = uiomove(dp, reclen, UIO_READ, uio); if (error) - break; + goto out; + /* See comment re. uio_offset above. */ uio->uio_offset = ++offset; } - if (error == ENOENT) + +out: + /* + * When we come to the end of a directory, the + * SMB-level functions return ENOENT, but the + * caller is not expecting an error return. + * + * Also note that we must delay the call to + * smbfs_smb_findclose(np->n_dirseq, ...) + * until smbfs_close so that all reads at the + * end of the directory will return no data. + */ + if (error == ENOENT) { error = 0; -out: + if (eofp) + *eofp = 1; + } + /* + * If we encountered an error (i.e. "access denied") + * from the FindFirst call, we will have copied out + * the "." and ".." entries leaving offset == 2. + * In that case, restore the original offset/resid + * so the caller gets no data with the error. + */ + if (error != 0 && offset == FIRST_DIROFS) { + uio->uio_loffset = save_offset; + uio->uio_resid = save_resid; + } + SMBVDEBUG("out: offset=%d, resid=%d\n", + (int)uio->uio_offset, (int)uio->uio_resid); + kmem_free(dp, dbufsiz); smb_credrele(&scred); return (error);