From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/lib/nt/Makefile.kup | 0 src/lib/nt/fts-nt.c | 1421 ++++++++ src/lib/nt/fts-nt.h | 188 + src/lib/nt/kFsCache.c | 4840 +++++++++++++++++++++++++ src/lib/nt/kFsCache.h | 594 +++ src/lib/nt/nt_child_inject_standard_handles.c | 462 +++ src/lib/nt/nt_child_inject_standard_handles.h | 32 + src/lib/nt/ntdir.c | 673 ++++ src/lib/nt/ntdir.h | 154 + src/lib/nt/nthlp.h | 119 + src/lib/nt/nthlpcore.c | 481 +++ src/lib/nt/nthlpfs.c | 636 ++++ src/lib/nt/ntopenat.c | 161 + src/lib/nt/ntopenat.h | 43 + src/lib/nt/ntstat.c | 1065 ++++++ src/lib/nt/ntstat.h | 144 + src/lib/nt/ntstuff.h | 573 +++ src/lib/nt/nttypes.h | 55 + src/lib/nt/ntunlink.c | 240 ++ src/lib/nt/ntunlink.h | 54 + src/lib/nt/ntutimes.c | 99 + src/lib/nt/ntutimes.h | 45 + src/lib/nt/tstNtFts.c | 257 ++ src/lib/nt/tstNtStat.c | 157 + src/lib/nt/tstkFsCache.c | 313 ++ 25 files changed, 12806 insertions(+) create mode 100644 src/lib/nt/Makefile.kup create mode 100644 src/lib/nt/fts-nt.c create mode 100644 src/lib/nt/fts-nt.h create mode 100644 src/lib/nt/kFsCache.c create mode 100644 src/lib/nt/kFsCache.h create mode 100644 src/lib/nt/nt_child_inject_standard_handles.c create mode 100644 src/lib/nt/nt_child_inject_standard_handles.h create mode 100644 src/lib/nt/ntdir.c create mode 100644 src/lib/nt/ntdir.h create mode 100644 src/lib/nt/nthlp.h create mode 100644 src/lib/nt/nthlpcore.c create mode 100644 src/lib/nt/nthlpfs.c create mode 100644 src/lib/nt/ntopenat.c create mode 100644 src/lib/nt/ntopenat.h create mode 100644 src/lib/nt/ntstat.c create mode 100644 src/lib/nt/ntstat.h create mode 100644 src/lib/nt/ntstuff.h create mode 100644 src/lib/nt/nttypes.h create mode 100644 src/lib/nt/ntunlink.c create mode 100644 src/lib/nt/ntunlink.h create mode 100644 src/lib/nt/ntutimes.c create mode 100644 src/lib/nt/ntutimes.h create mode 100644 src/lib/nt/tstNtFts.c create mode 100644 src/lib/nt/tstNtStat.c create mode 100644 src/lib/nt/tstkFsCache.c (limited to 'src/lib/nt') diff --git a/src/lib/nt/Makefile.kup b/src/lib/nt/Makefile.kup new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/nt/fts-nt.c b/src/lib/nt/fts-nt.c new file mode 100644 index 0000000..5f58abb --- /dev/null +++ b/src/lib/nt/fts-nt.c @@ -0,0 +1,1421 @@ +/* $Id: fts-nt.c 3535 2021-12-20 23:32:28Z bird $ */ +/** @file + * Source for the NT port of BSD fts.c. + * + * @copyright 1990, 1993, 1994 The Regents of the University of California. All rights reserved. + * @copyright NT modifications Copyright (C) 2016 knut st. osmundsen + * @licenses BSD3 + * + * + * Some hints about how the code works. + * + * The input directories & files are entered into a pseudo root directory and + * processed one after another, depth first. + * + * Directories are completely read into memory first and arranged as linked + * list anchored on FTS::fts_cur. fts_read does a pop-like operation on that + * list, freeing the nodes after they've been completely processed. + * Subdirectories are returned twice by fts_read, the first time when it + * decends into it (FTS_D), and the second time as it ascends from it (FTS_DP). + * + * In parallel to fts_read, there's the fts_children API that fetches the + * directory content in a similar manner, but for the consumption of the API + * caller rather than FTS itself. The result hangs on FTS::fts_child so it can + * be freed when the directory changes or used by fts_read when it is called + * upon to enumerate the directory. + * + * + * The NT port of the code does away with the directory changing in favor of + * using directory relative opens (present in NT since for ever, just not + * exposed thru Win32). A new FTSENT member fts_dirfd has been added to make + * this possible for API users too. + * + * Note! When using Win32 APIs with path input relative to the current + * directory, the internal DOS <-> NT path converter will expand it to a + * full path and subject it to the 260 char limit. + * + * The richer NT directory enumeration API allows us to do away with all the + * stat() calls, and not have to do link counting and other interesting things + * to try speed things up. (You typical stat() implementation on windows is + * actually a directory enum call with the name of the file as filter.) + */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $OpenBSD: fts.c,v 1.22 1999/10/03 19:22:22 millert Exp $ + */ + +#if 0 +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; +#endif /* LIBC_SCCS and not lint */ +#endif + +#include +#include "fts-nt.h" +#include +#include +#include +#include "nthlp.h" +#include "ntdir.h" +#include "ntopenat.h" /* for AT_FDCWD */ +#include //debug + +static FTSENT *fts_alloc(FTS *sp, char const *name, size_t namelen, wchar_t const *wcsname, size_t cwcname); +static FTSENT *fts_alloc_ansi(FTS *sp, char const *name, size_t namelen); +static FTSENT *fts_alloc_utf16(FTS *sp, wchar_t const *wcsname, size_t cwcname); +static void nt_fts_free_alloc_cache(FTS *sp); +static FTSENT *fts_build(FTS *, int); +static void fts_lfree(FTSENT *); +static void fts_load(FTS *, FTSENT *); +static size_t fts_maxarglen(char * const *); +static size_t fts_maxarglenw(wchar_t * const *); +static void fts_padjust(FTS *, FTSENT *); +static void fts_padjustw(FTS *, FTSENT *); +static int fts_palloc(FTS *, size_t, size_t); +static FTSENT *fts_sort(FTS *, FTSENT *, size_t); +static int fts_stat(FTS *, FTSENT *, int, HANDLE); +static int fts_process_stats(FTSENT *, BirdStat_T const *); + +#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) + +#define CLR(opt) (sp->fts_options &= ~(opt)) +#define ISSET(opt) (sp->fts_options & (opt)) +#define SET(opt) (sp->fts_options |= (opt)) + +/* fts_build flags */ +#define BCHILD 1 /* fts_children */ +#define BNAMES 2 /* fts_children, names only */ +#define BREAD 3 /* fts_read */ + +/* NT needs these: */ +#define MAXPATHLEN 260 +#define MAX(a, b) ( (a) >= (b) ? (a) : (b) ) + +/** Enables BirdDir_T reuse. (Saves malloc and free calls.) */ +#define FTS_WITH_DIRHANDLE_REUSE +/** Enables allocation statistics. */ +//#define FTS_WITH_STATISTICS +/** Enables FTSENT allocation cache. */ +#define FTS_WITH_ALLOC_CACHE +/** Number of size buckets for the FTSENT allocation cache. */ +#define FTS_NUM_FREE_BUCKETS 64 +/** Shift for converting size to free bucket index. */ +#define FTS_FREE_BUCKET_SHIFT 4 +/** The FTSENT allocation alignment. */ +#define FTS_ALIGN_FTSENT (1U << FTS_FREE_BUCKET_SHIFT) + +/* + * Internal representation of an FTS, including extra implementation + * details. The FTS returned from fts_open points to this structure's + * ftsp_fts member (and can be cast to an _fts_private as required) + */ +struct _fts_private { + FTS ftsp_fts; +#ifdef FTS_WITH_DIRHANDLE_REUSE + /** Statically allocate directory handle. */ + BirdDir_T dirhandle; +#endif +#ifdef FTS_WITH_ALLOC_CACHE + /** Number of free entries in the above buckets. */ + size_t numfree; +# ifdef FTS_WITH_STATISTICS + size_t allocs; + size_t hits; + size_t misses; +# endif + /** Free FTSENT buckets (by size). + * This is to avoid hitting the heap, which is a little sluggish on windows. */ + struct + { + FTSENT *head; + } freebuckets[FTS_NUM_FREE_BUCKETS]; +#endif +}; + + +static FTS * FTSCALL +nt_fts_open_common(char * const *argv, wchar_t * const *wcsargv, int options, + int (*compar)(const FTSENT * const *, const FTSENT * const *)) +{ + struct _fts_private *priv; + FTS *sp; + FTSENT *p, *root; + FTSENT *parent, *tmp; + size_t len, nitems; + + birdResolveImports(); + + /* Options check. */ + if (options & ~FTS_OPTIONMASK) { + errno = EINVAL; + return (NULL); + } + + /* fts_open() requires at least one path */ + if (wcsargv ? *wcsargv == NULL : *argv == NULL) { + errno = EINVAL; + return (NULL); + } + + /* Allocate/initialize the stream. */ + if ((priv = calloc(1, sizeof(*priv))) == NULL) + return (NULL); + sp = &priv->ftsp_fts; + sp->fts_compar = compar; + sp->fts_options = options; + SET(FTS_NOCHDIR); /* NT: FTS_NOCHDIR is always on (for external consumes) */ + sp->fts_cwd_fd = AT_FDCWD; + + /* Shush, GCC. */ + tmp = NULL; + + /* + * Start out with 1K of path space, and enough, in any case, + * to hold the user's paths. + */ + if (fts_palloc(sp, MAX(argv ? fts_maxarglen(argv) : 1, MAXPATHLEN), + MAX(wcsargv ? fts_maxarglenw(wcsargv) : 1, MAXPATHLEN)) ) + goto mem1; + + /* Allocate/initialize root's parent. */ + if ((parent = fts_alloc(sp, NULL, 0, NULL, 0)) == NULL) + goto mem2; + parent->fts_level = FTS_ROOTPARENTLEVEL; + + /* Allocate/initialize root(s). */ + for (root = NULL, nitems = 0; wcsargv ? *wcsargv != NULL : *argv != NULL; ++nitems) { + /* NT: We need to do some small input transformations to make this and + the API user code happy. 1. Lone drive letters get a dot + appended so it won't matter if a slash is appended afterwards. + 2. DOS slashes are converted to UNIX ones. */ + wchar_t *wcslash; + + if (wcsargv) { + len = wcslen(*wcsargv); + if (len == 2 && wcsargv[0][1] == ':') { + wchar_t wcsdrive[4]; + wcsdrive[0] = wcsargv[0][0]; + wcsdrive[1] = ':'; + wcsdrive[2] = '.'; + wcsdrive[3] = '\0'; + p = fts_alloc_utf16(sp, wcsdrive, 3); + } else { + p = fts_alloc_utf16(sp, *wcsargv, len); + } + wcsargv++; + } else { + len = strlen(*argv); + if (len == 2 && argv[0][1] == ':') { + char szdrive[4]; + szdrive[0] = argv[0][0]; + szdrive[1] = ':'; + szdrive[2] = '.'; + szdrive[3] = '\0'; + p = fts_alloc_ansi(sp, szdrive, 3); + } else { + p = fts_alloc_ansi(sp, *argv, len); + } + argv++; + } + if (p != NULL) { /* likely */ } else { goto mem3; } + + wcslash = wcschr(p->fts_wcsname, '\\'); + while (wcslash != NULL) { + *wcslash++ = '/'; + wcslash = wcschr(p->fts_wcsname, '\\'); + } + + if (p->fts_name) { + char *slash = strchr(p->fts_name, '\\'); + while (slash != NULL) { + *slash++ = '/'; + slash = strchr(p->fts_name, '\\'); + } + } + + p->fts_level = FTS_ROOTLEVEL; + p->fts_parent = parent; + p->fts_accpath = p->fts_name; + p->fts_wcsaccpath = p->fts_wcsname; + p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW), INVALID_HANDLE_VALUE); + + /* Command-line "." and ".." are real directories. */ + if (p->fts_info == FTS_DOT) + p->fts_info = FTS_D; + + /* + * If comparison routine supplied, traverse in sorted + * order; otherwise traverse in the order specified. + */ + if (compar) { + p->fts_link = root; + root = p; + } else { + p->fts_link = NULL; + if (root == NULL) + tmp = root = p; + else { + tmp->fts_link = p; + tmp = p; + } + } + } + if (compar && nitems > 1) + root = fts_sort(sp, root, nitems); + + /* + * Allocate a dummy pointer and make fts_read think that we've just + * finished the node before the root(s); set p->fts_info to FTS_INIT + * so that everything about the "current" node is ignored. + */ + if ((sp->fts_cur = fts_alloc(sp, NULL, 0, NULL, 0)) == NULL) + goto mem3; + sp->fts_cur->fts_link = root; + sp->fts_cur->fts_info = FTS_INIT; + + return (sp); + +mem3: + fts_lfree(root); + free(parent); +mem2: + free(sp->fts_path); + free(sp->fts_wcspath); +mem1: + free(sp); + return (NULL); +} + + +FTS * FTSCALL +nt_fts_open(char * const *argv, int options, + int (*compar)(const FTSENT * const *, const FTSENT * const *)) +{ + return nt_fts_open_common(argv, NULL, options, compar); +} + + +FTS * FTSCALL +nt_fts_openw(wchar_t * const *argv, int options, + int (*compar)(const FTSENT * const *, const FTSENT * const *)) +{ + return nt_fts_open_common(NULL, argv, options, compar); +} + + +/** + * Called by fts_read for FTS_ROOTLEVEL entries only. + */ +static void +fts_load(FTS *sp, FTSENT *p) +{ + size_t len; + wchar_t *pwc; + + /* + * Load the stream structure for the next traversal. Since we don't + * actually enter the directory until after the preorder visit, set + * the fts_accpath field specially so the chdir gets done to the right + * place and the user can access the first node. From fts_open it's + * known that the path will fit. + */ + if (!(sp->fts_options & FTS_NO_ANSI)) { + char *cp; + len = p->fts_pathlen = p->fts_namelen; + memmove(sp->fts_path, p->fts_name, len + 1); + cp = strrchr(p->fts_name, '/'); + if (cp != NULL && (cp != p->fts_name || cp[1])) { + len = strlen(++cp); + memmove(p->fts_name, cp, len + 1); + p->fts_namelen = len; + } + p->fts_accpath = p->fts_path = sp->fts_path; + } + + len = p->fts_cwcpath = p->fts_cwcname; + memmove(sp->fts_wcspath, p->fts_wcsname, (len + 1) * sizeof(wchar_t)); + pwc = wcsrchr(p->fts_wcsname, '/'); + if (pwc != NULL && (pwc != p->fts_wcsname || pwc[1])) { + len = wcslen(++pwc); + memmove(p->fts_wcsname, pwc, (len + 1) * sizeof(wchar_t)); + p->fts_cwcname = len; + } + p->fts_wcsaccpath = p->fts_wcspath = sp->fts_wcspath; + + sp->fts_dev = p->fts_dev; +} + + +int FTSCALL +nt_fts_close(FTS *sp) +{ + FTSENT *freep, *p; + /*int saved_errno;*/ + + /* + * This still works if we haven't read anything -- the dummy structure + * points to the root list, so we step through to the end of the root + * list which has a valid parent pointer. + */ + if (sp->fts_cur) { + for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { + freep = p; + p = p->fts_link != NULL ? p->fts_link : p->fts_parent; + free(freep); + } + free(p); + } + + /* Free up child linked list, sort array, path buffer. */ + if (sp->fts_child) + fts_lfree(sp->fts_child); + if (sp->fts_array) + free(sp->fts_array); + free(sp->fts_path); + free(sp->fts_wcspath); +#ifdef FTS_WITH_ALLOC_CACHE +# ifdef FTS_WITH_STATISTICS + { + struct _fts_private *priv = (struct _fts_private *)sp; + fprintf(stderr, "numfree=%u allocs=%u hits=%u (%uppt) misses=%u (%uppt) other=%u\n", + priv->numfree, priv->allocs, + priv->hits, (unsigned)((double)priv->hits * 1000.0 / priv->allocs), + priv->misses, (unsigned)((double)priv->misses * 1000.0 / priv->allocs), + priv->allocs - priv->misses - priv->hits); + } +# endif +#endif + nt_fts_free_alloc_cache(sp); +#ifdef FTS_WITH_DIRHANDLE_REUSE + birdDirClose(&((struct _fts_private *)sp)->dirhandle); +#endif + + /* Free up the stream pointer. */ + free(sp); + return (0); +} + + +/** + * Frees a FTSENT structure by way of the allocation cache. + */ +static void +fts_free_entry(FTS *sp, FTSENT *tmp) +{ + if (tmp != NULL) { + struct _fts_private *priv = (struct _fts_private *)sp; +#ifdef FTS_WITH_ALLOC_CACHE + size_t idx; +#endif + + if (tmp->fts_dirfd == INVALID_HANDLE_VALUE) { + /* There are probably more files than directories out there. */ + } else { + birdCloseFile(tmp->fts_dirfd); + tmp->fts_dirfd = INVALID_HANDLE_VALUE; + } + +#ifdef FTS_WITH_ALLOC_CACHE + idx = (tmp->fts_alloc_size - sizeof(FTSENT)) >> FTS_FREE_BUCKET_SHIFT; + if (idx < FTS_NUM_FREE_BUCKETS) { + tmp->fts_link = priv->freebuckets[idx].head; + priv->freebuckets[idx].head = tmp; + } else { + tmp->fts_link = priv->freebuckets[FTS_NUM_FREE_BUCKETS - 1].head; + priv->freebuckets[FTS_NUM_FREE_BUCKETS - 1].head = tmp; + } + + priv->numfree++; +#else + free(tmp); +#endif + } +} + + +/* + * Special case of "/" at the end of the path so that slashes aren't + * appended which would cause paths to be written as "....//foo". + */ +#define NAPPEND(p) ( p->fts_pathlen - (p->fts_path[p->fts_pathlen - 1] == '/') ) +#define NAPPENDW(p) ( p->fts_cwcpath - (p->fts_wcspath[p->fts_cwcpath - 1] == L'/') ) + +FTSENT * FTSCALL +nt_fts_read(FTS *sp) +{ + FTSENT *p, *tmp; + int instr; + wchar_t *pwc; + + /* Set current node pointer. */ + p = sp->fts_cur; + + /* If finished or unrecoverable error, return NULL. */ + if (p != NULL && !ISSET(FTS_STOP)) { + /* likely */ + } else { + return (NULL); + } + + /* Save and zero out user instructions. */ + instr = p->fts_instr; + p->fts_instr = FTS_NOINSTR; + + /* Any type of file may be re-visited; re-stat and re-turn. */ + if (instr != FTS_AGAIN) { + /* likely */ + } else { + p->fts_info = fts_stat(sp, p, 0, INVALID_HANDLE_VALUE); + return (p); + } + + /* + * Following a symlink -- SLNONE test allows application to see + * SLNONE and recover. If indirecting through a symlink, have + * keep a pointer to current location. If unable to get that + * pointer, follow fails. + * + * NT: Since we don't change directory, we just set FTS_SYMFOLLOW + * here in case a API client checks it. + */ + if ( instr != FTS_FOLLOW + || (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE)) { + /* likely */ + } else { + p->fts_info = fts_stat(sp, p, 1, INVALID_HANDLE_VALUE); + if (p->fts_info == FTS_D /*&& !ISSET(FTS_NOCHDIR)*/) { + p->fts_flags |= FTS_SYMFOLLOW; + } + return (p); + } + + /* Directory in pre-order. */ + if (p->fts_info == FTS_D) { + /* If skipped or crossed mount point, do post-order visit. */ + if ( instr == FTS_SKIP + || (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) { + if (sp->fts_child) { + fts_lfree(sp->fts_child); + sp->fts_child = NULL; + } + p->fts_info = FTS_DP; + return (p); + } + + /* Rebuild if only read the names and now traversing. */ + if (sp->fts_child != NULL && ISSET(FTS_NAMEONLY)) { + CLR(FTS_NAMEONLY); + fts_lfree(sp->fts_child); + sp->fts_child = NULL; + } + + /* + * Cd to the subdirectory. + * + * If have already read and now fail to chdir, whack the list + * to make the names come out right, and set the parent errno + * so the application will eventually get an error condition. + * Set the FTS_DONTCHDIR flag so that when we logically change + * directories back to the parent we don't do a chdir. + * + * If haven't read do so. If the read fails, fts_build sets + * FTS_STOP or the fts_info field of the node. + */ + if (sp->fts_child == NULL) { + p = fts_build(sp, BREAD); + if (p != NULL) { + /* likely */ + } else { + if (ISSET(FTS_STOP)) + return (NULL); + return sp->fts_cur; + } + + } else { + p = sp->fts_child; + sp->fts_child = NULL; + } + goto name; + } + + /* Move to the next node on this level. */ +next: tmp = p; + if ((p = p->fts_link) != NULL) { + /* + * If reached the top, return to the original directory (or + * the root of the tree), and load the paths for the next root. + */ + if (p->fts_level != FTS_ROOTLEVEL) { + /* likely */ + } else { + fts_free_entry(sp, tmp); + fts_load(sp, p); + return (sp->fts_cur = p); + } + + /* + * User may have called fts_set on the node. If skipped, + * ignore. If followed, get a file descriptor so we can + * get back if necessary. + */ + if (p->fts_instr != FTS_SKIP) { + /* likely */ + } else { + fts_free_entry(sp, tmp); + goto next; + } + if (p->fts_instr != FTS_FOLLOW) { + /* likely */ + } else { + p->fts_info = fts_stat(sp, p, 1, INVALID_HANDLE_VALUE); + /* NT: See above regarding fts_flags. */ + if (p->fts_info == FTS_D) { + p->fts_flags |= FTS_SYMFOLLOW; + } + p->fts_instr = FTS_NOINSTR; + } + + fts_free_entry(sp, tmp); + +name: + if (!(sp->fts_options & FTS_NO_ANSI)) { + char *t = sp->fts_path + NAPPEND(p->fts_parent); + *t++ = '/'; + memmove(t, p->fts_name, p->fts_namelen + 1); + } + pwc = sp->fts_wcspath + NAPPENDW(p->fts_parent); + *pwc++ = '/'; + memmove(pwc, p->fts_wcsname, (p->fts_cwcname + 1) * sizeof(wchar_t)); + return (sp->fts_cur = p); + } + + /* Move up to the parent node. */ + p = tmp->fts_parent; + + if (p->fts_level != FTS_ROOTPARENTLEVEL) { + /* likely */ + } else { + /* + * Done; free everything up and set errno to 0 so the user + * can distinguish between error and EOF. + */ + fts_free_entry(sp, tmp); + fts_free_entry(sp, p); + errno = 0; + return (sp->fts_cur = NULL); + } + + /* NUL terminate the pathname. */ + if (!(sp->fts_options & FTS_NO_ANSI)) + sp->fts_path[p->fts_pathlen] = '\0'; + sp->fts_wcspath[ p->fts_cwcpath] = '\0'; + + /* + * Return to the parent directory. If at a root node or came through + * a symlink, go back through the file descriptor. Otherwise, cd up + * one directory. + * + * NT: We're doing no fchdir, but we need to close the directory handle. + */ + if (p->fts_dirfd != INVALID_HANDLE_VALUE) { + birdCloseFile(p->fts_dirfd); + p->fts_dirfd = INVALID_HANDLE_VALUE; + } + fts_free_entry(sp, tmp); + p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; + return (sp->fts_cur = p); +} + +/* + * Fts_set takes the stream as an argument although it's not used in this + * implementation; it would be necessary if anyone wanted to add global + * semantics to fts using fts_set. An error return is allowed for similar + * reasons. + */ +/* ARGSUSED */ +int FTSCALL +nt_fts_set(FTS *sp, FTSENT *p, int instr) +{ + if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW && + instr != FTS_NOINSTR && instr != FTS_SKIP) { + errno = EINVAL; + return (1); + } + p->fts_instr = instr; + return (0); +} + +FTSENT * FTSCALL +nt_fts_children(FTS *sp, int instr) +{ + FTSENT *p; + + if (instr != 0 && instr != FTS_NAMEONLY) { + errno = EINVAL; + return (NULL); + } + + /* Set current node pointer. */ + p = sp->fts_cur; + + /* + * Errno set to 0 so user can distinguish empty directory from + * an error. + */ + errno = 0; + + /* Fatal errors stop here. */ + if (ISSET(FTS_STOP)) + return (NULL); + + /* Return logical hierarchy of user's arguments. */ + if (p->fts_info == FTS_INIT) + return (p->fts_link); + + /* + * If not a directory being visited in pre-order, stop here. Could + * allow FTS_DNR, assuming the user has fixed the problem, but the + * same effect is available with FTS_AGAIN. + */ + if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */) + return (NULL); + + /* Free up any previous child list. */ + if (sp->fts_child != NULL) { + fts_lfree(sp->fts_child); + sp->fts_child = NULL; /* (bird - double free for _open(".") failure in original) */ + } + + /* NT: Some BSD utility sets FTS_NAMEONLY? We don't really need this + optimization, but since it only hurts that utility, it can stay. */ + if (instr == FTS_NAMEONLY) { + assert(0); /* don't specify FTS_NAMEONLY on NT. */ + SET(FTS_NAMEONLY); + instr = BNAMES; + } else + instr = BCHILD; + + return (sp->fts_child = fts_build(sp, instr)); +} + +#ifndef fts_get_clientptr +#error "fts_get_clientptr not defined" +#endif + +void * +(FTSCALL fts_get_clientptr)(FTS *sp) +{ + + return (fts_get_clientptr(sp)); +} + +#ifndef fts_get_stream +#error "fts_get_stream not defined" +#endif + +FTS * +(FTSCALL fts_get_stream)(FTSENT *p) +{ + return (fts_get_stream(p)); +} + +void FTSCALL +nt_fts_set_clientptr(FTS *sp, void *clientptr) +{ + + sp->fts_clientptr = clientptr; +} + +/* + * This is the tricky part -- do not casually change *anything* in here. The + * idea is to build the linked list of entries that are used by fts_children + * and fts_read. There are lots of special cases. + * + * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is + * set and it's a physical walk (so that symbolic links can't be directories), + * we can do things quickly. First, if it's a 4.4BSD file system, the type + * of the file is in the directory entry. Otherwise, we assume that the number + * of subdirectories in a node is equal to the number of links to the parent. + * The former skips all stat calls. The latter skips stat calls in any leaf + * directories and for any files after the subdirectories in the directory have + * been found, cutting the stat calls by about 2/3. + * + * NT: We do not do any link counting or stat avoiding, which invalidates the + * above warnings. This function is very simple for us. + */ +static FTSENT * +fts_build(FTS *sp, int type) +{ + BirdDirEntryW_T *dp; + FTSENT *p, *cur; + FTSENT * volatile head,* volatile *tailp; /* volatile is to prevent aliasing trouble */ + DIR *dirp; + int saved_errno, doadjust, doadjust_utf16; + long level; + size_t len, cwcdir, maxlen, cwcmax, nitems; + unsigned fDirOpenFlags; + + /* Set current node pointer. */ + cur = sp->fts_cur; + + /* + * Open the directory for reading. If this fails, we're done. + * If being called from fts_read, set the fts_info field. + * + * NT: We do a two stage open so we can keep the directory handle around + * after we've enumerated the directory. The dir handle is used by + * us here and by the API users to more efficiently and safely open + * members of the directory. + */ + fDirOpenFlags = BIRDDIR_F_EXTRA_INFO | BIRDDIR_F_KEEP_HANDLE; + if (cur->fts_dirfd == INVALID_HANDLE_VALUE) { + if (cur->fts_parent->fts_dirfd != INVALID_HANDLE_VALUE) { + /* (This works fine for symlinks too, since we follow them.) */ + cur->fts_dirfd = birdOpenFileExW(cur->fts_parent->fts_dirfd, + cur->fts_wcsname, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + } else { + cur->fts_dirfd = birdOpenFileW(cur->fts_wcsaccpath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + } + if (cur->fts_dirfd != INVALID_HANDLE_VALUE) { /* likely */ } + else goto l_open_err; + + } else { + fDirOpenFlags |= BIRDDIR_F_RESTART_SCAN; + } +#ifdef FTS_WITH_DIRHANDLE_REUSE + dirp = birdDirOpenFromHandleWithReuse(&((struct _fts_private *)sp)->dirhandle, cur->fts_dirfd, NULL, + fDirOpenFlags | BIRDDIR_F_STATIC_ALLOC); +#else + dirp = birdDirOpenFromHandle(cur->fts_dirfd, NULL, fDirOpenFlags); +#endif + if (dirp == NULL) { +l_open_err: + if (type == BREAD) { + cur->fts_info = FTS_DNR; + cur->fts_errno = errno; + } + return (NULL); + } + + /* + * Figure out the max file name length that can be stored in the + * current path -- the inner loop allocates more path as necessary. + * We really wouldn't have to do the maxlen calculations here, we + * could do them in fts_read before returning the path, but it's a + * lot easier here since the length is part of the dirent structure. + */ + if (sp->fts_options & FTS_NO_ANSI) { + len = 0; + maxlen = 0x10000; + } else { + len = NAPPEND(cur); + len++; + maxlen = sp->fts_pathlen - len; + } + + cwcdir = NAPPENDW(cur); + cwcdir++; + cwcmax = sp->fts_cwcpath - len; + + level = cur->fts_level + 1; + + /* Read the directory, attaching each entry to the `link' pointer. */ + doadjust = doadjust_utf16 = 0; + nitems = 0; + head = NULL; + tailp = &head; + while ((dp = birdDirReadW(dirp)) != NULL) { + if (ISSET(FTS_SEEDOT) || !ISDOT(dp->d_name)) { + /* assume dirs have two or more entries */ + } else { + continue; + } + + if ((p = fts_alloc_utf16(sp, dp->d_name, dp->d_namlen)) != NULL) { + /* likely */ + } else { + goto mem1; + } + + /* include space for NUL */ + if (p->fts_namelen < maxlen && p->fts_cwcname < cwcmax) { + /* likely */ + } else { + void *oldaddr = sp->fts_path; + wchar_t *oldwcspath = sp->fts_wcspath; + if (fts_palloc(sp, + p->fts_namelen >= maxlen ? len + p->fts_namelen + 1 : 0, + p->fts_cwcname >= cwcmax ? cwcdir + p->fts_cwcname + 1 : 0)) { +mem1: + /* + * No more memory for path or structures. Save + * errno, free up the current structure and the + * structures already allocated. + */ + saved_errno = errno; + if (p) + free(p); + fts_lfree(head); +#ifndef FTS_WITH_DIRHANDLE_REUSE + birdDirClose(dirp); +#endif + birdCloseFile(cur->fts_dirfd); + cur->fts_dirfd = INVALID_HANDLE_VALUE; + cur->fts_info = FTS_ERR; + SET(FTS_STOP); + errno = saved_errno; + return (NULL); + } + /* Did realloc() change the pointer? */ + doadjust |= oldaddr != sp->fts_path; + doadjust_utf16 |= oldwcspath != sp->fts_wcspath; + maxlen = sp->fts_pathlen - len; + cwcmax = sp->fts_cwcpath - cwcdir; + } + + p->fts_level = level; + p->fts_parent = sp->fts_cur; + p->fts_pathlen = len + p->fts_namelen; + p->fts_cwcpath = cwcdir + p->fts_cwcname; + p->fts_accpath = p->fts_path; + p->fts_wcsaccpath = p->fts_wcspath; + p->fts_stat = dp->d_stat; + p->fts_info = fts_process_stats(p, &dp->d_stat); + + /* We walk in directory order so "ls -f" doesn't get upset. */ + p->fts_link = NULL; + *tailp = p; + tailp = &p->fts_link; + ++nitems; + } + +#ifndef FTS_WITH_DIRHANDLE_REUSE + birdDirClose(dirp); +#endif + + /* + * If realloc() changed the address of the path, adjust the + * addresses for the rest of the tree and the dir list. + */ + if (doadjust) + fts_padjust(sp, head); + if (doadjust_utf16) + fts_padjustw(sp, head); + + /* If didn't find anything, return NULL. */ + if (!nitems) { + if (type == BREAD) + cur->fts_info = FTS_DP; + return (NULL); + } + + /* Sort the entries. */ + if (sp->fts_compar && nitems > 1) + head = fts_sort(sp, head, nitems); + return (head); +} + + +/** + * @note Only used on NT with input arguments, FTS_AGAIN, and links that needs + * following. On link information is generally retrieved during directory + * enumeration on NT, in line with it's DOS/OS2/FAT API heritage. + */ +static int +fts_stat(FTS *sp, FTSENT *p, int follow, HANDLE dfd) +{ + int saved_errno; + const wchar_t *wcspath; + + if (dfd == INVALID_HANDLE_VALUE) { + wcspath = p->fts_wcsaccpath; + } else { + wcspath = p->fts_wcsname; + } + + /* + * If doing a logical walk, or application requested FTS_FOLLOW, do + * a stat(2). If that fails, check for a non-existent symlink. If + * fail, set the errno from the stat call. + */ + if (ISSET(FTS_LOGICAL) || follow) { + if (birdStatAtW(dfd, wcspath, &p->fts_stat, 1 /*fFollowLink*/)) { + saved_errno = errno; + if (birdStatAtW(dfd, wcspath, &p->fts_stat, 0 /*fFollowLink*/)) { + p->fts_errno = saved_errno; + goto err; + } + errno = 0; + if (S_ISLNK(p->fts_stat.st_mode)) + return (FTS_SLNONE); + } + } else if (birdStatAtW(dfd, wcspath, &p->fts_stat, 0 /*fFollowLink*/)) { + p->fts_errno = errno; +err: memset(&p->fts_stat, 0, sizeof(struct stat)); + return (FTS_NS); + } + return fts_process_stats(p, &p->fts_stat); +} + +/* Shared between fts_stat and fts_build. */ +static int +fts_process_stats(FTSENT *p, BirdStat_T const *sbp) +{ + if (S_ISDIR(sbp->st_mode)) { + FTSENT *t; + fts_dev_t dev; + fts_ino_t ino; + + /* + * Set the device/inode. Used to find cycles and check for + * crossing mount points. Also remember the link count, used + * in fts_build to limit the number of stat calls. It is + * understood that these fields are only referenced if fts_info + * is set to FTS_D. + */ + dev = p->fts_dev = sbp->st_dev; + ino = p->fts_ino = sbp->st_ino; + p->fts_nlink = sbp->st_nlink; + + if (ISDOT(p->fts_wcsname)) + return (FTS_DOT); + + /* + * Cycle detection is done by brute force when the directory + * is first encountered. If the tree gets deep enough or the + * number of symbolic links to directories is high enough, + * something faster might be worthwhile. + */ + for (t = p->fts_parent; + t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) + if (ino == t->fts_ino && dev == t->fts_dev) { + p->fts_cycle = t; + return (FTS_DC); + } + return (FTS_D); + } + if (S_ISLNK(sbp->st_mode)) + return (FTS_SL); + if (S_ISREG(sbp->st_mode)) + return (FTS_F); + return (FTS_DEFAULT); +} + +/* + * The comparison function takes pointers to pointers to FTSENT structures. + * Qsort wants a comparison function that takes pointers to void. + * (Both with appropriate levels of const-poisoning, of course!) + * Use a trampoline function to deal with the difference. + */ +static int +fts_compar(const void *a, const void *b) +{ + FTS *parent; + + parent = (*(const FTSENT * const *)a)->fts_fts; + return (*parent->fts_compar)(a, b); +} + +static FTSENT * +fts_sort(FTS *sp, FTSENT *head, size_t nitems) +{ + FTSENT **ap, *p; + + /* + * Construct an array of pointers to the structures and call qsort(3). + * Reassemble the array in the order returned by qsort. If unable to + * sort for memory reasons, return the directory entries in their + * current order. Allocate enough space for the current needs plus + * 40 so don't realloc one entry at a time. + */ + if (nitems > sp->fts_nitems) { + void *ptr; + sp->fts_nitems = nitems + 40; + ptr = realloc(sp->fts_array, sp->fts_nitems * sizeof(FTSENT *)); + if (ptr != NULL) { + sp->fts_array = ptr; + } else { + free(sp->fts_array); + sp->fts_array = NULL; + sp->fts_nitems = 0; + return (head); + } + } + for (ap = sp->fts_array, p = head; p; p = p->fts_link) + *ap++ = p; + qsort(sp->fts_array, nitems, sizeof(FTSENT *), fts_compar); + for (head = *(ap = sp->fts_array); --nitems; ++ap) + ap[0]->fts_link = ap[1]; + ap[0]->fts_link = NULL; + return (head); +} + +static FTSENT * +fts_alloc(FTS *sp, char const *name, size_t namelen, wchar_t const *wcsname, size_t cwcname) +{ + struct _fts_private *priv = (struct _fts_private *)sp; + FTSENT *p; + size_t len; +#ifdef FTS_WITH_ALLOC_CACHE + size_t aligned; + size_t idx; +#endif + +#if defined(FTS_WITH_STATISTICS) && defined(FTS_WITH_ALLOC_CACHE) + priv->allocs++; +#endif + /* + * The file name is a variable length array. Allocate the FTSENT + * structure and the file name. + */ + len = sizeof(FTSENT) + (cwcname + 1) * sizeof(wchar_t); + if (!(sp->fts_options & FTS_NO_ANSI)) + len += namelen + 1; + + /* + * To speed things up we cache entries. This code is a little insane, + * but that's preferable to slow code. + */ +#ifdef FTS_WITH_ALLOC_CACHE + aligned = (len + FTS_ALIGN_FTSENT + 1) & ~(size_t)(FTS_ALIGN_FTSENT - 1); + idx = ((aligned - sizeof(FTSENT)) >> FTS_FREE_BUCKET_SHIFT); + if ( idx < FTS_NUM_FREE_BUCKETS + && (p = priv->freebuckets[idx].head) + && p->fts_alloc_size >= len) { + priv->freebuckets[idx].head = p->fts_link; + priv->numfree--; +# ifdef FTS_WITH_STATISTICS + priv->hits++; +# endif + + } else { +# ifdef FTS_WITH_STATISTICS + priv->misses++; +# endif + p = malloc(aligned); + if (p) { + p->fts_alloc_size = (unsigned)aligned; + } else { + nt_fts_free_alloc_cache(sp); + p = malloc(len); + if (!p) + return NULL; + p->fts_alloc_size = (unsigned)len; + } + } +#else /* !FTS_WITH_ALLOC_CACHE */ + p = malloc(len); + if (p) { + p->fts_alloc_size = (unsigned)len; + } else { + return NULL; + } +#endif /* !FTS_WITH_ALLOC_CACHE */ + + /* Copy the names and guarantee NUL termination. */ + p->fts_wcsname = (wchar_t *)(p + 1); + memcpy(p->fts_wcsname, wcsname, cwcname * sizeof(wchar_t)); + p->fts_wcsname[cwcname] = '\0'; + p->fts_cwcname = cwcname; + if (!(sp->fts_options & FTS_NO_ANSI)) { + p->fts_name = (char *)(p->fts_wcsname + cwcname + 1); + memcpy(p->fts_name, name, namelen); + p->fts_name[namelen] = '\0'; + p->fts_namelen = namelen; + } else { + p->fts_name = NULL; + p->fts_namelen = 0; + } + + p->fts_path = sp->fts_path; + p->fts_wcspath = sp->fts_wcspath; + p->fts_statp = &p->fts_stat; + p->fts_errno = 0; + p->fts_flags = 0; + p->fts_instr = FTS_NOINSTR; + p->fts_number = 0; + p->fts_pointer = NULL; + p->fts_fts = sp; + p->fts_dirfd = INVALID_HANDLE_VALUE; + return (p); +} + + +/** + * Converts the ANSI name to UTF-16 and calls fts_alloc. + * + * @returns Pointer to allocated and mostly initialized FTSENT structure on + * success. NULL on failure, caller needs to record it. + * @param sp Pointer to FTS instance. + * @param name The ANSI name. + * @param namelen The ANSI name length. + */ +static FTSENT * +fts_alloc_ansi(FTS *sp, char const *name, size_t namelen) +{ + MY_UNICODE_STRING UniStr; + MY_ANSI_STRING AnsiStr; + MY_NTSTATUS rcNt; + FTSENT *pRet; + + UniStr.Buffer = NULL; + UniStr.MaximumLength = UniStr.Length = 0; + + AnsiStr.Buffer = (char *)name; + AnsiStr.Length = AnsiStr.MaximumLength = (USHORT)namelen; + + rcNt = g_pfnRtlAnsiStringToUnicodeString(&UniStr, &AnsiStr, TRUE /*fAllocate*/); + if (NT_SUCCESS(rcNt)) { + pRet = fts_alloc(sp, name, namelen, UniStr.Buffer, UniStr.Length / sizeof(wchar_t)); + HeapFree(GetProcessHeap(), 0, UniStr.Buffer); + } else { + pRet = NULL; + } + return pRet; +} + + +/** + * Converts the UTF-16 name to ANSI (if necessary) and calls fts_alloc. + * + * @returns Pointer to allocated and mostly initialized FTSENT structure on + * success. NULL on failure, caller needs to record it. + * @param sp Pointer to the FTS instance. + * @param wcsname The UTF-16 name. + * @param cwcname The UTF-16 name length. + */ +static FTSENT * +fts_alloc_utf16(FTS *sp, wchar_t const *wcsname, size_t cwcname) +{ + FTSENT *pRet; + + if (sp->fts_options & FTS_NO_ANSI) { + pRet = fts_alloc(sp, NULL, 0, wcsname, cwcname); + } else { + MY_UNICODE_STRING UniStr; + MY_ANSI_STRING AnsiStr; + MY_NTSTATUS rcNt; + + UniStr.Buffer = (wchar_t *)wcsname; + UniStr.MaximumLength = UniStr.Length = (USHORT)(cwcname * sizeof(wchar_t)); + + AnsiStr.Buffer = NULL; + AnsiStr.Length = AnsiStr.MaximumLength = 0; + + rcNt = g_pfnRtlUnicodeStringToAnsiString(&AnsiStr, &UniStr, TRUE /*fAllocate*/); + if (NT_SUCCESS(rcNt)) { + pRet = fts_alloc(sp, AnsiStr.Buffer, AnsiStr.Length, wcsname, cwcname); + HeapFree(GetProcessHeap(), 0, AnsiStr.Buffer); + } else { + pRet = NULL; + } + } + return pRet; +} + + +/** + * Frees up the FTSENT allocation cache. + * + * Used by nt_fts_close, but also called by fts_alloc on alloc failure. + * + * @param sp Pointer to the FTS instance. + */ +static void nt_fts_free_alloc_cache(FTS *sp) +{ +#ifdef FTS_WITH_ALLOC_CACHE + struct _fts_private *priv = (struct _fts_private *)sp; + unsigned i = K_ELEMENTS(priv->freebuckets); + while (i-- > 0) { + FTSENT *cur = priv->freebuckets[i].head; + priv->freebuckets[i].head = NULL; + while (cur) { + FTSENT *freeit = cur; + cur = cur->fts_link; + free(freeit); + } + } + priv->numfree = 0; +#else + (void)sp; +#endif +} + + +static void +fts_lfree(FTSENT *head) +{ + FTSENT *p; + + /* Free a linked list of structures. */ + while ((p = head)) { + head = head->fts_link; + assert(p->fts_dirfd == INVALID_HANDLE_VALUE); + free(p); + } +} + +/* + * Allow essentially unlimited paths; find, rm, ls should all work on any tree. + * Most systems will allow creation of paths much longer than MAXPATHLEN, even + * though the kernel won't resolve them. Add the size (not just what's needed) + * plus 256 bytes so don't realloc the path 2 bytes at a time. + */ +static int +fts_palloc(FTS *sp, size_t more, size_t cwcmore) +{ + void *ptr; + + /** @todo Isn't more and cwcmore minimum buffer sizes rather than what needs + * to be added to the buffer?? This code makes no sense when looking at + * the way the caller checks things out! */ + + if (more) { + sp->fts_pathlen += more + 256; + ptr = realloc(sp->fts_path, sp->fts_pathlen); + if (ptr) { + sp->fts_path = ptr; + } else { + free(sp->fts_path); + sp->fts_path = NULL; + free(sp->fts_wcspath); + sp->fts_wcspath = NULL; + return 1; + } + } + + if (cwcmore) { + sp->fts_cwcpath += cwcmore + 256; + ptr = realloc(sp->fts_wcspath, sp->fts_cwcpath); + if (ptr) { + sp->fts_wcspath = ptr; + } else { + free(sp->fts_path); + sp->fts_path = NULL; + free(sp->fts_wcspath); + sp->fts_wcspath = NULL; + return 1; + } + } + return 0; +} + +/* + * When the path is realloc'd, have to fix all of the pointers in structures + * already returned. + */ +static void +fts_padjust(FTS *sp, FTSENT *head) +{ + FTSENT *p; + char *addr = sp->fts_path; + +#define ADJUST(p) do { \ + if ((p)->fts_accpath != (p)->fts_name) { \ + (p)->fts_accpath = \ + (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ + } \ + (p)->fts_path = addr; \ +} while (0) + /* Adjust the current set of children. */ + for (p = sp->fts_child; p; p = p->fts_link) + ADJUST(p); + + /* Adjust the rest of the tree, including the current level. */ + for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { + ADJUST(p); + p = p->fts_link ? p->fts_link : p->fts_parent; + } +} + +/* + * When the UTF-16 path is realloc'd, have to fix all of the pointers in + * structures already returned. + */ +static void +fts_padjustw(FTS *sp, FTSENT *head) +{ + FTSENT *p; + wchar_t *addr = sp->fts_wcspath; + +#define ADJUSTW(p) \ + do { \ + if ((p)->fts_wcsaccpath != (p)->fts_wcsname) \ + (p)->fts_wcsaccpath = addr + ((p)->fts_wcsaccpath - (p)->fts_wcspath); \ + (p)->fts_wcspath = addr; \ + } while (0) + + /* Adjust the current set of children. */ + for (p = sp->fts_child; p; p = p->fts_link) + ADJUSTW(p); + + /* Adjust the rest of the tree, including the current level. */ + for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { + ADJUSTW(p); + p = p->fts_link ? p->fts_link : p->fts_parent; + } +} + +static size_t +fts_maxarglen(char * const *argv) +{ + size_t len, max; + + for (max = 0; *argv; ++argv) + if ((len = strlen(*argv)) > max) + max = len; + return (max + 1); +} + +/** Returns the max string size (including term). */ +static size_t +fts_maxarglenw(wchar_t * const *argv) +{ + size_t max = 0; + for (; *argv; ++argv) { + size_t len = wcslen(*argv); + if (len > max) + max = len; + } + return max + 1; +} + diff --git a/src/lib/nt/fts-nt.h b/src/lib/nt/fts-nt.h new file mode 100644 index 0000000..3d014d5 --- /dev/null +++ b/src/lib/nt/fts-nt.h @@ -0,0 +1,188 @@ +/* $Id: fts-nt.h 3535 2021-12-20 23:32:28Z bird $ */ +/** @file + * Header for the NT port of BSD fts.h. + * + * @copyright Copyright (c) 1989, 1993 The Regents of the University of California. All rights reserved. + * @copyright NT modifications Copyright (C) 2016 knut st. osmundsen + * @licenses BSD3 + */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)fts.h 8.3 (Berkeley) 8/14/94 + * $FreeBSD$ + * + */ + +#ifndef INCLUDED_FTS_NT_H +#define INCLUDED_FTS_NT_H + +#include +#include +#include "ntstat.h" /* ensure correct stat structure */ + +typedef uint64_t fts_dev_t; +typedef uint64_t fts_ino_t; +typedef uint32_t fts_nlink_t; +#ifdef _WINNT_ +typedef HANDLE fts_fd_t; +# define NT_FTS_INVALID_HANDLE_VALUE INVALID_HANDLE_VALUE +#else +typedef void * fts_fd_t; +# define NT_FTS_INVALID_HANDLE_VALUE ((void *)~(uintptr_t)0) +#endif +#define FTSCALL __cdecl + +typedef struct { + struct _ftsent *fts_cur; /* current node */ + struct _ftsent *fts_child; /* linked list of children */ + struct _ftsent **fts_array; /* sort array */ + fts_dev_t fts_dev; /* starting device # */ + char *fts_path; /* path for this descent */ + size_t fts_pathlen; /* sizeof(path) */ + wchar_t *fts_wcspath; /* NT: UTF-16 path for this descent. */ + size_t fts_cwcpath; /* NT: size of fts_wcspath buffer */ + size_t fts_nitems; /* elements in the sort array */ + int (FTSCALL *fts_compar) /* compare function */ + (const struct _ftsent * const *, const struct _ftsent * const *); + +#define FTS_COMFOLLOW 0x001 /* follow command line symlinks */ +#define FTS_LOGICAL 0x002 /* logical walk */ +#define FTS_NOCHDIR 0x004 /* don't change directories */ +#define FTS_NOSTAT 0x008 /* don't get stat info */ +#define FTS_PHYSICAL 0x010 /* physical walk */ +#define FTS_SEEDOT 0x020 /* return dot and dot-dot */ +#define FTS_XDEV 0x040 /* don't cross devices */ +#if 0 /* No whiteout on NT. */ +#define FTS_WHITEOUT 0x080 /* return whiteout information */ +#endif +#define FTS_CWDFD 0x100 /* For gnulib fts compatibility, enables fts_cwd_fd. */ +#define FTS_TIGHT_CYCLE_CHECK 0x200 /* Ignored currently */ +#define FTS_NO_ANSI 0x40000000 /* NT: No ansi name or access path. */ +#define FTS_OPTIONMASK 0x400003ff /* valid user option mask */ + +#define FTS_NAMEONLY 0x10000 /* (private) child names only */ +#define FTS_STOP 0x20000 /* (private) unrecoverable error */ + int fts_options; /* fts_open options, global flags */ + int fts_cwd_fd; /* FTS_CWDFD: AT_FDCWD or a virtual CWD file descriptor. */ + void *fts_clientptr; /* thunk for sort function */ +} FTS; + +typedef struct _ftsent { + struct _ftsent *fts_cycle; /* cycle node */ + struct _ftsent *fts_parent; /* parent directory */ + struct _ftsent *fts_link; /* next file in directory */ + int64_t fts_number; /* local numeric value */ +#define fts_bignum fts_number /* XXX non-std, should go away */ + void *fts_pointer; /* local address value */ + char *fts_accpath; /* access path */ + wchar_t *fts_wcsaccpath; /* NT: UTF-16 access path */ + char *fts_path; /* root path */ + wchar_t *fts_wcspath; /* NT: UTF-16 root path */ + int fts_errno; /* errno for this node */ + size_t fts_alloc_size; /* internal - size of the allocation for this entry. */ + fts_fd_t fts_dirfd; /* NT: Handle to the directory (NT_FTS_)INVALID_HANDLE_VALUE if not valid */ + size_t fts_pathlen; /* strlen(fts_path) */ + size_t fts_cwcpath; /* NT: length of fts_wcspath. */ + size_t fts_namelen; /* strlen(fts_name) */ + size_t fts_cwcname; /* NT: length of fts_wcsname. */ + + fts_ino_t fts_ino; /* inode */ + fts_dev_t fts_dev; /* device */ + fts_nlink_t fts_nlink; /* link count */ + +#define FTS_ROOTPARENTLEVEL -1 +#define FTS_ROOTLEVEL 0 + long fts_level; /* depth (-1 to N) */ + +#define FTS_D 1 /* preorder directory */ +#define FTS_DC 2 /* directory that causes cycles */ +#define FTS_DEFAULT 3 /* none of the above */ +#define FTS_DNR 4 /* unreadable directory */ +#define FTS_DOT 5 /* dot or dot-dot */ +#define FTS_DP 6 /* postorder directory */ +#define FTS_ERR 7 /* error; errno is set */ +#define FTS_F 8 /* regular file */ +#define FTS_INIT 9 /* initialized only */ +#define FTS_NS 10 /* stat(2) failed */ +#define FTS_NSOK 11 /* no stat(2) requested */ +#define FTS_SL 12 /* symbolic link */ +#define FTS_SLNONE 13 /* symbolic link without target */ +#define FTS_W 14 /* whiteout object */ + int fts_info; /* user status for FTSENT structure */ + +#define FTS_DONTCHDIR 0x01 /* don't chdir .. to the parent */ +#define FTS_SYMFOLLOW 0x02 /* followed a symlink to get here */ +#define FTS_ISW 0x04 /* this is a whiteout object */ + unsigned fts_flags; /* private flags for FTSENT structure */ + +#define FTS_AGAIN 1 /* read node again */ +#define FTS_FOLLOW 2 /* follow symbolic link */ +#define FTS_NOINSTR 3 /* no instructions */ +#define FTS_SKIP 4 /* discard node */ + int fts_instr; /* fts_set() instructions */ + + struct stat *fts_statp; /* stat(2) information */ + char *fts_name; /* file name */ + wchar_t *fts_wcsname; /* NT: UTF-16 file name. */ + FTS *fts_fts; /* back pointer to main FTS */ + BirdStat_T fts_stat; /* NT: We always got stat info. */ +} FTSENT; + + +#ifdef __cplusplus +extern "C" { +#endif + +FTSENT *FTSCALL nt_fts_children(FTS *, int); +int FTSCALL nt_fts_close(FTS *); +void *FTSCALL nt_fts_get_clientptr(FTS *); +#define fts_get_clientptr(fts) ((fts)->fts_clientptr) +FTS *FTSCALL nt_fts_get_stream(FTSENT *); +#define fts_get_stream(ftsent) ((ftsent)->fts_fts) +FTS *FTSCALL nt_fts_open(char * const *, int, int (FTSCALL*)(const FTSENT * const *, const FTSENT * const *)); +FTS *FTSCALL nt_fts_openw(wchar_t * const *, int, int (FTSCALL*)(const FTSENT * const *, const FTSENT * const *)); +FTSENT *FTSCALL nt_fts_read(FTS *); +int FTSCALL nt_fts_set(FTS *, FTSENT *, int); +void FTSCALL nt_fts_set_clientptr(FTS *, void *); + +/* API mappings. */ +#define fts_children(a_pFts, a_iInstr) nt_fts_children(a_pFts, a_iInstr) +#define fts_close(a_pFts) nt_fts_close(a_pFts) +#define fts_open(a_papszArgs, a_fOptions, a_pfnCompare) nt_fts_open(a_papszArgs, a_fOptions, a_pfnCompare) +#define fts_read(a_pFts) nt_fts_read(a_pFts) +#define fts_set(a_pFts, a_pFtsEntry, a_iInstr) nt_fts_set(a_pFts, a_pFtsEntry, a_iInstr) +#define fts_set_clientptr(a_pFts, a_pvUser) nt_fts_set_clientptr(a_pFts, a_pvUser) + +#ifdef __cplusplus +} +#endif + +#endif /* !INCLUDED_FTS_NT_H */ + diff --git a/src/lib/nt/kFsCache.c b/src/lib/nt/kFsCache.c new file mode 100644 index 0000000..77c9655 --- /dev/null +++ b/src/lib/nt/kFsCache.c @@ -0,0 +1,4840 @@ +/* $Id: kFsCache.c 3381 2020-06-12 11:36:10Z bird $ */ +/** @file + * ntdircache.c - NT directory content cache. + */ + +/* + * Copyright (c) 2016 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include "nthlp.h" +#include "ntstat.h" + +#include +#include +#include +#ifdef _MSC_VER +# include +#endif +//#include +//#include + + +//#include +//#include + +#include "kFsCache.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def KFSCACHE_LOG2 + * More logging. */ +#if 0 +# define KFSCACHE_LOG2(a) KFSCACHE_LOG(a) +#else +# define KFSCACHE_LOG2(a) do { } while (0) +#endif + +/** The minimum time between a directory last populated time and its + * modification time for the cache to consider it up-to-date. + * + * This helps work around races between us reading a directory and someone else + * adding / removing files and directories to /from it. Given that the + * effective time resolution typically is around 2000Hz these days, unless you + * use the new *TimePrecise API variants, there is plenty of room for a race + * here. + * + * The current value is 20ms in NT time units (100ns each), which translates + * to a 50Hz time update frequency. */ +#define KFSCACHE_MIN_LAST_POPULATED_VS_WRITE (20*1000*10) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Used by the code re-populating a directory. + */ +typedef struct KFSDIRREPOP +{ + /** The old papChildren array. */ + PKFSOBJ *papOldChildren; + /** Number of children in the array. */ + KU32 cOldChildren; + /** The index into papOldChildren we expect to find the next entry. */ + KU32 iNextOldChild; + /** Add this to iNextOldChild . */ + KI32 cNextOldChildInc; + /** Pointer to the cache (name changes). */ + PKFSCACHE pCache; +} KFSDIRREPOP; +/** Pointer to directory re-population data. */ +typedef KFSDIRREPOP *PKFSDIRREPOP; + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static KBOOL kFsCacheRefreshObj(PKFSCACHE pCache, PKFSOBJ pObj, KFSLOOKUPERROR *penmError); + + +/** + * Retains a reference to a cache object, internal version. + * + * @returns pObj + * @param pObj The object. + */ +K_INLINE PKFSOBJ kFsCacheObjRetainInternal(PKFSOBJ pObj) +{ + KU32 cRefs = ++pObj->cRefs; + kHlpAssert(cRefs < 16384); + K_NOREF(cRefs); + return pObj; +} + + +#ifndef NDEBUG + +/** + * Debug printing. + * @param pszFormat Debug format string. + * @param ... Format argument. + */ +void kFsCacheDbgPrintfV(const char *pszFormat, va_list va) +{ + if (1) + { + DWORD const dwSavedErr = GetLastError(); + + fprintf(stderr, "debug: "); + vfprintf(stderr, pszFormat, va); + + SetLastError(dwSavedErr); + } +} + + +/** + * Debug printing. + * @param pszFormat Debug format string. + * @param ... Format argument. + */ +void kFsCacheDbgPrintf(const char *pszFormat, ...) +{ + if (1) + { + va_list va; + va_start(va, pszFormat); + kFsCacheDbgPrintfV(pszFormat, va); + va_end(va); + } +} + +#endif /* !NDEBUG */ + + + +/** + * Hashes a string. + * + * @returns 32-bit string hash. + * @param pszString String to hash. + */ +static KU32 kFsCacheStrHash(const char *pszString) +{ + /* This algorithm was created for sdbm (a public-domain reimplementation of + ndbm) database library. it was found to do well in scrambling bits, + causing better distribution of the keys and fewer splits. it also happens + to be a good general hashing function with good distribution. the actual + function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below + is the faster version used in gawk. [there is even a faster, duff-device + version] the magic constant 65599 was picked out of thin air while + experimenting with different constants, and turns out to be a prime. + this is one of the algorithms used in berkeley db (see sleepycat) and + elsewhere. */ + KU32 uHash = 0; + KU32 uChar; + while ((uChar = (unsigned char)*pszString++) != 0) + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + return uHash; +} + + +/** + * Hashes a string. + * + * @returns The string length. + * @param pszString String to hash. + * @param puHash Where to return the 32-bit string hash. + */ +static KSIZE kFsCacheStrHashEx(const char *pszString, KU32 *puHash) +{ + const char * const pszStart = pszString; + KU32 uHash = 0; + KU32 uChar; + while ((uChar = (unsigned char)*pszString) != 0) + { + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + pszString++; + } + *puHash = uHash; + return pszString - pszStart; +} + + +/** + * Hashes a substring. + * + * @returns 32-bit substring hash. + * @param pchString Pointer to the substring (not terminated). + * @param cchString The length of the substring. + */ +static KU32 kFsCacheStrHashN(const char *pchString, KSIZE cchString) +{ + KU32 uHash = 0; + while (cchString-- > 0) + { + KU32 uChar = (unsigned char)*pchString++; + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * Hashes a UTF-16 string. + * + * @returns The string length in wchar_t units. + * @param pwszString String to hash. + * @param puHash Where to return the 32-bit string hash. + */ +static KSIZE kFsCacheUtf16HashEx(const wchar_t *pwszString, KU32 *puHash) +{ + const wchar_t * const pwszStart = pwszString; + KU32 uHash = 0; + KU32 uChar; + while ((uChar = *pwszString) != 0) + { + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + pwszString++; + } + *puHash = uHash; + return pwszString - pwszStart; +} + + +/** + * Hashes a UTF-16 substring. + * + * @returns 32-bit substring hash. + * @param pwcString Pointer to the substring (not terminated). + * @param cchString The length of the substring (in wchar_t's). + */ +static KU32 kFsCacheUtf16HashN(const wchar_t *pwcString, KSIZE cwcString) +{ + KU32 uHash = 0; + while (cwcString-- > 0) + { + KU32 uChar = *pwcString++; + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * For use when kFsCacheIAreEqualW hit's something non-trivial. + * + * @returns K_TRUE if equal, K_FALSE if different. + * @param pwcName1 The first string. + * @param pwcName2 The second string. + * @param cwcName The length of the two strings (in wchar_t's). + */ +KBOOL kFsCacheIAreEqualSlowW(const wchar_t *pwcName1, const wchar_t *pwcName2, KU16 cwcName) +{ + MY_UNICODE_STRING UniStr1 = { cwcName * sizeof(wchar_t), cwcName * sizeof(wchar_t), (wchar_t *)pwcName1 }; + MY_UNICODE_STRING UniStr2 = { cwcName * sizeof(wchar_t), cwcName * sizeof(wchar_t), (wchar_t *)pwcName2 }; + return g_pfnRtlEqualUnicodeString(&UniStr1, &UniStr2, TRUE /*fCaseInsensitive*/); +} + + +/** + * Compares two UTF-16 strings in a case-insensitive fashion. + * + * You would think we should be using _wscnicmp here instead, however it is + * locale dependent and defaults to ASCII upper/lower handling setlocale hasn't + * been called. + * + * @returns K_TRUE if equal, K_FALSE if different. + * @param pwcName1 The first string. + * @param pwcName2 The second string. + * @param cwcName The length of the two strings (in wchar_t's). + */ +K_INLINE KBOOL kFsCacheIAreEqualW(const wchar_t *pwcName1, const wchar_t *pwcName2, KU32 cwcName) +{ + while (cwcName > 0) + { + wchar_t wc1 = *pwcName1; + wchar_t wc2 = *pwcName2; + if (wc1 == wc2) + { /* not unlikely */ } + else if ( (KU16)wc1 < (KU16)0xc0 /* U+00C0 is the first upper/lower letter after 'z'. */ + && (KU16)wc2 < (KU16)0xc0) + { + /* ASCII upper case. */ + if ((KU16)wc1 - (KU16)0x61 < (KU16)26) + wc1 &= ~(wchar_t)0x20; + if ((KU16)wc2 - (KU16)0x61 < (KU16)26) + wc2 &= ~(wchar_t)0x20; + if (wc1 != wc2) + return K_FALSE; + } + else + return kFsCacheIAreEqualSlowW(pwcName1, pwcName2, (KU16)cwcName); + + pwcName2++; + pwcName1++; + cwcName--; + } + + return K_TRUE; +} + + +/** + * Looks for '..' in the path. + * + * @returns K_TRUE if '..' component found, K_FALSE if not. + * @param pszPath The path. + * @param cchPath The length of the path. + */ +static KBOOL kFsCacheHasDotDotA(const char *pszPath, KSIZE cchPath) +{ + const char *pchDot = (const char *)kHlpMemChr(pszPath, '.', cchPath); + while (pchDot) + { + if (pchDot[1] != '.') + { + pchDot++; + pchDot = (const char *)kHlpMemChr(pchDot, '.', &pszPath[cchPath] - pchDot); + } + else + { + char ch; + if ( (ch = pchDot[2]) != '\0' + && IS_SLASH(ch)) + { + if (pchDot == pszPath) + return K_TRUE; + ch = pchDot[-1]; + if ( IS_SLASH(ch) + || ch == ':') + return K_TRUE; + } + pchDot = (const char *)kHlpMemChr(pchDot + 2, '.', &pszPath[cchPath] - pchDot - 2); + } + } + + return K_FALSE; +} + + +/** + * Looks for '..' in the path. + * + * @returns K_TRUE if '..' component found, K_FALSE if not. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + */ +static KBOOL kFsCacheHasDotDotW(const wchar_t *pwszPath, KSIZE cwcPath) +{ + const wchar_t *pwcDot = wmemchr(pwszPath, '.', cwcPath); + while (pwcDot) + { + if (pwcDot[1] != '.') + { + pwcDot++; + pwcDot = wmemchr(pwcDot, '.', &pwszPath[cwcPath] - pwcDot); + } + else + { + wchar_t wch; + if ( (wch = pwcDot[2]) != '\0' + && IS_SLASH(wch)) + { + if (pwcDot == pwszPath) + return K_TRUE; + wch = pwcDot[-1]; + if ( IS_SLASH(wch) + || wch == ':') + return K_TRUE; + } + pwcDot = wmemchr(pwcDot + 2, '.', &pwszPath[cwcPath] - pwcDot - 2); + } + } + + return K_FALSE; +} + + +/** + * Creates an ANSI hash table entry for the given path. + * + * @returns The hash table entry or NULL if out of memory. + * @param pCache The hash + * @param pFsObj The resulting object. + * @param pszPath The path. + * @param cchPath The length of the path. + * @param uHashPath The hash of the path. + * @param fAbsolute Whether it can be refreshed using an absolute + * lookup or requires the slow treatment. + * @parma idxMissingGen The missing generation index. + * @param idxHashTab The hash table index of the path. + * @param enmError The lookup error. + */ +static PKFSHASHA kFsCacheCreatePathHashTabEntryA(PKFSCACHE pCache, PKFSOBJ pFsObj, const char *pszPath, KU32 cchPath, + KU32 uHashPath, KU32 idxHashTab, BOOL fAbsolute, KU32 idxMissingGen, + KFSLOOKUPERROR enmError) +{ + PKFSHASHA pHashEntry = (PKFSHASHA)kHlpAlloc(sizeof(*pHashEntry) + cchPath + 1); + if (pHashEntry) + { + pHashEntry->uHashPath = uHashPath; + pHashEntry->cchPath = (KU16)cchPath; + pHashEntry->fAbsolute = fAbsolute; + pHashEntry->idxMissingGen = (KU8)idxMissingGen; + pHashEntry->enmError = enmError; + pHashEntry->pszPath = (const char *)kHlpMemCopy(pHashEntry + 1, pszPath, cchPath + 1); + if (pFsObj) + { + pHashEntry->pFsObj = kFsCacheObjRetainInternal(pFsObj); + pHashEntry->uCacheGen = pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pFsObj->cPathHashRefs += 1; // for debugging + } + else + { + pHashEntry->pFsObj = NULL; + if (enmError != KFSLOOKUPERROR_UNSUPPORTED) + pHashEntry->uCacheGen = pCache->auGenerationsMissing[idxMissingGen]; + else + pHashEntry->uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + } + + pHashEntry->pNext = pCache->apAnsiPaths[idxHashTab]; + pCache->apAnsiPaths[idxHashTab] = pHashEntry; + + pCache->cbAnsiPaths += sizeof(*pHashEntry) + cchPath + 1; + pCache->cAnsiPaths++; + if (pHashEntry->pNext) + pCache->cAnsiPathCollisions++; + } + return pHashEntry; +} + + +/** + * Creates an UTF-16 hash table entry for the given path. + * + * @returns The hash table entry or NULL if out of memory. + * @param pCache The hash + * @param pFsObj The resulting object. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + * @param uHashPath The hash of the path. + * @param fAbsolute Whether it can be refreshed using an absolute + * lookup or requires the slow treatment. + * @parma idxMissingGen The missing generation index. + * @param idxHashTab The hash table index of the path. + * @param enmError The lookup error. + */ +static PKFSHASHW kFsCacheCreatePathHashTabEntryW(PKFSCACHE pCache, PKFSOBJ pFsObj, const wchar_t *pwszPath, KU32 cwcPath, + KU32 uHashPath, KU32 idxHashTab, BOOL fAbsolute, KU32 idxMissingGen, + KFSLOOKUPERROR enmError) +{ + PKFSHASHW pHashEntry = (PKFSHASHW)kHlpAlloc(sizeof(*pHashEntry) + (cwcPath + 1) * sizeof(wchar_t)); + if (pHashEntry) + { + pHashEntry->uHashPath = uHashPath; + pHashEntry->cwcPath = cwcPath; + pHashEntry->fAbsolute = fAbsolute; + pHashEntry->idxMissingGen = (KU8)idxMissingGen; + pHashEntry->enmError = enmError; + pHashEntry->pwszPath = (const wchar_t *)kHlpMemCopy(pHashEntry + 1, pwszPath, (cwcPath + 1) * sizeof(wchar_t)); + if (pFsObj) + { + pHashEntry->pFsObj = kFsCacheObjRetainInternal(pFsObj); + pHashEntry->uCacheGen = pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pFsObj->cPathHashRefs += 1; // for debugging + } + else + { + pHashEntry->pFsObj = NULL; + if (enmError != KFSLOOKUPERROR_UNSUPPORTED) + pHashEntry->uCacheGen = pCache->auGenerationsMissing[idxMissingGen]; + else + pHashEntry->uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + } + + pHashEntry->pNext = pCache->apUtf16Paths[idxHashTab]; + pCache->apUtf16Paths[idxHashTab] = pHashEntry; + + pCache->cbUtf16Paths += sizeof(*pHashEntry) + (cwcPath + 1) * sizeof(wchar_t); + pCache->cUtf16Paths++; + if (pHashEntry->pNext) + pCache->cAnsiPathCollisions++; + } + return pHashEntry; +} + + +/** + * Links the child in under the parent. + * + * @returns K_TRUE on success, K_FALSE if out of memory. + * @param pParent The parent node. + * @param pChild The child node. + */ +static KBOOL kFsCacheDirAddChild(PKFSCACHE pCache, PKFSDIR pParent, PKFSOBJ pChild, KFSLOOKUPERROR *penmError) +{ + if (pParent->cChildren >= pParent->cChildrenAllocated) + { + void *pvNew = kHlpRealloc(pParent->papChildren, (pParent->cChildrenAllocated + 16) * sizeof(pParent->papChildren[0])); + if (!pvNew) + return K_FALSE; + pParent->papChildren = (PKFSOBJ *)pvNew; + pParent->cChildrenAllocated += 16; + pCache->cbObjects += 16 * sizeof(pParent->papChildren[0]); + } + pParent->papChildren[pParent->cChildren++] = kFsCacheObjRetainInternal(pChild); + return K_TRUE; +} + + +/** + * Creates a new cache object. + * + * @returns Pointer (with 1 reference) to the new object. The object will not + * be linked to the parent directory yet. + * + * NULL if we're out of memory. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pszName The ANSI name. + * @param cchName The length of the ANSI name. + * @param pwszName The UTF-16 name. + * @param cwcName The length of the UTF-16 name. + * @param pszShortName The ANSI short name, NULL if none. + * @param cchShortName The length of the ANSI short name, 0 if none. + * @param pwszShortName The UTF-16 short name, NULL if none. + * @param cwcShortName The length of the UTF-16 short name, 0 if none. + * @param bObjType The objct type. + * @param penmError Where to explain failures. + */ +PKFSOBJ kFsCacheCreateObject(PKFSCACHE pCache, PKFSDIR pParent, + char const *pszName, KU16 cchName, wchar_t const *pwszName, KU16 cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + char const *pszShortName, KU16 cchShortName, wchar_t const *pwszShortName, KU16 cwcShortName, +#endif + KU8 bObjType, KFSLOOKUPERROR *penmError) +{ + /* + * Allocate the object. + */ + KBOOL const fDirish = bObjType != KFSOBJ_TYPE_FILE && bObjType != KFSOBJ_TYPE_OTHER; + KSIZE const cbObj = fDirish ? sizeof(KFSDIR) : sizeof(KFSOBJ); + KSIZE const cbNames = (cwcName + 1) * sizeof(wchar_t) + cchName + 1 +#ifdef KFSCACHE_CFG_SHORT_NAMES + + (cwcShortName > 0 ? (cwcShortName + 1) * sizeof(wchar_t) + cchShortName + 1 : 0) +#endif + ; + PKFSOBJ pObj; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + + pObj = (PKFSOBJ)kHlpAlloc(cbObj + cbNames); + if (pObj) + { + KU8 *pbExtra = (KU8 *)pObj + cbObj; + + KFSCACHE_LOCK(pCache); /** @todo reduce the amount of work done holding the lock */ + + pCache->cbObjects += cbObj + cbNames; + pCache->cObjects++; + + /* + * Initialize the object. + */ + pObj->u32Magic = KFSOBJ_MAGIC; + pObj->cRefs = 1; + pObj->uCacheGen = bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pObj->bObjType = bObjType; + pObj->fHaveStats = K_FALSE; + pObj->cPathHashRefs = 0; + pObj->idxUserDataLock = KU8_MAX; + pObj->fFlags = pParent->Obj.fFlags & KFSOBJ_F_INHERITED_MASK; + pObj->pParent = pParent; + pObj->uNameHash = 0; + pObj->pNextNameHash = NULL; + pObj->pNameAlloc = NULL; + pObj->pUserDataHead = NULL; + +#ifdef KFSCACHE_CFG_UTF16 + pObj->cwcParent = pParent->Obj.cwcParent + pParent->Obj.cwcName + !!pParent->Obj.cwcName; + pObj->pwszName = (wchar_t *)kHlpMemCopy(pbExtra, pwszName, cwcName * sizeof(wchar_t)); + pObj->cwcName = cwcName; + pbExtra += cwcName * sizeof(wchar_t); + *pbExtra++ = '\0'; + *pbExtra++ = '\0'; +# ifdef KFSCACHE_CFG_SHORT_NAMES + pObj->cwcShortParent = pParent->Obj.cwcShortParent + pParent->Obj.cwcShortName + !!pParent->Obj.cwcShortName; + if (cwcShortName) + { + pObj->pwszShortName = (wchar_t *)kHlpMemCopy(pbExtra, pwszShortName, cwcShortName * sizeof(wchar_t)); + pObj->cwcShortName = cwcShortName; + pbExtra += cwcShortName * sizeof(wchar_t); + *pbExtra++ = '\0'; + *pbExtra++ = '\0'; + } + else + { + pObj->pwszShortName = pObj->pwszName; + pObj->cwcShortName = cwcName; + } +# endif +#endif + pObj->cchParent = pParent->Obj.cchParent + pParent->Obj.cchName + !!pParent->Obj.cchName; + pObj->pszName = (char *)kHlpMemCopy(pbExtra, pszName, cchName); + pObj->cchName = cchName; + pbExtra += cchName; + *pbExtra++ = '\0'; +# ifdef KFSCACHE_CFG_SHORT_NAMES + pObj->cchShortParent = pParent->Obj.cchShortParent + pParent->Obj.cchShortName + !!pParent->Obj.cchShortName; + if (cchShortName) + { + pObj->pszShortName = (char *)kHlpMemCopy(pbExtra, pszShortName, cchShortName); + pObj->cchShortName = cchShortName; + pbExtra += cchShortName; + *pbExtra++ = '\0'; + } + else + { + pObj->pszShortName = pObj->pszName; + pObj->cchShortName = cchName; + } +#endif + kHlpAssert(pbExtra - (KU8 *)pObj == cbObj); + + /* + * Type specific initialization. + */ + if (fDirish) + { + PKFSDIR pDirObj = (PKFSDIR)pObj; + pDirObj->cChildren = 0; + pDirObj->cChildrenAllocated = 0; + pDirObj->papChildren = NULL; + pDirObj->fHashTabMask = 0; + pDirObj->papHashTab = NULL; + pDirObj->hDir = INVALID_HANDLE_VALUE; + pDirObj->uDevNo = pParent->uDevNo; + pDirObj->iLastWrite = 0; + pDirObj->iLastPopulated = 0; + pDirObj->fPopulated = K_FALSE; + } + + KFSCACHE_UNLOCK(pCache); + } + else + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return pObj; +} + + +/** + * Creates a new object given wide char names. + * + * This function just converts the paths and calls kFsCacheCreateObject. + * + * + * @returns Pointer (with 1 reference) to the new object. The object will not + * be linked to the parent directory yet. + * + * NULL if we're out of memory. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pszName The ANSI name. + * @param cchName The length of the ANSI name. + * @param pwszName The UTF-16 name. + * @param cwcName The length of the UTF-16 name. + * @param pwszShortName The UTF-16 short name, NULL if none. + * @param cwcShortName The length of the UTF-16 short name, 0 if none. + * @param bObjType The objct type. + * @param penmError Where to explain failures. + */ +PKFSOBJ kFsCacheCreateObjectW(PKFSCACHE pCache, PKFSDIR pParent, wchar_t const *pwszName, KU32 cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + wchar_t const *pwszShortName, KU32 cwcShortName, +#endif + KU8 bObjType, KFSLOOKUPERROR *penmError) +{ + /* Convert names to ANSI first so we know their lengths. */ + char szName[KFSCACHE_CFG_MAX_ANSI_NAME]; + int cchName = WideCharToMultiByte(CP_ACP, 0, pwszName, cwcName, szName, sizeof(szName) - 1, NULL, NULL); + if (cchName >= 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + char szShortName[12*3 + 1]; + int cchShortName = 0; + if ( cwcShortName == 0 + || (cchShortName = WideCharToMultiByte(CP_ACP, 0, pwszShortName, cwcShortName, + szShortName, sizeof(szShortName) - 1, NULL, NULL)) > 0) +#endif + { + /* No locking needed here, kFsCacheCreateObject takes care of that. */ + return kFsCacheCreateObject(pCache, pParent, + szName, cchName, pwszName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + szShortName, cchShortName, pwszShortName, cwcShortName, +#endif + bObjType, penmError); + } + } + *penmError = KFSLOOKUPERROR_ANSI_CONVERSION_ERROR; + return NULL; +} + + +/** + * Creates a missing object. + * + * This is used for caching negative results. + * + * @returns Pointer to the newly created object on success (already linked into + * pParent). No reference. + * + * NULL on failure. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pchName The name. + * @param cchName The length of the name. + * @param penmError Where to return failure explanations. + */ +static PKFSOBJ kFsCacheCreateMissingA(PKFSCACHE pCache, PKFSDIR pParent, const char *pchName, KU32 cchName, + KFSLOOKUPERROR *penmError) +{ + /* + * Just convert the name to UTF-16 and call kFsCacheCreateObject to do the job. + */ + wchar_t wszName[KFSCACHE_CFG_MAX_PATH]; + int cwcName = MultiByteToWideChar(CP_ACP, 0, pchName, cchName, wszName, KFSCACHE_CFG_MAX_UTF16_NAME - 1); + if (cwcName > 0) + { + /** @todo check that it actually doesn't exists before we add it. We should not + * trust the directory enumeration here, or maybe we should?? */ + + PKFSOBJ pMissing = kFsCacheCreateObject(pCache, pParent, pchName, cchName, wszName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, pParent, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + return NULL; + } + *penmError = KFSLOOKUPERROR_UTF16_CONVERSION_ERROR; + return NULL; +} + + +/** + * Creates a missing object, UTF-16 version. + * + * This is used for caching negative results. + * + * @returns Pointer to the newly created object on success (already linked into + * pParent). No reference. + * + * NULL on failure. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pwcName The name. + * @param cwcName The length of the name. + * @param penmError Where to return failure explanations. + */ +static PKFSOBJ kFsCacheCreateMissingW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwcName, KU32 cwcName, + KFSLOOKUPERROR *penmError) +{ + /** @todo check that it actually doesn't exists before we add it. We should not + * trust the directory enumeration here, or maybe we should?? */ + PKFSOBJ pMissing = kFsCacheCreateObjectW(pCache, pParent, pwcName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, pParent, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + return NULL; +} + + +/** + * Does the growing of names. + * + * @returns pCur + * @param pCache The cache. + * @param pCur The object. + * @param pchName The name (not necessarily terminated). + * @param cchName Name length. + * @param pwcName The UTF-16 name (not necessarily terminated). + * @param cwcName The length of the UTF-16 name in wchar_t's. + * @param pchShortName The short name. + * @param cchShortName The length of the short name. This is 0 if no short + * name. + * @param pwcShortName The short UTF-16 name. + * @param cwcShortName The length of the short UTF-16 name. This is 0 if + * no short name. + */ +static PKFSOBJ kFsCacheRefreshGrowNames(PKFSCACHE pCache, PKFSOBJ pCur, + const char *pchName, KU32 cchName, + wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , const char *pchShortName, KU32 cchShortName, + wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + PKFSOBJNAMEALLOC pNameAlloc; + char *pch; + KU32 cbNeeded; + + pCache->cNameGrowths++; + + /* + * Figure out our requirements. + */ + cbNeeded = sizeof(KU32) + cchName + 1; +#ifdef KFSCACHE_CFG_UTF16 + cbNeeded += (cwcName + 1) * sizeof(wchar_t); +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + cbNeeded += cchShortName + !!cchShortName; +# ifdef KFSCACHE_CFG_UTF16 + cbNeeded += (cwcShortName + !!cwcShortName) * sizeof(wchar_t); +# endif +#endif + cbNeeded = K_ALIGN_Z(cbNeeded, 8); /* Memory will likely be 8 or 16 byte aligned, so we might just claim it. */ + + /* + * Allocate memory. + */ + pNameAlloc = pCur->pNameAlloc; + if (!pNameAlloc) + { + pNameAlloc = (PKFSOBJNAMEALLOC)kHlpAlloc(cbNeeded); + if (!pNameAlloc) + return pCur; + pCache->cbObjects += cbNeeded; + pCur->pNameAlloc = pNameAlloc; + pNameAlloc->cb = cbNeeded; + } + else if (pNameAlloc->cb < cbNeeded) + { + pNameAlloc = (PKFSOBJNAMEALLOC)kHlpRealloc(pNameAlloc, cbNeeded); + if (!pNameAlloc) + return pCur; + pCache->cbObjects += cbNeeded - pNameAlloc->cb; + pCur->pNameAlloc = pNameAlloc; + pNameAlloc->cb = cbNeeded; + } + + /* + * Copy out the new names, starting with the wide char ones to avoid misaligning them. + */ + pch = &pNameAlloc->abSpace[0]; + +#ifdef KFSCACHE_CFG_UTF16 + pCur->pwszName = (wchar_t *)pch; + pCur->cwcName = cwcName; + pch = kHlpMemPCopy(pch, pwcName, cwcName * sizeof(wchar_t)); + *pch++ = '\0'; + *pch++ = '\0'; + +# ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0) + { + pCur->pwszShortName = pCur->pwszName; + pCur->cwcShortName = pCur->cwcName; + } + else + { + pCur->pwszShortName = (wchar_t *)pch; + pCur->cwcShortName = cwcShortName; + pch = kHlpMemPCopy(pch, pwcShortName, cwcShortName * sizeof(wchar_t)); + *pch++ = '\0'; + *pch++ = '\0'; + } +# endif +#endif + + pCur->pszName = pch; + pCur->cchName = cchName; + pch = kHlpMemPCopy(pch, pchName, cchName); + *pch++ = '\0'; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cchShortName == 0) + { + pCur->pszShortName = pCur->pszName; + pCur->cchShortName = pCur->cchName; + } + else + { + pCur->pszShortName = pch; + pCur->cchShortName = cchShortName; + pch = kHlpMemPCopy(pch, pchShortName, cchShortName); + *pch++ = '\0'; + } +#endif + + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that refreshes the file ID value on an + * object found by name. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param idFile The file ID. + */ +static PKFSOBJ kFsCacheDirRefreshOldChildFileId(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, KI64 idFile) +{ + KFSCACHE_LOG(("Refreshing %s/%s/ - %s changed file ID from %#llx -> %#llx...\n", + pCur->pParent->Obj.pParent->Obj.pszName, pCur->pParent->Obj.pszName, pCur->pszName, + pCur->Stats.st_ino, idFile)); + pCur->Stats.st_ino = idFile; + /** @todo inform user data items... */ + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that checks the names after an old object + * has been found the file ID. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +static PKFSOBJ kFsCacheDirRefreshOldChildName(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + char szName[KFSCACHE_CFG_MAX_ANSI_NAME]; + int cchName; + + pDirRePop->pCache->cNameChanges++; + + /* + * Convert the names to ANSI first, that way we know all the lengths. + */ + cchName = WideCharToMultiByte(CP_ACP, 0, pwcName, cwcName, szName, sizeof(szName) - 1, NULL, NULL); + if (cchName >= 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + char szShortName[12*3 + 1]; + int cchShortName = 0; + if ( cwcShortName == 0 + || (cchShortName = WideCharToMultiByte(CP_ACP, 0, pwcShortName, cwcShortName, + szShortName, sizeof(szShortName) - 1, NULL, NULL)) > 0) +#endif + { + /* + * Shortening is easy for non-directory objects, for + * directory object we're only good when the length doesn't change + * on any of the components (cchParent et al). + * + * This deals with your typical xxxx.ext.tmp -> xxxx.ext renames. + */ + if ( cchName <= pCur->cchName +#ifdef KFSCACHE_CFG_UTF16 + && cwcName <= pCur->cwcName +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + && ( cchShortName == 0 + || ( cchShortName <= pCur->cchShortName + && pCur->pszShortName != pCur->pszName +# ifdef KFSCACHE_CFG_UTF16 + && cwcShortName <= pCur->cwcShortName + && pCur->pwszShortName != pCur->pwszName +# endif + ) + ) +#endif + ) + { + if ( pCur->bObjType != KFSOBJ_TYPE_DIR + || ( cchName == pCur->cchName +#ifdef KFSCACHE_CFG_UTF16 + && cwcName == pCur->cwcName +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + && ( cchShortName == 0 + || ( cchShortName == pCur->cchShortName +# ifdef KFSCACHE_CFG_UTF16 + && cwcShortName == pCur->cwcShortName + ) +# endif + ) +#endif + ) + ) + { + KFSCACHE_LOG(("Refreshing %ls - name changed to '%*.*ls'\n", pCur->pwszName, cwcName, cwcName, pwcName)); + *(char *)kHlpMemPCopy((void *)pCur->pszName, szName, cchName) = '\0'; + pCur->cchName = cchName; +#ifdef KFSCACHE_CFG_UTF16 + *(wchar_t *)kHlpMemPCopy((void *)pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) = '\0'; + pCur->cwcName = cwcName; +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + *(char *)kHlpMemPCopy((void *)pCur->pszShortName, szShortName, cchShortName) = '\0'; + pCur->cchShortName = cchShortName; +# ifdef KFSCACHE_CFG_UTF16 + *(wchar_t *)kHlpMemPCopy((void *)pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) = '\0'; + pCur->cwcShortName = cwcShortName; +# endif +#endif + return pCur; + } + } + + return kFsCacheRefreshGrowNames(pDirRePop->pCache, pCur, szName, cchName, pwcName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + szShortName, cchShortName, pwcShortName, cwcShortName +#endif + ); + } + } + + fprintf(stderr, "kFsCacheDirRefreshOldChildName: WideCharToMultiByte error\n"); + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that checks the names after an old object + * has been found by the file ID. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +K_INLINE PKFSOBJ kFsCacheDirCheckOldChildName(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + if ( pCur->cwcName == cwcName + && kHlpMemComp(pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) == 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0 + ? pCur->pwszShortName == pCur->pwszName + || ( pCur->cwcShortName == cwcName + && kHlpMemComp(pCur->pwszShortName, pCur->pwszName, cwcName * sizeof(wchar_t)) == 0) + : pCur->cwcShortName == cwcShortName + && kHlpMemComp(pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) == 0 ) +#endif + { + return pCur; + } + } +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName); +#endif +} + + +/** + * Worker for kFsCachePopuplateOrRefreshDir that locates an old child object + * while re-populating a directory. + * + * @returns Pointer to the existing object if found, NULL if not. + * @param pDirRePop Repopulation data. + * @param idFile The file ID, 0 if none. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +static PKFSOBJ kFsCacheDirFindOldChildSlow(PKFSDIRREPOP pDirRePop, KI64 idFile, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + KU32 cOldChildren = pDirRePop->cOldChildren; + KU32 const iNextOldChild = K_MIN(pDirRePop->iNextOldChild, cOldChildren - 1); + KU32 iCur; + KI32 cInc; + KI32 cDirLefts; + + kHlpAssertReturn(cOldChildren > 0, NULL); + + /* + * Search by file ID first, if we've got one. + * ASSUMES that KU32 wraps around when -1 is added to 0. + */ + if ( idFile != 0 + && idFile != KI64_MAX + && idFile != KI64_MIN) + { + cInc = pDirRePop->cNextOldChildInc; + kHlpAssert(cInc == -1 || cInc == 1); + for (cDirLefts = 2; cDirLefts > 0; cDirLefts--) + { + for (iCur = iNextOldChild; iCur < cOldChildren; iCur += cInc) + { + PKFSOBJ pCur = pDirRePop->papOldChildren[iCur]; + if (pCur->Stats.st_ino == idFile) + { + /* Remove it and check the name. */ + pDirRePop->cOldChildren = --cOldChildren; + if (iCur < cOldChildren) + pDirRePop->papOldChildren[iCur] = pDirRePop->papOldChildren[cOldChildren]; + else + cInc = -1; + pDirRePop->cNextOldChildInc = cInc; + pDirRePop->iNextOldChild = iCur + cInc; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + } + cInc = -cInc; + } + } + + /* + * Search by name. + * ASSUMES that KU32 wraps around when -1 is added to 0. + */ + cInc = pDirRePop->cNextOldChildInc; + kHlpAssert(cInc == -1 || cInc == 1); + for (cDirLefts = 2; cDirLefts > 0; cDirLefts--) + { + for (iCur = iNextOldChild; iCur < cOldChildren; iCur += cInc) + { + PKFSOBJ pCur = pDirRePop->papOldChildren[iCur]; + if ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + { + /* Do this first so the compiler can share the rest with the above file ID return. */ + if (pCur->Stats.st_ino == idFile) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildFileId(pDirRePop, pCur, idFile); + + /* Remove it and check the name. */ + pDirRePop->cOldChildren = --cOldChildren; + if (iCur < cOldChildren) + pDirRePop->papOldChildren[iCur] = pDirRePop->papOldChildren[cOldChildren]; + else + cInc = -1; + pDirRePop->cNextOldChildInc = cInc; + pDirRePop->iNextOldChild = iCur + cInc; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + } + cInc = -cInc; + } + + return NULL; +} + + + +/** + * Worker for kFsCachePopuplateOrRefreshDir that locates an old child object + * while re-populating a directory. + * + * @returns Pointer to the existing object if found, NULL if not. + * @param pDirRePop Repopulation data. + * @param idFile The file ID, 0 if none. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +K_INLINE PKFSOBJ kFsCacheDirFindOldChild(PKFSDIRREPOP pDirRePop, KI64 idFile, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + /* + * We only check the iNextOldChild element here, hoping that the compiler + * will actually inline this code, letting the slow version of the function + * do the rest. + */ + KU32 cOldChildren = pDirRePop->cOldChildren; + if (cOldChildren > 0) + { + KU32 const iNextOldChild = K_MIN(pDirRePop->iNextOldChild, cOldChildren - 1); + PKFSOBJ pCur = pDirRePop->papOldChildren[iNextOldChild]; + + if ( pCur->Stats.st_ino == idFile + && idFile != 0 + && idFile != KI64_MAX + && idFile != KI64_MIN) + pCur = kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); + else if ( pCur->cwcName == cwcName + && kHlpMemComp(pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) == 0) + { + if (pCur->Stats.st_ino == idFile) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildFileId(pDirRePop, pCur, idFile); + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0 + ? pCur->pwszShortName == pCur->pwszName + || ( pCur->cwcShortName == cwcName + && kHlpMemComp(pCur->pwszShortName, pCur->pwszName, cwcName * sizeof(wchar_t)) == 0) + : pCur->cwcShortName == cwcShortName + && kHlpMemComp(pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) == 0 ) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + else + pCur = NULL; + if (pCur) + { + /* + * Got a match. Remove the child from the array, replacing it with + * the last element. (This means we're reversing the second half of + * the elements, which is why we need cNextOldChildInc.) + */ + pDirRePop->cOldChildren = --cOldChildren; + if (iNextOldChild < cOldChildren) + pDirRePop->papOldChildren[iNextOldChild] = pDirRePop->papOldChildren[cOldChildren]; + pDirRePop->iNextOldChild = iNextOldChild + pDirRePop->cNextOldChildInc; + return pCur; + } + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirFindOldChildSlow(pDirRePop, idFile, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirFindOldChildSlow(pDirRePop, idFile, pwcName, cwcName); +#endif + } + + return NULL; +} + + + +/** + * Does the initial directory populating or refreshes it if it has been + * invalidated. + * + * This assumes the parent directory is opened. + * + * @returns K_TRUE on success, K_FALSE on error. + * @param pCache The cache. + * @param pDir The directory. + * @param penmError Where to store K_FALSE explanation. + */ +static KBOOL kFsCachePopuplateOrRefreshDir(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError) +{ + KBOOL fRefreshing = K_FALSE; + KFSDIRREPOP DirRePop = { NULL, 0, 0, 0, NULL }; + MY_UNICODE_STRING UniStrStar = { 1 * sizeof(wchar_t), 2 * sizeof(wchar_t), L"*" }; + FILETIME Now; + + /** @todo May have to make this more flexible wrt information classes since + * older windows versions (XP, w2K) might not correctly support the + * ones with file ID on all file systems. */ +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_INFORMATION_CLASS const enmInfoClassWithId = MyFileIdBothDirectoryInformation; + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdBothDirectoryInformation; +#else + MY_FILE_INFORMATION_CLASS const enmInfoClassWithId = MyFileIdFullDirectoryInformation; + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdFullDirectoryInformation; +#endif + MY_NTSTATUS rcNt; + MY_IO_STATUS_BLOCK Ios; + union + { + /* Include the structures for better alignment. */ + MY_FILE_ID_BOTH_DIR_INFORMATION WithId; + MY_FILE_ID_FULL_DIR_INFORMATION NoId; + /** Buffer padding. We're using a 56KB buffer here to avoid size troubles + * with CIFS and such that starts at 64KB. */ + KU8 abBuf[56*1024]; + } uBuf; + + + /* + * Open the directory. + */ + if (pDir->hDir == INVALID_HANDLE_VALUE) + { + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_UNICODE_STRING UniStr; + + kHlpAssert(!pDir->fPopulated); + + Ios.Information = -1; + Ios.u.Status = -1; + + UniStr.Buffer = (wchar_t *)pDir->Obj.pwszName; + UniStr.Length = (USHORT)(pDir->Obj.cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssertStmtReturn(pDir->Obj.pParent, *penmError = KFSLOOKUPERROR_INTERNAL_ERROR, K_FALSE); + kHlpAssertStmtReturn(pDir->Obj.pParent->hDir != INVALID_HANDLE_VALUE, *penmError = KFSLOOKUPERROR_INTERNAL_ERROR, K_FALSE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pDir->Obj.pParent->hDir, NULL /*pSecAttr*/); + + /** @todo FILE_OPEN_REPARSE_POINT? */ + rcNt = g_pfnNtCreateFile(&pDir->hDir, + FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL, /*cbFileInitialAlloc */ + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, /*pEaBuffer*/ + 0); /*cbEaBuffer*/ + if (MY_NT_SUCCESS(rcNt)) + { /* likely */ } + else + { + pDir->hDir = INVALID_HANDLE_VALUE; + *penmError = KFSLOOKUPERROR_DIR_OPEN_ERROR; + return K_FALSE; + } + } + /* + * When re-populating, we replace papChildren in the directory and pick + * from the old one as we go along. + */ + else if (pDir->fPopulated) + { + KU32 cAllocated; + void *pvNew; + + /* Make sure we really need to do this first. */ + if (!pDir->fNeedRePopulating) + { + if ( pDir->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pDir->Obj.uCacheGen == pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + return K_TRUE; + if ( kFsCacheRefreshObj(pCache, &pDir->Obj, penmError) + && !pDir->fNeedRePopulating) + return K_TRUE; + } + + /* Yes we do need to. */ + cAllocated = K_ALIGN_Z(pDir->cChildren, 16); + pvNew = kHlpAlloc(sizeof(pDir->papChildren[0]) * cAllocated); + if (pvNew) + { + DirRePop.papOldChildren = pDir->papChildren; + DirRePop.cOldChildren = pDir->cChildren; + DirRePop.iNextOldChild = 0; + DirRePop.cNextOldChildInc = 1; + DirRePop.pCache = pCache; + + pDir->cChildren = 0; + pDir->cChildrenAllocated = cAllocated; + pDir->papChildren = (PKFSOBJ *)pvNew; + } + else + { + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return K_FALSE; + } + + fRefreshing = K_TRUE; + } + if (!fRefreshing) + KFSCACHE_LOG(("Populating %s...\n", pDir->Obj.pszName)); + else + KFSCACHE_LOG(("Refreshing %s...\n", pDir->Obj.pszName)); + + /* + * Enumerate the directory content. + * + * Note! The "*" filter is necessary because kFsCacheRefreshObj may have + * previously quried a single file name and just passing NULL would + * restart that single file name query. + */ + GetSystemTimeAsFileTime(&Now); + pDir->iLastPopulated = ((KI64)Now.dwHighDateTime << 32) | Now.dwLowDateTime; + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryDirectoryFile(pDir->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + FALSE, /* fReturnSingleEntry */ + &UniStrStar, /* Filter / restart pos. */ + TRUE); /* fRestartScan */ + while (MY_NT_SUCCESS(rcNt)) + { + /* + * Process the entries in the buffer. + */ + KSIZE offBuf = 0; + for (;;) + { + union + { + KU8 *pb; +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_ID_BOTH_DIR_INFORMATION *pWithId; + MY_FILE_BOTH_DIR_INFORMATION *pNoId; +#else + MY_FILE_ID_FULL_DIR_INFORMATION *pWithId; + MY_FILE_FULL_DIR_INFORMATION *pNoId; +#endif + } uPtr; + PKFSOBJ pCur; + KU32 offNext; + KU32 cbMinCur; + wchar_t *pwchFilename; + + /* ASSUME only the FileName member differs between the two structures. */ + uPtr.pb = &uBuf.abBuf[offBuf]; + if (enmInfoClass == enmInfoClassWithId) + { + pwchFilename = &uPtr.pWithId->FileName[0]; + cbMinCur = (KU32)((uintptr_t)&uPtr.pWithId->FileName[0] - (uintptr_t)uPtr.pWithId); + cbMinCur += uPtr.pNoId->FileNameLength; + } + else + { + pwchFilename = &uPtr.pNoId->FileName[0]; + cbMinCur = (KU32)((uintptr_t)&uPtr.pNoId->FileName[0] - (uintptr_t)uPtr.pNoId); + cbMinCur += uPtr.pNoId->FileNameLength; + } + + /* We need to skip the '.' and '..' entries. */ + if ( *pwchFilename != '.' + || uPtr.pNoId->FileNameLength > 4 + || !( uPtr.pNoId->FileNameLength == 2 + || ( uPtr.pNoId->FileNameLength == 4 + && pwchFilename[1] == '.') ) + ) + { + KBOOL fRc; + KU8 const bObjType = uPtr.pNoId->FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? KFSOBJ_TYPE_DIR + : uPtr.pNoId->FileAttributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT) + ? KFSOBJ_TYPE_OTHER : KFSOBJ_TYPE_FILE; + + /* + * If refreshing, we must first see if this directory entry already + * exists. + */ + if (!fRefreshing) + pCur = NULL; + else + { + pCur = kFsCacheDirFindOldChild(&DirRePop, + enmInfoClass == enmInfoClassWithId ? uPtr.pWithId->FileId.QuadPart : 0, + pwchFilename, uPtr.pWithId->FileNameLength / sizeof(wchar_t) +#ifdef KFSCACHE_CFG_SHORT_NAMES + , uPtr.pWithId->ShortName, uPtr.pWithId->ShortNameLength / sizeof(wchar_t) +#endif + ); + if (pCur) + { + if (pCur->bObjType == bObjType) + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + { + PKFSDIR pCurDir = (PKFSDIR)pCur; + if ( !pCurDir->fPopulated + || ( pCurDir->iLastWrite == uPtr.pWithId->LastWriteTime.QuadPart + && (pCur->fFlags & KFSOBJ_F_WORKING_DIR_MTIME) + && pCurDir->iLastPopulated - pCurDir->iLastWrite + >= KFSCACHE_MIN_LAST_POPULATED_VS_WRITE )) + { /* kind of likely */ } + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s/ needs re-populating...\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName)); + pCurDir->fNeedRePopulating = K_TRUE; + } + } + if (pCur->uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pCur->uCacheGen = pCache->auGenerations[pCur->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else if (pCur->bObjType == KFSOBJ_TYPE_MISSING) + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s appeared as %u, was missing.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName, bObjType)); + pCur->bObjType = bObjType; + if (pCur->uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pCur->uCacheGen = pCache->auGenerations[pCur->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s changed type from %u to %u! Dropping old object.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName, + pCur->bObjType, bObjType)); + kFsCacheObjRelease(pCache, pCur); + pCur = NULL; + } + } + else + KFSCACHE_LOG(("Refreshing %s/%s/ - %*.*ls added.\n", pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, + uPtr.pNoId->FileNameLength / sizeof(wchar_t), uPtr.pNoId->FileNameLength / sizeof(wchar_t), + pwchFilename)); + } + + if (!pCur) + { + /* + * Create the entry (not linked yet). + */ + pCur = kFsCacheCreateObjectW(pCache, pDir, pwchFilename, uPtr.pNoId->FileNameLength / sizeof(wchar_t), +#ifdef KFSCACHE_CFG_SHORT_NAMES + uPtr.pNoId->ShortName, uPtr.pNoId->ShortNameLength / sizeof(wchar_t), +#endif + bObjType, penmError); + if (!pCur) + return K_FALSE; + kHlpAssert(pCur->cRefs == 1); + } + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdBothDirInfo(&pCur->Stats, uPtr.pWithId); + else + birdStatFillFromFileBothDirInfo(&pCur->Stats, uPtr.pNoId); +#else + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdFullDirInfo(&pCur->Stats, uPtr.pWithId); + else + birdStatFillFromFileFullDirInfo(&pCur->Stats, uPtr.pNoId); +#endif + pCur->Stats.st_dev = pDir->uDevNo; + pCur->fHaveStats = K_TRUE; + + /* + * Add the entry to the directory. + */ + fRc = kFsCacheDirAddChild(pCache, pDir, pCur, penmError); + kFsCacheObjRelease(pCache, pCur); + if (fRc) + { /* likely */ } + else + { + rcNt = STATUS_NO_MEMORY; + break; + } + } + /* + * When seeing '.' we update the directory info. + */ + else if (uPtr.pNoId->FileNameLength == 2) + { + pDir->iLastWrite = uPtr.pNoId->LastWriteTime.QuadPart; +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdBothDirInfo(&pDir->Obj.Stats, uPtr.pWithId); + else + birdStatFillFromFileBothDirInfo(&pDir->Obj.Stats, uPtr.pNoId); +#else + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdFullDirInfo(&pDir->Obj.Stats, uPtr.pWithId); + else + birdStatFillFromFileFullDirInfo(&pDir->Obj.Stats, uPtr.pNoId); +#endif + } + + /* + * Advance. + */ + offNext = uPtr.pNoId->NextEntryOffset; + if ( offNext >= cbMinCur + && offNext < sizeof(uBuf)) + offBuf += offNext; + else + break; + } + + /* + * Read the next chunk. + */ + rcNt = g_pfnNtQueryDirectoryFile(pDir->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + FALSE, /* fReturnSingleEntry */ + &UniStrStar, /* Filter / restart pos. */ + FALSE); /* fRestartScan */ + } + + if (rcNt == MY_STATUS_NO_MORE_FILES) + { + /* + * If refreshing, add missing children objects and ditch the rest. + * We ignore errors while adding missing children (lazy bird). + */ + if (!fRefreshing) + { /* more likely */ } + else + { + while (DirRePop.cOldChildren > 0) + { + KFSLOOKUPERROR enmErrorIgn; + PKFSOBJ pOldChild = DirRePop.papOldChildren[--DirRePop.cOldChildren]; + if (pOldChild->bObjType == KFSOBJ_TYPE_MISSING) + kFsCacheDirAddChild(pCache, pDir, pOldChild, &enmErrorIgn); + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s was removed.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pOldChild->pszName)); + kHlpAssert(pOldChild->bObjType != KFSOBJ_TYPE_DIR); + /* Remove from hash table. */ + if (pOldChild->uNameHash != 0) + { + KU32 idx = pOldChild->uNameHash & pDir->fHashTabMask; + PKFSOBJ pPrev = pDir->papHashTab[idx]; + if (pPrev == pOldChild) + pDir->papHashTab[idx] = pOldChild->pNextNameHash; + else + { + while (pPrev && pPrev->pNextNameHash != pOldChild) + pPrev = pPrev->pNextNameHash; + kHlpAssert(pPrev); + if (pPrev) + pPrev->pNextNameHash = pOldChild->pNextNameHash; + } + pOldChild->uNameHash = 0; + } + } + kFsCacheObjRelease(pCache, pOldChild); + } + kHlpFree(DirRePop.papOldChildren); + } + + /* + * Mark the directory as fully populated and up to date. + */ + pDir->fPopulated = K_TRUE; + pDir->fNeedRePopulating = K_FALSE; + if (pDir->Obj.uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pDir->Obj.uCacheGen = pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + return K_TRUE; + } + + /* + * If we failed during refresh, add back remaining old children. + */ + if (!fRefreshing) + { + while (DirRePop.cOldChildren > 0) + { + KFSLOOKUPERROR enmErrorIgn; + PKFSOBJ pOldChild = DirRePop.papOldChildren[--DirRePop.cOldChildren]; + kFsCacheDirAddChild(pCache, pDir, pOldChild, &enmErrorIgn); + kFsCacheObjRelease(pCache, pOldChild); + } + kHlpFree(DirRePop.papOldChildren); + } + + kHlpAssertMsgFailed(("%#x\n", rcNt)); + *penmError = KFSLOOKUPERROR_DIR_READ_ERROR; + return K_TRUE; +} + + +/** + * Does the initial directory populating or refreshes it if it has been + * invalidated. + * + * This assumes the parent directory is opened. + * + * @returns K_TRUE on success, K_FALSE on error. + * @param pCache The cache. + * @param pDir The directory. + * @param penmError Where to store K_FALSE explanation. Optional. + */ +KBOOL kFsCacheDirEnsurePopuplated(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError) +{ + KFSLOOKUPERROR enmIgnored; + KBOOL fRet; + KFSCACHE_LOCK(pCache); + if ( pDir->fPopulated + && !pDir->fNeedRePopulating + && ( pDir->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pDir->Obj.uCacheGen == pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + fRet = K_TRUE; + else + fRet = kFsCachePopuplateOrRefreshDir(pCache, pDir, penmError ? penmError : &enmIgnored); + KFSCACHE_UNLOCK(pCache); + return fRet; +} + + +/** + * Checks whether the modified timestamp differs on this directory. + * + * @returns K_TRUE if possibly modified, K_FALSE if definitely not modified. + * @param pDir The directory.. + */ +static KBOOL kFsCacheDirIsModified(PKFSDIR pDir) +{ + if ( pDir->hDir != INVALID_HANDLE_VALUE + && (pDir->Obj.fFlags & KFSOBJ_F_WORKING_DIR_MTIME) ) + { + if (!pDir->fNeedRePopulating) + { + MY_IO_STATUS_BLOCK Ios; + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_NTSTATUS rcNt; + + Ios.Information = -1; + Ios.u.Status = -1; + + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &BasicInfo, sizeof(BasicInfo), MyFileBasicInformation); + if (MY_NT_SUCCESS(rcNt)) + { + if ( BasicInfo.LastWriteTime.QuadPart != pDir->iLastWrite + || pDir->iLastPopulated - pDir->iLastWrite < KFSCACHE_MIN_LAST_POPULATED_VS_WRITE) + { + pDir->fNeedRePopulating = K_TRUE; + return K_TRUE; + } + return K_FALSE; + } + } + } + /* The cache root never changes. */ + else if (!pDir->Obj.pParent) + return K_FALSE; + + return K_TRUE; +} + + +static KBOOL kFsCacheRefreshMissing(PKFSCACHE pCache, PKFSOBJ pMissing, KFSLOOKUPERROR *penmError) +{ + /* + * If we can, we start by checking whether the parent directory + * has been modified. If it has, we need to check if this entry + * was added or not, most likely it wasn't added. + */ + if (!kFsCacheDirIsModified(pMissing->pParent)) + { + KFSCACHE_LOG(("Parent of missing not written to %s/%s\n", pMissing->pParent->Obj.pszName, pMissing->pszName)); + pMissing->uCacheGen = pCache->auGenerationsMissing[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else + { + MY_UNICODE_STRING UniStr; + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_NTSTATUS rcNt; + + UniStr.Buffer = (wchar_t *)pMissing->pwszName; + UniStr.Length = (USHORT)(pMissing->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pMissing->pParent->hDir != INVALID_HANDLE_VALUE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pMissing->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtQueryAttributesFile(&ObjAttr, &BasicInfo); + if (!MY_NT_SUCCESS(rcNt)) + { + /* + * Probably more likely that a missing node stays missing. + */ + pMissing->uCacheGen = pCache->auGenerationsMissing[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + KFSCACHE_LOG(("Still missing %s/%s\n", pMissing->pParent->Obj.pszName, pMissing->pszName)); + } + else + { + /* + * We must metamorphose this node. This is tedious business + * because we need to check the file name casing. We might + * just as well update the parent directory... + */ + KU8 const bObjType = BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? KFSOBJ_TYPE_DIR + : BasicInfo.FileAttributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT) + ? KFSOBJ_TYPE_OTHER : KFSOBJ_TYPE_FILE; + + KFSCACHE_LOG(("Birth of %s/%s as %d with attribs %#x...\n", + pMissing->pParent->Obj.pszName, pMissing->pszName, bObjType, BasicInfo.FileAttributes)); + pMissing->bObjType = bObjType; + /* (auGenerations[] - 1): make sure it's not considered up to date */ + pMissing->uCacheGen = pCache->auGenerations[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] - 1; + /* Trigger parent directory repopulation. */ + if (pMissing->pParent->fPopulated) + pMissing->pParent->fNeedRePopulating = K_TRUE; +/** + * @todo refresh missing object names when it appears. + */ + } + } + + return K_TRUE; +} + + +static KBOOL kFsCacheRefreshMissingIntermediateDir(PKFSCACHE pCache, PKFSOBJ pMissing, KFSLOOKUPERROR *penmError) +{ + if (kFsCacheRefreshMissing(pCache, pMissing, penmError)) + { + if ( pMissing->bObjType == KFSOBJ_TYPE_DIR + || pMissing->bObjType == KFSOBJ_TYPE_MISSING) + return K_TRUE; + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + } + + return K_FALSE; +} + + +/** + * Generic object refresh. + * + * This does not refresh the content of directories. + * + * @returns K_TRUE on success. K_FALSE and *penmError on failure. + * @param pCache The cache. + * @param pObj The object. + * @param penmError Where to return error info. + */ +static KBOOL kFsCacheRefreshObj(PKFSCACHE pCache, PKFSOBJ pObj, KFSLOOKUPERROR *penmError) +{ + KBOOL fRc; + + /* + * Since we generally assume nothing goes away in this cache, we only really + * have a hard time with negative entries. So, missing stuff goes to + * complicated land. + */ + if (pObj->bObjType == KFSOBJ_TYPE_MISSING) + fRc = kFsCacheRefreshMissing(pCache, pObj, penmError); + else + { + /* + * This object is supposed to exist, so all we need to do is query essential + * stats again. Since we've already got handles on directories, there are + * two ways to go about this. + */ + union + { + MY_FILE_NETWORK_OPEN_INFORMATION FullInfo; + MY_FILE_STANDARD_INFORMATION StdInfo; +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_ID_BOTH_DIR_INFORMATION WithId; + //MY_FILE_BOTH_DIR_INFORMATION NoId; +#else + MY_FILE_ID_FULL_DIR_INFORMATION WithId; + //MY_FILE_FULL_DIR_INFORMATION NoId; +#endif + KU8 abPadding[ sizeof(wchar_t) * KFSCACHE_CFG_MAX_UTF16_NAME + + sizeof(MY_FILE_ID_BOTH_DIR_INFORMATION)]; + } uBuf; + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + if ( pObj->bObjType != KFSOBJ_TYPE_DIR + || ((PKFSDIR)pObj)->hDir == INVALID_HANDLE_VALUE) + { +#if 1 + /* This always works and doesn't mess up NtQueryDirectoryFile. */ + MY_UNICODE_STRING UniStr; + MY_OBJECT_ATTRIBUTES ObjAttr; + + UniStr.Buffer = (wchar_t *)pObj->pwszName; + UniStr.Length = (USHORT)(pObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pObj->pParent->hDir != INVALID_HANDLE_VALUE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pObj->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &uBuf.FullInfo); + if (MY_NT_SUCCESS(rcNt)) + { + pObj->Stats.st_size = uBuf.FullInfo.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.FullInfo.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.FullInfo.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.FullInfo.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + } +#else + /* This alternative lets us keep the inode number up to date and + detect name case changes. + Update: This doesn't work on windows 7, it ignores the UniStr + and continue with the "*" search. So, we're using the + above query instead for the time being. */ + MY_UNICODE_STRING UniStr; +# ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdBothDirectoryInformation; +# else + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdFullDirectoryInformation; +# endif + + UniStr.Buffer = (wchar_t *)pObj->pwszName; + UniStr.Length = (USHORT)(pObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pObj->pParent->hDir != INVALID_HANDLE_VALUE); + + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryDirectoryFile(pObj->pParent->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + TRUE, /* fReturnSingleEntry */ + &UniStr, /* Filter / restart pos. */ + TRUE); /* fRestartScan */ + + if (MY_NT_SUCCESS(rcNt)) + { + if (pObj->Stats.st_ino == uBuf.WithId.FileId.QuadPart) + KFSCACHE_LOG(("Refreshing %s/%s, no ID change...\n", pObj->pParent->Obj.pszName, pObj->pszName)); + else if ( pObj->cwcName == uBuf.WithId.FileNameLength / sizeof(wchar_t) +# ifdef KFSCACHE_CFG_SHORT_NAMES + && ( uBuf.WithId.ShortNameLength == 0 + ? pObj->pwszName == pObj->pwszShortName + || ( pObj->cwcName == pObj->cwcShortName + && memcmp(pObj->pwszName, pObj->pwszShortName, pObj->cwcName * sizeof(wchar_t)) == 0) + : pObj->cwcShortName == uBuf.WithId.ShortNameLength / sizeof(wchar_t) + && memcmp(pObj->pwszShortName, uBuf.WithId.ShortName, uBuf.WithId.ShortNameLength) == 0 + ) +# endif + && memcmp(pObj->pwszName, uBuf.WithId.FileName, uBuf.WithId.FileNameLength) == 0 + ) + { + KFSCACHE_LOG(("Refreshing %s/%s, ID changed %#llx -> %#llx...\n", + pObj->pParent->Obj.pszName, pObj->pszName, pObj->Stats.st_ino, uBuf.WithId.FileId.QuadPart)); + pObj->Stats.st_ino = uBuf.WithId.FileId.QuadPart; + } + else + { + KFSCACHE_LOG(("Refreshing %s/%s, ID changed %#llx -> %#llx and names too...\n", + pObj->pParent->Obj.pszName, pObj->pszName, pObj->Stats.st_ino, uBuf.WithId.FileId.QuadPart)); + fprintf(stderr, "kFsCacheRefreshObj - ID + name change not implemented!!\n"); + fflush(stderr); + __debugbreak(); + pObj->Stats.st_ino = uBuf.WithId.FileId.QuadPart; + /** @todo implement as needed. */ + } + + pObj->Stats.st_size = uBuf.WithId.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.WithId.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.WithId.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.WithId.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.WithId.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.WithId.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.WithId.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + } +#endif + if (MY_NT_SUCCESS(rcNt)) + { + pObj->uCacheGen = pCache->auGenerations[pObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + fRc = K_TRUE; + } + else + { + /* ouch! */ + kHlpAssertMsgFailed(("%#x\n", rcNt)); + fprintf(stderr, "kFsCacheRefreshObj - rcNt=%#x on non-dir - not implemented!\n", rcNt); + __debugbreak(); + fRc = K_FALSE; + } + } + else + { + /* + * An open directory. Query information via the handle, the + * file ID shouldn't have been able to change, so we can use + * NtQueryInformationFile. Right... + */ + PKFSDIR pDir = (PKFSDIR)pObj; + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &uBuf.FullInfo, sizeof(uBuf.FullInfo), + MyFileNetworkOpenInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + pObj->Stats.st_size = uBuf.FullInfo.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.FullInfo.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.FullInfo.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.FullInfo.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + + if ( pDir->iLastWrite == uBuf.FullInfo.LastWriteTime.QuadPart + && (pObj->fFlags & KFSOBJ_F_WORKING_DIR_MTIME) + && pDir->iLastPopulated - pDir->iLastWrite >= KFSCACHE_MIN_LAST_POPULATED_VS_WRITE) + KFSCACHE_LOG(("Refreshing %s/%s/ - no re-populating necessary.\n", + pObj->pParent->Obj.pszName, pObj->pszName)); + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - needs re-populating...\n", + pObj->pParent->Obj.pszName, pObj->pszName)); + pDir->fNeedRePopulating = K_TRUE; +#if 0 + /* Refresh the link count. */ + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.s.Status; + if (MY_NT_SUCCESS(rcNt)) + pObj->Stats.st_nlink = StdInfo.NumberOfLinks; +#endif + } + } + if (MY_NT_SUCCESS(rcNt)) + { + pObj->uCacheGen = pCache->auGenerations[pObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + fRc = K_TRUE; + } + else + { + /* ouch! */ + kHlpAssertMsgFailed(("%#x\n", rcNt)); + fprintf(stderr, "kFsCacheRefreshObj - rcNt=%#x on dir - not implemented!\n", rcNt); + fflush(stderr); + __debugbreak(); + fRc = K_FALSE; + } + } + } + + return fRc; +} + + + +/** + * Looks up a drive letter. + * + * Will enter the drive if necessary. + * + * @returns Pointer to the root directory of the drive or an update-to-date + * missing node. + * @param pCache The cache. + * @param chLetter The uppercased drive letter. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupDrive(PKFSCACHE pCache, char chLetter, KU32 fFlags, KFSLOOKUPERROR *penmError) +{ + KU32 const uNameHash = chLetter - 'A'; + PKFSOBJ pCur = pCache->RootDir.papHashTab[uNameHash]; + + KU32 cLeft; + PKFSOBJ *ppCur; + MY_UNICODE_STRING NtPath; + wchar_t wszTmp[8]; + char szTmp[4]; + + /* + * Custom drive letter hashing. + */ + kHlpAssert((uNameHash & pCache->RootDir.fHashTabMask) == uNameHash); + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && pCur->cchName == 2 + && pCur->pszName[0] == chLetter + && pCur->pszName[1] == ':') + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + return pCur; + if ( (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissingIntermediateDir(pCache, pCur, penmError)) + return pCur; + return NULL; + } + pCur = pCur->pNextNameHash; + } + + /* + * Make 100% sure it's not there. + */ + cLeft = pCache->RootDir.cChildren; + ppCur = pCache->RootDir.papChildren; + while (cLeft-- > 0) + { + pCur = *ppCur++; + if ( pCur->cchName == 2 + && pCur->pszName[0] == chLetter + && pCur->pszName[1] == ':') + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + return pCur; + kHlpAssert(pCur->bObjType == KFSOBJ_TYPE_MISSING); + if ( (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissingIntermediateDir(pCache, pCur, penmError)) + return pCur; + return NULL; + } + } + + if (fFlags & KFSCACHE_LOOKUP_F_NO_INSERT) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; /* close enough */ + return NULL; + } + + /* + * Need to add it. We always keep the drive letters open for the benefit + * of kFsCachePopuplateOrRefreshDir and others. + */ + wszTmp[0] = szTmp[0] = chLetter; + wszTmp[1] = szTmp[1] = ':'; + wszTmp[2] = szTmp[2] = '\\'; + wszTmp[3] = '.'; + wszTmp[4] = '\0'; + szTmp[2] = '\0'; + + NtPath.Buffer = NULL; + NtPath.Length = 0; + NtPath.MaximumLength = 0; + if (g_pfnRtlDosPathNameToNtPathName_U(wszTmp, &NtPath, NULL, NULL)) + { + HANDLE hDir; + MY_NTSTATUS rcNt; + rcNt = birdOpenFileUniStr(NULL /*hRoot*/, + &NtPath, + FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &hDir); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + { + PKFSDIR pDir = (PKFSDIR)kFsCacheCreateObject(pCache, &pCache->RootDir, szTmp, 2, wszTmp, 2, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_DIR, penmError); + if (pDir) + { + /* + * We need a little bit of extra info for a drive root. These things are typically + * inherited by subdirectories down the tree, so, we do it all here for till that changes. + */ + union + { + MY_FILE_FS_VOLUME_INFORMATION VolInfo; + MY_FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + char abPadding[sizeof(MY_FILE_FS_VOLUME_INFORMATION) + 512]; + } uBuf; + MY_IO_STATUS_BLOCK Ios; + KBOOL fRc; + + kHlpAssert(pDir->hDir == INVALID_HANDLE_VALUE); + pDir->hDir = hDir; + + if (birdStatHandle(hDir, &pDir->Obj.Stats, pDir->Obj.pszName) == 0) + { + pDir->Obj.fHaveStats = K_TRUE; + pDir->uDevNo = pDir->Obj.Stats.st_dev; + } + else + { + /* Just in case. */ + pDir->Obj.fHaveStats = K_FALSE; + rcNt = birdQueryVolumeDeviceNumber(hDir, &uBuf.VolInfo, sizeof(uBuf), &pDir->uDevNo); + kHlpAssertMsg(MY_NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + + /* Get the file system. */ + pDir->Obj.fFlags &= ~(KFSOBJ_F_NTFS | KFSOBJ_F_WORKING_DIR_MTIME); + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryVolumeInformationFile(hDir, &Ios, &uBuf.FsAttrInfo, sizeof(uBuf), + MyFileFsAttributeInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + if ( uBuf.FsAttrInfo.FileSystemName[0] == 'N' + && uBuf.FsAttrInfo.FileSystemName[1] == 'T' + && uBuf.FsAttrInfo.FileSystemName[2] == 'F' + && uBuf.FsAttrInfo.FileSystemName[3] == 'S' + && uBuf.FsAttrInfo.FileSystemName[4] == '\0') + { + DWORD dwDriveType = GetDriveTypeW(wszTmp); + if ( dwDriveType == DRIVE_FIXED + || dwDriveType == DRIVE_RAMDISK) + pDir->Obj.fFlags |= KFSOBJ_F_NTFS | KFSOBJ_F_WORKING_DIR_MTIME; + } + } + + /* + * Link the new drive letter into the root dir. + */ + fRc = kFsCacheDirAddChild(pCache, &pCache->RootDir, &pDir->Obj, penmError); + kFsCacheObjRelease(pCache, &pDir->Obj); + if (fRc) + { + pDir->Obj.pNextNameHash = pCache->RootDir.papHashTab[uNameHash]; + pCache->RootDir.papHashTab[uNameHash] = &pDir->Obj; + return &pDir->Obj; + } + return NULL; + } + + g_pfnNtClose(hDir); + return NULL; + } + + /* Assume it doesn't exist if this happens... This may be a little to + restrictive wrt status code checks. */ + kHlpAssertMsgStmtReturn( rcNt == MY_STATUS_OBJECT_NAME_NOT_FOUND + || rcNt == MY_STATUS_OBJECT_PATH_NOT_FOUND + || rcNt == MY_STATUS_OBJECT_PATH_INVALID + || rcNt == MY_STATUS_OBJECT_PATH_SYNTAX_BAD, + ("%#x\n", rcNt), + *penmError = KFSLOOKUPERROR_DIR_OPEN_ERROR, + NULL); + } + else + { + kHlpAssertFailed(); + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return NULL; + } + + /* + * Maybe create a missing entry. + */ + if (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + { + PKFSOBJ pMissing = kFsCacheCreateObject(pCache, &pCache->RootDir, szTmp, 2, wszTmp, 2, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, &pCache->RootDir, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + } + else + { + /** @todo this isn't necessary correct for a root spec. */ + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + } + return NULL; +} + + +/** + * Slow path that allocates the child hash table and enters the given one. + * + * Allocation fialures are ignored. + * + * @param pCache The cache (for stats). + * @param pDir The directory. + * @param uNameHash The name hash to enter @a pChild under. + * @param pChild The child to enter into the hash table. + */ +static void kFsCacheDirAllocHashTabAndEnterChild(PKFSCACHE pCache, PKFSDIR pDir, KU32 uNameHash, PKFSOBJ pChild) +{ + if (uNameHash != 0) /* paranoia ^ 4! */ + { + /* + * Double the current number of children and round up to a multiple of + * two so we can avoid division. + */ + KU32 cbHashTab; + KU32 cEntries; + kHlpAssert(pDir->cChildren > 0); + if (pDir->cChildren <= KU32_MAX / 4) + { +#if defined(_MSC_VER) && 1 + KU32 cEntriesRaw = pDir->cChildren * 2; + KU32 cEntriesShift; + kHlpAssert(sizeof(cEntries) == (unsigned long)); + if (_BitScanReverse(&cEntriesShift, cEntriesRaw)) + { + if ( K_BIT32(cEntriesShift) < cEntriesRaw + && cEntriesShift < 31U) + cEntriesShift++; + cEntries = K_BIT32(cEntriesShift); + } + else + { + kHlpAssertFailed(); + cEntries = KU32_MAX / 2 + 1; + } +#else + cEntries = pDir->cChildren * 2 - 1; + cEntries |= cEntries >> 1; + cEntries |= cEntries >> 2; + cEntries |= cEntries >> 4; + cEntries |= cEntries >> 8; + cEntries |= cEntries >> 16; + cEntries++; +#endif + } + else + cEntries = KU32_MAX / 2 + 1; + kHlpAssert((cEntries & (cEntries - 1)) == 0); + + cbHashTab = cEntries * sizeof(pDir->papHashTab[0]); + pDir->papHashTab = (PKFSOBJ *)kHlpAllocZ(cbHashTab); + if (pDir->papHashTab) + { + KU32 idx; + pDir->fHashTabMask = cEntries - 1; + pCache->cbObjects += cbHashTab; + pCache->cChildHashTabs++; + pCache->cChildHashEntriesTotal += cEntries; + + /* + * Insert it. + */ + pChild->uNameHash = uNameHash; + idx = uNameHash & (pDir->fHashTabMask); + pChild->pNextNameHash = pDir->papHashTab[idx]; + pDir->papHashTab[idx] = pChild; + pCache->cChildHashed++; + } + } +} + + +/** + * Look up a child node, ANSI version. + * + * @returns Pointer to the child if found, NULL if not. + * @param pCache The cache. + * @param pParent The parent directory to search. + * @param pchName The child name to search for (not terminated). + * @param cchName The length of the child name. + */ +static PKFSOBJ kFsCacheFindChildA(PKFSCACHE pCache, PKFSDIR pParent, const char *pchName, KU32 cchName) +{ + /* + * Check for '.' first ('..' won't appear). + */ + if (cchName != 1 || *pchName != '.') + { + PKFSOBJ *ppCur; + KU32 cLeft; + KU32 uNameHash; + + /* + * Do hash table lookup. + * + * This caches previous lookups, which should be useful when looking up + * intermediate directories at least. + */ + if (pParent->papHashTab != NULL) + { + PKFSOBJ pCur; + uNameHash = kFsCacheStrHashN(pchName, cchName); + pCur = pParent->papHashTab[uNameHash & pParent->fHashTabMask]; + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && ( ( pCur->cchName == cchName + && _mbsnicmp(pCur->pszName, pchName, cchName) == 0) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cchShortName == cchName + && pCur->pszShortName != pCur->pszName + && _mbsnicmp(pCur->pszShortName, pchName, cchName) == 0) +#endif + ) + ) + { + pCache->cChildHashHits++; + pCache->cChildSearches++; + return pCur; + } + pCur = pCur->pNextNameHash; + } + } + else + uNameHash = 0; + + /* + * Do linear search. + */ + cLeft = pParent->cChildren; + ppCur = pParent->papChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if ( ( pCur->cchName == cchName + && _mbsnicmp(pCur->pszName, pchName, cchName) == 0) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cchShortName == cchName + && pCur->pszShortName != pCur->pszName + && _mbsnicmp(pCur->pszShortName, pchName, cchName) == 0) +#endif + ) + { + /* + * Consider entering it into the parent hash table. + * Note! We hash the input, not the name we found. + */ + if ( pCur->uNameHash == 0 + && pParent->cChildren >= 2) + { + if (pParent->papHashTab) + { + if (uNameHash != 0) + { + KU32 idxNameHash = uNameHash & pParent->fHashTabMask; + pCur->uNameHash = uNameHash; + pCur->pNextNameHash = pParent->papHashTab[idxNameHash]; + pParent->papHashTab[idxNameHash] = pCur; + if (pCur->pNextNameHash) + pCache->cChildHashCollisions++; + pCache->cChildHashed++; + } + } + else + kFsCacheDirAllocHashTabAndEnterChild(pCache, pParent, kFsCacheStrHashN(pchName, cchName), pCur); + } + + pCache->cChildSearches++; + return pCur; + } + } + + pCache->cChildSearches++; + return NULL; + } + return &pParent->Obj; +} + + +/** + * Look up a child node, UTF-16 version. + * + * @returns Pointer to the child if found, NULL if not. + * @param pCache The cache. + * @param pParent The parent directory to search. + * @param pwcName The child name to search for (not terminated). + * @param cwcName The length of the child name (in wchar_t's). + */ +static PKFSOBJ kFsCacheFindChildW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwcName, KU32 cwcName) +{ + /* + * Check for '.' first ('..' won't appear). + */ + if (cwcName != 1 || *pwcName != '.') + { + PKFSOBJ *ppCur; + KU32 cLeft; + KU32 uNameHash; + + /* + * Do hash table lookup. + * + * This caches previous lookups, which should be useful when looking up + * intermediate directories at least. + */ + if (pParent->papHashTab != NULL) + { + PKFSOBJ pCur; + uNameHash = kFsCacheUtf16HashN(pwcName, cwcName); + pCur = pParent->papHashTab[uNameHash & pParent->fHashTabMask]; + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + ) + { + pCache->cChildHashHits++; + pCache->cChildSearches++; + return pCur; + } + pCur = pCur->pNextNameHash; + } + } + else + uNameHash = 0; + + /* + * Do linear search. + */ + cLeft = pParent->cChildren; + ppCur = pParent->papChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + { + /* + * Consider entering it into the parent hash table. + * Note! We hash the input, not the name we found. + */ + if ( pCur->uNameHash == 0 + && pParent->cChildren >= 4) + { + if (pParent->papHashTab) + { + if (uNameHash != 0) + { + KU32 idxNameHash = uNameHash & pParent->fHashTabMask; + pCur->uNameHash = uNameHash; + pCur->pNextNameHash = pParent->papHashTab[idxNameHash]; + pParent->papHashTab[idxNameHash] = pCur; + if (pCur->pNextNameHash) + pCache->cChildHashCollisions++; + pCache->cChildHashed++; + } + } + else + kFsCacheDirAllocHashTabAndEnterChild(pCache, pParent, kFsCacheUtf16HashN(pwcName, cwcName), pCur); + } + + pCache->cChildSearches++; + return pCur; + } + } + pCache->cChildSearches++; + return NULL; + } + return &pParent->Obj; +} + + +/** + * Looks up a UNC share, ANSI version. + * + * We keep both the server and share in the root directory entry. This means we + * have to clean up the entry name before we can insert it. + * + * @returns Pointer to the share root directory or an update-to-date missing + * node. + * @param pCache The cache. + * @param pszPath The path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param poff Where to return the root dire. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupUncShareA(PKFSCACHE pCache, const char *pszPath, KU32 fFlags, + KU32 *poff, KFSLOOKUPERROR *penmError) +{ + /* + * Special case: Long path prefix w/ drive letter following it. + * Note! Must've been converted from wide char to ANSI. + */ + if ( IS_SLASH(pszPath[0]) + && IS_SLASH(pszPath[1]) + && pszPath[2] == '?' + && IS_SLASH(pszPath[3]) + && IS_ALPHA(pszPath[4]) + && pszPath[5] == ':' + && IS_SLASH(pszPath[6]) ) + { + *poff = 4 + 2; + return kFsCacheLookupDrive(pCache, pszPath[4], fFlags, penmError); + } + +#if 0 /* later */ + KU32 offStartServer; + KU32 offEndServer; + KU32 offStartShare; + + KU32 offEnd = 2; + while (IS_SLASH(pszPath[offEnd])) + offEnd++; + + offStartServer = offEnd; + while ( (ch = pszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; + offEndServer = offEnd; + + if (ch != '\0') + { /* likely */ } + else + { + *penmError = KFSLOOKUPERROR_NOT_FOUND; + return NULL; + } + + while (IS_SLASH(pszPath[offEnd])) + offEnd++; + offStartServer = offEnd; + while ( (ch = pszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; +#endif + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; +} + + +/** + * Looks up a UNC share, UTF-16 version. + * + * We keep both the server and share in the root directory entry. This means we + * have to clean up the entry name before we can insert it. + * + * @returns Pointer to the share root directory or an update-to-date missing + * node. + * @param pCache The cache. + * @param pwszPath The path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param poff Where to return the root dir. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupUncShareW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 fFlags, + KU32 *poff, KFSLOOKUPERROR *penmError) +{ + /* + * Special case: Long path prefix w/ drive letter following it. + */ + if ( IS_SLASH(pwszPath[0]) + && IS_SLASH(pwszPath[1]) + && pwszPath[2] == '?' + && IS_SLASH(pwszPath[3]) + && IS_ALPHA(pwszPath[4]) + && pwszPath[5] == ':' + && IS_SLASH(pwszPath[6]) ) + { + *poff = 4 + 2; + return kFsCacheLookupDrive(pCache, (char)pwszPath[4], fFlags, penmError); + } + + +#if 0 /* later */ + KU32 offStartServer; + KU32 offEndServer; + KU32 offStartShare; + + KU32 offEnd = 2; + while (IS_SLASH(pwszPath[offEnd])) + offEnd++; + + offStartServer = offEnd; + while ( (ch = pwszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; + offEndServer = offEnd; + + if (ch != '\0') + { /* likely */ } + else + { + *penmError = KFSLOOKUPERROR_NOT_FOUND; + return NULL; + } + + while (IS_SLASH(pwszPath[offEnd])) + offEnd++; + offStartServer = offEnd; + while ( (ch = pwszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; +#endif + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; +} + + +/** + * Walks an full path relative to the given directory, ANSI version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pParent The directory to start the lookup in. + * @param pszPath The path to walk. + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error like an path/file + * not found problem. Optional. + */ +PKFSOBJ kFsCacheLookupRelativeToDirA(PKFSCACHE pCache, PKFSDIR pParent, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * Walk loop. + */ + KU32 off = 0; + if (ppLastAncestor) + *ppLastAncestor = NULL; + KFSCACHE_LOCK(pCache); + for (;;) + { + PKFSOBJ pChild; + + /* + * Find the end of the component, counting trailing slashes. + */ + char ch; + KU32 cchSlashes = 0; + KU32 offEnd = off + 1; + while ((ch = pszPath[offEnd]) != '\0') + { + if (!IS_SLASH(ch)) + offEnd++; + else + { + do + cchSlashes++; + while (IS_SLASH(pszPath[offEnd + cchSlashes])); + break; + } + } + + /* + * Do we need to populate or refresh this directory first? + */ + if ( !pParent->fNeedRePopulating + && pParent->fPopulated + && ( pParent->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pParent->Obj.uCacheGen == pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + { /* likely */ } + else if ( (fFlags & (KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH)) + || kFsCachePopuplateOrRefreshDir(pCache, pParent, penmError)) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* + * Search the current node for the name. + * + * If we don't find it, we may insert a missing node depending on + * the cache configuration. + */ + pChild = kFsCacheFindChildA(pCache, pParent, &pszPath[off], offEnd - off); + if (pChild != NULL) + { /* probably likely */ } + else + { + if ( (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + && !(fFlags & KFSCACHE_LOOKUP_F_NO_INSERT)) + pChild = kFsCacheCreateMissingA(pCache, pParent, &pszPath[off], offEnd - off, penmError); + if (cchSlashes == 0 || offEnd + cchSlashes >= cchPath) + { + if (pChild) + { + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + else + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* Advance off and check if we're done already. */ + off = offEnd + cchSlashes; + if ( cchSlashes == 0 + || off >= cchPath) + { + if ( pChild->bObjType != KFSOBJ_TYPE_MISSING + || pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissing(pCache, pChild, penmError) ) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + + /* + * Check that it's a directory. If a missing entry, we may have to + * refresh it and re-examin it. + */ + if (pChild->bObjType == KFSOBJ_TYPE_DIR) + pParent = (PKFSDIR)pChild; + else if (pChild->bObjType != KFSOBJ_TYPE_MISSING) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if ( pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH)) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if (kFsCacheRefreshMissingIntermediateDir(pCache, pChild, penmError)) + pParent = (PKFSDIR)pChild; + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + } + + /* not reached */ + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Walks an full path relative to the given directory, UTF-16 version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pParent The directory to start the lookup in. + * @param pszPath The path to walk. No dot-dot bits allowed! + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error like an path/file + * not found problem. Optional. + */ +PKFSOBJ kFsCacheLookupRelativeToDirW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * Walk loop. + */ + KU32 off = 0; + if (ppLastAncestor) + *ppLastAncestor = NULL; + KFSCACHE_LOCK(pCache); + for (;;) + { + PKFSOBJ pChild; + + /* + * Find the end of the component, counting trailing slashes. + */ + wchar_t wc; + KU32 cwcSlashes = 0; + KU32 offEnd = off + 1; + while ((wc = pwszPath[offEnd]) != '\0') + { + if (!IS_SLASH(wc)) + offEnd++; + else + { + do + cwcSlashes++; + while (IS_SLASH(pwszPath[offEnd + cwcSlashes])); + break; + } + } + + /* + * Do we need to populate or refresh this directory first? + */ + if ( !pParent->fNeedRePopulating + && pParent->fPopulated + && ( pParent->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pParent->Obj.uCacheGen == pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + { /* likely */ } + else if ( (fFlags & (KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH)) + || kFsCachePopuplateOrRefreshDir(pCache, pParent, penmError)) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* + * Search the current node for the name. + * + * If we don't find it, we may insert a missing node depending on + * the cache configuration. + */ + pChild = kFsCacheFindChildW(pCache, pParent, &pwszPath[off], offEnd - off); + if (pChild != NULL) + { /* probably likely */ } + else + { + if ( (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + && !(fFlags & KFSCACHE_LOOKUP_F_NO_INSERT)) + pChild = kFsCacheCreateMissingW(pCache, pParent, &pwszPath[off], offEnd - off, penmError); + if (cwcSlashes == 0 || offEnd + cwcSlashes >= cwcPath) + { + if (pChild) + { + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + else + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* Advance off and check if we're done already. */ + off = offEnd + cwcSlashes; + if ( cwcSlashes == 0 + || off >= cwcPath) + { + if ( pChild->bObjType != KFSOBJ_TYPE_MISSING + || pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissing(pCache, pChild, penmError) ) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + + /* + * Check that it's a directory. If a missing entry, we may have to + * refresh it and re-examin it. + */ + if (pChild->bObjType == KFSOBJ_TYPE_DIR) + pParent = (PKFSDIR)pChild; + else if (pChild->bObjType != KFSOBJ_TYPE_MISSING) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if ( pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) ) + + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if (kFsCacheRefreshMissingIntermediateDir(pCache, pChild, penmError)) + pParent = (PKFSDIR)pChild; + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + } + + KFSCACHE_UNLOCK(pCache); + return NULL; +} + +/** + * Walk the file system tree for the given absolute path, entering it into the + * hash table. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pszPath The path to walk. No dot-dot bits allowed! + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupAbsoluteA(PKFSCACHE pCache, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + PKFSOBJ pRoot; + KU32 cchSlashes; + KU32 offEnd; + + KFSCACHE_LOG2(("kFsCacheLookupAbsoluteA(%s)\n", pszPath)); + + /* + * The root "directory" needs special handling, so we keep it outside the + * main search loop. (Special: Cannot enumerate it, UNCs, ++.) + */ + cchSlashes = 0; + if ( pszPath[1] == ':' + && IS_ALPHA(pszPath[0])) + { + /* Drive letter. */ + offEnd = 2; + kHlpAssert(IS_SLASH(pszPath[2])); + pRoot = kFsCacheLookupDrive(pCache, toupper(pszPath[0]), fFlags, penmError); + } + else if ( IS_SLASH(pszPath[0]) + && IS_SLASH(pszPath[1]) ) + pRoot = kFsCacheLookupUncShareA(pCache, pszPath, fFlags, &offEnd, penmError); + else + { + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; + } + if (pRoot) + { /* likely */ } + else + return NULL; + + /* Count slashes trailing the root spec. */ + if (offEnd < cchPath) + { + kHlpAssert(IS_SLASH(pszPath[offEnd])); + do + cchSlashes++; + while (IS_SLASH(pszPath[offEnd + cchSlashes])); + } + + /* Done already? */ + if (offEnd >= cchPath) + { + if ( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == ( pRoot->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshObj(pCache, pRoot, penmError)) + return kFsCacheObjRetainInternal(pRoot); + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(pRoot); + return NULL; + } + + /* Check that we've got a valid result and not a cached negative one. */ + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + { /* likely */ } + else + { + kHlpAssert(pRoot->bObjType == KFSOBJ_TYPE_MISSING); + kHlpAssert( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]); + return pRoot; + } + + /* + * Now that we've found a valid root directory, lookup the + * remainder of the path starting with it. + */ + return kFsCacheLookupRelativeToDirA(pCache, (PKFSDIR)pRoot, &pszPath[offEnd + cchSlashes], + cchPath - offEnd - cchSlashes, fFlags, penmError, ppLastAncestor); +} + + +/** + * Walk the file system tree for the given absolute path, UTF-16 version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pwszPath The path to walk. + * @param cwcPath The length of the path (in wchar_t's). + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupAbsoluteW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + PKFSDIR pParent = &pCache->RootDir; + PKFSOBJ pRoot; + KU32 off; + KU32 cwcSlashes; + KU32 offEnd; + + KFSCACHE_LOG2(("kFsCacheLookupAbsoluteW(%ls)\n", pwszPath)); + + /* + * The root "directory" needs special handling, so we keep it outside the + * main search loop. (Special: Cannot enumerate it, UNCs, ++.) + */ + cwcSlashes = 0; + off = 0; + if ( pwszPath[1] == ':' + && IS_ALPHA(pwszPath[0])) + { + /* Drive letter. */ + offEnd = 2; + kHlpAssert(IS_SLASH(pwszPath[2])); + pRoot = kFsCacheLookupDrive(pCache, toupper(pwszPath[0]), fFlags, penmError); + } + else if ( IS_SLASH(pwszPath[0]) + && IS_SLASH(pwszPath[1]) ) + pRoot = kFsCacheLookupUncShareW(pCache, pwszPath, fFlags, &offEnd, penmError); + else + { + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; + } + if (pRoot) + { /* likely */ } + else + return NULL; + + /* Count slashes trailing the root spec. */ + if (offEnd < cwcPath) + { + kHlpAssert(IS_SLASH(pwszPath[offEnd])); + do + cwcSlashes++; + while (IS_SLASH(pwszPath[offEnd + cwcSlashes])); + } + + /* Done already? */ + if (offEnd >= cwcPath) + { + if ( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == (pRoot->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshObj(pCache, pRoot, penmError)) + return kFsCacheObjRetainInternal(pRoot); + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(pRoot); + return NULL; + } + + /* Check that we've got a valid result and not a cached negative one. */ + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + { /* likely */ } + else + { + kHlpAssert(pRoot->bObjType == KFSOBJ_TYPE_MISSING); + kHlpAssert( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]); + return pRoot; + } + + /* + * Now that we've found a valid root directory, lookup the + * remainder of the path starting with it. + */ + return kFsCacheLookupRelativeToDirW(pCache, (PKFSDIR)pRoot, &pwszPath[offEnd + cwcSlashes], + cwcPath - offEnd - cwcSlashes, fFlags, penmError, ppLastAncestor); +} + + +/** + * This deals with paths that are relative and paths that contains '..' + * elements, ANSI version. + * + * @returns Pointer to object corresponding to @a pszPath on success. + * NULL if this isn't a path we care to cache. + * + * @param pCache The cache. + * @param pszPath The path. + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupSlowA(PKFSCACHE pCache, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * We just call GetFullPathNameA here to do the job as getcwd and _getdcwd + * ends up calling it anyway. + */ + char szFull[KFSCACHE_CFG_MAX_PATH]; + UINT cchFull = GetFullPathNameA(pszPath, sizeof(szFull), szFull, NULL); + if ( cchFull >= 3 + && cchFull < sizeof(szFull)) + { + KFSCACHE_LOG2(("kFsCacheLookupSlowA(%s)\n", pszPath)); + return kFsCacheLookupAbsoluteA(pCache, szFull, cchFull, fFlags, penmError, ppLastAncestor); + } + + /* The path is too long! */ + kHlpAssertMsgFailed(("'%s' -> cchFull=%u\n", pszPath, cchFull)); + *penmError = cchFull >= 3 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * This deals with paths that are relative and paths that contains '..' + * elements, UTF-16 version. + * + * @returns Pointer to object corresponding to @a pszPath on success. + * NULL if this isn't a path we care to cache. + * + * @param pCache The cache. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupSlowW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 wcwPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * We just call GetFullPathNameA here to do the job as getcwd and _getdcwd + * ends up calling it anyway. + */ + wchar_t wszFull[KFSCACHE_CFG_MAX_PATH]; + UINT cwcFull = GetFullPathNameW(pwszPath, KFSCACHE_CFG_MAX_PATH, wszFull, NULL); + if ( cwcFull >= 3 + && cwcFull < KFSCACHE_CFG_MAX_PATH) + { + KFSCACHE_LOG2(("kFsCacheLookupSlowA(%ls)\n", pwszPath)); + return kFsCacheLookupAbsoluteW(pCache, wszFull, cwcFull, fFlags, penmError, ppLastAncestor); + } + + /* The path is too long! */ + kHlpAssertMsgFailed(("'%ls' -> cwcFull=%u\n", pwszPath, cwcFull)); + *penmError = cwcFull >= 3 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * Refreshes a path hash that has expired, ANSI version. + * + * @returns pHash on success, NULL if removed. + * @param pCache The cache. + * @param pHashEntry The path hash. + * @param idxHashTab The hash table entry. + */ +static PKFSHASHA kFsCacheRefreshPathA(PKFSCACHE pCache, PKFSHASHA pHashEntry, KU32 idxHashTab) +{ + PKFSOBJ pLastAncestor = NULL; + if (!pHashEntry->pFsObj) + { + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + else + { + KU8 bOldType = pHashEntry->pFsObj->bObjType; + KFSLOOKUPERROR enmError; + if (kFsCacheRefreshObj(pCache, pHashEntry->pFsObj, &enmError)) + { + if (pHashEntry->pFsObj->bObjType == bOldType) + { } + else + { + pHashEntry->pFsObj->cPathHashRefs -= 1; + kFsCacheObjRelease(pCache, pHashEntry->pFsObj); + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + } + else + { + fprintf(stderr, "kFsCacheRefreshPathA - refresh failure handling not implemented!\n"); + __debugbreak(); + /** @todo just remove this entry. */ + return NULL; + } + } + + if (pLastAncestor && !pHashEntry->pFsObj) + pHashEntry->idxMissingGen = pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN; + pHashEntry->uCacheGen = !pHashEntry->pFsObj + ? pCache->auGenerationsMissing[pHashEntry->idxMissingGen] + : pHashEntry->pFsObj->bObjType == KFSOBJ_TYPE_MISSING + ? pCache->auGenerationsMissing[pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerations[ pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + return pHashEntry; +} + + +/** + * Refreshes a path hash that has expired, UTF-16 version. + * + * @returns pHash on success, NULL if removed. + * @param pCache The cache. + * @param pHashEntry The path hash. + * @param idxHashTab The hash table entry. + */ +static PKFSHASHW kFsCacheRefreshPathW(PKFSCACHE pCache, PKFSHASHW pHashEntry, KU32 idxHashTab) +{ + PKFSOBJ pLastAncestor = NULL; + if (!pHashEntry->pFsObj) + { + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + else + { + KU8 bOldType = pHashEntry->pFsObj->bObjType; + KFSLOOKUPERROR enmError; + if (kFsCacheRefreshObj(pCache, pHashEntry->pFsObj, &enmError)) + { + if (pHashEntry->pFsObj->bObjType == bOldType) + { } + else + { + pHashEntry->pFsObj->cPathHashRefs -= 1; + kFsCacheObjRelease(pCache, pHashEntry->pFsObj); + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + } + else + { + fprintf(stderr, "kFsCacheRefreshPathW - refresh failure handling not implemented!\n"); + fflush(stderr); + __debugbreak(); + /** @todo just remove this entry. */ + return NULL; + } + } + if (pLastAncestor && !pHashEntry->pFsObj) + pHashEntry->idxMissingGen = pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN; + pHashEntry->uCacheGen = !pHashEntry->pFsObj + ? pCache->auGenerationsMissing[pHashEntry->idxMissingGen] + : pHashEntry->pFsObj->bObjType == KFSOBJ_TYPE_MISSING + ? pCache->auGenerationsMissing[pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerations[ pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + return pHashEntry; +} + + +/** + * Internal lookup worker that looks up a KFSOBJ for the given ANSI path with + * length and hash. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pchPath The path to lookup. + * @param cchPath The path length. + * @param uHashPath The hash of the path. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupHashedA(PKFSCACHE pCache, const char *pchPath, KU32 cchPath, KU32 uHashPath, + KFSLOOKUPERROR *penmError) +{ + /* + * Do hash table lookup of the path. + */ + KU32 idxHashTab = uHashPath % K_ELEMENTS(pCache->apAnsiPaths); + PKFSHASHA pHashEntry = pCache->apAnsiPaths[idxHashTab]; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + if (pHashEntry) + { + do + { + if ( pHashEntry->uHashPath == uHashPath + && pHashEntry->cchPath == cchPath + && kHlpMemComp(pHashEntry->pszPath, pchPath, cchPath) == 0) + { + PKFSOBJ pFsObj; + if ( pHashEntry->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pHashEntry->uCacheGen == ( (pFsObj = pHashEntry->pFsObj) != NULL + ? pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pHashEntry->idxMissingGen]) + || (pHashEntry = kFsCacheRefreshPathA(pCache, pHashEntry, idxHashTab)) ) + { + pCache->cLookups++; + pCache->cPathHashHits++; + KFSCACHE_LOG2(("kFsCacheLookupA(%*.*s) - hit %p\n", cchPath, cchPath, pchPath, pHashEntry->pFsObj)); + *penmError = pHashEntry->enmError; + if (pHashEntry->pFsObj) + return kFsCacheObjRetainInternal(pHashEntry->pFsObj); + return NULL; + } + break; + } + pHashEntry = pHashEntry->pNext; + } while (pHashEntry); + } + + /* + * Create an entry for it by walking the file system cache and filling in the blanks. + */ + if ( cchPath > 0 + && cchPath < KFSCACHE_CFG_MAX_PATH) + { + PKFSOBJ pFsObj; + KBOOL fAbsolute; + PKFSOBJ pLastAncestor = NULL; + + /* Is absolute without any '..' bits? */ + if ( cchPath >= 3 + && ( ( pchPath[1] == ':' /* Drive letter */ + && IS_SLASH(pchPath[2]) + && IS_ALPHA(pchPath[0]) ) + || ( IS_SLASH(pchPath[0]) /* UNC */ + && IS_SLASH(pchPath[1]) ) ) + && !kFsCacheHasDotDotA(pchPath, cchPath) ) + { + pFsObj = kFsCacheLookupAbsoluteA(pCache, pchPath, cchPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_TRUE; + } + else + { + pFsObj = kFsCacheLookupSlowA(pCache, pchPath, cchPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_FALSE; + } + if ( pFsObj + || ( (pCache->fFlags & KFSCACHE_F_MISSING_PATHS) + && *penmError != KFSLOOKUPERROR_PATH_TOO_LONG) + || *penmError == KFSLOOKUPERROR_UNSUPPORTED ) + kFsCacheCreatePathHashTabEntryA(pCache, pFsObj, pchPath, cchPath, uHashPath, idxHashTab, fAbsolute, + pLastAncestor ? pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN : 0, *penmError); + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + + pCache->cLookups++; + if (pFsObj) + pCache->cWalkHits++; + return pFsObj; + } + + *penmError = cchPath > 0 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * Internal lookup worker that looks up a KFSOBJ for the given UTF-16 path with + * length and hash. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwcPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwcPath The path to lookup. + * @param cwcPath The length of the path (in wchar_t's). + * @param uHashPath The hash of the path. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupHashedW(PKFSCACHE pCache, const wchar_t *pwcPath, KU32 cwcPath, KU32 uHashPath, + KFSLOOKUPERROR *penmError) +{ + /* + * Do hash table lookup of the path. + */ + KU32 idxHashTab = uHashPath % K_ELEMENTS(pCache->apAnsiPaths); + PKFSHASHW pHashEntry = pCache->apUtf16Paths[idxHashTab]; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + if (pHashEntry) + { + do + { + if ( pHashEntry->uHashPath == uHashPath + && pHashEntry->cwcPath == cwcPath + && kHlpMemComp(pHashEntry->pwszPath, pwcPath, cwcPath) == 0) + { + PKFSOBJ pFsObj; + if ( pHashEntry->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pHashEntry->uCacheGen == ((pFsObj = pHashEntry->pFsObj) != NULL + ? pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pHashEntry->idxMissingGen]) + || (pHashEntry = kFsCacheRefreshPathW(pCache, pHashEntry, idxHashTab)) ) + { + pCache->cLookups++; + pCache->cPathHashHits++; + KFSCACHE_LOG2(("kFsCacheLookupW(%*.*ls) - hit %p\n", cwcPath, cwcPath, pwcPath, pHashEntry->pFsObj)); + *penmError = pHashEntry->enmError; + if (pHashEntry->pFsObj) + return kFsCacheObjRetainInternal(pHashEntry->pFsObj); + return NULL; + } + break; + } + pHashEntry = pHashEntry->pNext; + } while (pHashEntry); + } + + /* + * Create an entry for it by walking the file system cache and filling in the blanks. + */ + if ( cwcPath > 0 + && cwcPath < KFSCACHE_CFG_MAX_PATH) + { + PKFSOBJ pFsObj; + KBOOL fAbsolute; + PKFSOBJ pLastAncestor = NULL; + + /* Is absolute without any '..' bits? */ + if ( cwcPath >= 3 + && ( ( pwcPath[1] == ':' /* Drive letter */ + && IS_SLASH(pwcPath[2]) + && IS_ALPHA(pwcPath[0]) ) + || ( IS_SLASH(pwcPath[0]) /* UNC */ + && IS_SLASH(pwcPath[1]) ) ) + && !kFsCacheHasDotDotW(pwcPath, cwcPath) ) + { + pFsObj = kFsCacheLookupAbsoluteW(pCache, pwcPath, cwcPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_TRUE; + } + else + { + pFsObj = kFsCacheLookupSlowW(pCache, pwcPath, cwcPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_FALSE; + } + if ( pFsObj + || ( (pCache->fFlags & KFSCACHE_F_MISSING_PATHS) + && *penmError != KFSLOOKUPERROR_PATH_TOO_LONG) + || *penmError == KFSLOOKUPERROR_UNSUPPORTED ) + kFsCacheCreatePathHashTabEntryW(pCache, pFsObj, pwcPath, cwcPath, uHashPath, idxHashTab, fAbsolute, + pLastAncestor ? pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN : 0, *penmError); + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + + pCache->cLookups++; + if (pFsObj) + pCache->cWalkHits++; + return pFsObj; + } + + *penmError = cwcPath > 0 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + + +/** + * Looks up a KFSOBJ for the given ANSI path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath; + KU32 cchPath = (KU32)kFsCacheStrHashEx(pszPath, &uHashPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedA(pCache, pszPath, cchPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given UTF-16 path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath; + KU32 cwcPath = (KU32)kFsCacheUtf16HashEx(pwszPath, &uHashPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedW(pCache, pwszPath, cwcPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given ANSI path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pchPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pchPath The path to lookup (does not need to be nul + * terminated). + * @param cchPath The path length. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupWithLengthA(PKFSCACHE pCache, const char *pchPath, KSIZE cchPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath = kFsCacheStrHashN(pchPath, cchPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedA(pCache, pchPath, (KU32)cchPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given UTF-16 path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwchPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwcPath The path to lookup (does not need to be nul + * terminated). + * @param cwcPath The path length (in wchar_t's). + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupWithLengthW(PKFSCACHE pCache, const wchar_t *pwcPath, KSIZE cwcPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath = kFsCacheUtf16HashN(pwcPath, cwcPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedW(pCache, pwcPath, (KU32)cwcPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Wrapper around kFsCacheLookupA that drops KFSOBJ_TYPE_MISSING and returns + * KFSLOOKUPERROR_NOT_FOUND instead. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupNoMissingA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError) +{ + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); /* probably not necessary */ + pObj = kFsCacheLookupA(pCache, pszPath, penmError); + if (pObj) + { + if (pObj->bObjType != KFSOBJ_TYPE_MISSING) + { + KFSCACHE_UNLOCK(pCache); + return pObj; + } + + kFsCacheObjRelease(pCache, pObj); + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Wrapper around kFsCacheLookupW that drops KFSOBJ_TYPE_MISSING and returns + * KFSLOOKUPERROR_NOT_FOUND instead. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupNoMissingW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError) +{ + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); /* probably not necessary */ + pObj = kFsCacheLookupW(pCache, pwszPath, penmError); + if (pObj) + { + if (pObj->bObjType != KFSOBJ_TYPE_MISSING) + { + KFSCACHE_UNLOCK(pCache); + return pObj; + } + + kFsCacheObjRelease(pCache, pObj); + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Destroys a cache object which has a zero reference count. + * + * @returns 0 + * @param pCache The cache. + * @param pObj The object. + * @param pszWhere Where it was released from. + */ +KU32 kFsCacheObjDestroy(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere) +{ + kHlpAssert(pObj->cRefs == 0); + kHlpAssert(pObj->pParent == NULL); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + KFSCACHE_LOG(("Destroying %s/%s, type=%d, pObj=%p, pszWhere=%s\n", + pObj->pParent ? pObj->pParent->Obj.pszName : "", pObj->pszName, pObj->bObjType, pObj, pszWhere)); + if (pObj->cPathHashRefs != 0) + { + fprintf(stderr, "Destroying %s/%s, type=%d, path hash entries: %d!\n", pObj->pParent ? pObj->pParent->Obj.pszName : "", + pObj->pszName, pObj->bObjType, pObj->cPathHashRefs); + fflush(stderr); + __debugbreak(); + } + + /* + * Invalidate the structure. + */ + pObj->u32Magic = ~KFSOBJ_MAGIC; + + /* + * Destroy any user data first. + */ + while (pObj->pUserDataHead != NULL) + { + PKFSUSERDATA pUserData = pObj->pUserDataHead; + pObj->pUserDataHead = pUserData->pNext; + if (pUserData->pfnDestructor) + pUserData->pfnDestructor(pCache, pObj, pUserData); + kHlpFree(pUserData); + } + + /* + * Do type specific destruction + */ + switch (pObj->bObjType) + { + case KFSOBJ_TYPE_MISSING: + /* nothing else to do here */ + pCache->cbObjects -= sizeof(KFSDIR); + break; + + case KFSOBJ_TYPE_DIR: + { + PKFSDIR pDir = (PKFSDIR)pObj; + KU32 cChildren = pDir->cChildren; + pCache->cbObjects -= sizeof(*pDir) + + K_ALIGN_Z(cChildren, 16) * sizeof(pDir->papChildren) + + (pDir->fHashTabMask + !!pDir->fHashTabMask) * sizeof(pDir->papHashTab[0]); + + pDir->cChildren = 0; + while (cChildren-- > 0) + kFsCacheObjRelease(pCache, pDir->papChildren[cChildren]); + kHlpFree(pDir->papChildren); + pDir->papChildren = NULL; + + kHlpFree(pDir->papHashTab); + pDir->papHashTab = NULL; + break; + } + + case KFSOBJ_TYPE_FILE: + case KFSOBJ_TYPE_OTHER: + pCache->cbObjects -= sizeof(*pObj); + break; + + default: + KFSCACHE_UNLOCK(pCache); + return 0; + } + + /* + * Common bits. + */ + pCache->cbObjects -= pObj->cchName + 1; +#ifdef KFSCACHE_CFG_UTF16 + pCache->cbObjects -= (pObj->cwcName + 1) * sizeof(wchar_t); +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (pObj->pszName != pObj->pszShortName) + { + pCache->cbObjects -= pObj->cchShortName + 1; +# ifdef KFSCACHE_CFG_UTF16 + pCache->cbObjects -= (pObj->cwcShortName + 1) * sizeof(wchar_t); +# endif + } +#endif + pCache->cObjects--; + + if (pObj->pNameAlloc) + { + pCache->cbObjects -= pObj->pNameAlloc->cb; + kHlpFree(pObj->pNameAlloc); + } + + KFSCACHE_UNLOCK(pCache); + + kHlpFree(pObj); + return 0; +} + + +/** + * Releases a reference to a cache object. + * + * @returns New reference count. + * @param pCache The cache. + * @param pObj The object. + */ +#undef kFsCacheObjRelease +KU32 kFsCacheObjRelease(PKFSCACHE pCache, PKFSOBJ pObj) +{ + if (pObj) + { + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedDecrement(&pObj->cRefs); + if (cRefs) + return cRefs; + return kFsCacheObjDestroy(pCache, pObj, "kFsCacheObjRelease"); + } + return 0; +} + + +/** + * Debug version of kFsCacheObjRelease + * + * @returns New reference count. + * @param pCache The cache. + * @param pObj The object. + * @param pszWhere Where it's invoked from. + */ +KU32 kFsCacheObjReleaseTagged(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere) +{ + if (pObj) + { + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedDecrement(&pObj->cRefs); + if (cRefs) + return cRefs; + return kFsCacheObjDestroy(pCache, pObj, pszWhere); + } + return 0; +} + + +/** + * Retains a reference to a cahce object. + * + * @returns New reference count. + * @param pObj The object. + */ +KU32 kFsCacheObjRetain(PKFSOBJ pObj) +{ + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedIncrement(&pObj->cRefs); + kHlpAssert(cRefs < 16384); + return cRefs; +} + + +/** + * Associates an item of user data with the given object. + * + * If the data needs cleaning up before being free, set the + * PKFSUSERDATA::pfnDestructor member of the returned structure. + * + * @returns Pointer to the user data on success. + * NULL if out of memory or key already in use. + * + * @param pCache The cache. + * @param pObj The object. + * @param uKey The user data key. + * @param cbUserData The size of the user data. + */ +PKFSUSERDATA kFsCacheObjAddUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey, KSIZE cbUserData) +{ + kHlpAssert(cbUserData >= sizeof(*pNew)); + KFSCACHE_OBJUSERDATA_LOCK(pCache, pObj); + + if (kFsCacheObjGetUserData(pCache, pObj, uKey) == NULL) + { + PKFSUSERDATA pNew = (PKFSUSERDATA)kHlpAllocZ(cbUserData); + if (pNew) + { + pNew->uKey = uKey; + pNew->pfnDestructor = NULL; + pNew->pNext = pObj->pUserDataHead; + pObj->pUserDataHead = pNew; + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return pNew; + } + } + + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return NULL; +} + + +/** + * Retrieves an item of user data associated with the given object. + * + * @returns Pointer to the associated user data if found, otherwise NULL. + * @param pCache The cache. + * @param pObj The object. + * @param uKey The user data key. + */ +PKFSUSERDATA kFsCacheObjGetUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey) +{ + PKFSUSERDATA pCur; + + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_OBJUSERDATA_LOCK(pCache, pObj); + + for (pCur = pObj->pUserDataHead; pCur; pCur = pCur->pNext) + if (pCur->uKey == uKey) + { + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return pCur; + } + + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return NULL; +} + + +/** + * Determins the idxUserDataLock value. + * + * Called by KFSCACHE_OBJUSERDATA_LOCK when idxUserDataLock is set to KU8_MAX. + * + * @returns The proper idxUserDataLock value. + * @param pCache The cache. + * @param pObj The object. + */ +KU8 kFsCacheObjGetUserDataLockIndex(PKFSCACHE pCache, PKFSOBJ pObj) +{ + KU8 idxUserDataLock = pObj->idxUserDataLock; + if (idxUserDataLock == KU8_MAX) + { + KFSCACHE_LOCK(pCache); + idxUserDataLock = pObj->idxUserDataLock; + if (idxUserDataLock == KU8_MAX) + { + idxUserDataLock = pCache->idxUserDataNext++; + idxUserDataLock %= K_ELEMENTS(pCache->auUserDataLocks); + pObj->idxUserDataLock = idxUserDataLock; + } + KFSCACHE_UNLOCK(pCache); + } + return idxUserDataLock; +} + +/** + * Gets the full path to @a pObj, ANSI version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param chSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cchParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cchName; + if (offEnd < cbPath) + { + PKFSDIR pAncestor; + + pszPath[off + pObj->cchName] = '\0'; + memcpy(&pszPath[off], pObj->pszName, pObj->cchName); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cchName > 0); + pszPath[--off] = chSlash; + off -= pAncestor->Obj.cchName; + kHlpAssert(pAncestor->Obj.cchParent == off); + memcpy(&pszPath[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchName == 2 && pObj->pszName[2] == ':'; + off = pObj->cchName; + if (off + fDriveLetter < cbPath) + { + memcpy(pszPath, pObj->pszName, off); + if (fDriveLetter) + pszPath[off++] = chSlash; + pszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +/** + * Gets the full path to @a pObj, UTF-16 version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param wcSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cwcParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cwcName; + if (offEnd < cwcPath) + { + PKFSDIR pAncestor; + + pwszPath[off + pObj->cwcName] = '\0'; + memcpy(&pwszPath[off], pObj->pwszName, pObj->cwcName * sizeof(wchar_t)); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cwcName > 0); + pwszPath[--off] = wcSlash; + off -= pAncestor->Obj.cwcName; + kHlpAssert(pAncestor->Obj.cwcParent == off); + memcpy(&pwszPath[off], pAncestor->Obj.pwszName, pAncestor->Obj.cwcName * sizeof(wchar_t)); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchName == 2 && pObj->pszName[2] == ':'; + off = pObj->cwcName; + if (off + fDriveLetter < cwcPath) + { + memcpy(pwszPath, pObj->pwszName, off * sizeof(wchar_t)); + if (fDriveLetter) + pwszPath[off++] = wcSlash; + pwszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +#ifdef KFSCACHE_CFG_SHORT_NAMES + +/** + * Gets the full short path to @a pObj, ANSI version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param chSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullShortPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cchShortParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cchShortName; + if (offEnd < cbPath) + { + PKFSDIR pAncestor; + + pszPath[off + pObj->cchShortName] = '\0'; + memcpy(&pszPath[off], pObj->pszShortName, pObj->cchShortName); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cchShortName > 0); + pszPath[--off] = chSlash; + off -= pAncestor->Obj.cchShortName; + kHlpAssert(pAncestor->Obj.cchShortParent == off); + memcpy(&pszPath[off], pAncestor->Obj.pszShortName, pAncestor->Obj.cchShortName); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchShortName == 2 && pObj->pszShortName[2] == ':'; + off = pObj->cchShortName; + if (off + fDriveLetter < cbPath) + { + memcpy(pszPath, pObj->pszShortName, off); + if (fDriveLetter) + pszPath[off++] = chSlash; + pszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +/** + * Gets the full short path to @a pObj, UTF-16 version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param wcSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullShortPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cwcShortParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cwcShortName; + if (offEnd < cwcPath) + { + PKFSDIR pAncestor; + + pwszPath[off + pObj->cwcShortName] = '\0'; + memcpy(&pwszPath[off], pObj->pwszShortName, pObj->cwcShortName * sizeof(wchar_t)); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cwcShortName > 0); + pwszPath[--off] = wcSlash; + off -= pAncestor->Obj.cwcShortName; + kHlpAssert(pAncestor->Obj.cwcShortParent == off); + memcpy(&pwszPath[off], pAncestor->Obj.pwszShortName, pAncestor->Obj.cwcShortName * sizeof(wchar_t)); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchShortName == 2 && pObj->pszShortName[2] == ':'; + off = pObj->cwcShortName; + if (off + fDriveLetter < cwcPath) + { + memcpy(pwszPath, pObj->pwszShortName, off * sizeof(wchar_t)); + if (fDriveLetter) + pwszPath[off++] = wcSlash; + pwszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + +#endif /* KFSCACHE_CFG_SHORT_NAMES */ + + + +/** + * Read the specified bits from the files into the given buffer, simple version. + * + * @returns K_TRUE on success (all requested bytes read), + * K_FALSE on any kind of failure. + * + * @param pCache The cache. + * @param pFileObj The file object. + * @param offStart Where to start reading. + * @param pvBuf Where to store what we read. + * @param cbToRead How much to read (exact). + */ +KBOOL kFsCacheFileSimpleOpenReadClose(PKFSCACHE pCache, PKFSOBJ pFileObj, KU64 offStart, void *pvBuf, KSIZE cbToRead) +{ + /* + * Open the file relative to the parent directory. + */ + MY_NTSTATUS rcNt; + HANDLE hFile; + MY_IO_STATUS_BLOCK Ios; + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_UNICODE_STRING UniStr; + + kHlpAssertReturn(pFileObj->bObjType == KFSOBJ_TYPE_FILE, K_FALSE); + kHlpAssert(pFileObj->pParent); + kHlpAssertReturn(pFileObj->pParent->hDir != INVALID_HANDLE_VALUE, K_FALSE); + kHlpAssertReturn(offStart == 0, K_FALSE); /** @todo when needed */ + + Ios.Information = -1; + Ios.u.Status = -1; + + UniStr.Buffer = (wchar_t *)pFileObj->pwszName; + UniStr.Length = (USHORT)(pFileObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + +/** @todo potential race against kFsCacheInvalidateDeletedDirectoryA */ + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pFileObj->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtCreateFile(&hFile, + GENERIC_READ | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL, /*cbFileInitialAlloc */ + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, /*pEaBuffer*/ + 0); /*cbEaBuffer*/ + if (MY_NT_SUCCESS(rcNt)) + { + LARGE_INTEGER offFile; + offFile.QuadPart = offStart; + + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtReadFile(hFile, NULL /*hEvent*/, NULL /*pfnApcComplete*/, NULL /*pvApcCtx*/, &Ios, + pvBuf, (KU32)cbToRead, !offStart ? &offFile : NULL, NULL /*puKey*/); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + if (Ios.Information == cbToRead) + { + g_pfnNtClose(hFile); + return K_TRUE; + } + KFSCACHE_LOG(("Error reading %#x bytes from '%ls': Information=%p\n", pFileObj->pwszName, Ios.Information)); + } + else + KFSCACHE_LOG(("Error reading %#x bytes from '%ls': %#x\n", pFileObj->pwszName, rcNt)); + g_pfnNtClose(hFile); + } + else + KFSCACHE_LOG(("Error opening '%ls' for caching: %#x\n", pFileObj->pwszName, rcNt)); + return K_FALSE; +} + + +/** + * Invalidate all cache entries of missing files. + * + * @param pCache The cache. + */ +void kFsCacheInvalidateMissing(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[0]++; + kHlpAssert(pCache->uGenerationMissing < KU32_MAX); + + KFSCACHE_LOG(("Invalidate missing %#x\n", pCache->auGenerationsMissing[0])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Recursively close directories. + */ +static void kFsCacheCloseDirs(PKFSOBJ *papChildren, KU32 cChildren) +{ + while (cChildren-- > 0) + { + PKFSDIR pDir = (PKFSDIR)papChildren[cChildren]; + if (pDir && pDir->Obj.bObjType == KFSOBJ_TYPE_DIR) + { + if (pDir->hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pDir->hDir); + pDir->hDir = INVALID_HANDLE_VALUE; + } + kFsCacheCloseDirs(pDir->papChildren, pDir->cChildren); + } + } +} + + +/** + * Worker for kFsCacheInvalidateAll and kFsCacheInvalidateAllAndCloseDirs + */ +static void kFsCacheInvalidateAllWorker(PKFSCACHE pCache, KBOOL fCloseDirs, KBOOL fIncludingRoot) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[0]++; + kHlpAssert(pCache->auGenerationsMissing[0] < KU32_MAX); + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + pCache->auGenerations[0]++; + kHlpAssert(pCache->auGenerations[0] < KU32_MAX); + pCache->auGenerations[1]++; + kHlpAssert(pCache->auGenerations[1] < KU32_MAX); + + if (fCloseDirs) + { + kFsCacheCloseDirs(pCache->RootDir.papChildren, pCache->RootDir.cChildren); + if (fCloseDirs && pCache->RootDir.hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pCache->RootDir.hDir); + pCache->RootDir.hDir = INVALID_HANDLE_VALUE; + } + } + + KFSCACHE_LOG(("Invalidate all - default: %#x/%#x, custom: %#x/%#x\n", + pCache->auGenerationsMissing[0], pCache->auGenerations[0], + pCache->auGenerationsMissing[1], pCache->auGenerations[1])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Invalidate all cache entries (regular, custom & missing). + * + * @param pCache The cache. + */ +void kFsCacheInvalidateAll(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + kFsCacheInvalidateAllWorker(pCache, K_FALSE, K_FALSE); +} + + +/** + * Invalidate all cache entries (regular, custom & missing) and close all the + * directory handles. + * + * @param pCache The cache. + * @param fIncludingRoot Close the root directory handle too. + */ +void kFsCacheInvalidateAllAndCloseDirs(PKFSCACHE pCache, KBOOL fIncludingRoot) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + kFsCacheInvalidateAllWorker(pCache, K_TRUE, fIncludingRoot); +} + + +/** + * Invalidate all cache entries with custom generation handling set. + * + * @see kFsCacheSetupCustomRevisionForTree, KFSOBJ_F_USE_CUSTOM_GEN + * @param pCache The cache. + */ +void kFsCacheInvalidateCustomMissing(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + KFSCACHE_LOG(("Invalidate missing custom %#x\n", pCache->auGenerationsMissing[1])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Invalidate all cache entries with custom generation handling set, both + * missing and regular present entries. + * + * @see kFsCacheSetupCustomRevisionForTree, KFSOBJ_F_USE_CUSTOM_GEN + * @param pCache The cache. + */ +void kFsCacheInvalidateCustomBoth(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerations[1]++; + kHlpAssert(pCache->auGenerations[1] < KU32_MAX); + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + KFSCACHE_LOG(("Invalidate both custom %#x/%#x\n", pCache->auGenerationsMissing[1], pCache->auGenerations[1])); + KFSCACHE_UNLOCK(pCache); +} + + + +/** + * Applies the given flags to all the objects in a tree. + * + * @param pRoot Where to start applying the flag changes. + * @param fAndMask The AND mask. + * @param fOrMask The OR mask. + */ +static void kFsCacheApplyFlagsToTree(PKFSDIR pRoot, KU32 fAndMask, KU32 fOrMask) +{ + PKFSOBJ *ppCur = ((PKFSDIR)pRoot)->papChildren; + KU32 cLeft = ((PKFSDIR)pRoot)->cChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if (pCur->bObjType != KFSOBJ_TYPE_DIR) + pCur->fFlags = (fAndMask & pCur->fFlags) | fOrMask; + else + kFsCacheApplyFlagsToTree((PKFSDIR)pCur, fAndMask, fOrMask); + } + + pRoot->Obj.fFlags = (fAndMask & pRoot->Obj.fFlags) | fOrMask; +} + + +/** + * Sets up using custom revisioning for the specified directory tree or file. + * + * There are some restrictions of the current implementation: + * - If the root of the sub-tree is ever deleted from the cache (i.e. + * deleted in real life and reflected in the cache), the setting is lost. + * - It is not automatically applied to the lookup paths caches. + * + * @returns K_TRUE on success, K_FALSE on failure. + * @param pCache The cache. + * @param pRoot The root of the subtree. A non-directory is + * fine, like a missing node. + */ +KBOOL kFsCacheSetupCustomRevisionForTree(PKFSCACHE pCache, PKFSOBJ pRoot) +{ + if (pRoot) + { + KFSCACHE_LOCK(pCache); + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + kFsCacheApplyFlagsToTree((PKFSDIR)pRoot, KU32_MAX, KFSOBJ_F_USE_CUSTOM_GEN); + else + pRoot->fFlags |= KFSOBJ_F_USE_CUSTOM_GEN; + KFSCACHE_UNLOCK(pCache); + return K_TRUE; + } + return K_FALSE; +} + + +/** + * Invalidates a deleted directory, ANSI version. + * + * @returns K_TRUE if found and is a non-root directory. Otherwise K_FALSE. + * @param pCache The cache. + * @param pszDir The directory. + */ +KBOOL kFsCacheInvalidateDeletedDirectoryA(PKFSCACHE pCache, const char *pszDir) +{ + KU32 cchDir = (KU32)kHlpStrLen(pszDir); + KFSLOOKUPERROR enmError; + PKFSOBJ pFsObj; + + KFSCACHE_LOCK(pCache); + + /* Is absolute without any '..' bits? */ + if ( cchDir >= 3 + && ( ( pszDir[1] == ':' /* Drive letter */ + && IS_SLASH(pszDir[2]) + && IS_ALPHA(pszDir[0]) ) + || ( IS_SLASH(pszDir[0]) /* UNC */ + && IS_SLASH(pszDir[1]) ) ) + && !kFsCacheHasDotDotA(pszDir, cchDir) ) + pFsObj = kFsCacheLookupAbsoluteA(pCache, pszDir, cchDir, KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH, + &enmError, NULL); + else + pFsObj = kFsCacheLookupSlowA(pCache, pszDir, cchDir, KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH, + &enmError, NULL); + if (pFsObj) + { + /* Is directory? */ + if (pFsObj->bObjType == KFSOBJ_TYPE_DIR) + { + if (pFsObj->pParent != &pCache->RootDir) + { + PKFSDIR pDir = (PKFSDIR)pFsObj; + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: %s hDir=%p\n", pszDir, pDir->hDir)); + if (pDir->hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pDir->hDir); + pDir->hDir = INVALID_HANDLE_VALUE; + } + pDir->fNeedRePopulating = K_TRUE; + pDir->Obj.uCacheGen = pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN] - 1; + kFsCacheObjRelease(pCache, &pDir->Obj); + KFSCACHE_UNLOCK(pCache); + return K_TRUE; + } + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: Trying to invalidate a root directory was deleted! %s\n", pszDir)); + } + else + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: Trying to invalidate a non-directory: bObjType=%d %s\n", + pFsObj->bObjType, pszDir)); + kFsCacheObjRelease(pCache, pFsObj); + } + else + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: '%s' was not found\n", pszDir)); + KFSCACHE_UNLOCK(pCache); + return K_FALSE; +} + + +PKFSCACHE kFsCacheCreate(KU32 fFlags) +{ + PKFSCACHE pCache; + birdResolveImports(); + + pCache = (PKFSCACHE)kHlpAllocZ(sizeof(*pCache)); + if (pCache) + { + /* Dummy root dir entry. */ + pCache->RootDir.Obj.u32Magic = KFSOBJ_MAGIC; + pCache->RootDir.Obj.cRefs = 1; + pCache->RootDir.Obj.uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + pCache->RootDir.Obj.bObjType = KFSOBJ_TYPE_DIR; + pCache->RootDir.Obj.fHaveStats = K_FALSE; + pCache->RootDir.Obj.pParent = NULL; + pCache->RootDir.Obj.pszName = ""; + pCache->RootDir.Obj.cchName = 0; + pCache->RootDir.Obj.cchParent = 0; +#ifdef KFSCACHE_CFG_UTF16 + pCache->RootDir.Obj.cwcName = 0; + pCache->RootDir.Obj.cwcParent = 0; + pCache->RootDir.Obj.pwszName = L""; +#endif + +#ifdef KFSCACHE_CFG_SHORT_NAMES + pCache->RootDir.Obj.pszShortName = NULL; + pCache->RootDir.Obj.cchShortName = 0; + pCache->RootDir.Obj.cchShortParent = 0; +# ifdef KFSCACHE_CFG_UTF16 + pCache->RootDir.Obj.cwcShortName; + pCache->RootDir.Obj.cwcShortParent; + pCache->RootDir.Obj.pwszShortName; +# endif +#endif + pCache->RootDir.cChildren = 0; + pCache->RootDir.cChildrenAllocated = 0; + pCache->RootDir.papChildren = NULL; + pCache->RootDir.hDir = INVALID_HANDLE_VALUE; + pCache->RootDir.fHashTabMask = 255; /* 256: 26 drive letters and 102 UNCs before we're half ways. */ + pCache->RootDir.papHashTab = (PKFSOBJ *)kHlpAllocZ(256 * sizeof(pCache->RootDir.papHashTab[0])); + if (pCache->RootDir.papHashTab) + { + /* The cache itself. */ + pCache->u32Magic = KFSCACHE_MAGIC; + pCache->fFlags = fFlags; + pCache->auGenerations[0] = KU32_MAX / 4; + pCache->auGenerations[1] = KU32_MAX / 32; + pCache->auGenerationsMissing[0] = KU32_MAX / 256; + pCache->auGenerationsMissing[1] = 1; + pCache->cObjects = 1; + pCache->cbObjects = sizeof(pCache->RootDir) + + (pCache->RootDir.fHashTabMask + 1) * sizeof(pCache->RootDir.papHashTab[0]); + pCache->cPathHashHits = 0; + pCache->cWalkHits = 0; + pCache->cChildSearches = 0; + pCache->cChildHashHits = 0; + pCache->cChildHashed = 0; + pCache->cChildHashTabs = 1; + pCache->cChildHashEntriesTotal = pCache->RootDir.fHashTabMask + 1; + pCache->cChildHashCollisions = 0; + pCache->cNameChanges = 0; + pCache->cNameGrowths = 0; + pCache->cAnsiPaths = 0; + pCache->cAnsiPathCollisions = 0; + pCache->cbAnsiPaths = 0; +#ifdef KFSCACHE_CFG_UTF16 + pCache->cUtf16Paths = 0; + pCache->cUtf16PathCollisions = 0; + pCache->cbUtf16Paths = 0; +#endif + +#ifdef KFSCACHE_CFG_LOCKING + { + KSIZE idx = K_ELEMENTS(pCache->auUserDataLocks); + while (idx-- > 0) + InitializeCriticalSection(&pCache->auUserDataLocks[idx].CritSect); + InitializeCriticalSection(&pCache->u.CritSect); + } +#endif + return pCache; + } + + kHlpFree(pCache); + } + return NULL; +} + diff --git a/src/lib/nt/kFsCache.h b/src/lib/nt/kFsCache.h new file mode 100644 index 0000000..de0f87c --- /dev/null +++ b/src/lib/nt/kFsCache.h @@ -0,0 +1,594 @@ +/* $Id: kFsCache.h 3381 2020-06-12 11:36:10Z bird $ */ +/** @file + * kFsCache.c - NT directory content cache. + */ + +/* + * Copyright (c) 2016 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___lib_nt_kFsCache_h___ +#define ___lib_nt_kFsCache_h___ + + +#include +#include "ntstat.h" +#ifndef NDEBUG +# include +#endif + + +/** @def KFSCACHE_CFG_UTF16 + * Whether to compile in the UTF-16 names support. */ +#define KFSCACHE_CFG_UTF16 1 +/** @def KFSCACHE_CFG_SHORT_NAMES + * Whether to compile in the short name support. */ +#define KFSCACHE_CFG_SHORT_NAMES 1 +/** @def KFSCACHE_CFG_PATH_HASH_TAB_SIZE + * Size of the path hash table. */ +#define KFSCACHE_CFG_PATH_HASH_TAB_SIZE 99991 +/** The max length paths we consider. */ +#define KFSCACHE_CFG_MAX_PATH 1024 +/** The max ANSI name length. */ +#define KFSCACHE_CFG_MAX_ANSI_NAME (256*3 + 16) +/** The max UTF-16 name length. */ +#define KFSCACHE_CFG_MAX_UTF16_NAME (256*2 + 16) +/** Enables locking of the cache and thereby making it thread safe. */ +#define KFSCACHE_CFG_LOCKING 1 + + + +/** Special KFSOBJ::uCacheGen number indicating that it does not apply. */ +#define KFSOBJ_CACHE_GEN_IGNORE KU32_MAX + + +/** @name KFSOBJ_TYPE_XXX - KFSOBJ::bObjType + * @{ */ +/** Directory, type KFSDIR. */ +#define KFSOBJ_TYPE_DIR KU8_C(0x01) +/** Regular file - type KFSOBJ. */ +#define KFSOBJ_TYPE_FILE KU8_C(0x02) +/** Other file - type KFSOBJ. */ +#define KFSOBJ_TYPE_OTHER KU8_C(0x03) +/** Caching of a negative result - type KFSOBJ. + * @remarks We will allocate enough space for the largest cache node, so this + * can metamorph into any other object should it actually turn up. */ +#define KFSOBJ_TYPE_MISSING KU8_C(0x04) +///** Invalidated entry flag. */ +//#define KFSOBJ_TYPE_F_INVALID KU8_C(0x20) +/** @} */ + +/** @name KFSOBJ_F_XXX - KFSOBJ::fFlags + * @{ */ + /** Use custom generation. + * @remarks This is given the value 1, as we use it as an index into + * KFSCACHE::auGenerations, 0 being the default. */ +#define KFSOBJ_F_USE_CUSTOM_GEN KU32_C(0x00000001) + +/** Whether the file system update the modified timestamp of directories + * when something is removed from it or added to it. + * @remarks They say NTFS is the only windows filesystem doing this. */ +#define KFSOBJ_F_WORKING_DIR_MTIME KU32_C(0x00000002) +/** NTFS file system volume. */ +#define KFSOBJ_F_NTFS KU32_C(0x80000000) +/** Flags that are automatically inherited. */ +#define KFSOBJ_F_INHERITED_MASK KU32_C(0xffffffff) +/** @} */ + + +#define IS_ALPHA(ch) ( ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z') ) +#define IS_SLASH(ch) ((ch) == '\\' || (ch) == '/') + + + + +/** Pointer to a cache. */ +typedef struct KFSCACHE *PKFSCACHE; +/** Pointer to a core object. */ +typedef struct KFSOBJ *PKFSOBJ; +/** Pointer to a directory object. */ +typedef struct KFSDIR *PKFSDIR; +/** Pointer to a directory hash table entry. */ +typedef struct KFSOBJHASH *PKFSOBJHASH; + + + +/** Pointer to a user data item. */ +typedef struct KFSUSERDATA *PKFSUSERDATA; +/** + * User data item associated with a cache node. + */ +typedef struct KFSUSERDATA +{ + /** Pointer to the next piece of user data. */ + PKFSUSERDATA pNext; + /** The key identifying this user. */ + KUPTR uKey; + /** The destructor. */ + void (*pfnDestructor)(PKFSCACHE pCache, PKFSOBJ pObj, PKFSUSERDATA pData); +} KFSUSERDATA; + + +/** + * Storage for name strings for the unlikely event that they should grow in + * length after the KFSOBJ was created. + */ +typedef struct KFSOBJNAMEALLOC +{ + /** Size of the allocation. */ + KU32 cb; + /** The space for names. */ + char abSpace[1]; +} KFSOBJNAMEALLOC; +/** Name growth allocation. */ +typedef KFSOBJNAMEALLOC *PKFSOBJNAMEALLOC; + + +/** + * Base cache node. + */ +typedef struct KFSOBJ +{ + /** Magic value (KFSOBJ_MAGIC). */ + KU32 u32Magic; + /** Number of references. */ + KU32 volatile cRefs; + /** The cache generation, see KFSOBJ_CACHE_GEN_IGNORE. */ + KU32 uCacheGen; + /** The object type, KFSOBJ_TYPE_XXX. */ + KU8 bObjType; + /** Set if the Stats member is valid, clear if not. */ + KBOOL fHaveStats; + /** Internal debug field for counting path hash references. + * @internal */ + KU8 cPathHashRefs; + /** Index into KFSCACHE::auUserData. */ + KU8 idxUserDataLock; + /** Flags, KFSOBJ_F_XXX. */ + KU32 fFlags; + + /** Hash value of the name inserted into the parent hash table. + * This is 0 if not inserted. Names are only hashed and inserted as they are + * first found thru linear searching of its siblings, and which name it is + * dependens on the lookup function (W or A) and whether the normal name or + * short name seems to have matched. + * + * @note It was ruled out as too much work to hash and track all four names, + * so instead this minimalist approach was choosen in stead. */ + KU32 uNameHash; + /** Pointer to the next child with the same name hash value. */ + PKFSOBJ pNextNameHash; + /** Pointer to the parent (directory). + * This is only NULL for a root. */ + PKFSDIR pParent; + + /** The directory name. (Allocated after the structure.) */ + const char *pszName; + /** The length of pszName. */ + KU16 cchName; + /** The length of the parent path (up to where pszName starts). + * @note This is valuable when constructing an absolute path to this node by + * means of the parent pointer (no need for recursion). */ + KU16 cchParent; +#ifdef KFSCACHE_CFG_UTF16 + /** The length of pwszName (in wchar_t's). */ + KU16 cwcName; + /** The length of the parent UTF-16 path (in wchar_t's). + * @note This is valuable when constructing an absolute path to this node by + * means of the parent pointer (no need for recursion). */ + KU16 cwcParent; + /** The UTF-16 object name. (Allocated after the structure.) */ + const wchar_t *pwszName; +#endif + +#ifdef KFSCACHE_CFG_SHORT_NAMES + /** The short object name. (Allocated after the structure, could be same + * as pszName.) */ + const char *pszShortName; + /** The length of pszShortName. */ + KU16 cchShortName; + /** The length of the short parent path (up to where pszShortName starts). */ + KU16 cchShortParent; +# ifdef KFSCACHE_CFG_UTF16 + /** The length of pwszShortName (in wchar_t's). */ + KU16 cwcShortName; + /** The length of the short parent UTF-16 path (in wchar_t's). */ + KU16 cwcShortParent; + /** The UTF-16 short object name. (Allocated after the structure, possibly + * same as pwszName.) */ + const wchar_t *pwszShortName; +# endif +#endif + + /** Allocation for handling name length increases. */ + PKFSOBJNAMEALLOC pNameAlloc; + + /** Pointer to the first user data item */ + PKFSUSERDATA pUserDataHead; + + /** Stats - only valid when fHaveStats is set. */ + BirdStat_T Stats; +} KFSOBJ; + +/** The magic for a KFSOBJ structure (Thelonious Sphere Monk). */ +#define KFSOBJ_MAGIC KU32_C(0x19171010) + + +/** + * Directory node in the cache. + */ +typedef struct KFSDIR +{ + /** The core object information. */ + KFSOBJ Obj; + + /** Child objects. */ + PKFSOBJ *papChildren; + /** The number of child objects. */ + KU32 cChildren; + /** The allocated size of papChildren. */ + KU32 cChildrenAllocated; + + /** Pointer to the child hash table. */ + PKFSOBJ *papHashTab; + /** The mask shift of the hash table. + * Hash table size is a power of two, this is the size minus one. + * + * @remarks The hash table is optional and populated by lookup hits. The + * assumption being that a lookup is repeated and will choose a good + * name to hash on. We've got up to 4 different hashes, so this + * was the easy way out. */ + KU32 fHashTabMask; + + /** Handle to the directory (we generally keep it open). */ +#ifndef DECLARE_HANDLE + KUPTR hDir; +#else + HANDLE hDir; +#endif + /** The device number we queried/inherited when opening it. */ + KU64 uDevNo; + + /** The last write time sampled the last time the directory was refreshed. + * @remarks May differ from st_mtim because it will be updated when the + * parent directory is refreshed. */ + KI64 iLastWrite; + /** The time that iLastWrite was read. */ + KI64 iLastPopulated; + + /** Set if populated. */ + KBOOL fPopulated; + /** Set if it needs re-populated. */ + KBOOL fNeedRePopulating; +} KFSDIR; + + +/** + * Lookup errors. + */ +typedef enum KFSLOOKUPERROR +{ + /** Lookup was a success. */ + KFSLOOKUPERROR_SUCCESS = 0, + /** A path component was not found. */ + KFSLOOKUPERROR_PATH_COMP_NOT_FOUND, + /** A path component is not a directory. */ + KFSLOOKUPERROR_PATH_COMP_NOT_DIR, + /** The final path entry is not a directory (trailing slash). */ + KFSLOOKUPERROR_NOT_DIR, + /** Not found. */ + KFSLOOKUPERROR_NOT_FOUND, + /** The path is too long. */ + KFSLOOKUPERROR_PATH_TOO_LONG, + /** The path is too short. */ + KFSLOOKUPERROR_PATH_TOO_SHORT, + /** Unsupported path type. */ + KFSLOOKUPERROR_UNSUPPORTED, + /** We're out of memory. */ + KFSLOOKUPERROR_OUT_OF_MEMORY, + + /** Error opening directory. */ + KFSLOOKUPERROR_DIR_OPEN_ERROR, + /** Error reading directory. */ + KFSLOOKUPERROR_DIR_READ_ERROR, + /** UTF-16 to ANSI conversion error. */ + KFSLOOKUPERROR_ANSI_CONVERSION_ERROR, + /** ANSI to UTF-16 conversion error. */ + KFSLOOKUPERROR_UTF16_CONVERSION_ERROR, + /** Internal error. */ + KFSLOOKUPERROR_INTERNAL_ERROR +} KFSLOOKUPERROR; + + +/** Pointer to an ANSI path hash table entry. */ +typedef struct KFSHASHA *PKFSHASHA; +/** + * ANSI file system path hash table entry. + * The path hash table allows us to skip parsing and walking a path. + */ +typedef struct KFSHASHA +{ + /** Next entry with the same hash table slot. */ + PKFSHASHA pNext; + /** Path hash value. */ + KU32 uHashPath; + /** The path length. */ + KU16 cchPath; + /** Set if aboslute path. */ + KBOOL fAbsolute; + /** Index into KFSCACHE:auGenerationsMissing when pFsObj is NULL. */ + KU8 idxMissingGen; + /** The cache generation ID. */ + KU32 uCacheGen; + /** The lookup error (when pFsObj is NULL). */ + KFSLOOKUPERROR enmError; + /** The path. (Allocated after the structure.) */ + const char *pszPath; + /** Pointer to the matching FS object. + * This is NULL for negative path entries? */ + PKFSOBJ pFsObj; +} KFSHASHA; + + +#ifdef KFSCACHE_CFG_UTF16 +/** Pointer to an UTF-16 path hash table entry. */ +typedef struct KFSHASHW *PKFSHASHW; +/** + * UTF-16 file system path hash table entry. The path hash table allows us + * to skip parsing and walking a path. + */ +typedef struct KFSHASHW +{ + /** Next entry with the same hash table slot. */ + PKFSHASHW pNext; + /** Path hash value. */ + KU32 uHashPath; + /** The path length (in wchar_t units). */ + KU16 cwcPath; + /** Set if aboslute path. */ + KBOOL fAbsolute; + /** Index into KFSCACHE:auGenerationsMissing when pFsObj is NULL. */ + KU8 idxMissingGen; + /** The cache generation ID. */ + KU32 uCacheGen; + /** The lookup error (when pFsObj is NULL). */ + KFSLOOKUPERROR enmError; + /** The path. (Allocated after the structure.) */ + const wchar_t *pwszPath; + /** Pointer to the matching FS object. + * This is NULL for negative path entries? */ + PKFSOBJ pFsObj; +} KFSHASHW; +#endif + + +/** @def KFSCACHE_LOCK + * Locks the cache exclusively. */ +/** @def KFSCACHE_UNLOCK + * Counterpart to KFSCACHE_LOCK. */ +/** @def KFSCACHE_OBJUSERDATA_LOCK + * Locks the user data list of an object exclusively. */ +/** @def KFSCACHE_OBJUSERDATA_UNLOCK + * Counterpart to KFSCACHE_OBJUSERDATA_LOCK. */ +#ifdef KFSCACHE_CFG_LOCKING +# define KFSCACHE_LOCK(a_pCache) EnterCriticalSection(&(a_pCache)->u.CritSect) +# define KFSCACHE_UNLOCK(a_pCache) LeaveCriticalSection(&(a_pCache)->u.CritSect) +# define KFSCACHE_OBJUSERDATA_LOCK(a_pCache, a_pObj) do { \ + KU8 idxUserDataLock = (a_pObj)->idxUserDataLock; \ + if (idxUserDataLock != KU8_MAX) \ + { /* likely */ } \ + else \ + idxUserDataLock = kFsCacheObjGetUserDataLockIndex(a_pCache, a_pObj); \ + idxUserDataLock &= (KU8)(K_ELEMENTS((a_pCache)->auUserDataLocks) - 1); \ + EnterCriticalSection(&(a_pCache)->auUserDataLocks[idxUserDataLock].CritSect); \ + } while (0) +# define KFSCACHE_OBJUSERDATA_UNLOCK(a_pCache, a_pObj) \ + LeaveCriticalSection(&(a_pCache)->auUserDataLocks[(a_pObj)->idxUserDataLock & (K_ELEMENTS((a_pCache)->auUserDataLocks) - 1)].CritSect) +#else +# define KFSCACHE_LOCK(a_pCache) do { } while (0) +# define KFSCACHE_UNLOCK(a_pCache) do { } while (0) +# define KFSCACHE_OBJUSERDATA_LOCK(a_pCache, a_pObj) do { } while (0) +# define KFSCACHE_OBJUSERDATA_UNLOCK(a_pCache, a_pObj) do { } while (0) +#endif + + +/** @name KFSCACHE_F_XXX + * @{ */ +/** Whether to cache missing directory entries (KFSOBJ_TYPE_MISSING). */ +#define KFSCACHE_F_MISSING_OBJECTS KU32_C(0x00000001) +/** Whether to cache missing paths. */ +#define KFSCACHE_F_MISSING_PATHS KU32_C(0x00000002) +/** @} */ + + +/** + * Directory cache instance. + */ +typedef struct KFSCACHE +{ + /** Magic value (KFSCACHE_MAGIC). */ + KU32 u32Magic; + /** Cache flags. */ + KU32 fFlags; + + /** The default and custom cache generations for stuff that exists, indexed by + * KFSOBJ_F_USE_CUSTOM_GEN. + * + * The custom generation can be used to invalidate parts of the file system that + * are known to be volatile without triggering refreshing of the more static + * parts. Like the 'out' directory in a kBuild setup or a 'TEMP' directory are + * expected to change and you need to invalidate the caching of these frequently + * to stay on top of things. Whereas the sources, headers, compilers, sdk, + * ddks, windows directory and such generally doesn't change all that often. + */ + KU32 auGenerations[2]; + /** The current cache generation for missing objects, negative results, ++. + * This comes with a custom variant too. Indexed by KFSOBJ_F_USE_CUSTOM_GEN. */ + KU32 auGenerationsMissing[2]; + + /** Number of cache objects. */ + KSIZE cObjects; + /** Memory occupied by the cache object structures. */ + KSIZE cbObjects; + /** Number of lookups. */ + KSIZE cLookups; + /** Number of hits in the path hash tables. */ + KSIZE cPathHashHits; + /** Number of hits walking the file system hierarchy. */ + KSIZE cWalkHits; + /** Number of child searches. */ + KSIZE cChildSearches; + /** Number of cChildLookups resolved thru hash table hits. */ + KSIZE cChildHashHits; + /** The number of child hash tables. */ + KSIZE cChildHashTabs; + /** The sum of all child hash table sizes. */ + KSIZE cChildHashEntriesTotal; + /** Number of children inserted into the hash tables. */ + KSIZE cChildHashed; + /** Number of collisions in the child hash tables. */ + KSIZE cChildHashCollisions; + /** Number times a object name changed. */ + KSIZE cNameChanges; + /** Number times a object name grew and needed KFSOBJNAMEALLOC. + * (Subset of cNameChanges) */ + KSIZE cNameGrowths; + + /** The root directory. */ + KFSDIR RootDir; + +#ifdef KFSCACHE_CFG_LOCKING + union + { +# ifdef _WINBASE_ + CRITICAL_SECTION CritSect; +# endif + KU64 abPadding[2 * 4 + 4 * sizeof(void *)]; + } + /** Critical section protecting the cache. */ + u, + /** Critical section protecting the pUserDataHead of objects. + * @note Array size must be a power of two. */ + auUserDataLocks[8]; + /** The next auUserData index. */ + KU8 idxUserDataNext; +#endif + + /** File system hash table for ANSI filename strings. */ + PKFSHASHA apAnsiPaths[KFSCACHE_CFG_PATH_HASH_TAB_SIZE]; + /** Number of paths in the apAnsiPaths hash table. */ + KSIZE cAnsiPaths; + /** Number of collisions in the apAnsiPaths hash table. */ + KSIZE cAnsiPathCollisions; + /** Amount of memory used by the path entries. */ + KSIZE cbAnsiPaths; + +#ifdef KFSCACHE_CFG_UTF16 + /** Number of paths in the apUtf16Paths hash table. */ + KSIZE cUtf16Paths; + /** Number of collisions in the apUtf16Paths hash table. */ + KSIZE cUtf16PathCollisions; + /** Amount of memory used by the UTF-16 path entries. */ + KSIZE cbUtf16Paths; + /** File system hash table for UTF-16 filename strings. */ + PKFSHASHW apUtf16Paths[KFSCACHE_CFG_PATH_HASH_TAB_SIZE]; +#endif +} KFSCACHE; + +/** Magic value for KFSCACHE::u32Magic (Jon Batiste). */ +#define KFSCACHE_MAGIC KU32_C(0x19861111) + + +/** @def KW_LOG + * Generic logging. + * @param a Argument list for kFsCacheDbgPrintf */ +#if 1 /*def NDEBUG - enable when needed! */ +# define KFSCACHE_LOG(a) do { } while (0) +#else +# define KFSCACHE_LOG(a) kFsCacheDbgPrintf a +void kFsCacheDbgPrintfV(const char *pszFormat, va_list va); +void kFsCacheDbgPrintf(const char *pszFormat, ...); +#endif + + +KBOOL kFsCacheDirEnsurePopuplated(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError); +KBOOL kFsCacheDirAddChild(PKFSCACHE pCache, PKFSDIR pParent, PKFSOBJ pChild, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheCreateObject(PKFSCACHE pCache, PKFSDIR pParent, + char const *pszName, KU16 cchName, wchar_t const *pwszName, KU16 cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + char const *pszShortName, KU16 cchShortName, wchar_t const *pwszShortName, KU16 cwcShortName, +#endif + KU8 bObjType, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheCreateObjectW(PKFSCACHE pCache, PKFSDIR pParent, wchar_t const *pwszName, KU32 cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + wchar_t const *pwszShortName, KU32 cwcShortName, +#endif + KU8 bObjType, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupRelativeToDirA(PKFSCACHE pCache, PKFSDIR pParent, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor); +PKFSOBJ kFsCacheLookupRelativeToDirW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor); +PKFSOBJ kFsCacheLookupWithLengthA(PKFSCACHE pCache, const char *pchPath, KSIZE cchPath, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupWithLengthW(PKFSCACHE pCache, const wchar_t *pwcPath, KSIZE cwcPath, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupNoMissingA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError); +PKFSOBJ kFsCacheLookupNoMissingW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError); + +/** @name KFSCACHE_LOOKUP_F_XXX - lookup flags + * @{ */ +/** No inserting new cache entries. + * This effectively prevent directories from being repopulated too. */ +#define KFSCACHE_LOOKUP_F_NO_INSERT KU32_C(1) +/** No refreshing cache entries. */ +#define KFSCACHE_LOOKUP_F_NO_REFRESH KU32_C(2) +/** @} */ + +KU32 kFsCacheObjRelease(PKFSCACHE pCache, PKFSOBJ pObj); +KU32 kFsCacheObjReleaseTagged(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere); +#ifndef NDEBUG /* enable to debug object release. */ +# define kFsCacheObjRelease(a_pCache, a_pObj) kFsCacheObjReleaseTagged(a_pCache, a_pObj, __FUNCTION__) +#endif +KU32 kFsCacheObjRetain(PKFSOBJ pObj); +PKFSUSERDATA kFsCacheObjAddUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey, KSIZE cbUserData); +PKFSUSERDATA kFsCacheObjGetUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey); +KU8 kFsCacheObjGetUserDataLockIndex(PKFSCACHE pCache, PKFSOBJ pObj); +KBOOL kFsCacheObjGetFullPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash); +KBOOL kFsCacheObjGetFullPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash); +KBOOL kFsCacheObjGetFullShortPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash); +KBOOL kFsCacheObjGetFullShortPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash); + +KBOOL kFsCacheFileSimpleOpenReadClose(PKFSCACHE pCache, PKFSOBJ pFileObj, KU64 offStart, void *pvBuf, KSIZE cbToRead); + +PKFSCACHE kFsCacheCreate(KU32 fFlags); +void kFsCacheDestroy(PKFSCACHE); +void kFsCacheInvalidateMissing(PKFSCACHE pCache); +void kFsCacheInvalidateAll(PKFSCACHE pCache); +void kFsCacheInvalidateAllAndCloseDirs(PKFSCACHE pCache, KBOOL fIncludingRoot); +void kFsCacheInvalidateCustomMissing(PKFSCACHE pCache); +void kFsCacheInvalidateCustomBoth(PKFSCACHE pCache); +KBOOL kFsCacheSetupCustomRevisionForTree(PKFSCACHE pCache, PKFSOBJ pRoot); +KBOOL kFsCacheInvalidateDeletedDirectoryA(PKFSCACHE pCache, const char *pszDir); + +#endif diff --git a/src/lib/nt/nt_child_inject_standard_handles.c b/src/lib/nt/nt_child_inject_standard_handles.c new file mode 100644 index 0000000..93b139d --- /dev/null +++ b/src/lib/nt/nt_child_inject_standard_handles.c @@ -0,0 +1,462 @@ +/* $Id: nt_child_inject_standard_handles.c 3584 2023-01-20 15:29:36Z bird $ */ +/** @file + * Injecting standard handles into a child process. + */ + +/* + * Copyright (c) 2004-2018 knut st. osmundsen + * + * This file is part of kBuild. + * + * kBuild 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 3 of the License, or + * (at your option) any later version. + * + * kBuild 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 kBuild. If not, see + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include "nt_child_inject_standard_handles.h" + +/** + * Wrapper around ReadProcessMemory in case WOW64 tricks are needed. + * + * @returns Success indicator. + * @param hProcess The target process. + * @param ullSrc The source address (in @a hProcess). + * @param pvDst The target address (this process). + * @param cbToRead How much to read. + * @param pcbRead Where to return how much was actually read. + */ +static BOOL MyReadProcessMemory(HANDLE hProcess, ULONGLONG ullSrc, void *pvDst, SIZE_T cbToRead, SIZE_T *pcbRead) +{ +#if K_ARCH_BITS != 64 + if (ullSrc + cbToRead - 1 > ~(uintptr_t)0) + { + typedef NTSTATUS(NTAPI *PFN_NtWow64ReadVirtualMemory64)(HANDLE, ULONGLONG, PVOID, ULONGLONG, PULONGLONG); + static PFN_NtWow64ReadVirtualMemory64 volatile s_pfnNtWow64ReadVirtualMemory64= NULL; + static BOOL volatile s_fInitialized = FALSE; + PFN_NtWow64ReadVirtualMemory64 pfnNtWow64ReadVirtualMemory64 = s_pfnNtWow64ReadVirtualMemory64; + if (!pfnNtWow64ReadVirtualMemory64 && !s_fInitialized) + { + *(FARPROC *)&pfnNtWow64ReadVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64ReadVirtualMemory64"); + s_pfnNtWow64ReadVirtualMemory64 = pfnNtWow64ReadVirtualMemory64; + } + if (pfnNtWow64ReadVirtualMemory64) + { + struct + { + ULONGLONG volatile ullBefore; + ULONGLONG cbRead64; + ULONGLONG volatile ullAfter; + } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 }; + NTSTATUS rcNt = pfnNtWow64ReadVirtualMemory64(hProcess, ullSrc, pvDst, cbToRead, &Wtf.cbRead64); + *pcbRead = (SIZE_T)Wtf.cbRead64; + SetLastError(rcNt); /* lazy bird */ + return NT_SUCCESS(rcNt); + } + } +#endif + return ReadProcessMemory(hProcess, (void *)(uintptr_t)ullSrc, pvDst, cbToRead, pcbRead); +} + + +/** + * Wrapper around WriteProcessMemory in case WOW64 tricks are needed. + * + * @returns Success indicator. + * @param hProcess The target process. + * @param ullDst The target address (in @a hProcess). + * @param pvSrc The source address (this process). + * @param cbToWrite How much to write. + * @param pcbWritten Where to return how much was actually written. + */ +static BOOL MyWriteProcessMemory(HANDLE hProcess, ULONGLONG ullDst, void const *pvSrc, SIZE_T cbToWrite, SIZE_T *pcbWritten) +{ +#if K_ARCH_BITS != 64 + if (ullDst + cbToWrite - 1 > ~(uintptr_t)0) + { + typedef NTSTATUS (NTAPI *PFN_NtWow64WriteVirtualMemory64)(HANDLE, ULONGLONG, VOID const *, ULONGLONG, PULONGLONG); + static PFN_NtWow64WriteVirtualMemory64 volatile s_pfnNtWow64WriteVirtualMemory64= NULL; + static BOOL volatile s_fInitialized = FALSE; + PFN_NtWow64WriteVirtualMemory64 pfnNtWow64WriteVirtualMemory64 = s_pfnNtWow64WriteVirtualMemory64; + if (!pfnNtWow64WriteVirtualMemory64 && !s_fInitialized) + { + *(FARPROC *)&pfnNtWow64WriteVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64WriteVirtualMemory64"); + s_pfnNtWow64WriteVirtualMemory64 = pfnNtWow64WriteVirtualMemory64; + } + if (pfnNtWow64WriteVirtualMemory64) + { + struct + { + ULONGLONG volatile ullBefore; + ULONGLONG cbWritten64; + ULONGLONG volatile ullAfter; + } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 }; + NTSTATUS rcNt = pfnNtWow64WriteVirtualMemory64(hProcess, ullDst, pvSrc, cbToWrite, &Wtf.cbWritten64); + *pcbWritten = (SIZE_T)Wtf.cbWritten64; + SetLastError(rcNt); /* lazy bird */ + return NT_SUCCESS(rcNt); + } + } +#endif + return WriteProcessMemory(hProcess, (void *)(uintptr_t)ullDst, pvSrc, cbToWrite, pcbWritten); +} + + +/** + * Injects standard handles into a child process (created suspended). + * + * @returns 0 on success. On failure a non-zero windows error or NT status, + * with details in @a pszErr. + * @param hProcess The child process (created suspended). + * @param pafReplace Selects which handles to actually replace (TRUE) and + * which to leave as-is (FALSE). The first entry is + * starndard input, second is standard output, and the + * final is standard error. + * @param pahHandles The handle in the current process to inject into the + * child process. This runs parallel to pafReplace. The + * values NULL and INVALID_HANDLE_VALUE will be written + * directly to the child without duplication. + * @param pszErr Pointer to error message buffer. + * @param cbErr Size of error message buffer. + */ +int nt_child_inject_standard_handles(HANDLE hProcess, BOOL pafReplace[3], HANDLE pahHandles[3], char *pszErr, size_t cbErr) +{ + typedef NTSTATUS (NTAPI *PFN_NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); + static PFN_NtQueryInformationProcess volatile s_pfnNtQueryInformationProcess = NULL; + PFN_NtQueryInformationProcess pfnNtQueryInformationProcess; +#if K_ARCH_BITS != 64 + static PFN_NtQueryInformationProcess volatile s_pfnNtWow64QueryInformationProcess64 = NULL; + PFN_NtQueryInformationProcess pfnNtWow64QueryInformationProcess64; + + static BOOL s_fHostIs64Bit = K_ARCH_BITS == 64; + static BOOL volatile s_fCheckedHost = FALSE; +#endif + + static const unsigned s_offProcessParametersInPeb32 = 0x10; + static const unsigned s_offProcessParametersInPeb64 = 0x20; + static const unsigned s_offStandardInputInProcParams32 = 0x18; + static const unsigned s_offStandardInputInProcParams64 = 0x20; + static const char * const s_apszNames[3] = { "standard input", "standard output", "standard error" }; + + + ULONG cbActual1 = 0; + union + { + PROCESS_BASIC_INFORMATION Natural; + struct + { + NTSTATUS ExitStatus; + ULONGLONG PebBaseAddress; + ULONGLONG AffinityMask; + ULONG BasePriority; + ULONGLONG UniqueProcessId; + ULONGLONG InheritedFromUniqueProcessId; + } Wow64; + } BasicInfo = { { 0, 0, } }; + ULONGLONG ullBasicInfoPeb; + NTSTATUS rcNt; + ULONGLONG ullPeb32 = 0; + ULONGLONG ullPeb64 = 0; + ULONGLONG ullProcParams32 = 0; + ULONGLONG ullProcParams64 = 0; + DWORD au32Handles[3] = { 0, 0, 0 }; + ULONGLONG au64Handles[3] = { 0, 0, 0 }; + unsigned iFirstToInject; + unsigned cHandlesToInject; + unsigned i; + + /* + * Analyze the input to figure out exactly what we need to do. + */ + iFirstToInject = 0; + while (iFirstToInject < 3 && !pafReplace[iFirstToInject]) + iFirstToInject++; + if (iFirstToInject >= 3) + return 0; + + cHandlesToInject = 3 - iFirstToInject; + while ( cHandlesToInject > 1 + && !pafReplace[iFirstToInject + cHandlesToInject - 1]) + cHandlesToInject--; + +#if K_ARCH_BITS != 64 + /* + * Determine host bit count first time through. + */ + if (!s_fCheckedHost) + { + BOOL fAmIWow64 = FALSE; + if ( IsWow64Process(GetCurrentProcess(), &fAmIWow64) + && fAmIWow64) + s_fHostIs64Bit = TRUE; + else + s_fHostIs64Bit = FALSE; + s_fCheckedHost = TRUE; + } +#endif + + /* + * Resolve NT API first time through. + */ + pfnNtQueryInformationProcess = s_pfnNtQueryInformationProcess; +#if K_ARCH_BITS != 64 + pfnNtWow64QueryInformationProcess64 = s_pfnNtWow64QueryInformationProcess64; +#endif + if (!pfnNtQueryInformationProcess) + { + HMODULE hmodNtDll = GetModuleHandleA("NTDLL.DLL"); +#if K_ARCH_BITS != 64 + *(FARPROC *)&pfnNtWow64QueryInformationProcess64 = GetProcAddress(hmodNtDll, "NtWow64QueryInformationProcess64"); + s_pfnNtWow64QueryInformationProcess64 = pfnNtWow64QueryInformationProcess64; +#endif + *(FARPROC *)&pfnNtQueryInformationProcess = GetProcAddress(hmodNtDll, "NtQueryInformationProcess"); + if (!pfnNtQueryInformationProcess) + { + _snprintf(pszErr, cbErr, "The NtQueryInformationProcess API was not found in NTDLL"); + return ERROR_PROC_NOT_FOUND; + } + s_pfnNtQueryInformationProcess = pfnNtQueryInformationProcess; + } + + /* + * Get the PEB address. + * + * If we're a WOW64 process, we must use NtWow64QueryInformationProcess64 + * here or the PEB address will be set to zero for 64-bit children. + */ +#if K_ARCH_BITS != 64 + if (s_fHostIs64Bit && pfnNtWow64QueryInformationProcess64) + { + rcNt = pfnNtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &BasicInfo.Wow64, + sizeof(BasicInfo.Wow64), &cbActual1); + if (!NT_SUCCESS(rcNt)) + { + _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 failed: %#x", rcNt); + return rcNt; + } + if ((ULONGLONG)BasicInfo.Wow64.PebBaseAddress < 0x1000) + { + _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 returned bad PebBaseAddress: %#llx", + BasicInfo.Wow64.PebBaseAddress); + return ERROR_INVALID_ADDRESS; + } + ullBasicInfoPeb = BasicInfo.Wow64.PebBaseAddress; + } + else +#endif + { + rcNt = pfnNtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo.Natural, + sizeof(BasicInfo.Natural), &cbActual1); + if (!NT_SUCCESS(rcNt)) + { + _snprintf(pszErr, cbErr, "NtQueryInformationProcess failed: %#x", rcNt); + return rcNt; + } + if ((uintptr_t)BasicInfo.Natural.PebBaseAddress < 0x1000) + { + _snprintf(pszErr, cbErr, "NtQueryInformationProcess returned bad PebBaseAddress: %#llx", + (ULONGLONG)BasicInfo.Natural.PebBaseAddress); + return ERROR_INVALID_ADDRESS; + } + ullBasicInfoPeb = (uintptr_t)BasicInfo.Natural.PebBaseAddress; + } + + /* + * Get the 32-bit PEB if it's a WOW64 process. + * This query should return 0 for non-WOW64 processes, but we quietly + * ignore failures and assume non-WOW64 child. + */ +#if K_ARCH_BITS != 64 + if (!s_fHostIs64Bit) + ullPeb32 = ullBasicInfoPeb; + else +#endif + { + ULONG_PTR uPeb32Ptr = 0; + cbActual1 = 0; + rcNt = pfnNtQueryInformationProcess(hProcess, ProcessWow64Information, &uPeb32Ptr, sizeof(uPeb32Ptr), &cbActual1); + if (NT_SUCCESS(rcNt) && uPeb32Ptr != 0) + { + ullPeb32 = uPeb32Ptr; + ullPeb64 = ullBasicInfoPeb; +#if K_ARCH_BITS != 64 + assert(ullPeb64 != ullPeb32); + if (ullPeb64 == ullPeb32) + ullPeb64 = 0; +#endif + } + else + { + assert(NT_SUCCESS(rcNt)); + ullPeb64 = ullBasicInfoPeb; + } + } + + /* + * Read the process parameter pointers. + */ + if (ullPeb32) + { + DWORD uProcParamPtr = 0; + SIZE_T cbRead = 0; + if ( MyReadProcessMemory(hProcess, ullPeb32 + s_offProcessParametersInPeb32, + &uProcParamPtr, sizeof(uProcParamPtr), &cbRead) + && cbRead == sizeof(uProcParamPtr)) + ullProcParams32 = uProcParamPtr; + else + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to read PEB32!ProcessParameter at %#llx: %u/%#x (%u read)", + ullPeb32 + s_offProcessParametersInPeb32, dwErr, dwErr, cbRead); + return dwErr ? dwErr : -1; + } + if (uProcParamPtr < 0x1000) + { + _snprintf(pszErr, cbErr, "Bad PEB32!ProcessParameter value: %#llx", ullProcParams32); + return ERROR_INVALID_ADDRESS; + } + } + + if (ullPeb64) + { + ULONGLONG uProcParamPtr = 0; + SIZE_T cbRead = 0; + if ( MyReadProcessMemory(hProcess, ullPeb64 + s_offProcessParametersInPeb64, + &uProcParamPtr, sizeof(uProcParamPtr), &cbRead) + && cbRead == sizeof(uProcParamPtr)) + ullProcParams64 = uProcParamPtr; + else + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to read PEB64!ProcessParameter at %p: %u/%#x (%u read)", + ullPeb64 + s_offProcessParametersInPeb64, dwErr, dwErr, cbRead); + return dwErr ? dwErr : -1; + } + if (uProcParamPtr < 0x1000) + { + _snprintf(pszErr, cbErr, "Bad PEB64!ProcessParameter value: %#llx", uProcParamPtr); + return ERROR_INVALID_ADDRESS; + } + } + + /* + * If we're replacing standard input and standard error but not standard + * output, we must read the standard output handle. We ASSUME that in + * WOW64 processes the two PEBs have the same value, saving a read. + */ + if (iFirstToInject == 0 && cHandlesToInject == 3 && !pafReplace[1]) + { + if (ullProcParams64) + { + SIZE_T cbRead = 0; + if ( MyReadProcessMemory(hProcess, ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]), + &au64Handles[1], sizeof(au64Handles[1]), &cbRead) + && cbRead == sizeof(au64Handles[1])) + au32Handles[1] = (DWORD)au64Handles[1]; + else + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to read ProcessParameter64!StandardOutput at %#llx: %u/%#x (%u read)", + ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]), dwErr, dwErr, cbRead); + return dwErr ? dwErr : -1; + } + } + else if (ullProcParams32) + { + SIZE_T cbRead = 0; + if ( !MyReadProcessMemory(hProcess, ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]), + &au32Handles[1], sizeof(au32Handles[1]), &cbRead) + || cbRead != sizeof(au32Handles[1])) + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to read ProcessParameter32!StandardOutput at %#llx: %u/%#x (%u read)", + ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]), dwErr, dwErr, cbRead); + return dwErr ? dwErr : -1; + } + } + } + + /* + * Duplicate the handles into process, preparing the two handle arrays + * that we'll write to the guest afterwards. + */ + for (i = iFirstToInject; i < 3; i++) + if (pafReplace[i]) + { + HANDLE hInChild = pahHandles[i]; + if ( hInChild == NULL + || hInChild == INVALID_HANDLE_VALUE + || DuplicateHandle(GetCurrentProcess(), pahHandles[i], hProcess, &hInChild, + 0 /*fDesiredAccess*/, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS)) + { + au32Handles[i] = (DWORD)(uintptr_t)hInChild; + au64Handles[i] = (uintptr_t)hInChild; + } + else + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to duplicate handle %p into the child as %s: %u", + pahHandles[i], s_apszNames[i], dwErr); + return dwErr ? dwErr : -1; + } + } + + /* + * Write the handle arrays to the child. + * + * If we're a WOW64 we need to use NtWow64WriteVirtualMemory64 instead of + * WriteProcessMemory because the latter fails with ERROR_NOACCESS (998). + * So, we use a wrapper for doing the writing. + */ + if (ullProcParams32) + { + ULONGLONG ullDst = ullProcParams32 + s_offStandardInputInProcParams32 + iFirstToInject * sizeof(au32Handles[0]); + SIZE_T cbToWrite = cHandlesToInject * sizeof(au32Handles[0]); + SIZE_T cbWritten = 0; + if ( !MyWriteProcessMemory(hProcess, ullDst, &au32Handles[iFirstToInject], cbToWrite, &cbWritten) + || cbWritten != cbToWrite) + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter32 (%#llx LB %u): %u/%#x (%u written)", + ullDst, cbToWrite, dwErr, dwErr, cbWritten); + return dwErr ? dwErr : ERROR_MORE_DATA; + } + } + + if (ullProcParams64) + { + ULONGLONG ullDst = ullProcParams64 + s_offStandardInputInProcParams64 + iFirstToInject * sizeof(au64Handles[0]); + SIZE_T cbToWrite = cHandlesToInject * sizeof(au64Handles[0]); + SIZE_T cbWritten = 0; + if ( !MyWriteProcessMemory(hProcess, ullDst, &au64Handles[iFirstToInject], cbToWrite, &cbWritten) + || cbWritten != cbToWrite) + { + DWORD dwErr = GetLastError(); + _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter64 (%#llx LB %u): %u/%#x (%u written)", + ullDst, cbToWrite, dwErr, dwErr, cbWritten); + return dwErr ? dwErr : ERROR_MORE_DATA; + } + } + + /* Done successfully! */ + return 0; +} + diff --git a/src/lib/nt/nt_child_inject_standard_handles.h b/src/lib/nt/nt_child_inject_standard_handles.h new file mode 100644 index 0000000..851706b --- /dev/null +++ b/src/lib/nt/nt_child_inject_standard_handles.h @@ -0,0 +1,32 @@ +/* $Id: nt_child_inject_standard_handles.h 3179 2018-03-22 19:50:04Z bird $ */ +/** @file + * Injecting standard handles into a child process. + */ + +/* + * Copyright (c) 2004-2018 knut st. osmundsen + * + * This file is part of kBuild. + * + * kBuild 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 3 of the License, or + * (at your option) any later version. + * + * kBuild 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 kBuild. If not, see + * + */ + +#ifndef ___nt_child_inject_standard_handles +#define ___nt_child_inject_standard_handles + +int nt_child_inject_standard_handles(HANDLE hProcess, BOOL pafReplace[3], HANDLE pahHandles[3], char *pszErr, size_t cbErr); + +#endif + diff --git a/src/lib/nt/ntdir.c b/src/lib/nt/ntdir.c new file mode 100644 index 0000000..61a58e3 --- /dev/null +++ b/src/lib/nt/ntdir.c @@ -0,0 +1,673 @@ +/* $Id: ntdir.c 3007 2016-11-06 16:46:43Z bird $ */ +/** @file + * MSC + NT opendir, readdir, telldir, seekdir, and closedir. + */ + +/* + * Copyright (c) 2005-2016 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include +#include + +#include "ntstuff.h" +#include "nthlp.h" +#include "ntdir.h" + + +/** + * Implements opendir. + */ +BirdDir_T *birdDirOpen(const char *pszPath) +{ + HANDLE hDir = birdOpenFile(pszPath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + if (hDir != INVALID_HANDLE_VALUE) + { + BirdDir_T *pDir = birdDirOpenFromHandle((void *)hDir, NULL, BIRDDIR_F_CLOSE_HANDLE); + if (pDir) + return pDir; + birdCloseFile(hDir); + } + return NULL; +} + + +/** + * Alternative opendir, with extra stat() info returned by readdir(). + */ +BirdDir_T *birdDirOpenExtraInfo(const char *pszPath) +{ + HANDLE hDir = birdOpenFile(pszPath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + if (hDir != INVALID_HANDLE_VALUE) + { + BirdDir_T *pDir = birdDirOpenFromHandle((void *)hDir, NULL, BIRDDIR_F_CLOSE_HANDLE | BIRDDIR_F_EXTRA_INFO); + if (pDir) + return pDir; + birdCloseFile(hDir); + } + return NULL; +} + + +BirdDir_T *birdDirOpenExW(void *hRoot, const wchar_t *pwszPath, const wchar_t *pwszFilter, unsigned fFlags) +{ + HANDLE hDir = birdOpenFileExW((HANDLE)hRoot, + pwszPath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + if (hDir != INVALID_HANDLE_VALUE) + { + BirdDir_T *pDir = birdDirOpenFromHandle((void *)hDir, pwszFilter, fFlags | BIRDDIR_F_CLOSE_HANDLE); + if (pDir) + return pDir; + birdCloseFile(hDir); + } + return NULL; +} + + +/** + * Internal worker for birdStatModTimeOnly. + */ +BirdDir_T *birdDirOpenFromHandle(void *pvHandle, const void *pvReserved, unsigned fFlags) +{ + if (!pvReserved && !(fFlags & BIRDDIR_F_STATIC_ALLOC)) + { + /* + * Allocate and initialize the directory enum handle. + */ + BirdDir_T *pDir = (BirdDir_T *)birdMemAlloc(sizeof(*pDir)); + if (pDir) + { + pDir->uMagic = BIRD_DIR_MAGIC; + pDir->fFlags = fFlags; + pDir->pvHandle = pvHandle; + pDir->uDev = 0; + pDir->offPos = 0; + pDir->fHaveData = 0; + pDir->fFirst = 1; + pDir->iInfoClass = fFlags & BIRDDIR_F_EXTRA_INFO ? MyFileIdFullDirectoryInformation : MyFileNamesInformation; + pDir->offBuf = 0; + pDir->cbBuf = 0; + pDir->pabBuf = NULL; + return pDir; + } + } + else + { + assert(!(fFlags & BIRDDIR_F_STATIC_ALLOC)); + assert(pvReserved == NULL); + } + birdSetErrnoToInvalidArg(); + return NULL; +} + + +/** + * Special API that takes a preallocated BirdDir_T and can be called again + * without involving birdDirClose. + * + * + */ +BirdDir_T *birdDirOpenFromHandleWithReuse(BirdDir_T *pDir, void *pvHandle, const void *pvReserved, unsigned fFlags) +{ + if (!pvReserved) + { + /* + * Allocate and initialize the directory enum handle. + */ + if (pDir) + { + if (pDir->uMagic == BIRD_DIR_MAGIC) + { + if ( (pDir->fFlags & BIRDDIR_F_CLOSE_HANDLE) + && pDir->pvHandle != INVALID_HANDLE_VALUE) + birdCloseFile((HANDLE)pDir->pvHandle); + } + else + { + pDir->cbBuf = 0; + pDir->pabBuf = NULL; + pDir->uMagic = BIRD_DIR_MAGIC; + } + pDir->pvHandle = pvHandle; + pDir->fFlags = fFlags; + pDir->uDev = 0; + pDir->offPos = 0; + pDir->fHaveData = 0; + pDir->fFirst = 1; + pDir->iInfoClass = fFlags & BIRDDIR_F_EXTRA_INFO ? MyFileIdFullDirectoryInformation : MyFileNamesInformation; + pDir->offBuf = 0; + return pDir; + } + } + else + assert(pvReserved == NULL); + birdSetErrnoToInvalidArg(); + return NULL; +} + + +static int birdDirReadMore(BirdDir_T *pDir) +{ + MY_NTSTATUS rcNt; + MY_IO_STATUS_BLOCK Ios; + int fFirst; + + /* + * Retrieve the volume serial number + creation time and create the + * device number the first time around. Also allocate a buffer. + */ + fFirst = pDir->fFirst; + if (fFirst) + { + union + { + MY_FILE_FS_VOLUME_INFORMATION VolInfo; + unsigned char abBuf[1024]; + } uBuf; + + Ios.Information = 0; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryVolumeInformationFile((HANDLE)pDir->pvHandle, &Ios, &uBuf, sizeof(uBuf), MyFileFsVolumeInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + pDir->uDev = uBuf.VolInfo.VolumeSerialNumber + | (uBuf.VolInfo.VolumeCreationTime.QuadPart << 32); + else + pDir->uDev = 0; + + if (!pDir->pabBuf) + { + /* + * Allocate a buffer. + * + * Better not exceed 64KB or CIFS may throw a fit. Also, on win10/64 + * here there is a noticable speedup when going one byte below 64KB. + */ + pDir->cbBuf = 0xffe0; + pDir->pabBuf = birdMemAlloc(pDir->cbBuf); + if (!pDir->pabBuf) + return birdSetErrnoToNoMem(); + } + + pDir->fFirst = 0; + } + + /* + * Read another buffer full. + */ + Ios.Information = 0; + Ios.u.Status = -1; + + rcNt = g_pfnNtQueryDirectoryFile((HANDLE)pDir->pvHandle, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + pDir->pabBuf, + pDir->cbBuf, + (MY_FILE_INFORMATION_CLASS)pDir->iInfoClass, + FALSE, /* fReturnSingleEntry */ + NULL, /* Filter / restart pos. */ + pDir->fFlags & BIRDDIR_F_RESTART_SCAN ? TRUE : FALSE); /* fRestartScan */ + if (!MY_NT_SUCCESS(rcNt)) + { + int rc; + if (rcNt == MY_STATUS_NO_MORE_FILES) + rc = 0; + else + rc = birdSetErrnoFromNt(rcNt); + pDir->fHaveData = 0; + pDir->offBuf = pDir->cbBuf; + return rc; + } + + pDir->offBuf = 0; + pDir->fHaveData = 1; + pDir->fFlags &= ~BIRDDIR_F_RESTART_SCAN; + + return 0; +} + + +static int birdDirCopyNameToEntry(WCHAR const *pwcName, ULONG cbName, BirdDirEntry_T *pEntry) +{ + int cchOut = WideCharToMultiByte(CP_ACP, 0, + pwcName, cbName / sizeof(WCHAR), + pEntry->d_name, sizeof(pEntry->d_name) - 1, + NULL, NULL); + if (cchOut > 0) + { + pEntry->d_name[cchOut] = '\0'; + pEntry->d_namlen = (unsigned __int16)cchOut; + pEntry->d_reclen = (unsigned __int16)((size_t)&pEntry->d_name[cchOut + 1] - (size_t)pEntry); + return 0; + } + return -1; +} + + +/** + * Deals with mount points. + * + * @param pDir The directory handle. + * @param pInfo The NT entry information. + * @param pEntryStat The stats for the mount point directory that needs + * updating (a d_stat member). + */ +static void birdDirUpdateMountPointInfo(BirdDir_T *pDir, MY_FILE_ID_FULL_DIR_INFORMATION *pInfo, + BirdStat_T *pEntryStat) +{ + /* + * Try open the root directory of the mount. + * (Can't use birdStatAtW here because the name isn't terminated.) + */ + HANDLE hRoot = INVALID_HANDLE_VALUE; + MY_NTSTATUS rcNt; + MY_UNICODE_STRING Name; + Name.Buffer = pInfo->FileName; + Name.Length = Name.MaximumLength = (USHORT)pInfo->FileNameLength; + + rcNt = birdOpenFileUniStr((HANDLE)pDir->pvHandle, &Name, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hRoot); + if (MY_NT_SUCCESS(rcNt)) + { + int iSavedErrno = errno; + BirdStat_T RootStat; + if (birdStatHandle(hRoot, &RootStat, NULL) == 0) + { + RootStat.st_ismountpoint = 2; + *pEntryStat = RootStat; + } + birdCloseFile(hRoot); + errno = iSavedErrno; + } + /* else: don't mind failures, we've got some info. */ +} + + +/** + * Implements readdir_r(). + * + * @remarks birdDirReadReentrantW is a copy of this. Keep them in sync! + */ +int birdDirReadReentrant(BirdDir_T *pDir, BirdDirEntry_T *pEntry, BirdDirEntry_T **ppResult) +{ + int fSkipEntry; + + *ppResult = NULL; + + if (!pDir || pDir->uMagic != BIRD_DIR_MAGIC) + return birdSetErrnoToBadFileNo(); + + do + { + ULONG offNext; + ULONG cbMinCur; + + /* + * Read more? + */ + if (!pDir->fHaveData) + { + if (birdDirReadMore(pDir) != 0) + return -1; + if (!pDir->fHaveData) + return 0; + } + + /* + * Convert the NT data to the unixy output structure. + */ + fSkipEntry = 0; + switch (pDir->iInfoClass) + { + case MyFileNamesInformation: + { + MY_FILE_NAMES_INFORMATION *pInfo = (MY_FILE_NAMES_INFORMATION *)&pDir->pabBuf[pDir->offBuf]; + if ( pDir->offBuf >= pDir->cbBuf - MIN_SIZEOF_MY_FILE_NAMES_INFORMATION + || pInfo->FileNameLength >= pDir->cbBuf + || pDir->offBuf + pInfo->FileNameLength + MIN_SIZEOF_MY_FILE_NAMES_INFORMATION > pDir->cbBuf) + { + fSkipEntry = 1; + pDir->fHaveData = 0; + continue; + } + + memset(&pEntry->d_stat, 0, sizeof(pEntry->d_stat)); + pEntry->d_stat.st_mode = S_IFMT; + pEntry->d_type = DT_UNKNOWN; + pEntry->d_reclen = 0; + pEntry->d_namlen = 0; + if (birdDirCopyNameToEntry(pInfo->FileName, pInfo->FileNameLength, pEntry) != 0) + fSkipEntry = 1; + + cbMinCur = MIN_SIZEOF_MY_FILE_NAMES_INFORMATION + pInfo->FileNameLength; + offNext = pInfo->NextEntryOffset; + break; + } + + case MyFileIdFullDirectoryInformation: + { + MY_FILE_ID_FULL_DIR_INFORMATION *pInfo = (MY_FILE_ID_FULL_DIR_INFORMATION *)&pDir->pabBuf[pDir->offBuf]; + if ( pDir->offBuf >= pDir->cbBuf - MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION + || pInfo->FileNameLength >= pDir->cbBuf + || pDir->offBuf + pInfo->FileNameLength + MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION > pDir->cbBuf) + { + fSkipEntry = 1; + pDir->fHaveData = 0; + continue; + } + + pEntry->d_type = DT_UNKNOWN; + pEntry->d_reclen = 0; + pEntry->d_namlen = 0; + if (birdDirCopyNameToEntry(pInfo->FileName, pInfo->FileNameLength, pEntry) != 0) + fSkipEntry = 1; + birdStatFillFromFileIdFullDirInfo(&pEntry->d_stat, pInfo); + pEntry->d_stat.st_dev = pDir->uDev; + switch (pEntry->d_stat.st_mode & S_IFMT) + { + case S_IFREG: pEntry->d_type = DT_REG; break; + case S_IFDIR: pEntry->d_type = DT_DIR; break; + case S_IFLNK: pEntry->d_type = DT_LNK; break; + case S_IFIFO: pEntry->d_type = DT_FIFO; break; + case S_IFCHR: pEntry->d_type = DT_CHR; break; + default: +#ifndef NDEBUG + __debugbreak(); +#endif + pEntry->d_type = DT_UNKNOWN; + break; + } + + if (pEntry->d_stat.st_ismountpoint != 1) + { /* likely. */ } + else + birdDirUpdateMountPointInfo(pDir, pInfo, &pEntry->d_stat); + + cbMinCur = MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION + pInfo->FileNameLength; + offNext = pInfo->NextEntryOffset; + break; + } + + default: + return birdSetErrnoToBadFileNo(); + } + + /* + * Advance. + */ + if ( offNext >= cbMinCur + && offNext < pDir->cbBuf) + pDir->offBuf += offNext; + else + { + pDir->fHaveData = 0; + pDir->offBuf = pDir->cbBuf; + } + pDir->offPos++; + } while (fSkipEntry); + + + /* + * Successful return. + */ + *ppResult = pEntry; + return 0; +} + + +/** + * Implements readdir(). + */ +BirdDirEntry_T *birdDirRead(BirdDir_T *pDir) +{ + BirdDirEntry_T *pRet = NULL; + birdDirReadReentrant(pDir, &pDir->u.DirEntry, &pRet); + return pRet; +} + + +static int birdDirCopyNameToEntryW(WCHAR const *pwcName, ULONG cbName, BirdDirEntryW_T *pEntry) +{ + ULONG cwcName = cbName / sizeof(wchar_t); + if (cwcName < sizeof(pEntry->d_name)) + { + memcpy(pEntry->d_name, pwcName, cbName); + pEntry->d_name[cwcName] = '\0'; + pEntry->d_namlen = (unsigned __int16)cwcName; + pEntry->d_reclen = (unsigned __int16)((size_t)&pEntry->d_name[cwcName + 1] - (size_t)pEntry); + return 0; + } + return -1; +} + + +/** + * Implements readdir_r(), UTF-16 version. + * + * @remarks This is a copy of birdDirReadReentrant where only the name handling + * and entry type differs. Remember to keep them in sync! + */ +int birdDirReadReentrantW(BirdDir_T *pDir, BirdDirEntryW_T *pEntry, BirdDirEntryW_T **ppResult) +{ + int fSkipEntry; + + *ppResult = NULL; + + if (!pDir || pDir->uMagic != BIRD_DIR_MAGIC) + return birdSetErrnoToBadFileNo(); + + do + { + ULONG offNext; + ULONG cbMinCur; + + /* + * Read more? + */ + if (!pDir->fHaveData) + { + if (birdDirReadMore(pDir) != 0) + return -1; + if (!pDir->fHaveData) + return 0; + } + + /* + * Convert the NT data to the unixy output structure. + */ + fSkipEntry = 0; + switch (pDir->iInfoClass) + { + case MyFileNamesInformation: + { + MY_FILE_NAMES_INFORMATION *pInfo = (MY_FILE_NAMES_INFORMATION *)&pDir->pabBuf[pDir->offBuf]; + if ( pDir->offBuf >= pDir->cbBuf - MIN_SIZEOF_MY_FILE_NAMES_INFORMATION + || pInfo->FileNameLength >= pDir->cbBuf + || pDir->offBuf + pInfo->FileNameLength + MIN_SIZEOF_MY_FILE_NAMES_INFORMATION > pDir->cbBuf) + { + fSkipEntry = 1; + pDir->fHaveData = 0; + continue; + } + + memset(&pEntry->d_stat, 0, sizeof(pEntry->d_stat)); + pEntry->d_stat.st_mode = S_IFMT; + pEntry->d_type = DT_UNKNOWN; + pEntry->d_reclen = 0; + pEntry->d_namlen = 0; + if (birdDirCopyNameToEntryW(pInfo->FileName, pInfo->FileNameLength, pEntry) != 0) + fSkipEntry = 1; + + cbMinCur = MIN_SIZEOF_MY_FILE_NAMES_INFORMATION + pInfo->FileNameLength; + offNext = pInfo->NextEntryOffset; + break; + } + + case MyFileIdFullDirectoryInformation: + { + MY_FILE_ID_FULL_DIR_INFORMATION *pInfo = (MY_FILE_ID_FULL_DIR_INFORMATION *)&pDir->pabBuf[pDir->offBuf]; + if ( pDir->offBuf >= pDir->cbBuf - MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION + || pInfo->FileNameLength >= pDir->cbBuf + || pDir->offBuf + pInfo->FileNameLength + MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION > pDir->cbBuf) + { + fSkipEntry = 1; + pDir->fHaveData = 0; + continue; + } + + pEntry->d_type = DT_UNKNOWN; + pEntry->d_reclen = 0; + pEntry->d_namlen = 0; + if (birdDirCopyNameToEntryW(pInfo->FileName, pInfo->FileNameLength, pEntry) != 0) + fSkipEntry = 1; + birdStatFillFromFileIdFullDirInfo(&pEntry->d_stat, pInfo); + pEntry->d_stat.st_dev = pDir->uDev; + switch (pEntry->d_stat.st_mode & S_IFMT) + { + case S_IFREG: pEntry->d_type = DT_REG; break; + case S_IFDIR: pEntry->d_type = DT_DIR; break; + case S_IFLNK: pEntry->d_type = DT_LNK; break; + case S_IFIFO: pEntry->d_type = DT_FIFO; break; + case S_IFCHR: pEntry->d_type = DT_CHR; break; + default: +#ifndef NDEBUG + __debugbreak(); +#endif + pEntry->d_type = DT_UNKNOWN; + break; + } + + if (pEntry->d_stat.st_ismountpoint != 1) + { /* likely. */ } + else + birdDirUpdateMountPointInfo(pDir, pInfo, &pEntry->d_stat); + + cbMinCur = MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION + pInfo->FileNameLength; + offNext = pInfo->NextEntryOffset; + break; + } + + default: + return birdSetErrnoToBadFileNo(); + } + + /* + * Advance. + */ + if ( offNext >= cbMinCur + && offNext < pDir->cbBuf) + pDir->offBuf += offNext; + else + { + pDir->fHaveData = 0; + pDir->offBuf = pDir->cbBuf; + } + pDir->offPos++; + } while (fSkipEntry); + + + /* + * Successful return. + */ + *ppResult = pEntry; + return 0; +} + +/** + * Implements readdir(). + */ +BirdDirEntryW_T *birdDirReadW(BirdDir_T *pDir) +{ + BirdDirEntryW_T *pRet = NULL; + birdDirReadReentrantW(pDir, &pDir->u.DirEntryW, &pRet); + return pRet; +} + + +/** + * Implements telldir(). + */ +long birdDirTell(BirdDir_T *pDir) +{ + if (!pDir || pDir->uMagic != BIRD_DIR_MAGIC) + return birdSetErrnoToBadFileNo(); + return pDir->offPos; +} + + +void birdDirSeek(BirdDir_T *pDir, long offDir); + + +/** + * Implements closedir(). + */ +int birdDirClose(BirdDir_T *pDir) +{ + if (!pDir || pDir->uMagic != BIRD_DIR_MAGIC) + return birdSetErrnoToBadFileNo(); + + pDir->uMagic++; + if (pDir->fFlags & BIRDDIR_F_CLOSE_HANDLE) + birdCloseFile((HANDLE)pDir->pvHandle); + pDir->pvHandle = (void *)INVALID_HANDLE_VALUE; + birdMemFree(pDir->pabBuf); + pDir->pabBuf = NULL; + if (!(pDir->fFlags & BIRDDIR_F_STATIC_ALLOC)) + birdMemFree(pDir); + + return 0; +} diff --git a/src/lib/nt/ntdir.h b/src/lib/nt/ntdir.h new file mode 100644 index 0000000..c6d6c3c --- /dev/null +++ b/src/lib/nt/ntdir.h @@ -0,0 +1,154 @@ +/* $Id: ntdir.h 3005 2016-11-06 00:07:37Z bird $ */ +/** @file + * MSC + NT opendir, readdir, closedir and friends. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_ntdir_h +#define ___nt_ntdir_h + +#include "nttypes.h" +#include "ntstat.h" + +typedef struct dirent +{ + /** Optional stat information. + * Only provided if using birdDirOpenExtraInfo(). */ + BirdStat_T d_stat; + /** The record length. */ + unsigned __int16 d_reclen; + /** The name length. */ + unsigned __int16 d_namlen; + /** The name type. */ + unsigned char d_type; + /** The name. */ + char d_name[512 - sizeof(BirdStat_T) - 2 - 2 - 1]; +} BirdDirEntry_T; + +typedef struct direntw +{ + /** Optional stat information. + * Only provided if using birdDirOpenExtraInfo(). */ + BirdStat_T d_stat; + /** The record length. */ + unsigned __int16 d_reclen; + /** The name length (in wchar_t). */ + unsigned __int16 d_namlen; + /** The name type. */ + unsigned char d_type; + /** The name. */ + wchar_t d_name[512 - sizeof(BirdStat_T) - 2 - 2 - 1]; +} BirdDirEntryW_T; + +#define d_ino d_stat.st_ino; + +/** @name d_type values. + * @{ */ +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 +/** @} */ + +/** @name BIRDDIR_F_XXX - birdDirOpenFromHandle & BirdDir_T::fFlags + * @{ */ +/** birdDirClose should also close pvHandle. */ +#define BIRDDIR_F_CLOSE_HANDLE 1U +/** birdDirClose should not close the handle. */ +#define BIRDDIR_F_KEEP_HANDLE 0U +/** Provide extra info (stat). */ +#define BIRDDIR_F_EXTRA_INFO 2U +/** Whether to restart the scan. */ +#define BIRDDIR_F_RESTART_SCAN 4U +/** Set if the BirdDir_T structure is statically allocated. */ +#define BIRDDIR_F_STATIC_ALLOC 8U +/** @} */ + +typedef struct BirdDir +{ + /** Magic value. */ + unsigned uMagic; + /** Flags. */ + unsigned fFlags; + /** The directory handle. */ + void *pvHandle; + /** The device number (st_dev). */ + unsigned __int64 uDev; + /** The current position. */ + long offPos; + + /** Set if we haven't yet read anything. */ + int fFirst; + /** Set if we have data in the buffer. */ + int fHaveData; + /** The info type we're querying. */ + int iInfoClass; + /** The current buffer position. */ + unsigned offBuf; + /** The number of bytes allocated for pabBuf. */ + unsigned cbBuf; + /** Buffer of size cbBuf. */ + unsigned char *pabBuf; + + /** Static directory entry. */ + union + { + BirdDirEntry_T DirEntry; + BirdDirEntryW_T DirEntryW; + } u; +} BirdDir_T; +/** Magic value for BirdDir. */ +#define BIRD_DIR_MAGIC 0x19731120 + + +BirdDir_T *birdDirOpen(const char *pszPath); +BirdDir_T *birdDirOpenExtraInfo(const char *pszPath); +BirdDir_T *birdDirOpenExW(void *hRoot, const wchar_t *pwszPath, const wchar_t *pwszFilter, unsigned fFlags); +BirdDir_T *birdDirOpenFromHandle(void *hDir, const void *pvReserved, unsigned fFlags); +BirdDir_T *birdDirOpenFromHandleWithReuse(BirdDir_T *pDir, void *pvHandle, const void *pvReserved, unsigned fFlags); +BirdDirEntry_T *birdDirRead(BirdDir_T *pDir); +BirdDirEntryW_T *birdDirReadW(BirdDir_T *pDir); +long birdDirTell(BirdDir_T *pDir); +void birdDirSeek(BirdDir_T *pDir, long offDir); +int birdDirClose(BirdDir_T *pDir); + +#define opendir birdDirOpen +#define readdir birdDirRead +#define telldir birdDirTell +#define seekdir birdDirSeek +#define rewinddir(a_pDir, a_offDir) birdDirSeek(a_pDir, 0) +#define closedir birdDirClose +#define _D_NAMLEN(a_pEnt) ((a_pEnt)->d_namlen) +typedef BirdDir_T DIR; + +#endif + diff --git a/src/lib/nt/nthlp.h b/src/lib/nt/nthlp.h new file mode 100644 index 0000000..a24792c --- /dev/null +++ b/src/lib/nt/nthlp.h @@ -0,0 +1,119 @@ +/* $Id: nthlp.h 3337 2020-04-22 17:56:36Z bird $ */ +/** @file + * MSC + NT helper functions. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_nthlp_h +#define ___nt_nthlp_h + +#include "ntstuff.h" +#include "nttypes.h" + + +/** Lazy resolving of the NTDLL imports. */ +#define birdResolveImports() do { if (g_fResolvedNtImports) {} else birdResolveImportsWorker(); } while (0) +void birdResolveImportsWorker(void); +extern int g_fResolvedNtImports; + +void *birdTmpAlloc(size_t cb); +void birdTmpFree(void *pv); + +void *birdMemAlloc(size_t cb); +void *birdMemAllocZ(size_t cb); +void birdMemFree(void *pv); + +int birdSetErrnoFromNt(MY_NTSTATUS rcNt); +int birdSetErrnoFromWin32(DWORD dwErr); +int birdSetErrnoToNoMem(void); +int birdSetErrnoToInvalidArg(void); +int birdSetErrnoToBadFileNo(void); + +HANDLE birdOpenFile(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs); +HANDLE birdOpenFileW(const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs); +HANDLE birdOpenFileEx(HANDLE hRoot, const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs); +HANDLE birdOpenFileExW(HANDLE hRoot, const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs); +HANDLE birdOpenParentDir(HANDLE hRoot, const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + MY_UNICODE_STRING *pNameUniStr); +HANDLE birdOpenParentDirW(HANDLE hRoot, const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + MY_UNICODE_STRING *pNameUniStr); +MY_NTSTATUS birdOpenFileUniStr(HANDLE hRoot, MY_UNICODE_STRING *pNtPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + HANDLE *phFile); +HANDLE birdOpenCurrentDirectory(void); +void birdCloseFile(HANDLE hFile); + +int birdIsPathDirSpec(const char *pszPath); +int birdDosToNtPath(const char *pszPath, MY_UNICODE_STRING *pNtPath); +int birdDosToNtPathW(const wchar_t *pwszPath, MY_UNICODE_STRING *pNtPath); +int birdDosToRelativeNtPath(const char *pszPath, MY_UNICODE_STRING *pNtPath); +int birdDosToRelativeNtPathW(const wchar_t *pszPath, MY_UNICODE_STRING *pNtPath); +void birdFreeNtPath(MY_UNICODE_STRING *pNtPath); + + +static __inline void birdNtTimeToTimeSpec(__int64 iNtTime, BirdTimeSpec_T *pTimeSpec) +{ + iNtTime -= BIRD_NT_EPOCH_OFFSET_UNIX_100NS; + pTimeSpec->tv_sec = iNtTime / 10000000; + pTimeSpec->tv_nsec = (iNtTime % 10000000) * 100; +} + + +static __inline __int64 birdNtTimeFromTimeSpec(BirdTimeSpec_T const *pTimeSpec) +{ + __int64 iNtTime = pTimeSpec->tv_sec * 10000000; + iNtTime += pTimeSpec->tv_nsec / 100; + iNtTime += BIRD_NT_EPOCH_OFFSET_UNIX_100NS; + return iNtTime; +} + + +static __inline void birdNtTimeToTimeVal(__int64 iNtTime, BirdTimeVal_T *pTimeVal) +{ + iNtTime -= BIRD_NT_EPOCH_OFFSET_UNIX_100NS; + pTimeVal->tv_sec = iNtTime / 10000000; + pTimeVal->tv_usec = (iNtTime % 10000000) / 10; +} + + +static __inline __int64 birdNtTimeFromTimeVal(BirdTimeVal_T const *pTimeVal) +{ + __int64 iNtTime = pTimeVal->tv_sec * 10000000; + iNtTime += pTimeVal->tv_usec * 10; + iNtTime += BIRD_NT_EPOCH_OFFSET_UNIX_100NS; + return iNtTime; +} + + +#endif + diff --git a/src/lib/nt/nthlpcore.c b/src/lib/nt/nthlpcore.c new file mode 100644 index 0000000..fe40c5e --- /dev/null +++ b/src/lib/nt/nthlpcore.c @@ -0,0 +1,481 @@ +/* $Id: nthlpcore.c 3534 2021-12-20 23:31:55Z bird $ */ +/** @file + * MSC + NT core helpers functions and globals. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include "nthlp.h" +#ifndef NDEBUG +# include +#endif + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +MY_NTSTATUS (WINAPI *g_pfnNtClose)(HANDLE); +MY_NTSTATUS (WINAPI *g_pfnNtCreateFile)(PHANDLE, MY_ACCESS_MASK, MY_OBJECT_ATTRIBUTES *, MY_IO_STATUS_BLOCK *, + PLARGE_INTEGER, ULONG, ULONG, ULONG, ULONG, PVOID, ULONG); +MY_NTSTATUS (WINAPI *g_pfnNtDeleteFile)(MY_OBJECT_ATTRIBUTES *); +MY_NTSTATUS (WINAPI *g_pfnNtDuplicateObject)(HANDLE hSrcProc, HANDLE hSrc, HANDLE hDstProc, HANDLE *phRet, + MY_ACCESS_MASK fDesiredAccess, ULONG fAttribs, ULONG fOptions); +MY_NTSTATUS (WINAPI *g_pfnNtReadFile)(HANDLE hFile, HANDLE hEvent, MY_IO_APC_ROUTINE *pfnApc, PVOID pvApcCtx, + MY_IO_STATUS_BLOCK *, PVOID pvBuf, ULONG cbToRead, PLARGE_INTEGER poffFile, + PULONG puKey); +MY_NTSTATUS (WINAPI *g_pfnNtQueryInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, PVOID, LONG, MY_FILE_INFORMATION_CLASS); +MY_NTSTATUS (WINAPI *g_pfnNtQueryVolumeInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, PVOID, LONG, MY_FS_INFORMATION_CLASS); +MY_NTSTATUS (WINAPI *g_pfnNtQueryDirectoryFile)(HANDLE, HANDLE, MY_IO_APC_ROUTINE *, PVOID, MY_IO_STATUS_BLOCK *, + PVOID, ULONG, MY_FILE_INFORMATION_CLASS, BOOLEAN, + MY_UNICODE_STRING *, BOOLEAN); +MY_NTSTATUS (WINAPI *g_pfnNtQueryAttributesFile)(MY_OBJECT_ATTRIBUTES *, MY_FILE_BASIC_INFORMATION *); +MY_NTSTATUS (WINAPI *g_pfnNtQueryFullAttributesFile)(MY_OBJECT_ATTRIBUTES *, MY_FILE_NETWORK_OPEN_INFORMATION *); +MY_NTSTATUS (WINAPI *g_pfnNtSetInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, PVOID, LONG, MY_FILE_INFORMATION_CLASS); +BOOLEAN (WINAPI *g_pfnRtlDosPathNameToNtPathName_U)(PCWSTR, MY_UNICODE_STRING *, PCWSTR *, MY_RTL_RELATIVE_NAME_U *); +MY_NTSTATUS (WINAPI *g_pfnRtlAnsiStringToUnicodeString)(MY_UNICODE_STRING *, MY_ANSI_STRING const *, BOOLEAN); +MY_NTSTATUS (WINAPI *g_pfnRtlUnicodeStringToAnsiString)(MY_ANSI_STRING *, MY_UNICODE_STRING *, BOOLEAN); +BOOLEAN (WINAPI *g_pfnRtlEqualUnicodeString)(MY_UNICODE_STRING const *, MY_UNICODE_STRING const *, BOOLEAN); +BOOLEAN (WINAPI *g_pfnRtlEqualString)(MY_ANSI_STRING const *, MY_ANSI_STRING const *, BOOLEAN); +UCHAR (WINAPI *g_pfnRtlUpperChar)(UCHAR uch); +ULONG (WINAPI *g_pfnRtlNtStatusToDosError)(MY_NTSTATUS rcNt); +VOID (WINAPI *g_pfnRtlAcquirePebLock)(VOID); +VOID (WINAPI *g_pfnRtlReleasePebLock)(VOID); + +static struct +{ + FARPROC *ppfn; + const char *pszName; +} const g_apfnDynamicNtdll[] = +{ + { (FARPROC *)&g_pfnNtClose, "NtClose" }, + { (FARPROC *)&g_pfnNtCreateFile, "NtCreateFile" }, + { (FARPROC *)&g_pfnNtDeleteFile, "NtDeleteFile" }, + { (FARPROC *)&g_pfnNtDuplicateObject, "NtDuplicateObject" }, + { (FARPROC *)&g_pfnNtReadFile, "NtReadFile" }, + { (FARPROC *)&g_pfnNtQueryInformationFile, "NtQueryInformationFile" }, + { (FARPROC *)&g_pfnNtQueryVolumeInformationFile, "NtQueryVolumeInformationFile" }, + { (FARPROC *)&g_pfnNtQueryDirectoryFile, "NtQueryDirectoryFile" }, + { (FARPROC *)&g_pfnNtQueryAttributesFile, "NtQueryAttributesFile" }, + { (FARPROC *)&g_pfnNtQueryFullAttributesFile, "NtQueryFullAttributesFile" }, + { (FARPROC *)&g_pfnNtSetInformationFile, "NtSetInformationFile" }, + { (FARPROC *)&g_pfnRtlDosPathNameToNtPathName_U, "RtlDosPathNameToNtPathName_U" }, + { (FARPROC *)&g_pfnRtlAnsiStringToUnicodeString, "RtlAnsiStringToUnicodeString" }, + { (FARPROC *)&g_pfnRtlUnicodeStringToAnsiString, "RtlUnicodeStringToAnsiString" }, + { (FARPROC *)&g_pfnRtlEqualUnicodeString, "RtlEqualUnicodeString" }, + { (FARPROC *)&g_pfnRtlEqualString, "RtlEqualString" }, + { (FARPROC *)&g_pfnRtlUpperChar, "RtlUpperChar" }, + { (FARPROC *)&g_pfnRtlNtStatusToDosError, "RtlNtStatusToDosError" }, + { (FARPROC *)&g_pfnRtlAcquirePebLock, "RtlAcquirePebLock" }, + { (FARPROC *)&g_pfnRtlReleasePebLock, "RtlReleasePebLock" }, +}; +/** Set to 1 if we've successfully resolved the imports, otherwise 0. */ +int g_fResolvedNtImports = 0; + + + +void birdResolveImportsWorker(void) +{ + HMODULE hMod = LoadLibraryW(L"ntdll.dll"); + int i = sizeof(g_apfnDynamicNtdll) / sizeof(g_apfnDynamicNtdll[0]); + while (i-- > 0) + { + const char *pszSym = g_apfnDynamicNtdll[i].pszName; + FARPROC pfn; + *g_apfnDynamicNtdll[i].ppfn = pfn = GetProcAddress(hMod, pszSym); + if (!pfn) + { + /* Write short message and die. */ + static const char s_szMsg[] = "\r\nFatal error resolving NTDLL.DLL symbols!\r\nSymbol: "; + DWORD cbWritten; + if ( !WriteFile(GetStdHandle(STD_ERROR_HANDLE), s_szMsg, sizeof(s_szMsg) - 1, &cbWritten, NULL) + || !WriteFile(GetStdHandle(STD_ERROR_HANDLE), pszSym, (DWORD)strlen(pszSym), &cbWritten, NULL) + || !WriteFile(GetStdHandle(STD_ERROR_HANDLE), "\r\n", sizeof("\r\n") - 1, &cbWritten, NULL) + ) + *(void **)(size_t)i = NULL; + ExitProcess(127); + } + } + + g_fResolvedNtImports = 1; +} + + +void *birdTmpAlloc(size_t cb) +{ + return malloc(cb); +} + + +void birdTmpFree(void *pv) +{ + if (pv) + free(pv); +} + + +void *birdMemAlloc(size_t cb) +{ + return malloc(cb); +} + + +void *birdMemAllocZ(size_t cb) +{ + return calloc(cb, 1); +} + + +void birdMemFree(void *pv) +{ + if (pv) + free(pv); +} + + +int birdErrnoFromNtStatus(MY_NTSTATUS rcNt) +{ + switch (rcNt) + { + /* EPERM = 1 */ + case STATUS_CANNOT_DELETE: + return EPERM; + /* ENOENT = 2 */ + case STATUS_NOT_FOUND: + case STATUS_OBJECT_NAME_NOT_FOUND: + case STATUS_OBJECT_PATH_NOT_FOUND: + case STATUS_OBJECT_NAME_INVALID: + case STATUS_INVALID_COMPUTER_NAME: + case STATUS_VARIABLE_NOT_FOUND: + case STATUS_MESSAGE_NOT_FOUND: + case STATUS_DLL_NOT_FOUND: + case STATUS_ORDINAL_NOT_FOUND: + case STATUS_ENTRYPOINT_NOT_FOUND: + case STATUS_PATH_NOT_COVERED: + case STATUS_BAD_NETWORK_PATH: + case STATUS_DFS_EXIT_PATH_FOUND: + case RPC_NT_OBJECT_NOT_FOUND: + case STATUS_DELETE_PENDING: + return ENOENT; + /* ESRCH = 3 */ + case STATUS_PROCESS_NOT_IN_JOB: + return ESRCH; + /* EINTR = 4 */ + case STATUS_ALERTED: + case STATUS_USER_APC: + return EINTR; + /* EIO = 5 */ + /* ENXIO = 6 */ + /* E2BIG = 7 */ + /* ENOEXEC = 8 */ + case STATUS_INVALID_IMAGE_FORMAT: + case STATUS_INVALID_IMAGE_NE_FORMAT: + case STATUS_INVALID_IMAGE_LE_FORMAT: + case STATUS_INVALID_IMAGE_NOT_MZ: + case STATUS_INVALID_IMAGE_PROTECT: + case STATUS_INVALID_IMAGE_WIN_16: + case STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT: + case STATUS_IMAGE_CHECKSUM_MISMATCH: + case STATUS_IMAGE_MP_UP_MISMATCH: + case STATUS_IMAGE_MACHINE_TYPE_MISMATCH: + case STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE: + case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE: + case STATUS_SECTION_NOT_IMAGE: + case STATUS_INVALID_IMAGE_WIN_32: + case STATUS_INVALID_IMAGE_WIN_64: + case STATUS_INVALID_IMAGE_HASH: + case STATUS_IMAGE_CERT_REVOKED: + return ENOEXEC; + /* EBADF = 9 */ + case STATUS_INVALID_HANDLE: + case STATUS_PORT_CLOSED: + case STATUS_OPLOCK_HANDLE_CLOSED: + case STATUS_HANDLES_CLOSED: + case STATUS_FILE_FORCED_CLOSED: + return EBADF; + /* ECHILD = 10 */ + /* EAGAIN = 11 */ + case STATUS_WMI_TRY_AGAIN: + case STATUS_GRAPHICS_TRY_AGAIN_LATER: + case STATUS_GRAPHICS_TRY_AGAIN_NOW: + return EAGAIN; + /* ENOMEM = 12 */ + case STATUS_NO_MEMORY: + case STATUS_HV_INSUFFICIENT_MEMORY: + case STATUS_INSUFFICIENT_RESOURCES: + case STATUS_REMOTE_RESOURCES: + case STATUS_INSUFF_SERVER_RESOURCES: + return ENOMEM; + /* EACCES = 13 */ + case STATUS_ACCESS_DENIED: + case STATUS_NETWORK_ACCESS_DENIED: + case RPC_NT_PROXY_ACCESS_DENIED: + case STATUS_CTX_SHADOW_DENIED: + case STATUS_CTX_WINSTATION_ACCESS_DENIED: + return EACCES; + /* EFAULT = 14 */ + case STATUS_ACCESS_VIOLATION: + case STATUS_HARDWARE_MEMORY_ERROR: + return EFAULT; + /* EBUSY = 16 */ + case STATUS_PIPE_BUSY: + case STATUS_RESOURCE_IN_USE: + return EBUSY; + /* EEXIST = 17 */ + case STATUS_OBJECT_NAME_EXISTS: + case STATUS_OBJECT_NAME_COLLISION: + case STATUS_DUPLICATE_NAME: + return EEXIST; + /* EXDEV = 18 */ + case STATUS_NOT_SAME_DEVICE: + return EXDEV; + /* ENODEV = 19 */ + /* ENOTDIR = 20 */ + case STATUS_NOT_A_DIRECTORY: + case STATUS_DIRECTORY_IS_A_REPARSE_POINT: + case STATUS_OBJECT_PATH_SYNTAX_BAD: + case STATUS_OBJECT_PATH_INVALID: + case STATUS_OBJECT_TYPE_MISMATCH: + return ENOTDIR; + /* EISDIR = 21 */ + case STATUS_FILE_IS_A_DIRECTORY: + return EISDIR; + /* EINVAL = 22 */ + case STATUS_INVALID_PARAMETER: + case STATUS_INVALID_PARAMETER_1: + case STATUS_INVALID_PARAMETER_2: + case STATUS_INVALID_PARAMETER_3: + case STATUS_INVALID_PARAMETER_4: + case STATUS_INVALID_PARAMETER_5: + case STATUS_INVALID_PARAMETER_6: + case STATUS_INVALID_PARAMETER_7: + case STATUS_INVALID_PARAMETER_8: + case STATUS_INVALID_PARAMETER_9: + case STATUS_INVALID_PARAMETER_10: + case STATUS_INVALID_PARAMETER_11: + case STATUS_INVALID_PARAMETER_12: + case STATUS_INVALID_PARAMETER_MIX: + return EINVAL; + /* ENFILE = 23 */ + /* EMFILE = 24 */ + case STATUS_TOO_MANY_OPENED_FILES: + return EMFILE; + /* ENOTTY = 25 */ + /* EFBIG = 27 */ + /* ENOSPC = 28 */ + case STATUS_DISK_FULL: + return ENOSPC; + /* ESPIPE = 29 */ + /* EROFS = 30 */ + /* EMLINK = 31 */ + /* EPIPE = 32 */ + case STATUS_PIPE_BROKEN: + case RPC_NT_PIPE_CLOSED: + return EPIPE; + /* EDOM = 33 */ + /* ERANGE = 34 */ + /* EDEADLK = 36 */ + case STATUS_POSSIBLE_DEADLOCK: + return EDEADLK; + /* ENAMETOOLONG = 38 */ + case STATUS_NAME_TOO_LONG: + return ENAMETOOLONG; + /* ENOLCK = 39 */ + /* ENOSYS = 40 */ + case STATUS_NOT_SUPPORTED: + return ENOSYS; + /* ENOTEMPTY = 41 */ + case STATUS_DIRECTORY_NOT_EMPTY: + return ENOTEMPTY; + /* EILSEQ = 42 */ + /* EADDRINUSE = 100 */ + /* EADDRNOTAVAIL = 101 */ + /* EAFNOSUPPORT = 102 */ + /* EALREADY = 103 */ + case STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED: + case STATUS_DEVICE_ALREADY_ATTACHED: + case STATUS_PORT_ALREADY_SET: + case STATUS_IMAGE_ALREADY_LOADED: + case STATUS_TOKEN_ALREADY_IN_USE: + case STATUS_IMAGE_ALREADY_LOADED_AS_DLL: + case STATUS_ADDRESS_ALREADY_EXISTS: + case STATUS_ADDRESS_ALREADY_ASSOCIATED: + return EALREADY; + /* EBADMSG = 104 */ + /* ECANCELED = 105 */ + /* ECONNABORTED = 106 */ + /* ECONNREFUSED = 107 */ + /* ECONNRESET = 108 */ + /* EDESTADDRREQ = 109 */ + /* EHOSTUNREACH = 110 */ + case STATUS_HOST_UNREACHABLE: + return EHOSTUNREACH; + /* EIDRM = 111 */ + /* EINPROGRESS = 112 */ + /* EISCONN = 113 */ + /* ELOOP = 114 */ + /* EMSGSIZE = 115 */ + /* ENETDOWN = 116 */ + /* ENETRESET = 117 */ + /* ENETUNREACH = 118 */ + case STATUS_NETWORK_UNREACHABLE: + return ENETUNREACH; + /* ENOBUFS = 119 */ + /* ENODATA = 120 */ + /* ENOLINK = 121 */ + /* ENOMSG = 122 */ + /* ENOPROTOOPT = 123 */ + /* ENOSR = 124 */ + /* ENOSTR = 125 */ + /* ENOTCONN = 126 */ + /* ENOTRECOVERABLE = 127 */ + /* ENOTSOCK = 128 */ + /* ENOTSUP = 129 */ + /* EOPNOTSUPP = 130 */ + /* EOTHER = 131 */ + /* EOVERFLOW = 132 */ + /* EOWNERDEAD = 133 */ + /* EPROTO = 134 */ + /* EPROTONOSUPPORT = 135 */ + /* EPROTOTYPE = 136 */ + /* ETIME = 137 */ + /* ETIMEDOUT = 138 */ + case STATUS_VIRTUAL_CIRCUIT_CLOSED: + case STATUS_TIMEOUT: + return ETIMEDOUT; + + /* ETXTBSY = 139 */ + case STATUS_SHARING_VIOLATION: + return ETXTBSY; + /* EWOULDBLOCK = 140 */ + } + +#ifndef NDEBUG + __debugbreak(); + fprintf(stderr, "rcNt=%#x (%d)\n", rcNt, rcNt); +#endif + return EINVAL; +} + + +int birdSetErrnoFromNt(MY_NTSTATUS rcNt) +{ + errno = birdErrnoFromNtStatus(rcNt); +#if 0 + { + ULONG rcWin32; + _doserrno = rcWin32 = g_pfnRtlNtStatusToDosError(rcNt); + SetLastError(rcWin32); + } +#endif + return -1; +} + + +int birdSetErrnoFromWin32(DWORD dwErr) +{ + switch (dwErr) + { + default: + case ERROR_INVALID_FUNCTION: errno = EINVAL; break; + case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; + case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; + case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break; + case ERROR_ACCESS_DENIED: errno = EACCES; break; + case ERROR_INVALID_HANDLE: errno = EBADF; break; + case ERROR_ARENA_TRASHED: errno = ENOMEM; break; + case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break; + case ERROR_INVALID_BLOCK: errno = ENOMEM; break; + case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break; + case ERROR_BAD_FORMAT: errno = ENOEXEC; break; + case ERROR_INVALID_ACCESS: errno = EINVAL; break; + case ERROR_INVALID_DATA: errno = EINVAL; break; + case ERROR_INVALID_DRIVE: errno = ENOENT; break; + case ERROR_CURRENT_DIRECTORY: errno = EACCES; break; + case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break; + case ERROR_NO_MORE_FILES: errno = ENOENT; break; + case ERROR_LOCK_VIOLATION: errno = EACCES; break; + case ERROR_BAD_NETPATH: errno = ENOENT; break; + case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break; + case ERROR_BAD_NET_NAME: errno = ENOENT; break; + case ERROR_FILE_EXISTS: errno = EEXIST; break; + case ERROR_CANNOT_MAKE: errno = EACCES; break; + case ERROR_FAIL_I24: errno = EACCES; break; + case ERROR_INVALID_PARAMETER: errno = EINVAL; break; + case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break; + case ERROR_DRIVE_LOCKED: errno = EACCES; break; + case ERROR_BROKEN_PIPE: errno = EPIPE; break; + case ERROR_DISK_FULL: errno = ENOSPC; break; + case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break; + case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break; + case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break; + case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break; + case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; + case ERROR_SEEK_ON_DEVICE: errno = EACCES; break; + case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break; + case ERROR_NOT_LOCKED: errno = EACCES; break; + case ERROR_BAD_PATHNAME: errno = ENOENT; break; + case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break; + case ERROR_LOCK_FAILED: errno = EACCES; break; + case ERROR_ALREADY_EXISTS: errno = EEXIST; break; + case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break; + case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break; +#ifdef EMLINK + case ERROR_TOO_MANY_LINKS: errno = EMLINK; break; +#endif + + case ERROR_SHARING_VIOLATION: + errno = ETXTBSY; + break; + } + + return -1; +} + + +int birdSetErrnoToNoMem(void) +{ + errno = ENOMEM; + return -1; +} + + +int birdSetErrnoToInvalidArg(void) +{ + errno = EINVAL; + return -1; +} + + +int birdSetErrnoToBadFileNo(void) +{ + errno = EBADF; + return -1; +} + diff --git a/src/lib/nt/nthlpfs.c b/src/lib/nt/nthlpfs.c new file mode 100644 index 0000000..a85c517 --- /dev/null +++ b/src/lib/nt/nthlpfs.c @@ -0,0 +1,636 @@ +/* $Id: nthlpfs.c 3223 2018-03-31 02:29:56Z bird $ */ +/** @file + * MSC + NT helpers for file system related functions. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "nthlp.h" +#include +#include +#include +#include + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +static int g_fHaveOpenReparsePoint = -1; + + + +static int birdHasTrailingSlash(const char *pszPath) +{ + char ch, ch2; + + /* Skip leading slashes. */ + while ((ch = *pszPath) == '/' || ch == '\\') + pszPath++; + if (ch == '\0') + return 0; + + /* Find the last char. */ + while ((ch2 = *++pszPath) != '\0') + ch = ch2; + + return ch == '/' || ch == '\\' || ch == ':'; +} + + +static int birdHasTrailingSlashW(const wchar_t *pwszPath) +{ + wchar_t wc, wc2; + + /* Skip leading slashes. */ + while ((wc = *pwszPath) == '/' || wc == '\\') + pwszPath++; + if (wc == '\0') + return 0; + + /* Find the last char. */ + while ((wc2 = *++pwszPath) != '\0') + wc = wc2; + + return wc == '/' || wc == '\\' || wc == ':'; +} + + +int birdIsPathDirSpec(const char *pszPath) +{ + char ch, ch2; + + /* Check for empty string. */ + ch = *pszPath; + if (ch == '\0') + return 0; + + /* Find the last char. */ + while ((ch2 = *++pszPath) != '\0') + ch = ch2; + + return ch == '/' || ch == '\\' || ch == ':'; +} + + +static int birdIsPathDirSpecW(const wchar_t *pwszPath) +{ + wchar_t wc, wc2; + + /* Check for empty string. */ + wc = *pwszPath; + if (wc == '\0') + return 0; + + /* Find the last char. */ + while ((wc2 = *++pwszPath) != '\0') + wc = wc2; + + return wc == '/' || wc == '\\' || wc == ':'; +} + + +int birdDosToNtPath(const char *pszPath, MY_UNICODE_STRING *pNtPath) +{ + MY_NTSTATUS rcNt; + WCHAR wszTmp[4096]; + MY_UNICODE_STRING TmpUniStr; + MY_ANSI_STRING Src; + + birdResolveImports(); + + pNtPath->Length = pNtPath->MaximumLength = 0; + pNtPath->Buffer = NULL; + + /* + * Convert the input to wide char. + */ + Src.Buffer = (PCHAR)pszPath; + Src.MaximumLength = Src.Length = (USHORT)strlen(pszPath); + + TmpUniStr.Length = 0; + TmpUniStr.MaximumLength = sizeof(wszTmp) - sizeof(WCHAR); + TmpUniStr.Buffer = wszTmp; + + rcNt = g_pfnRtlAnsiStringToUnicodeString(&TmpUniStr, &Src, FALSE); + if (MY_NT_SUCCESS(rcNt)) + { + if (TmpUniStr.Length > 0 && !(TmpUniStr.Length & 1)) + { + wszTmp[TmpUniStr.Length / sizeof(WCHAR)] = '\0'; + + /* + * Convert the wide DOS path to an NT path. + */ + if (g_pfnRtlDosPathNameToNtPathName_U(wszTmp, pNtPath, NULL, FALSE)) + return 0; + } + rcNt = -1; + } + return birdSetErrnoFromNt(rcNt); +} + + +int birdDosToNtPathW(const wchar_t *pwszPath, MY_UNICODE_STRING *pNtPath) +{ + birdResolveImports(); + + pNtPath->Length = pNtPath->MaximumLength = 0; + pNtPath->Buffer = NULL; + + /* + * Convert the wide DOS path to an NT path. + */ + if (g_pfnRtlDosPathNameToNtPathName_U(pwszPath, pNtPath, NULL, FALSE)) + return 0; + return birdSetErrnoFromNt(STATUS_NO_MEMORY); +} + + +/** + * Converts UNIX slashes to DOS ones. + * + * @returns 0 + * @param pNtPath The relative NT path to fix up. + */ +static int birdFixRelativeNtPathSlashesAndReturn0(MY_UNICODE_STRING *pNtPath) +{ + size_t cwcLeft = pNtPath->Length / sizeof(wchar_t); + wchar_t *pwcStart = pNtPath->Buffer; + wchar_t *pwcHit; + + /* Convert slashes. */ + while ((pwcHit = wmemchr(pwcStart, '/', cwcLeft)) != NULL) + { + *pwcHit = '\\'; + cwcLeft -= pwcHit - pwcStart; + pwcHit = pwcStart; + } + +#if 0 + /* Strip trailing slashes (NT doesn't like them). */ + while ( pNtPath->Length >= sizeof(wchar_t) + && pNtPath->Buffer[(pNtPath->Length - sizeof(wchar_t)) / sizeof(wchar_t)] == '\\') + { + pNtPath->Length -= sizeof(wchar_t); + pNtPath->Buffer[pNtPath->Length / sizeof(wchar_t)] = '\0'; + } + + /* If it was all trailing slashes we convert it to a dot path. */ + if ( pNtPath->Length == 0 + && pNtPath->MaximumLength >= sizeof(wchar_t) * 2) + { + pNtPath->Length = sizeof(wchar_t); + pNtPath->Buffer[0] = '.'; + pNtPath->Buffer[1] = '\0'; + } +#endif + + return 0; +} + + +/** + * Similar to birdDosToNtPath, but it does call RtlDosPathNameToNtPathName_U. + * + * @returns 0 on success, -1 + errno on failure. + * @param pszPath The relative path. + * @param pNtPath Where to return the NT path. Call birdFreeNtPath when done. + */ +int birdDosToRelativeNtPath(const char *pszPath, MY_UNICODE_STRING *pNtPath) +{ + MY_NTSTATUS rcNt; + MY_ANSI_STRING Src; + + birdResolveImports(); + + /* + * Just convert to wide char. + */ + pNtPath->Length = pNtPath->MaximumLength = 0; + pNtPath->Buffer = NULL; + + Src.Buffer = (PCHAR)pszPath; + Src.MaximumLength = Src.Length = (USHORT)strlen(pszPath); + + rcNt = g_pfnRtlAnsiStringToUnicodeString(pNtPath, &Src, TRUE /* Allocate */); + if (MY_NT_SUCCESS(rcNt)) + return birdFixRelativeNtPathSlashesAndReturn0(pNtPath); + return birdSetErrnoFromNt(rcNt); +} + + +/** + * Similar to birdDosToNtPathW, but it does call RtlDosPathNameToNtPathName_U. + * + * @returns 0 on success, -1 + errno on failure. + * @param pwszPath The relative path. + * @param pNtPath Where to return the NT path. Call birdFreeNtPath when done. + */ +int birdDosToRelativeNtPathW(const wchar_t *pwszPath, MY_UNICODE_STRING *pNtPath) +{ + size_t cwcPath = wcslen(pwszPath); + if (cwcPath < 0xfffe) + { + pNtPath->Length = (USHORT)(cwcPath * sizeof(wchar_t)); + pNtPath->MaximumLength = pNtPath->Length + sizeof(wchar_t); + pNtPath->Buffer = HeapAlloc(GetProcessHeap(), 0, pNtPath->MaximumLength); + if (pNtPath->Buffer) + { + memcpy(pNtPath->Buffer, pwszPath, pNtPath->MaximumLength); + return birdFixRelativeNtPathSlashesAndReturn0(pNtPath); + } + errno = ENOMEM; + } + else + errno = ENAMETOOLONG; + return -1; +} + + +/** + * Frees a string returned by birdDosToNtPath, birdDosToNtPathW or + * birdDosToRelativeNtPath. + * + * @param pNtPath The the NT path to free. + */ +void birdFreeNtPath(MY_UNICODE_STRING *pNtPath) +{ + HeapFree(GetProcessHeap(), 0, pNtPath->Buffer); + pNtPath->Buffer = NULL; + pNtPath->Length = 0; + pNtPath->MaximumLength = 0; +} + + +MY_NTSTATUS birdOpenFileUniStr(HANDLE hRoot, MY_UNICODE_STRING *pNtPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + HANDLE *phFile) +{ + MY_IO_STATUS_BLOCK Ios; + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_NTSTATUS rcNt; + + birdResolveImports(); + + if ( (fCreateOptions & FILE_OPEN_REPARSE_POINT) + && g_fHaveOpenReparsePoint == 0) + fCreateOptions &= ~FILE_OPEN_REPARSE_POINT; + + Ios.Information = -1; + Ios.u.Status = 0; + MyInitializeObjectAttributes(&ObjAttr, pNtPath, fObjAttribs, hRoot, NULL /*pSecAttr*/); + + rcNt = g_pfnNtCreateFile(phFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL, /* cbFileInitialAlloc */ + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL, /* pEaBuffer */ + 0); /* cbEaBuffer*/ + if ( rcNt == STATUS_INVALID_PARAMETER + && g_fHaveOpenReparsePoint < 0 + && (fCreateOptions & FILE_OPEN_REPARSE_POINT)) + { + fCreateOptions &= ~FILE_OPEN_REPARSE_POINT; + + Ios.Information = -1; + Ios.u.Status = 0; + MyInitializeObjectAttributes(&ObjAttr, pNtPath, fObjAttribs, NULL /*hRoot*/, NULL /*pSecAttr*/); + + rcNt = g_pfnNtCreateFile(phFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL, /* cbFileInitialAlloc */ + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL, /* pEaBuffer */ + 0); /* cbEaBuffer*/ + if (rcNt != STATUS_INVALID_PARAMETER) + g_fHaveOpenReparsePoint = 0; + } + return rcNt; +} + + +HANDLE birdOpenFile(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs) +{ + MY_UNICODE_STRING NtPath; + MY_NTSTATUS rcNt; + + /* + * Adjust inputs. + */ + if (birdIsPathDirSpec(pszPath)) + fCreateOptions |= FILE_DIRECTORY_FILE; + + /* + * Convert the path and call birdOpenFileUniStr to do the real work. + */ + if (birdDosToNtPath(pszPath, &NtPath) == 0) + { + HANDLE hFile; + rcNt = birdOpenFileUniStr(NULL /*hRoot*/, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, &hFile); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + return hFile; + birdSetErrnoFromNt(rcNt); + } + + return INVALID_HANDLE_VALUE; +} + + +HANDLE birdOpenFileW(const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs) +{ + MY_UNICODE_STRING NtPath; + MY_NTSTATUS rcNt; + + /* + * Adjust inputs. + */ + if (birdIsPathDirSpecW(pwszPath)) + fCreateOptions |= FILE_DIRECTORY_FILE; + + /* + * Convert the path and call birdOpenFileUniStr to do the real work. + */ + if (birdDosToNtPathW(pwszPath, &NtPath) == 0) + { + HANDLE hFile; + rcNt = birdOpenFileUniStr(NULL /*hRoot*/, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, &hFile); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + return hFile; + birdSetErrnoFromNt(rcNt); + } + + return INVALID_HANDLE_VALUE; +} + + +HANDLE birdOpenFileEx(HANDLE hRoot, const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs) +{ + MY_UNICODE_STRING NtPath; + MY_NTSTATUS rcNt; + + /* + * Adjust inputs. + */ + if (birdIsPathDirSpec(pszPath)) + fCreateOptions |= FILE_DIRECTORY_FILE; + + /* + * Convert the path and call birdOpenFileUniStr to do the real work. + */ + if (hRoot == INVALID_HANDLE_VALUE) + hRoot = NULL; + if ((hRoot != NULL ? birdDosToRelativeNtPath(pszPath, &NtPath) : birdDosToNtPath(pszPath, &NtPath)) == 0) + { + HANDLE hFile; + rcNt = birdOpenFileUniStr(hRoot, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, &hFile); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + return hFile; + birdSetErrnoFromNt(rcNt); + } + + return INVALID_HANDLE_VALUE; +} + + +HANDLE birdOpenFileExW(HANDLE hRoot, const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs) +{ + MY_UNICODE_STRING NtPath; + MY_NTSTATUS rcNt; + + /* + * Adjust inputs. + */ + if (birdIsPathDirSpecW(pwszPath)) + fCreateOptions |= FILE_DIRECTORY_FILE; + + /* + * Convert the path (could save ourselves this if pwszPath is perfect) and + * call birdOpenFileUniStr to do the real work. + */ + if (hRoot == INVALID_HANDLE_VALUE) + hRoot = NULL; + if ((hRoot != NULL ? birdDosToRelativeNtPathW(pwszPath, &NtPath) : birdDosToNtPathW(pwszPath, &NtPath)) == 0) + { + HANDLE hFile; + rcNt = birdOpenFileUniStr(hRoot, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, &hFile); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + return hFile; + birdSetErrnoFromNt(rcNt); + } + + return INVALID_HANDLE_VALUE; +} + + +static HANDLE birdOpenParentDirCommon(HANDLE hRoot, MY_UNICODE_STRING *pNtPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + MY_UNICODE_STRING *pNameUniStr) +{ + MY_NTSTATUS rcNt; + + /* + * Strip the path down to the directory. + */ + USHORT offName = pNtPath->Length / sizeof(WCHAR); + USHORT cwcName = offName; + WCHAR wc = 0; + while ( offName > 0 + && (wc = pNtPath->Buffer[offName - 1]) != '\\' + && wc != '/' + && wc != ':') + offName--; + if ( offName > 0 + || (hRoot != NULL && cwcName > 0)) + { + cwcName -= offName; + + /* Make a copy of the file name, if requested. */ + rcNt = STATUS_SUCCESS; + if (pNameUniStr) + { + pNameUniStr->Length = cwcName * sizeof(WCHAR); + pNameUniStr->MaximumLength = pNameUniStr->Length + sizeof(WCHAR); + pNameUniStr->Buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, pNameUniStr->MaximumLength); + if (pNameUniStr->Buffer) + { + memcpy(pNameUniStr->Buffer, &pNtPath->Buffer[offName], pNameUniStr->Length); + pNameUniStr->Buffer[cwcName] = '\0'; + } + else + rcNt = STATUS_NO_MEMORY; + } + + /* Chop, chop. */ + // Bad idea, breaks \\?\c:\pagefile.sys. //while ( offName > 0 + // Bad idea, breaks \\?\c:\pagefile.sys. // && ( (wc = pNtPath->Buffer[offName - 1]) == '\\' + // Bad idea, breaks \\?\c:\pagefile.sys. // || wc == '/')) + // Bad idea, breaks \\?\c:\pagefile.sys. // offName--; + if (offName == 0) + pNtPath->Buffer[offName++] = '.'; /* Hack for dir handle + dir entry name. */ + pNtPath->Length = offName * sizeof(WCHAR); + pNtPath->Buffer[offName] = '\0'; + if (MY_NT_SUCCESS(rcNt)) + { + /* + * Finally, try open the directory. + */ + HANDLE hFile; + fCreateOptions |= FILE_DIRECTORY_FILE; + rcNt = birdOpenFileUniStr(hRoot, pNtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, &hFile); + if (MY_NT_SUCCESS(rcNt)) + { + birdFreeNtPath(pNtPath); + return hFile; + } + } + + if (pNameUniStr) + birdFreeNtPath(pNameUniStr); + } + else + rcNt = STATUS_INVALID_PARAMETER; + + birdFreeNtPath(pNtPath); + birdSetErrnoFromNt(rcNt); + return INVALID_HANDLE_VALUE; +} + + +HANDLE birdOpenParentDir(HANDLE hRoot, const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + MY_UNICODE_STRING *pNameUniStr) +{ + /* + * Convert the path and join up with the UTF-16 version (it'll free NtPath). + */ + MY_UNICODE_STRING NtPath; + if (hRoot == INVALID_HANDLE_VALUE) + hRoot = NULL; + if ( hRoot == NULL + ? birdDosToNtPath(pszPath, &NtPath) == 0 + : birdDosToRelativeNtPath(pszPath, &NtPath) == 0) + return birdOpenParentDirCommon(hRoot, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, pNameUniStr); + return INVALID_HANDLE_VALUE; +} + + +HANDLE birdOpenParentDirW(HANDLE hRoot, const wchar_t *pwszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, + ULONG fShareAccess, ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + MY_UNICODE_STRING *pNameUniStr) +{ + /* + * Convert the path and join up with the ansi version (it'll free NtPath). + */ + MY_UNICODE_STRING NtPath; + if (hRoot == INVALID_HANDLE_VALUE) + hRoot = NULL; + if ( hRoot == NULL + ? birdDosToNtPathW(pwszPath, &NtPath) == 0 + : birdDosToRelativeNtPathW(pwszPath, &NtPath) == 0) + return birdOpenParentDirCommon(hRoot, &NtPath, fDesiredAccess, fFileAttribs, fShareAccess, + fCreateDisposition, fCreateOptions, fObjAttribs, pNameUniStr); + return INVALID_HANDLE_VALUE; +} + + +/** + * Returns a handle to the current working directory of the process. + * + * @returns CWD handle with FILE_TRAVERSE and SYNCHRONIZE access. May return + * INVALID_HANDLE_VALUE w/ errno for invalid CWD. + */ +HANDLE birdOpenCurrentDirectory(void) +{ + PMY_RTL_USER_PROCESS_PARAMETERS pProcParams; + MY_NTSTATUS rcNt; + HANDLE hRet = INVALID_HANDLE_VALUE; + + birdResolveImports(); + + /* + * We'll try get this from the PEB. + */ + g_pfnRtlAcquirePebLock(); + pProcParams = (PMY_RTL_USER_PROCESS_PARAMETERS)MY_NT_CURRENT_PEB()->ProcessParameters; + if (pProcParams != NULL) + rcNt = g_pfnNtDuplicateObject(MY_NT_CURRENT_PROCESS, pProcParams->CurrentDirectory.Handle, + MY_NT_CURRENT_PROCESS, &hRet, + FILE_TRAVERSE | SYNCHRONIZE, + 0 /*fAttribs*/, + 0 /*fOptions*/); + else + rcNt = STATUS_INVALID_PARAMETER; + g_pfnRtlReleasePebLock(); + if (MY_NT_SUCCESS(rcNt)) + return hRet; + + /* + * Fallback goes thru birdOpenFileW. + */ + return birdOpenFileW(L".", + FILE_TRAVERSE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); +} + + +void birdCloseFile(HANDLE hFile) +{ + birdResolveImports(); + g_pfnNtClose(hFile); +} + diff --git a/src/lib/nt/ntopenat.c b/src/lib/nt/ntopenat.c new file mode 100644 index 0000000..6db4de7 --- /dev/null +++ b/src/lib/nt/ntopenat.c @@ -0,0 +1,161 @@ +/* $Id: ntdir.c 3007 2016-11-06 16:46:43Z bird $ */ +/** @file + * MSC + NT openat API. + */ + +/* + * Copyright (c) 2005-2021 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include +#include + +#include "ntstuff.h" +#include "nthlp.h" +#include "ntopenat.h" +#include "ntstat.h" + + +#define IS_ALPHA(ch) ( ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z') ) +#define IS_SLASH(ch) ((ch) == '\\' || (ch) == '/') + + + +static int birdOpenInt(const char *pszPath, int fFlags, unsigned __int16 fMode) +{ + /* + * Try open it using the CRT's open function, but deal with opening + * directories as the CRT doesn't allow doing that. + */ + int const iErrnoSaved = errno; + int fd = open(pszPath, fFlags, fMode); + if ( fd < 0 + && (errno == EACCES || errno == ENOENT || errno == EISDIR) + && (fFlags & (_O_WRONLY | _O_RDWR | _O_RDONLY)) == _O_RDONLY + && (fFlags & (_O_CREAT | _O_TRUNC | _O_EXCL)) == 0 ) + { + BirdStat_T Stat; + if (!birdStatFollowLink(pszPath, &Stat)) + { + if (S_ISDIR(Stat.st_mode)) + { + HANDLE hDir; + errno = iErrnoSaved; + hDir = birdOpenFile(pszPath, + FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE); + if (hDir != INVALID_HANDLE_VALUE) + { + fd = _open_osfhandle((intptr_t)hDir, fFlags); + if (fd >= 0) + return fd; + birdCloseFile(hDir); + } + } + else + errno = EACCES; + } + else + errno = EACCES; + } + return fd; +} + + +int birdOpen(const char *pszPath, int fFlags, ...) +{ + unsigned __int16 fMode; + va_list va; + va_start(va, fFlags); + fMode = va_arg(va, unsigned __int16); + va_end(va); + return birdOpenInt(pszPath, fFlags, fMode); +} + + + +/** + * Implements opendir. + */ +int birdOpenAt(int fdDir, const char *pszPath, int fFlags, ...) +{ + HANDLE hDir; + + /* + * Retrieve the mode mask. + */ + unsigned __int16 fMode; + va_list va; + va_start(va, fFlags); + fMode = va_arg(va, unsigned __int16); + va_end(va); + + /* + * Just call 'open' directly if we can get away with it: + */ + if (fdDir == AT_FDCWD) + return birdOpenInt(pszPath, fFlags, fMode); + + if (IS_SLASH(pszPath[0])) + { + if (IS_SLASH(pszPath[1]) && !IS_SLASH(pszPath[2]) && pszPath[2] != '\0') + return birdOpenInt(pszPath, fFlags, fMode); + } + else if (IS_ALPHA(pszPath[0]) && pszPath[1] == ':') + { + if (IS_SLASH(pszPath[2])) + return birdOpenInt(pszPath, fFlags, fMode); + /* + * Drive letter relative path like "C:kernel32.dll". + * We could try use fdDir as the CWD here if it refers to the same drive, + * however that's can be implemented later... + */ + return birdOpenInt(pszPath, fFlags, fMode); + } + + /* + * Otherwise query the path of fdDir and construct an absolute path from all that. + * This isn't atomic and safe and stuff, but it gets the work done for now. + */ + hDir = (HANDLE)_get_osfhandle(fdDir); + if (hDir != INVALID_HANDLE_VALUE) + { + /** @todo implement me. */ + __debugbreak(); + errno = EBADF; + } + return -1; +} + + diff --git a/src/lib/nt/ntopenat.h b/src/lib/nt/ntopenat.h new file mode 100644 index 0000000..8ea3caa --- /dev/null +++ b/src/lib/nt/ntopenat.h @@ -0,0 +1,43 @@ +/* $Id: ntdir.h 3005 2016-11-06 00:07:37Z bird $ */ +/** @file + * MSC + NT openat. + */ + +/* + * Copyright (c) 2005-2021 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_ntopenat_h +#define ___nt_ntopenat_h + +#include "nttypes.h" + +extern int birdOpenAt(int fdDir, const char *pszPath, int fFlags, ...); + +#define openat birdOpenAt + +#define AT_FDCWD (-987654321) + +#endif + diff --git a/src/lib/nt/ntstat.c b/src/lib/nt/ntstat.c new file mode 100644 index 0000000..0aa30ab --- /dev/null +++ b/src/lib/nt/ntstat.c @@ -0,0 +1,1065 @@ +/* $Id: ntstat.c 3485 2020-09-21 12:25:08Z bird $ */ +/** @file + * MSC + NT stat, lstat and fstat. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include + +#include "ntstuff.h" +#include "nthlp.h" +#include "ntstat.h" + + +#undef stat + +static int birdIsExecutableExtension(const char *pszExt) +{ + switch (pszExt[0]) + { + default: + return 0; + + case 'e': /* exe */ + return pszExt[1] == 'x' && pszExt[2] == 'e' && pszExt[3] == '\0'; + + case 'b': /* bat */ + return pszExt[1] == 'a' && pszExt[2] == 't' && pszExt[3] == '\0'; + + case 'v': /* vbs */ + return pszExt[1] == 'b' && pszExt[2] == 's' && pszExt[3] == '\0'; + + case 'c': /* com and cmd */ + return (pszExt[1] == 'o' && pszExt[2] == 'm' && pszExt[3] == '\0') + || (pszExt[1] == 'm' && pszExt[2] == 'd' && pszExt[3] == '\0'); + } +} + + +static int birdIsFileExecutable(const char *pszName) +{ + if (pszName) + { + const char *pszExt = NULL; + char szExt[8]; + size_t cchExt; + unsigned i; + char ch; + + /* Look for a 3 char extension. */ + ch = *pszName++; + if (!ch) + return 0; + + while ((ch = *pszName++) != '\0') + if (ch == '.') + pszExt = pszName; + + if (!pszExt) + return 0; + pszExt++; + cchExt = pszName - pszExt; + if (cchExt != 3) + return 0; + + /* Copy the extension out and lower case it. Fail immediately on non-alpha chars. */ + for (i = 0; i < cchExt; i++, pszExt++) + { + ch = *pszExt; + if (ch >= 'a' && ch <= 'z') + { /* likely */ } + else if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + else + return 0; + szExt[i] = ch; + } + szExt[i] = '\0'; + + return birdIsExecutableExtension(szExt); + } + + return 0; +} + + +/** + * @a pwcName could be the full path. + */ +static int birdIsFileExecutableW(WCHAR const *pwcName, size_t cwcName) +{ + char szExt[8]; + unsigned cchExt; + unsigned i; + WCHAR const *pwc; + + /* Look for a 3 char extension. */ + if (cwcName > 2 && pwcName[cwcName - 2] == '.') + return 0; + else if (cwcName > 3 && pwcName[cwcName - 3] == '.') + return 0; + else if (cwcName > 4 && pwcName[cwcName - 4] == '.') + cchExt = 3; + else + return 0; + + /* Copy the extension out and lower case it. Fail immediately on non-alpha chars. */ + pwc = &pwcName[cwcName - cchExt]; + for (i = 0; i < cchExt; i++, pwc++) + { + WCHAR wc = *pwc; + if (wc >= 'a' && wc <= 'z') + { /* likely */ } + else if (wc >= 'A' && wc <= 'Z') + wc += 'a' - 'A'; + else + return 0; + szExt[i] = (char)wc; + } + szExt[i] = '\0'; + + return birdIsExecutableExtension(szExt); +} + + +static unsigned short birdFileInfoToMode(ULONG fAttribs, ULONG uReparseTag, + const char *pszName, const wchar_t *pwszName, size_t cbNameW, + unsigned __int8 *pfIsDirSymlink, unsigned __int8 *pfIsMountPoint) +{ + unsigned short fMode; + + /* File type. */ + *pfIsDirSymlink = 0; + *pfIsMountPoint = 0; + if (!(fAttribs & FILE_ATTRIBUTE_REPARSE_POINT)) + { + if (fAttribs & FILE_ATTRIBUTE_DIRECTORY) + fMode = S_IFDIR; + else + fMode = S_IFREG; + } + else + { + switch (uReparseTag) + { + case IO_REPARSE_TAG_SYMLINK: + *pfIsDirSymlink = !!(fAttribs & FILE_ATTRIBUTE_DIRECTORY); + fMode = S_IFLNK; + break; + + case IO_REPARSE_TAG_MOUNT_POINT: + *pfIsMountPoint = 1; + default: + if (fAttribs & FILE_ATTRIBUTE_DIRECTORY) + fMode = S_IFDIR; + else + fMode = S_IFREG; + break; + } + } + + /* Access mask. */ + fMode |= S_IROTH | S_IRGRP | S_IRUSR; + if (!(fAttribs & FILE_ATTRIBUTE_READONLY)) + fMode |= S_IWOTH | S_IWGRP | S_IWUSR; + if ( (fAttribs & FILE_ATTRIBUTE_DIRECTORY) + || (pwszName + ? birdIsFileExecutableW(pwszName, cbNameW / sizeof(wchar_t)) + : birdIsFileExecutable(pszName)) ) + fMode |= S_IXOTH | S_IXGRP | S_IXUSR; + + return fMode; +} + + +/** + * Fills in a stat structure from an MY_FILE_ID_FULL_DIR_INFORMATION entry. + * + * @param pStat The stat structure. + * @param pBuf The MY_FILE_ID_FULL_DIR_INFORMATION entry. + * @remarks Caller sets st_dev. + */ +void birdStatFillFromFileIdFullDirInfo(BirdStat_T *pStat, MY_FILE_ID_FULL_DIR_INFORMATION const *pBuf) +{ + pStat->st_mode = birdFileInfoToMode(pBuf->FileAttributes, pBuf->EaSize, NULL /*pszPath*/, pBuf->FileName, + pBuf->FileNameLength, &pStat->st_isdirsymlink, &pStat->st_ismountpoint); + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = pBuf->EndOfFile.QuadPart; + birdNtTimeToTimeSpec(pBuf->CreationTime.QuadPart, &pStat->st_birthtim); + birdNtTimeToTimeSpec(pBuf->ChangeTime.QuadPart, &pStat->st_ctim); + birdNtTimeToTimeSpec(pBuf->LastWriteTime.QuadPart, &pStat->st_mtim); + birdNtTimeToTimeSpec(pBuf->LastAccessTime.QuadPart, &pStat->st_atim); + pStat->st_ino = pBuf->FileId.QuadPart; + pStat->st_nlink = 1; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = pBuf->FileAttributes; + pStat->st_blksize = 65536; + pStat->st_blocks = (pBuf->AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; +} + + +/** + * Fills in a stat structure from an MY_FILE_ID_BOTH_DIR_INFORMATION entry. + * + * @param pStat The stat structure. + * @param pBuf The MY_FILE_ID_BOTH_DIR_INFORMATION entry. + * @remarks Caller sets st_dev. + */ +void birdStatFillFromFileIdBothDirInfo(BirdStat_T *pStat, MY_FILE_ID_BOTH_DIR_INFORMATION const *pBuf) +{ + pStat->st_mode = birdFileInfoToMode(pBuf->FileAttributes, pBuf->EaSize, NULL /*pszPath*/, pBuf->FileName, + pBuf->FileNameLength, &pStat->st_isdirsymlink, &pStat->st_ismountpoint); + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = pBuf->EndOfFile.QuadPart; + birdNtTimeToTimeSpec(pBuf->CreationTime.QuadPart, &pStat->st_birthtim); + birdNtTimeToTimeSpec(pBuf->ChangeTime.QuadPart, &pStat->st_ctim); + birdNtTimeToTimeSpec(pBuf->LastWriteTime.QuadPart, &pStat->st_mtim); + birdNtTimeToTimeSpec(pBuf->LastAccessTime.QuadPart, &pStat->st_atim); + pStat->st_ino = pBuf->FileId.QuadPart; + pStat->st_nlink = 1; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = pBuf->FileAttributes; + pStat->st_blksize = 65536; + pStat->st_blocks = (pBuf->AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; +} + + +/** + * Fills in a stat structure from an MY_FILE_BOTH_DIR_INFORMATION entry. + * + * @param pStat The stat structure. + * @param pBuf The MY_FILE_BOTH_DIR_INFORMATION entry. + * @remarks Caller sets st_dev. + */ +void birdStatFillFromFileBothDirInfo(BirdStat_T *pStat, MY_FILE_BOTH_DIR_INFORMATION const *pBuf) +{ + pStat->st_mode = birdFileInfoToMode(pBuf->FileAttributes, pBuf->EaSize, NULL /*pszPath*/, pBuf->FileName, + pBuf->FileNameLength, &pStat->st_isdirsymlink, &pStat->st_ismountpoint); + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = pBuf->EndOfFile.QuadPart; + birdNtTimeToTimeSpec(pBuf->CreationTime.QuadPart, &pStat->st_birthtim); + birdNtTimeToTimeSpec(pBuf->ChangeTime.QuadPart, &pStat->st_ctim); + birdNtTimeToTimeSpec(pBuf->LastWriteTime.QuadPart, &pStat->st_mtim); + birdNtTimeToTimeSpec(pBuf->LastAccessTime.QuadPart, &pStat->st_atim); + pStat->st_ino = 0; + pStat->st_nlink = 1; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = pBuf->FileAttributes; + pStat->st_blksize = 65536; + pStat->st_blocks = (pBuf->AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; +} + + +int birdStatHandle2(HANDLE hFile, BirdStat_T *pStat, const char *pszPath, const wchar_t *pwszPath) +{ + int rc; + MY_NTSTATUS rcNt; +#if 0 + ULONG cbAll = sizeof(MY_FILE_ALL_INFORMATION) + 0x10000; + MY_FILE_ALL_INFORMATION *pAll = (MY_FILE_ALL_INFORMATION *)birdTmpAlloc(cbAll); + if (pAll) + { + MY_IO_STATUS_BLOCK Ios; + Ios.Information = 0; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, pAll, cbAll, MyFileAllInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + pStat->st_mode = birdFileInfoToMode(pAll->BasicInformation.FileAttributes, pszPath, + pAll->NameInformation.FileNamepAll->NameInformation.FileNameLength, + hFile, &pStat->st_isdirsymlink, &pStat->st_ismountpoint); + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = pAll->StandardInformation.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(pAll->BasicInformation.CreationTime.QuadPart, &pStat->st_birthtim); + birdNtTimeToTimeSpec(pAll->BasicInformation.ChangeTime.QuadPart, &pStat->st_ctim); + birdNtTimeToTimeSpec(pAll->BasicInformation.LastWriteTime.QuadPart, &pStat->st_mtim); + birdNtTimeToTimeSpec(pAll->BasicInformation.LastAccessTime.QuadPart, &pStat->st_atim); + pStat->st_ino = pAll->InternalInformation.IndexNumber.QuadPart; + pStat->st_nlink = pAll->StandardInformation.NumberOfLinks; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = pAll->StandardInformation.FileAttributes; + pStat->st_blksize = 65536; + pStat->st_blocks = (pAll->StandardInformation.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + + /* Get the serial number, reusing the buffer from above. */ + rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, pAll, cbAll, MyFileFsVolumeInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + MY_FILE_FS_VOLUME_INFORMATION const *pVolInfo = (MY_FILE_FS_VOLUME_INFORMATION const *)pAll; + pStat->st_dev = pVolInfo->VolumeSerialNumber + | (pVolInfo->VolumeCreationTime.QuadPart << 32); + rc = 0; + } + else + { + pStat->st_dev = 0; + rc = birdSetErrnoFromNt(rcNt); + } + } + else + rc = birdSetErrnoFromNt(rcNt); + } + else + rc = birdSetErrnoToNoMem(); +#else + ULONG cbNameInfo = 0; + MY_FILE_NAME_INFORMATION *pNameInfo = NULL; + MY_FILE_STANDARD_INFORMATION StdInfo; + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_FILE_INTERNAL_INFORMATION InternalInfo; + MY_FILE_ATTRIBUTE_TAG_INFORMATION TagInfo; + MY_IO_STATUS_BLOCK Ios; + + Ios.Information = 0; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, &StdInfo, sizeof(StdInfo), MyFileStandardInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + + if (MY_NT_SUCCESS(rcNt)) + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, &BasicInfo, sizeof(BasicInfo), MyFileBasicInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + + if (MY_NT_SUCCESS(rcNt)) + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, &InternalInfo, sizeof(InternalInfo), MyFileInternalInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + + if (MY_NT_SUCCESS(rcNt)) + { + if (!(BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + TagInfo.ReparseTag = 0; + else + { + MY_NTSTATUS rcNt2 = g_pfnNtQueryInformationFile(hFile, &Ios, &TagInfo, sizeof(TagInfo), MyFileAttributeTagInformation); + if ( !MY_NT_SUCCESS(rcNt2) + || !MY_NT_SUCCESS(Ios.u.Status)) + TagInfo.ReparseTag = 0; + } + } + + if ( MY_NT_SUCCESS(rcNt) + && !pszPath + && !pwszPath + && !(BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + cbNameInfo = 0x10020; + pNameInfo = (MY_FILE_NAME_INFORMATION *)alloca(cbNameInfo); + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, pNameInfo, cbNameInfo, MyFileNameInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + } + + if (MY_NT_SUCCESS(rcNt)) + { + pStat->st_mode = birdFileInfoToMode(BasicInfo.FileAttributes, TagInfo.ReparseTag, pszPath, + pNameInfo ? pNameInfo->FileName : pwszPath, + pNameInfo ? pNameInfo->FileNameLength + : pwszPath ? wcslen(pwszPath) * sizeof(wchar_t) : 0, + &pStat->st_isdirsymlink, &pStat->st_ismountpoint); + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = StdInfo.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(BasicInfo.CreationTime.QuadPart, &pStat->st_birthtim); + birdNtTimeToTimeSpec(BasicInfo.ChangeTime.QuadPart, &pStat->st_ctim); + birdNtTimeToTimeSpec(BasicInfo.LastWriteTime.QuadPart, &pStat->st_mtim); + birdNtTimeToTimeSpec(BasicInfo.LastAccessTime.QuadPart, &pStat->st_atim); + pStat->st_ino = InternalInfo.IndexNumber.QuadPart; + pStat->st_nlink = StdInfo.NumberOfLinks; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = BasicInfo.FileAttributes; + pStat->st_blksize = 65536; + pStat->st_blocks = (StdInfo.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + + /* Get the serial number, reusing the buffer from above. */ + if (!cbNameInfo) + { + cbNameInfo = sizeof(MY_FILE_FS_VOLUME_INFORMATION) + 1024; + pNameInfo = (MY_FILE_NAME_INFORMATION *)alloca(cbNameInfo); + } + rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, pNameInfo, cbNameInfo, MyFileFsVolumeInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + MY_FILE_FS_VOLUME_INFORMATION const *pVolInfo = (MY_FILE_FS_VOLUME_INFORMATION const *)pNameInfo; + pStat->st_dev = pVolInfo->VolumeSerialNumber + | (pVolInfo->VolumeCreationTime.QuadPart << 32); + rc = 0; + } + else + { + pStat->st_dev = 0; + rc = birdSetErrnoFromNt(rcNt); + } + } + else + rc = birdSetErrnoFromNt(rcNt); + +#endif + return rc; +} + + +int birdStatHandle(HANDLE hFile, BirdStat_T *pStat, const char *pszPath) +{ + return birdStatHandle2(hFile, pStat, pszPath, NULL); +} + + +/** + * Generates a device number from the volume information. + * + * @returns Device number. + * @param pVolInfo Volume information. + */ +unsigned __int64 birdVolumeInfoToDeviceNumber(const MY_FILE_FS_VOLUME_INFORMATION *pVolInfo) +{ + return pVolInfo->VolumeSerialNumber + | (pVolInfo->VolumeCreationTime.QuadPart << 32); +} + + +/** + * Quries the volume information and generates a device number from it. + * + * @returns NT status code. + * @param hFile The file/dir/whatever to query the volume info + * and device number for. + * @param pVolInfo User provided buffer for volume information. + * @param cbVolInfo The size of the buffer. + * @param puDevNo Where to return the device number. This is set + * to zero on failure. + */ +MY_NTSTATUS birdQueryVolumeDeviceNumber(HANDLE hFile, MY_FILE_FS_VOLUME_INFORMATION *pVolInfo, size_t cbVolInfo, + unsigned __int64 *puDevNo) +{ + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + + Ios.u.Status = -1; + Ios.Information = -1; + + pVolInfo->VolumeSerialNumber = 0; + pVolInfo->VolumeCreationTime.QuadPart = 0; + + rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, pVolInfo, (LONG)cbVolInfo, MyFileFsVolumeInformation); + if (MY_NT_SUCCESS(rcNt)) + { + *puDevNo = birdVolumeInfoToDeviceNumber(pVolInfo); + return Ios.u.Status; + } + *puDevNo = 0; + return rcNt; +} + + +static int birdStatInternal(HANDLE hRoot, const char *pszPath, BirdStat_T *pStat, int fFollow) +{ + int rc; + HANDLE hFile = birdOpenFileEx(hRoot, pszPath, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | (fFollow ? 0 : FILE_OPEN_REPARSE_POINT), + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + rc = birdStatHandle2(hFile, pStat, pszPath, NULL); + birdCloseFile(hFile); + + if (rc || !pStat->st_ismountpoint) + { /* very likely */ } + else + { + /* + * If we hit a mount point (NTFS volume mounted under an empty NTFS directory), + * we should return information about what's mounted there rather than the + * directory it is mounted at as this is what UNIX does. + */ + hFile = birdOpenFileEx(hRoot, pszPath, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + rc = birdStatHandle2(hFile, pStat, pszPath, NULL); + pStat->st_ismountpoint = 2; + birdCloseFile(hFile); + } + } + +#if 0 + { + static char s_szPrev[256]; + size_t cchPath = strlen(pszPath); + if (memcmp(s_szPrev, pszPath, cchPath >= 255 ? 255 : cchPath + 1) == 0) + fprintf(stderr, "stat: %s -> rc/errno=%d/%u\n", pszPath, rc, errno); + else + memcpy(s_szPrev, pszPath, cchPath + 1); + } +#endif + //fprintf(stderr, "stat: %s -> rc/errno=%d/%u\n", pszPath, rc, errno); + } + else + { + //fprintf(stderr, "stat: %s -> %u\n", pszPath, GetLastError()); + + /* + * On things like pagefile.sys we may get sharing violation. We fall + * back on directory enumeration for dealing with that. + */ + if ( errno == ETXTBSY + && strchr(pszPath, '*') == NULL /* Serious paranoia... */ + && strchr(pszPath, '?') == NULL) + { + MY_UNICODE_STRING NameUniStr; + hFile = birdOpenParentDir(hRoot, pszPath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &NameUniStr); + if (hFile != INVALID_HANDLE_VALUE) + { + MY_FILE_ID_FULL_DIR_INFORMATION *pBuf; + ULONG cbBuf = sizeof(*pBuf) + NameUniStr.MaximumLength + 1024; + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + + pBuf = (MY_FILE_ID_FULL_DIR_INFORMATION *)alloca(cbBuf); + Ios.u.Status = -1; + Ios.Information = -1; + rcNt = g_pfnNtQueryDirectoryFile(hFile, NULL, NULL, NULL, &Ios, pBuf, cbBuf, + MyFileIdFullDirectoryInformation, FALSE, &NameUniStr, TRUE); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + /* + * Convert the data. + */ + birdStatFillFromFileIdFullDirInfo(pStat, pBuf); + + /* Get the serial number, reusing the buffer from above. */ + rcNt = birdQueryVolumeDeviceNumber(hFile, (MY_FILE_FS_VOLUME_INFORMATION *)pBuf, cbBuf, &pStat->st_dev); + if (MY_NT_SUCCESS(rcNt)) + rc = 0; + else + rc = birdSetErrnoFromNt(rcNt); + } + + birdFreeNtPath(&NameUniStr); + birdCloseFile(hFile); + + if (MY_NT_SUCCESS(rcNt)) + return 0; + birdSetErrnoFromNt(rcNt); + } + } + rc = -1; + } + + return rc; +} + + +static int birdStatInternalW(HANDLE hRoot, const wchar_t *pwszPath, BirdStat_T *pStat, int fFollow) +{ + int rc; + HANDLE hFile = birdOpenFileExW(hRoot, pwszPath, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | (fFollow ? 0 : FILE_OPEN_REPARSE_POINT), + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + rc = birdStatHandle2(hFile, pStat, NULL, pwszPath); + birdCloseFile(hFile); + + if (rc || !pStat->st_ismountpoint) + { /* very likely */ } + else + { + /* + * If we hit a mount point (NTFS volume mounted under an empty NTFS directory), + * we should return information about what's mounted there rather than the + * directory it is mounted at as this is what UNIX does. + */ + hFile = birdOpenFileExW(hRoot, pwszPath, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + rc = birdStatHandle2(hFile, pStat, NULL, pwszPath); + pStat->st_ismountpoint = 2; + birdCloseFile(hFile); + } + } + } + else + { + /* + * On things like pagefile.sys we may get sharing violation. We fall + * back on directory enumeration for dealing with that. + */ + if ( errno == ETXTBSY + && wcschr(pwszPath, '*') == NULL /* Serious paranoia... */ + && wcschr(pwszPath, '?') == NULL) + { + MY_UNICODE_STRING NameUniStr; + hFile = birdOpenParentDirW(hRoot, pwszPath, + FILE_READ_DATA | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &NameUniStr); + if (hFile != INVALID_HANDLE_VALUE) + { + MY_FILE_ID_FULL_DIR_INFORMATION *pBuf; + ULONG cbBuf = sizeof(*pBuf) + NameUniStr.MaximumLength + 1024; + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + + pBuf = (MY_FILE_ID_FULL_DIR_INFORMATION *)alloca(cbBuf); + Ios.u.Status = -1; + Ios.Information = -1; + rcNt = g_pfnNtQueryDirectoryFile(hFile, NULL, NULL, NULL, &Ios, pBuf, cbBuf, + MyFileIdFullDirectoryInformation, FALSE, &NameUniStr, TRUE); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + /* + * Convert the data. + */ + birdStatFillFromFileIdFullDirInfo(pStat, pBuf); + + /* Get the serial number, reusing the buffer from above. */ + rcNt = birdQueryVolumeDeviceNumber(hFile, (MY_FILE_FS_VOLUME_INFORMATION *)pBuf, cbBuf, &pStat->st_dev); + if (MY_NT_SUCCESS(rcNt)) + rc = 0; + else + rc = birdSetErrnoFromNt(rcNt); + } + + birdFreeNtPath(&NameUniStr); + birdCloseFile(hFile); + + if (MY_NT_SUCCESS(rcNt)) + return 0; + birdSetErrnoFromNt(rcNt); + } + } + rc = -1; + } + + return rc; +} + + +/** + * Implements UNIX fstat(). + */ +int birdStatOnFd(int fd, BirdStat_T *pStat) +{ + int rc; + HANDLE hFile = (HANDLE)_get_osfhandle(fd); + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD fFileType; + + birdResolveImports(); + + SetLastError(NO_ERROR); + fFileType = GetFileType(hFile) & ~FILE_TYPE_REMOTE; + switch (fFileType) + { + case FILE_TYPE_DISK: + rc = birdStatHandle2(hFile, pStat, NULL, NULL); + break; + + case FILE_TYPE_CHAR: + case FILE_TYPE_PIPE: + if (fFileType == FILE_TYPE_PIPE) + pStat->st_mode = S_IFIFO | 0666; + else + pStat->st_mode = S_IFCHR | 0666; + pStat->st_padding0[0] = 0; + pStat->st_padding0[1] = 0; + pStat->st_size = 0; + pStat->st_atim.tv_sec = 0; + pStat->st_atim.tv_nsec = 0; + pStat->st_mtim.tv_sec = 0; + pStat->st_mtim.tv_nsec = 0; + pStat->st_ctim.tv_sec = 0; + pStat->st_ctim.tv_nsec = 0; + pStat->st_birthtim.tv_sec = 0; + pStat->st_birthtim.tv_nsec = 0; + pStat->st_ino = 0; + pStat->st_dev = 0; + pStat->st_rdev = 0; + pStat->st_uid = 0; + pStat->st_gid = 0; + pStat->st_padding1 = 0; + pStat->st_attribs = fFileType == FILE_TYPE_PIPE ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DEVICE; + pStat->st_blksize = 512; + pStat->st_blocks = 0; + if (fFileType == FILE_TYPE_PIPE) + { + DWORD cbAvail; + if (PeekNamedPipe(hFile, NULL, 0, NULL, &cbAvail, NULL)) + pStat->st_size = cbAvail; + } + rc = 0; + break; + + case FILE_TYPE_UNKNOWN: + default: + if (GetLastError() == NO_ERROR) + rc = birdSetErrnoToBadFileNo(); + else + rc = birdSetErrnoFromWin32(GetLastError()); + break; + } + } + else + rc = -1; + return rc; +} + + +/** + * Special case that only gets the file size and nothing else. + */ +int birdStatOnFdJustSize(int fd, __int64 *pcbFile) +{ + int rc; + HANDLE hFile = (HANDLE)_get_osfhandle(fd); + if (hFile != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER cbLocal; + if (GetFileSizeEx(hFile, &cbLocal)) + { + *pcbFile = cbLocal.QuadPart; + rc = 0; + } + else + { + BirdStat_T Stat; + rc = birdStatOnFd(fd, &Stat); + if (rc == 0) + *pcbFile = Stat.st_size; + } + } + else + rc = -1; + return rc; +} + + +/** + * Implements UNIX stat(). + */ +int birdStatFollowLink(const char *pszPath, BirdStat_T *pStat) +{ + return birdStatInternal(NULL, pszPath, pStat, 1 /*fFollow*/); +} + + +/** + * Implements UNIX stat(). + */ +int birdStatFollowLinkW(const wchar_t *pwszPath, BirdStat_T *pStat) +{ + return birdStatInternalW(NULL, pwszPath, pStat, 1 /*fFollow*/); +} + + +/** + * Implements UNIX lstat(). + */ +int birdStatOnLink(const char *pszPath, BirdStat_T *pStat) +{ + return birdStatInternal(NULL, pszPath, pStat, 0 /*fFollow*/); +} + + +/** + * Implements UNIX lstat(). + */ +int birdStatOnLinkW(const wchar_t *pwszPath, BirdStat_T *pStat) +{ + return birdStatInternalW(NULL, pwszPath, pStat, 0 /*fFollow*/); +} + + +/** + * Implements an API like UNIX fstatat(). + * + * @returns 0 on success, -1 and errno on failure. + * @param hRoot NT handle pwszPath is relative to. + * @param pszPath The path. + * @param pStat Where to return stats. + * @param fFollowLink Whether to follow links. + */ +int birdStatAt(HANDLE hRoot, const char *pszPath, BirdStat_T *pStat, int fFollowLink) +{ + return birdStatInternal(hRoot, pszPath, pStat, fFollowLink != 0); +} + + +/** + * Implements an API like UNIX fstatat(). + * + * @returns 0 on success, -1 and errno on failure. + * @param hRoot NT handle pwszPath is relative to. + * @param pwszPath The path. + * @param pStat Where to return stats. + * @param fFollowLink Whether to follow links. + */ +int birdStatAtW(HANDLE hRoot, const wchar_t *pwszPath, BirdStat_T *pStat, int fFollowLink) +{ + return birdStatInternalW(hRoot, pwszPath, pStat, fFollowLink != 0); +} + + +/** + * Internal worker for birdStatModTimeOnly. + */ +static int birdStatOnlyInternal(const char *pszPath, int fFollowLink, MY_FILE_BASIC_INFORMATION *pBasicInfo) +{ + int rc; + HANDLE hFile = birdOpenFile(pszPath, + FILE_READ_ATTRIBUTES, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | (fFollowLink ? 0 : FILE_OPEN_REPARSE_POINT), + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + MY_NTSTATUS rcNt = 0; + MY_IO_STATUS_BLOCK Ios; + Ios.Information = 0; + Ios.u.Status = -1; + + if (pBasicInfo) + { + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, pBasicInfo, sizeof(*pBasicInfo), MyFileBasicInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + } + birdCloseFile(hFile); + + if (MY_NT_SUCCESS(rcNt)) + rc = 0; + else + { + birdSetErrnoFromNt(rcNt); + rc = -1; + } + } + else + { + //fprintf(stderr, "stat: %s -> %u\n", pszPath, GetLastError()); + + /* On things like pagefile.sys we may get sharing violation. */ + if (GetLastError() == ERROR_SHARING_VIOLATION) + { + /** @todo Fall back on the parent directory enum if we run into a sharing + * violation. */ + } + rc = -1; + } + return rc; +} + + +/** + * Special function for getting the modification time. + */ +int birdStatModTimeOnly(const char *pszPath, BirdTimeSpec_T *pTimeSpec, int fFollowLink) +{ + /* + * Convert the path and call NtQueryFullAttributesFile. + * + * Note! NtQueryAttributesFile cannot be used as it only returns attributes. + */ + MY_UNICODE_STRING NtPath; + + birdResolveImports(); + if (birdDosToNtPath(pszPath, &NtPath) == 0) + { + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_FILE_NETWORK_OPEN_INFORMATION Info; + MY_NTSTATUS rcNt; + + memset(&Info, 0xfe, sizeof(Info)); + + MyInitializeObjectAttributes(&ObjAttr, &NtPath, OBJ_CASE_INSENSITIVE, NULL /*hRoot*/, NULL /*pSecAttr*/); + rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &Info); + + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + { + birdNtTimeToTimeSpec(Info.LastWriteTime.QuadPart, pTimeSpec); + + /* Do the trailing slash check. */ + if ( (Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + || !birdIsPathDirSpec(pszPath)) + { + MY_FILE_BASIC_INFORMATION BasicInfo; + if ( !(Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + || !fFollowLink) + return 0; + + /* Fallback on birdStatOnlyInternal to follow the reparse point. */ + if (!birdStatOnlyInternal(pszPath, fFollowLink, &BasicInfo)) + { + birdNtTimeToTimeSpec(BasicInfo.LastWriteTime.QuadPart, pTimeSpec); + return 0; + } + } + else + errno = ENOTDIR; + } + else + birdSetErrnoFromNt(rcNt); + } + return -1; +} + +/** + * Special function for getting the file mode. + */ +int birdStatModeOnly(const char *pszPath, unsigned __int16 *pMode, int fFollowLink) +{ + /* + * Convert the path and call NtQueryFullAttributesFile. + */ + MY_UNICODE_STRING NtPath; + + birdResolveImports(); + if (birdDosToNtPath(pszPath, &NtPath) == 0) + { + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_FILE_BASIC_INFORMATION Info; + MY_NTSTATUS rcNt; + + memset(&Info, 0xfe, sizeof(Info)); + + MyInitializeObjectAttributes(&ObjAttr, &NtPath, OBJ_CASE_INSENSITIVE, NULL /*hRoot*/, NULL /*pSecAttr*/); + rcNt = g_pfnNtQueryAttributesFile(&ObjAttr, &Info); + + if (MY_NT_SUCCESS(rcNt)) + { + unsigned __int8 isdirsymlink = 0; + unsigned __int8 ismountpoint = 0; + *pMode = birdFileInfoToMode(Info.FileAttributes, 0, pszPath, NtPath.Buffer, NtPath.Length, + &isdirsymlink, &ismountpoint); + + /* Do the trailing slash check. */ + if ( (Info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + || !birdIsPathDirSpec(pszPath)) + { + if ( !(Info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + || !fFollowLink) + { + birdFreeNtPath(&NtPath); + return 0; + } + + /* Fallback on birdStatOnlyInternal to follow the reparse point. */ + if (!birdStatOnlyInternal(pszPath, fFollowLink, &Info)) + { + *pMode = birdFileInfoToMode(Info.FileAttributes, 0, pszPath, NtPath.Buffer, NtPath.Length, + &isdirsymlink, &ismountpoint); + birdFreeNtPath(&NtPath); + return 0; + } + } + else + errno = ENOTDIR; + } + else + birdSetErrnoFromNt(rcNt); + birdFreeNtPath(&NtPath); + } + return -1; +} + + diff --git a/src/lib/nt/ntstat.h b/src/lib/nt/ntstat.h new file mode 100644 index 0000000..52e41f3 --- /dev/null +++ b/src/lib/nt/ntstat.h @@ -0,0 +1,144 @@ +/* $Id: ntstat.h 3485 2020-09-21 12:25:08Z bird $ */ +/** @file + * MSC + NT stat, lstat and fstat implementation and wrappers. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_ntstat_h +#define ___nt_ntstat_h + +#include "nttypes.h" + +#include +#include +#include + +#undef stat +#undef lstat +#undef fstat + + +/** The distance between the NT and unix epochs given in NT time (units of 100 + * ns). */ +#define BIRD_NT_EPOCH_OFFSET_UNIX_100NS 116444736000000000LL + +typedef struct BirdStat +{ + unsigned __int16 st_mode; + unsigned __int8 st_isdirsymlink; /**< Set if directory symlink. */ + unsigned __int8 st_ismountpoint; /**< Set if mount point; 1 if not followed, 2 if followed (lstat & readdir only). */ + unsigned __int16 st_padding0[2]; + __int64 st_size; + BirdTimeSpec_T st_atim; + BirdTimeSpec_T st_mtim; + BirdTimeSpec_T st_ctim; + BirdTimeSpec_T st_birthtim; + unsigned __int64 st_ino; + unsigned __int64 st_dev; + unsigned __int32 st_nlink; + unsigned __int16 st_rdev; + __int16 st_uid; + __int16 st_gid; + unsigned __int16 st_padding1; + unsigned __int32 st_attribs; + unsigned __int32 st_blksize; + __int64 st_blocks; +} BirdStat_T; + +#define BIRD_STAT_BLOCK_SIZE 512 + +#define st_atime st_atim.tv_sec +#define st_ctime st_ctim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_birthtime st_birthtim.tv_sec + +int birdStatFollowLink(const char *pszPath, BirdStat_T *pStat); +int birdStatFollowLinkW(const wchar_t *pwszPath, BirdStat_T *pStat); +int birdStatOnLink(const char *pszPath, BirdStat_T *pStat); +int birdStatOnLinkW(const wchar_t *pwszPath, BirdStat_T *pStat); +int birdStatAt(void *hRoot, const char *pszPath, BirdStat_T *pStat, int fFollowLink); +int birdStatAtW(void *hRoot, const wchar_t *pwszPath, BirdStat_T *pStat, int fFollowLink); +int birdStatOnFd(int fd, BirdStat_T *pStat); +int birdStatOnFdJustSize(int fd, __int64 *pcbFile); +int birdStatModTimeOnly(const char *pszPath, BirdTimeSpec_T *pTimeSpec, int fFollowLink); +int birdStatModeOnly(const char *pszPath, unsigned __int16 *pMode, int fFollowLink); +#ifdef ___nt_ntstuff_h +int birdStatHandle(HANDLE hFile, BirdStat_T *pStat, const char *pszPath); +void birdStatFillFromFileIdFullDirInfo(BirdStat_T *pStat, MY_FILE_ID_FULL_DIR_INFORMATION const *pBuf); +void birdStatFillFromFileIdBothDirInfo(BirdStat_T *pStat, MY_FILE_ID_BOTH_DIR_INFORMATION const *pBuf); +void birdStatFillFromFileBothDirInfo(BirdStat_T *pStat, MY_FILE_BOTH_DIR_INFORMATION const *pBuf); +MY_NTSTATUS birdQueryVolumeDeviceNumber(HANDLE hFile, MY_FILE_FS_VOLUME_INFORMATION *pVolInfo, size_t cbVolInfo, + unsigned __int64 *puDevNo); +unsigned __int64 birdVolumeInfoToDeviceNumber(MY_FILE_FS_VOLUME_INFORMATION const *pVolInfo); +#endif + +#define STAT_REDEFINED_ALREADY + +#define stat BirdStat +#define BirdStat(a_pszPath, a_pStat) birdStatFollowLink(a_pszPath, a_pStat) +#define lstat(a_pszPath, a_pStat) birdStatOnLink(a_pszPath, a_pStat) +#define fstat(a_fd, a_pStat) birdStatOnFd(a_fd, a_pStat) + + +#ifndef _S_IFLNK +# define _S_IFLNK 0xa000 +#endif +#ifndef S_IFLNK +# define S_IFLNK _S_IFLNK +#endif +#ifndef S_IFIFO +# define S_IFIFO _S_IFIFO +#endif + +#ifndef S_ISLNK +# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#endif +#ifndef S_ISDIR +# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif +#ifndef S_ISREG +# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#define S_IRWXU (_S_IREAD | _S_IWRITE | _S_IEXEC) +#define S_IXUSR _S_IEXEC +#define S_IWUSR _S_IWRITE +#define S_IRUSR _S_IREAD +#define S_IRWXG 0000070 +#define S_IRGRP 0000040 +#define S_IWGRP 0000020 +#define S_IXGRP 0000010 +#define S_IRWXO 0000007 +#define S_IROTH 0000004 +#define S_IWOTH 0000002 +#define S_IXOTH 0000001 +#define S_ISUID 0004000 +#define S_ISGID 0002000 +#define ALLPERMS 0000777 + +#endif + diff --git a/src/lib/nt/ntstuff.h b/src/lib/nt/ntstuff.h new file mode 100644 index 0000000..03439b4 --- /dev/null +++ b/src/lib/nt/ntstuff.h @@ -0,0 +1,573 @@ +/* $Id: ntstuff.h 3223 2018-03-31 02:29:56Z bird $ */ +/** @file + * Definitions, types, prototypes and globals for NT. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +#ifndef ___nt_ntstuff_h +#define ___nt_ntstuff_h + +#define timeval timeval_Windows +#define WIN32_NO_STATUS +#include +#include +#undef WIN32_NO_STATUS +#include +#undef timeval + +#include + + +/** @defgroup grp_nt_ntstuff NT Stuff + * @{ */ + +typedef LONG MY_NTSTATUS; +typedef ULONG MY_ACCESS_MASK; + +typedef struct MY_IO_STATUS_BLOCK +{ + union + { + MY_NTSTATUS Status; + PVOID Pointer; + } u; + ULONG_PTR Information; +} MY_IO_STATUS_BLOCK; + +typedef VOID WINAPI MY_IO_APC_ROUTINE(PVOID, MY_IO_STATUS_BLOCK *, ULONG); + +typedef struct MY_UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} MY_UNICODE_STRING; + +typedef struct MY_STRING +{ + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} MY_STRING; +typedef MY_STRING MY_ANSI_STRING; + +typedef struct MY_CURDIR +{ + UNICODE_STRING DosPath; + HANDLE Handle; +} MY_CURDIR; +typedef MY_CURDIR *PMY_CURDIR; + +typedef struct MY_RTL_DRIVE_LETTER_CURDIR +{ + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + MY_ANSI_STRING DosPath; +} MY_RTL_DRIVE_LETTER_CURDIR; +typedef MY_RTL_DRIVE_LETTER_CURDIR *PRTL_DRIVE_LETTER_CURDIR; + +typedef struct MY_RTL_USER_PROCESS_PARAMETERS +{ + ULONG MaximumLength; + ULONG Length; + ULONG Flags; + ULONG DebugFlags; + HANDLE ConsoleHandle; + ULONG ConsoleFlags; + HANDLE StandardInput; + HANDLE StandardOutput; + HANDLE StandardError; + MY_CURDIR CurrentDirectory; + MY_UNICODE_STRING DllPath; + MY_UNICODE_STRING ImagePathName; + MY_UNICODE_STRING CommandLine; + PWSTR Environment; + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + ULONG WindowFlags; + ULONG ShowWindowFlags; + MY_UNICODE_STRING WindowTitle; + MY_UNICODE_STRING DesktopInfo; + MY_UNICODE_STRING ShellInfo; + MY_UNICODE_STRING RuntimeInfo; + MY_RTL_DRIVE_LETTER_CURDIR CurrentDirectories[0x20]; + SIZE_T EnvironmentSize; /* >= Vista+ */ + SIZE_T EnvironmentVersion; /* >= Windows 7. */ + PVOID PackageDependencyData; /* >= Windows 8 or Windows 8.1. */ + ULONG ProcessGroupId; /* >= Windows 8 or Windows 8.1. */ +} MY_RTL_USER_PROCESS_PARAMETERS; +typedef MY_RTL_USER_PROCESS_PARAMETERS *PMY_RTL_USER_PROCESS_PARAMETERS; + +typedef struct MY_OBJECT_ATTRIBUTES +{ + ULONG Length; + HANDLE RootDirectory; + MY_UNICODE_STRING *ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} MY_OBJECT_ATTRIBUTES; + +#define MyInitializeObjectAttributes(a_pAttr, a_pName, a_fAttribs, a_hRoot, a_pSecDesc) \ + do { \ + (a_pAttr)->Length = sizeof(MY_OBJECT_ATTRIBUTES); \ + (a_pAttr)->RootDirectory = (a_hRoot); \ + (a_pAttr)->Attributes = (a_fAttribs); \ + (a_pAttr)->ObjectName = (a_pName); \ + (a_pAttr)->SecurityDescriptor = (a_pSecDesc); \ + (a_pAttr)->SecurityQualityOfService = NULL; \ + } while (0) + + + +typedef struct MY_FILE_BASIC_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} MY_FILE_BASIC_INFORMATION; + +typedef struct MY_FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} MY_FILE_STANDARD_INFORMATION; + +typedef struct MY_FILE_NETWORK_OPEN_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; + ULONG AlignmentPadding; +} MY_FILE_NETWORK_OPEN_INFORMATION; + +typedef struct MY_FILE_INTERNAL_INFORMATION +{ + LARGE_INTEGER IndexNumber; +} MY_FILE_INTERNAL_INFORMATION; + +typedef struct MY_FILE_EA_INFORMATION +{ + ULONG EaSize; +} MY_FILE_EA_INFORMATION; + +typedef struct MY_FILE_ACCESS_INFORMATION +{ + ACCESS_MASK AccessFlags; +} MY_FILE_ACCESS_INFORMATION; + +typedef struct MY_FILE_POSITION_INFORMATION +{ + LARGE_INTEGER CurrentByteOffset; +} MY_FILE_POSITION_INFORMATION; + +typedef struct MY_FILE_MODE_INFORMATION +{ + ULONG Mode; +} MY_FILE_MODE_INFORMATION; + +typedef struct MY_FILE_ALIGNMENT_INFORMATION +{ + ULONG AlignmentRequirement; +} MY_FILE_ALIGNMENT_INFORMATION; + +typedef struct MY_FILE_NAME_INFORMATION +{ + ULONG FileNameLength; + WCHAR FileName[1]; +} MY_FILE_NAME_INFORMATION; + +typedef struct MY_FILE_ALL_INFORMATION +{ + MY_FILE_BASIC_INFORMATION BasicInformation; + MY_FILE_STANDARD_INFORMATION StandardInformation; + MY_FILE_INTERNAL_INFORMATION InternalInformation; + MY_FILE_EA_INFORMATION EaInformation; + MY_FILE_ACCESS_INFORMATION AccessInformation; + MY_FILE_POSITION_INFORMATION PositionInformation; + MY_FILE_MODE_INFORMATION ModeInformation; + MY_FILE_ALIGNMENT_INFORMATION AlignmentInformation; + MY_FILE_NAME_INFORMATION NameInformation; +} MY_FILE_ALL_INFORMATION; + +typedef struct MY_FILE_ATTRIBUTE_TAG_INFORMATION +{ + ULONG FileAttributes; + ULONG ReparseTag; +} MY_FILE_ATTRIBUTE_TAG_INFORMATION; + + +typedef struct MY_FILE_NAMES_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + ULONG FileNameLength; + WCHAR FileName[1]; +} MY_FILE_NAMES_INFORMATION; +/** The sizeof(MY_FILE_NAMES_INFORMATION) without the FileName. */ +#define MIN_SIZEOF_MY_FILE_NAMES_INFORMATION (4 + 4 + 4) + + +typedef struct MY_FILE_ID_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} MY_FILE_ID_FULL_DIR_INFORMATION; +/** The sizeof(MY_FILE_NAMES_INFORMATION) without the FileName. */ +#define MIN_SIZEOF_MY_FILE_ID_FULL_DIR_INFORMATION ( (size_t)&((MY_FILE_ID_FULL_DIR_INFORMATION *)0)->FileName ) + +typedef struct MY_FILE_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} MY_FILE_BOTH_DIR_INFORMATION; +/** The sizeof(MY_FILE_BOTH_DIR_INFORMATION) without the FileName. */ +#define MIN_SIZEOF_MY_FILE_BOTH_DIR_INFORMATION ( (size_t)&((MY_FILE_BOTH_DIR_INFORMATION *)0)->FileName ) + + +typedef struct MY_FILE_ID_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} MY_FILE_ID_BOTH_DIR_INFORMATION; +/** The sizeof(MY_FILE_NAMES_INFORMATION) without the FileName. */ +#define MIN_SIZEOF_MY_FILE_ID_BOTH_DIR_INFORMATION ( (size_t)&((MY_FILE_ID_BOTH_DIR_INFORMATION *)0)->FileName ) + + +typedef struct MY_FILE_DISPOSITION_INFORMATION +{ + BOOLEAN DeleteFile; +} MY_FILE_DISPOSITION_INFORMATION; + + +typedef enum MY_FILE_INFORMATION_CLASS +{ + MyFileDirectoryInformation = 1, + MyFileFullDirectoryInformation, /* = 2 */ + MyFileBothDirectoryInformation, /* = 3 */ + MyFileBasicInformation, /* = 4 */ + MyFileStandardInformation, /* = 5 */ + MyFileInternalInformation, /* = 6 */ + MyFileEaInformation, /* = 7 */ + MyFileAccessInformation, /* = 8 */ + MyFileNameInformation, /* = 9 */ + MyFileRenameInformation, /* = 10 */ + MyFileLinkInformation, /* = 11 */ + MyFileNamesInformation, /* = 12 */ + MyFileDispositionInformation, /* = 13 */ + MyFilePositionInformation, /* = 14 */ + MyFileFullEaInformation, /* = 15 */ + MyFileModeInformation, /* = 16 */ + MyFileAlignmentInformation, /* = 17 */ + MyFileAllInformation, /* = 18 */ + MyFileAllocationInformation, /* = 19 */ + MyFileEndOfFileInformation, /* = 20 */ + MyFileAlternateNameInformation, /* = 21 */ + MyFileStreamInformation, /* = 22 */ + MyFilePipeInformation, /* = 23 */ + MyFilePipeLocalInformation, /* = 24 */ + MyFilePipeRemoteInformation, /* = 25 */ + MyFileMailslotQueryInformation, /* = 26 */ + MyFileMailslotSetInformation, /* = 27 */ + MyFileCompressionInformation, /* = 28 */ + MyFileObjectIdInformation, /* = 29 */ + MyFileCompletionInformation, /* = 30 */ + MyFileMoveClusterInformation, /* = 31 */ + MyFileQuotaInformation, /* = 32 */ + MyFileReparsePointInformation, /* = 33 */ + MyFileNetworkOpenInformation, /* = 34 */ + MyFileAttributeTagInformation, /* = 35 */ + MyFileTrackingInformation, /* = 36 */ + MyFileIdBothDirectoryInformation, /* = 37 */ + MyFileIdFullDirectoryInformation, /* = 38 */ + MyFileValidDataLengthInformation, /* = 39 */ + MyFileShortNameInformation, /* = 40 */ + MyFileIoCompletionNotificationInformation, /* = 41 */ + MyFileIoStatusBlockRangeInformation, /* = 42 */ + MyFileIoPriorityHintInformation, /* = 43 */ + MyFileSfioReserveInformation, /* = 44 */ + MyFileSfioVolumeInformation, /* = 45 */ + MyFileHardLinkInformation, /* = 46 */ + MyFileProcessIdsUsingFileInformation, /* = 47 */ + MyFileNormalizedNameInformation, /* = 48 */ + MyFileNetworkPhysicalNameInformation, /* = 49 */ + MyFileIdGlobalTxDirectoryInformation, /* = 50 */ + MyFileIsRemoteDeviceInformation, /* = 51 */ + MyFileAttributeCacheInformation, /* = 52 */ + MyFileNumaNodeInformation, /* = 53 */ + MyFileStandardLinkInformation, /* = 54 */ + MyFileRemoteProtocolInformation, /* = 55 */ + MyFileMaximumInformation +} MY_FILE_INFORMATION_CLASS; + + +typedef struct MY_FILE_FS_VOLUME_INFORMATION +{ + LARGE_INTEGER VolumeCreationTime; + ULONG VolumeSerialNumber; + ULONG VolumeLabelLength; + BOOLEAN SupportsObjects; + WCHAR VolumeLabel[1]; +} MY_FILE_FS_VOLUME_INFORMATION; + +typedef struct _MY_FILE_FS_ATTRIBUTE_INFORMATION +{ + ULONG FileSystemAttributes; + LONG MaximumComponentNameLength; + ULONG FileSystemNameLength; + WCHAR FileSystemName[1]; +} MY_FILE_FS_ATTRIBUTE_INFORMATION; + +typedef enum MY_FSINFOCLASS +{ + MyFileFsVolumeInformation = 1, + MyFileFsLabelInformation, /* = 2 */ + MyFileFsSizeInformation, /* = 3 */ + MyFileFsDeviceInformation, /* = 4 */ + MyFileFsAttributeInformation, /* = 5 */ + MyFileFsControlInformation, /* = 6 */ + MyFileFsFullSizeInformation, /* = 7 */ + MyFileFsObjectIdInformation, /* = 8 */ + MyFileFsDriverPathInformation, /* = 9 */ + MyFileFsVolumeFlagsInformation, /* = 10 */ + MyFileFsMaximumInformation +} MY_FS_INFORMATION_CLASS; + + +typedef struct MY_RTLP_CURDIR_REF +{ + LONG RefCount; + HANDLE Handle; +} MY_RTLP_CURDIR_REF; + +typedef struct MY_RTL_RELATIVE_NAME_U +{ + MY_UNICODE_STRING RelativeName; + HANDLE ContainingDirectory; + MY_RTLP_CURDIR_REF CurDirRef; +} MY_RTL_RELATIVE_NAME_U; + + +#ifndef OBJ_INHERIT +# define OBJ_INHERIT 0x00000002U +# define OBJ_PERMANENT 0x00000010U +# define OBJ_EXCLUSIVE 0x00000020U +# define OBJ_CASE_INSENSITIVE 0x00000040U +# define OBJ_OPENIF 0x00000080U +# define OBJ_OPENLINK 0x00000100U +# define OBJ_KERNEL_HANDLE 0x00000200U +# define OBJ_FORCE_ACCESS_CHECK 0x00000400U +# define OBJ_VALID_ATTRIBUTES 0x000007f2U +#endif + +#ifndef FILE_OPEN +# define FILE_SUPERSEDE 0x00000000U +# define FILE_OPEN 0x00000001U +# define FILE_CREATE 0x00000002U +# define FILE_OPEN_IF 0x00000003U +# define FILE_OVERWRITE 0x00000004U +# define FILE_OVERWRITE_IF 0x00000005U +# define FILE_MAXIMUM_DISPOSITION 0x00000005U +#endif + +#ifndef FILE_DIRECTORY_FILE +# define FILE_DIRECTORY_FILE 0x00000001U +# define FILE_WRITE_THROUGH 0x00000002U +# define FILE_SEQUENTIAL_ONLY 0x00000004U +# define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008U +# define FILE_SYNCHRONOUS_IO_ALERT 0x00000010U +# define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020U +# define FILE_NON_DIRECTORY_FILE 0x00000040U +# define FILE_CREATE_TREE_CONNECTION 0x00000080U +# define FILE_COMPLETE_IF_OPLOCKED 0x00000100U +# define FILE_NO_EA_KNOWLEDGE 0x00000200U +# define FILE_OPEN_REMOTE_INSTANCE 0x00000400U +# define FILE_RANDOM_ACCESS 0x00000800U +# define FILE_DELETE_ON_CLOSE 0x00001000U +# define FILE_OPEN_BY_FILE_ID 0x00002000U +# define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000U +# define FILE_NO_COMPRESSION 0x00008000U +# define FILE_RESERVE_OPFILTER 0x00100000U +# define FILE_OPEN_REPARSE_POINT 0x00200000U +# define FILE_OPEN_NO_RECALL 0x00400000U +# define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000U +#endif + +#ifndef DUPLICATE_CLOSE_SOURCE /* For the misnomer NtDuplicateObject. */ +# define DUPLICATE_CLOSE_SOURCE 0x00000001U +# define DUPLICATE_SAME_ACCESS 0x00000002U +#endif +#ifndef DUPLICATE_SAME_ATTRIBUTES +# define DUPLICATE_SAME_ATTRIBUTES 0x00000004U +#endif + + +/** @name NT status codes and associated macros. + * @{ */ +#define MY_NT_SUCCESS(a_ntRc) ((MY_NTSTATUS)(a_ntRc) >= 0) +#define MY_NT_FAILURE(a_ntRc) ((MY_NTSTATUS)(a_ntRc) < 0) +#define MY_STATUS_NO_MORE_FILES ((MY_NTSTATUS)0x80000006) +#define MY_STATUS_OBJECT_NAME_INVALID ((MY_NTSTATUS)0xc0000033) +#define MY_STATUS_OBJECT_NAME_NOT_FOUND ((MY_NTSTATUS)0xc0000034) +#define MY_STATUS_OBJECT_PATH_INVALID ((MY_NTSTATUS)0xc0000039) +#define MY_STATUS_OBJECT_PATH_NOT_FOUND ((MY_NTSTATUS)0xc000003a) +#define MY_STATUS_OBJECT_PATH_SYNTAX_BAD ((MY_NTSTATUS)0xc000003b) +/** @} */ + +/** The pseudohandle for the current process. */ +#define MY_NT_CURRENT_PROCESS ((HANDLE)~(uintptr_t)0) +/** The pseudohandle for the current thread. */ +#define MY_NT_CURRENT_THREAD ((HANDLE)~(uintptr_t)1) + +typedef struct MY_CLIENT_ID +{ + HANDLE UniqueProcess; + HANDLE UniqueThread; +} MY_CLIENT_ID; + +/** Partial TEB. */ +typedef struct MY_PARTIAL_TEB +{ + NT_TIB NtTib; + PVOID EnvironmentPointer; + MY_CLIENT_ID ClientId; + PVOID ActiveRpcHandle; + PVOID ThreadLocalStoragePointer; + PPEB ProcessEnvironmentBlock; + KU32 LastErrorValue; + KU32 CountOfOwnedCriticalSections; + PVOID CsrClientThread; + PVOID Win32ThreadInfo; +} MY_PARTIAL_TEB; + +/** Internal macro for reading uintptr_t sized TEB members. */ +#if K_ARCH == K_ARCH_AMD64 +# define MY_NT_READ_TEB_WORKER(a_offTebMember) ( __readgsqword(a_offTebMember) ) +#elif K_ARCH == K_ARCH_X86_32 +# define MY_NT_READ_TEB_WORKER(a_offTebMember) ( __readfsdword(a_offTebMember) ) +#else +# error "Port me!" +#endif +/** Get the PEB pointer. + * @remark Needs stddef.h. */ +#define MY_NT_CURRENT_PEB() ( (PPEB)MY_NT_READ_TEB_WORKER(offsetof(MY_PARTIAL_TEB, ProcessEnvironmentBlock)) ) +/** Get the TEB pointer. + * @remark Needs stddef.h. */ +#define MY_NT_CURRENT_TEB() ( (PTEB)MY_NT_READ_TEB_WORKER(offsetof(NT_TIB, Self)) ) + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +extern MY_NTSTATUS (WINAPI * g_pfnNtClose)(HANDLE); +extern MY_NTSTATUS (WINAPI * g_pfnNtCreateFile)(PHANDLE, MY_ACCESS_MASK, MY_OBJECT_ATTRIBUTES *, MY_IO_STATUS_BLOCK *, + PLARGE_INTEGER, ULONG, ULONG, ULONG, ULONG, PVOID, ULONG); +extern MY_NTSTATUS (WINAPI * g_pfnNtDeleteFile)(MY_OBJECT_ATTRIBUTES *); +extern MY_NTSTATUS (WINAPI * g_pfnNtDuplicateObject)(HANDLE hSrcProc, HANDLE hSrc, HANDLE hDstProc, HANDLE *phRet, + MY_ACCESS_MASK fDesiredAccess, ULONG fAttribs, ULONG fOptions); +extern MY_NTSTATUS (WINAPI * g_pfnNtReadFile)(HANDLE hFile, HANDLE hEvent, MY_IO_APC_ROUTINE *pfnApc, PVOID pvApcCtx, + MY_IO_STATUS_BLOCK *, PVOID pvBuf, ULONG cbToRead, PLARGE_INTEGER poffFile, + PULONG puKey); +extern MY_NTSTATUS (WINAPI * g_pfnNtQueryInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, + PVOID, LONG, MY_FILE_INFORMATION_CLASS); +extern MY_NTSTATUS (WINAPI * g_pfnNtQueryVolumeInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, + PVOID, LONG, MY_FS_INFORMATION_CLASS); +extern MY_NTSTATUS (WINAPI * g_pfnNtQueryDirectoryFile)(HANDLE, HANDLE, MY_IO_APC_ROUTINE *, PVOID, MY_IO_STATUS_BLOCK *, + PVOID, ULONG, MY_FILE_INFORMATION_CLASS, BOOLEAN, + MY_UNICODE_STRING *, BOOLEAN); +extern MY_NTSTATUS (WINAPI * g_pfnNtQueryAttributesFile)(MY_OBJECT_ATTRIBUTES *, MY_FILE_BASIC_INFORMATION *); +extern MY_NTSTATUS (WINAPI * g_pfnNtQueryFullAttributesFile)(MY_OBJECT_ATTRIBUTES *, MY_FILE_NETWORK_OPEN_INFORMATION *); +extern MY_NTSTATUS (WINAPI * g_pfnNtSetInformationFile)(HANDLE, MY_IO_STATUS_BLOCK *, PVOID, LONG, MY_FILE_INFORMATION_CLASS); +extern BOOLEAN (WINAPI * g_pfnRtlDosPathNameToNtPathName_U)(PCWSTR, MY_UNICODE_STRING *, PCWSTR *, MY_RTL_RELATIVE_NAME_U *); +extern MY_NTSTATUS (WINAPI * g_pfnRtlAnsiStringToUnicodeString)(MY_UNICODE_STRING *, MY_ANSI_STRING const *, BOOLEAN); +extern MY_NTSTATUS (WINAPI * g_pfnRtlUnicodeStringToAnsiString)(MY_ANSI_STRING *, MY_UNICODE_STRING *, BOOLEAN); +extern BOOLEAN (WINAPI * g_pfnRtlEqualUnicodeString)(MY_UNICODE_STRING const *pUniStr1, MY_UNICODE_STRING const *pUniStr2, + BOOLEAN fCaseInsensitive); +extern BOOLEAN (WINAPI * g_pfnRtlEqualString)(MY_ANSI_STRING const *pAnsiStr1, MY_ANSI_STRING const *pAnsiStr2, + BOOLEAN fCaseInsensitive); +extern UCHAR (WINAPI * g_pfnRtlUpperChar)(UCHAR uch); +extern ULONG (WINAPI * g_pfnRtlNtStatusToDosError)(MY_NTSTATUS rcNt); +extern VOID (WINAPI * g_pfnRtlAcquirePebLock)(VOID); +extern VOID (WINAPI * g_pfnRtlReleasePebLock)(VOID); + + +/** @} */ + +#endif + diff --git a/src/lib/nt/nttypes.h b/src/lib/nt/nttypes.h new file mode 100644 index 0000000..fe669c8 --- /dev/null +++ b/src/lib/nt/nttypes.h @@ -0,0 +1,55 @@ +/* $Id: nttypes.h 3060 2017-09-21 15:11:07Z bird $ */ +/** @file + * MSC + NT basic & common types, various definitions. + */ + +/* + * Copyright (c) 2005-2017 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_nttypes_h +#define ___nt_nttypes_h + +#include + +typedef struct BirdTimeVal +{ + __int64 tv_sec; + __int32 tv_usec; + __int32 tv_padding0; +} BirdTimeVal_T; + +typedef struct BirdTimeSpec +{ + __int64 tv_sec; + __int32 tv_nsec; + __int32 tv_padding0; +} BirdTimeSpec_T; + +/** The distance between the NT and unix epochs given in NT time (units of 100 + * ns). */ +#define BIRD_NT_EPOCH_OFFSET_UNIX_100NS 116444736000000000LL + +#endif + diff --git a/src/lib/nt/ntunlink.c b/src/lib/nt/ntunlink.c new file mode 100644 index 0000000..8d037d5 --- /dev/null +++ b/src/lib/nt/ntunlink.c @@ -0,0 +1,240 @@ +/* $Id: ntunlink.c 3504 2021-12-15 22:50:14Z bird $ */ +/** @file + * MSC + NT unlink and variations. + */ + +/* + * Copyright (c) 2005-2017 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "ntunlink.h" + +#include "ntstuff.h" +#include "nthlp.h" + + +static MY_NTSTATUS birdMakeWritable(HANDLE hRoot, MY_UNICODE_STRING *pNtPath) +{ + MY_NTSTATUS rcNt; + HANDLE hFile; + + rcNt = birdOpenFileUniStr(hRoot, + pNtPath, + FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &hFile); + if (MY_NT_SUCCESS(rcNt)) + { + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_IO_STATUS_BLOCK Ios; + DWORD dwAttr; + + Ios.Information = -1; + Ios.u.Status = -1; + memset(&BasicInfo, 0, sizeof(BasicInfo)); + rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, &BasicInfo, sizeof(BasicInfo), MyFileBasicInformation); + + if (MY_NT_SUCCESS(rcNt) && MY_NT_SUCCESS(Ios.u.Status) && BasicInfo.FileAttributes != FILE_ATTRIBUTE_READONLY) + dwAttr = BasicInfo.FileAttributes & ~FILE_ATTRIBUTE_READONLY; + else + dwAttr = FILE_ATTRIBUTE_NORMAL; + memset(&BasicInfo, 0, sizeof(BasicInfo)); + BasicInfo.FileAttributes = dwAttr; + + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtSetInformationFile(hFile, &Ios, &BasicInfo, sizeof(BasicInfo), MyFileBasicInformation); + + birdCloseFile(hFile); + } + + return rcNt; +} + + +static int birdUnlinkInternal(HANDLE hRoot, const char *pszFile, const wchar_t *pwszFile, int fReadOnlyToo, int fFast) +{ + MY_UNICODE_STRING NtPath; + int rc; + + if (hRoot == INVALID_HANDLE_VALUE) + hRoot = NULL; + if (hRoot == NULL) + { + if (pwszFile) + rc = birdDosToNtPathW(pwszFile, &NtPath); + else + rc = birdDosToNtPath(pszFile, &NtPath); + } + else + { + if (pwszFile) + rc = birdDosToRelativeNtPathW(pwszFile, &NtPath); + else + rc = birdDosToRelativeNtPath(pszFile, &NtPath); + } + if (rc == 0) + { + MY_NTSTATUS rcNt; + if (fFast) + { + /* This uses FILE_DELETE_ON_CLOSE. Probably only suitable when in a hurry... */ + MY_OBJECT_ATTRIBUTES ObjAttr; + MyInitializeObjectAttributes(&ObjAttr, &NtPath, OBJ_CASE_INSENSITIVE, hRoot, NULL /*pSecAttr*/); + rcNt = g_pfnNtDeleteFile(&ObjAttr); + + /* In case some file system does things differently than NTFS. */ + if (rcNt == STATUS_CANNOT_DELETE && fReadOnlyToo) + { + birdMakeWritable(hRoot, &NtPath); + rcNt = g_pfnNtDeleteFile(&ObjAttr); + } + } + else + { + /* Use the set information stuff. Probably more reliable. */ + HANDLE hFile; + for (;;) + { + rcNt = birdOpenFileUniStr(hRoot, + &NtPath, + DELETE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT, + OBJ_CASE_INSENSITIVE, + &hFile); + if (MY_NT_SUCCESS(rcNt)) + { + MY_FILE_DISPOSITION_INFORMATION DispInfo; + MY_IO_STATUS_BLOCK Ios; + + DispInfo.DeleteFile = TRUE; + + Ios.Information = -1; + Ios.u.Status = -1; + + rcNt = g_pfnNtSetInformationFile(hFile, &Ios, &DispInfo, sizeof(DispInfo), MyFileDispositionInformation); + + birdCloseFile(hFile); + } + if (rcNt != STATUS_CANNOT_DELETE || !fReadOnlyToo) + break; + + fReadOnlyToo = 0; + birdMakeWritable(hRoot, &NtPath); + } + } + + birdFreeNtPath(&NtPath); + + if (MY_NT_SUCCESS(rcNt)) + rc = 0; + else + rc = birdSetErrnoFromNt(rcNt); + } + return rc; +} + + +int birdUnlink(const char *pszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, pszFile, NULL /*pwszFile*/, 0 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkW(const wchar_t *pwszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, NULL /*pwszFile*/, pwszFile, 0 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkEx(void *hRoot, const char *pszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, pszFile, NULL /*pwszFile*/, 0 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkExW(void *hRoot, const wchar_t *pwszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, NULL /*pszFile*/, pwszFile, 0 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkForced(const char *pszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, pszFile, NULL /*pwszFile*/, 1 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkForcedW(const wchar_t *pwszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, NULL /*pszFile*/, pwszFile, 1 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkForcedEx(void *hRoot, const char *pszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, pszFile, NULL /*pwszFile*/, 1 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkForcedExW(void *hRoot, const wchar_t *pwszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, NULL /*pszFile*/, pwszFile, 1 /*fReadOnlyToo*/, 0 /*fFast*/); +} + + +int birdUnlinkForcedFast(const char *pszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, pszFile, NULL /*pwszFile*/, 1 /*fReadOnlyToo*/, 1 /*fFast*/); +} + + +int birdUnlinkForcedFastW(const wchar_t *pwszFile) +{ + return birdUnlinkInternal(NULL /*hRoot*/, NULL /*pszFile*/, pwszFile, 1 /*fReadOnlyToo*/, 1 /*fFast*/); +} + + +int birdUnlinkForcedFastEx(void *hRoot, const char *pszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, pszFile, NULL /*pwszFile*/, 1 /*fReadOnlyToo*/, 1 /*fFast*/); +} + + +int birdUnlinkForcedFastExW(void *hRoot, const wchar_t *pwszFile) +{ + return birdUnlinkInternal((HANDLE)hRoot, NULL /*pszFile*/, pwszFile, 1 /*fReadOnlyToo*/, 1 /*fFast*/); +} + diff --git a/src/lib/nt/ntunlink.h b/src/lib/nt/ntunlink.h new file mode 100644 index 0000000..e7934be --- /dev/null +++ b/src/lib/nt/ntunlink.h @@ -0,0 +1,54 @@ +/* $Id: ntunlink.h 3060 2017-09-21 15:11:07Z bird $ */ +/** @file + * MSC + NT unlink and variations. + */ + +/* + * Copyright (c) 2005-2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_ntunlink_h +#define ___nt_ntunlink_h + +#include "nttypes.h" +#include + +int birdUnlink(const char *pszFile); +int birdUnlinkW(const wchar_t *pwszFile); +int birdUnlinkEx(void *hRoot, const char *pszFile); +int birdUnlinkExW(void *hRoot, const wchar_t *pwszFile); +int birdUnlinkForced(const char *pszFile); +int birdUnlinkForcedW(const wchar_t *pwszFile); +int birdUnlinkForcedEx(void *hRoot, const char *pszFile); +int birdUnlinkForcedExW(void *hRoot, const wchar_t *pszFile); +int birdUnlinkForcedFast(const char *pszFile); +int birdUnlinkForcedFastW(const wchar_t *pwszFile); +int birdUnlinkForcedFastEx(void *hRoot, const char *pszFile); +int birdUnlinkForcedFastExW(void *hRoot, const wchar_t *pwszFile); + +#undef unlink +#define unlink(a_pszPath) birdUnlinkForced(a_pszPath) + +#endif + diff --git a/src/lib/nt/ntutimes.c b/src/lib/nt/ntutimes.c new file mode 100644 index 0000000..554e6e6 --- /dev/null +++ b/src/lib/nt/ntutimes.c @@ -0,0 +1,99 @@ +/* $Id: ntutimes.c 3097 2017-10-14 03:52:44Z bird $ */ +/** @file + * MSC + NT utimes and lutimes + */ + +/* + * Copyright (c) 2005-2017 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "ntutimes.h" + +#include "ntstuff.h" +#include "nthlp.h" + + + +static int birdUtimesInternal(const char *pszPath, BirdTimeVal_T paTimes[2], int fFollowLink) +{ + HANDLE hFile = birdOpenFileEx(NULL, + pszPath, + FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | (fFollowLink ? 0 : FILE_OPEN_REPARSE_POINT), + OBJ_CASE_INSENSITIVE); + if (hFile != INVALID_HANDLE_VALUE) + { + MY_FILE_BASIC_INFORMATION Info; + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + + memset(&Info, 0, sizeof(Info)); + if (paTimes) + { + Info.LastAccessTime.QuadPart = birdNtTimeFromTimeVal(&paTimes[0]); + Info.LastWriteTime.QuadPart = birdNtTimeFromTimeVal(&paTimes[1]); + } + else + { + /** @todo replace this with something from ntdll */ + FILETIME Now; + GetSystemTimeAsFileTime(&Now); + Info.LastAccessTime.HighPart = Now.dwHighDateTime; + Info.LastAccessTime.LowPart = Now.dwLowDateTime; + Info.LastWriteTime.HighPart = Now.dwHighDateTime; + Info.LastWriteTime.LowPart = Now.dwLowDateTime; + } + + Ios.Information = -1; + Ios.u.Status = -1; + + rcNt = g_pfnNtSetInformationFile(hFile, &Ios, &Info, sizeof(Info), MyFileBasicInformation); + + birdCloseFile(hFile); + + if (MY_NT_SUCCESS(rcNt)) + return 0; + birdSetErrnoFromNt(rcNt); + } + return -1; +} + + +int birdUtimes(const char *pszFile, BirdTimeVal_T paTimes[2]) +{ + return birdUtimesInternal(pszFile, paTimes, 1 /*fFollowLink*/); +} + +int birdLUtimes(const char *pszFile, BirdTimeVal_T paTimes[2]) +{ + return birdUtimesInternal(pszFile, paTimes, 0 /*fFollowLink*/); +} + diff --git a/src/lib/nt/ntutimes.h b/src/lib/nt/ntutimes.h new file mode 100644 index 0000000..42589ff --- /dev/null +++ b/src/lib/nt/ntutimes.h @@ -0,0 +1,45 @@ +/* $Id: ntutimes.h 3060 2017-09-21 15:11:07Z bird $ */ +/** @file + * MSC + NT utimes and lutimes. + */ + +/* + * Copyright (c) 2005-2017 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +#ifndef ___nt_ntutimes_h +#define ___nt_ntutimes_h + +#include "nttypes.h" + +int birdUtimes(const char *pszFile, BirdTimeVal_T paTimes[2]); +int birdLUtimes(const char *pszFile, BirdTimeVal_T paTimes[2]); + +#undef utimes +#define utimes(a_pszPath, a_paTimes) birdUtimes(a_pszPath, a_paTimes) +#undef lutimes +#define lutimes(a_pszPath, a_paTimes) birdLUtimes(a_pszPath, a_paTimes) + +#endif + diff --git a/src/lib/nt/tstNtFts.c b/src/lib/nt/tstNtFts.c new file mode 100644 index 0000000..8d8136c --- /dev/null +++ b/src/lib/nt/tstNtFts.c @@ -0,0 +1,257 @@ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef USE_OLD_FTS +# include "fts-nt.h" +#else +# include "kmkbuiltin/ftsfake.h" +#endif +#include +#include +#include +#include + + +static int usage(const char *argv0) +{ + printf("usage: %s [options] \n", argv0); + printf("\n" + "options:\n" + " -d, --see-dot\n" + " FTS_SEEDOT\n" + " -p, --physical\n" + " FTS_PHYSICAL\n" + " -l, --logical\n" + " FTS_LOGICAL\n" + " -H, --dereference-command-line\n" + " FTS_COMFOLLOW\n" + " -L, --dereference\n" + " Follow symbolic links while scanning directories.\n" + " -P, --no-dereference\n" + " Do not follow symbolic links while scanning directories.\n" + " -c, --no-chdir\n" + " FTS_NOCHDIR\n" + " -s, --no-stat\n" + " FTS_NOSTAT\n" + " -x, --one-file-system\n" + " FTS_XDEV\n" + " -q, --quiet\n" + " Quiet operation, no output.\n" + " -v, --verbose\n" + " Verbose operation (default).\n" + ); + return 0; +} + + +int main(int argc, char **argv) +{ + FTS *pFts; + int i; + int rcExit = 0; + int cVerbosity = 1; + int fFollowLinks = 0; + int fFtsFlags = 0; + unsigned fDoneOptions = 0; + unsigned cFtsArgs = 0; + char const **papszFtsArgs = calloc(argc + 1, sizeof(char *)); + + /* + * Parse options and heap up non-options. + */ + for (i = 1; i < argc; i++) + { + const char *pszArg = argv[i]; + if (*pszArg == '-' && !fDoneOptions) + { + char chOpt = *++pszArg; + pszArg++; + if (chOpt == '-') + { + if (!chOpt) + { + fDoneOptions = 1; + continue; + } + if (strcmp(pszArg, "help") == 0) + chOpt = 'h'; + else if (strcmp(pszArg, "version") == 0) + chOpt = 'V'; + else if (strcmp(pszArg, "see-dot") == 0) + chOpt = 'd'; + else if (strcmp(pszArg, "physical") == 0) + chOpt = 'p'; + else if (strcmp(pszArg, "logical") == 0) + chOpt = 'l'; + else if (strcmp(pszArg, "dereference-command-line") == 0) + chOpt = 'H'; + else if (strcmp(pszArg, "no-chdir") == 0) + chOpt = 'c'; + else if (strcmp(pszArg, "no-stat") == 0) + chOpt = 's'; + else if (strcmp(pszArg, "one-file-system") == 0) + chOpt = 'x'; + else if (strcmp(pszArg, "quiet") == 0) + chOpt = 'q'; + else if (strcmp(pszArg, "verbose") == 0) + chOpt = 'v'; + else if (strcmp(pszArg, "no-ansi") == 0) + chOpt = 'w'; + else + { + fprintf(stderr, "syntax error: Unknown option: %s (%s)\n", argv[i], pszArg); + return 2; + } + pszArg = ""; + } + do + { + switch (chOpt) + { + case '?': + case 'h': + return usage(argv[0]); + case 'V': + printf("v0.0.0\n"); + return 0; + + case 'd': + fFtsFlags |= FTS_SEEDOT; + break; + case 'l': + fFtsFlags |= FTS_LOGICAL; + break; + case 'p': + fFtsFlags |= FTS_PHYSICAL; + break; + case 'H': + fFtsFlags |= FTS_COMFOLLOW; + break; + case 'c': + fFtsFlags |= FTS_NOCHDIR; + break; + case 's': + fFtsFlags |= FTS_NOSTAT; + break; + case 'x': + fFtsFlags |= FTS_XDEV; + break; +#ifdef FTS_NO_ANSI + case 'w': + fFtsFlags |= FTS_NO_ANSI; + break; +#endif + case 'L': + fFollowLinks = 1; + break; + case 'P': + fFollowLinks = 0; + break; + + case 'q': + cVerbosity = 0; + break; + case 'v': + cVerbosity++; + break; + + default: + fprintf(stderr, "syntax error: Unknown option: -%c (%s)\n", chOpt, argv[i]); + return 2; + } + chOpt = *pszArg++; + } while (chOpt != '\0'); + } + else + papszFtsArgs[cFtsArgs++] = pszArg; + } + +#ifdef USE_OLD_FTS + if (papszFtsArgs[0] == NULL) + { + fprintf(stderr, "Nothing to do\n"); + return 1; + } +#endif + + /* + * Do the traversal. + */ + errno = 0; + pFts = fts_open((char **)papszFtsArgs, fFtsFlags, NULL /*pfnCompare*/); + if (pFts) + { + for (;;) + { + FTSENT *pFtsEnt = fts_read(pFts); + if (pFtsEnt) + { + const char *pszState; + switch (pFtsEnt->fts_info) + { + case FTS_D: pszState = "D"; break; + case FTS_DC: pszState = "DC"; break; + case FTS_DEFAULT: pszState = "DEFAULT"; break; + case FTS_DNR: pszState = "DNR"; break; + case FTS_DOT: pszState = "DOT"; break; + case FTS_DP: pszState = "DP"; break; + case FTS_ERR: pszState = "ERR"; break; + case FTS_F: pszState = "F"; break; + case FTS_INIT: pszState = "INIT"; break; + case FTS_NS: pszState = "NS"; break; + case FTS_NSOK: pszState = "NSOK"; break; + case FTS_SL: pszState = "SL"; break; + case FTS_SLNONE: pszState = "SLNONE"; break; + default: + pszState = "Invalid"; + rcExit = 1; + break; + } + + if (cVerbosity > 0) + { +#ifdef FTS_NO_ANSI + if (fFtsFlags & FTS_NO_ANSI) + printf("%8s %ls\n", pszState, pFtsEnt->fts_wcsaccpath); + else +#endif + printf("%8s %s\n", pszState, pFtsEnt->fts_accpath); + } + if ( pFtsEnt->fts_info == FTS_SL + && pFtsEnt->fts_number == 0 + && fFollowLinks + && ( (fFtsFlags & FTS_COMFOLLOW) + || pFtsEnt->fts_level > FTS_ROOTLEVEL) ) { + pFtsEnt->fts_number++; + fts_set(pFts, pFtsEnt, FTS_FOLLOW); + } + } + else + { + if (errno != 0) + { + fprintf(stderr, "fts_read failed: errno=%d\n", errno); + rcExit = 1; + } + break; + } + } /* enum loop */ + + errno = 0; + i = fts_close(pFts); + if (i != 0) + { + fprintf(stderr, "fts_close failed: errno=%d\n", errno); + rcExit = 1; + } + } + else + { + fprintf(stderr, "fts_open failed: errno=%d (cFtsArgs=%u)\n", errno, cFtsArgs); + rcExit = 1; + } + + return rcExit; +} diff --git a/src/lib/nt/tstNtStat.c b/src/lib/nt/tstNtStat.c new file mode 100644 index 0000000..3823ab7 --- /dev/null +++ b/src/lib/nt/tstNtStat.c @@ -0,0 +1,157 @@ +/* $Id: tstNtStat.c 3007 2016-11-06 16:46:43Z bird $ */ +/** @file + * Manual lstat/stat testcase. + */ + +/* + * Copyright (c) 2013 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include "ntstat.h" + + +static int IsLeapYear(int iYear) +{ + return iYear % 4 == 0 + && ( iYear % 100 != 0 + || iYear % 400 == 0); +} + +static int DaysInMonth(int iYear, int iMonth) +{ + switch (iMonth) + { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + case 4: + case 6: + case 9: + case 11: + return 30; + case 2: + return IsLeapYear(iYear) ? 29 : 28; + + default: + *(void **)(size_t)iMonth = 0; /* crash! */ + return 0; + } +} + + +static char *FormatTimeSpec(char *pszBuf, BirdTimeSpec_T const *pTimeSpec) +{ + if (pTimeSpec->tv_sec >= 0) + { + int iYear = 1970; + int iMonth = 1; + int iDay = 1; + int iHour = 0; + int iMin = 0; + int iSec = 0; + __int64 cSecs = pTimeSpec->tv_sec; + + /* lazy bird approach, find date, day by day */ + while (cSecs >= 24*3600) + { + if ( iDay < 28 + || iDay < DaysInMonth(iYear, iMonth)) + iDay++; + else + { + if (iMonth < 12) + iMonth++; + else + { + iYear++; + iMonth = 1; + } + iDay = 1; + } + cSecs -= 24*3600; + } + + iHour = (int)cSecs / 3600; + cSecs %= 3600; + iMin = (int)cSecs / 60; + iSec = (int)cSecs % 60; + + sprintf(pszBuf, "%04d-%02d-%02dT%02u:%02u:%02u.%09u (%I64d.%09u)", + iYear, iMonth, iDay, iHour, iMin, iSec, pTimeSpec->tv_nsec, + pTimeSpec->tv_sec, pTimeSpec->tv_nsec); + } + else + sprintf(pszBuf, "%I64d.%09u (before 1970-01-01)", pTimeSpec->tv_sec, pTimeSpec->tv_nsec); + return pszBuf; +} + + +int main(int argc, char **argv) +{ + int rc = 0; + int i; + + for (i = 1; i < argc; i++) + { + struct stat st; + if (lstat(argv[i], &st) == 0) + { + char szBuf[256]; + printf("%s:\n", argv[i]); + printf(" st_mode: %o\n", st.st_mode); + printf(" st_isdirsymlink: %d\n", st.st_isdirsymlink); + printf(" st_ismountpoint: %d\n", st.st_ismountpoint); + printf(" st_size: %I64u (%#I64x)\n", st.st_size, st.st_size); + printf(" st_atim: %s\n", FormatTimeSpec(szBuf, &st.st_atim)); + printf(" st_mtim: %s\n", FormatTimeSpec(szBuf, &st.st_mtim)); + printf(" st_ctim: %s\n", FormatTimeSpec(szBuf, &st.st_ctim)); + printf(" st_birthtim: %s\n", FormatTimeSpec(szBuf, &st.st_birthtim)); + printf(" st_ino: %#I64x\n", st.st_ino); + printf(" st_dev: %#I64x\n", st.st_dev); + printf(" st_nlink: %u\n", st.st_nlink); + printf(" st_rdev: %#x\n", st.st_rdev); + printf(" st_uid: %d\n", st.st_uid); + printf(" st_gid: %d\n", st.st_gid); + printf(" st_blksize: %d (%#x)\n", st.st_blksize, st.st_blksize); + printf(" st_blocks: %I64u (%#I64x)\n", st.st_blocks, st.st_blocks); + } + else + { + fprintf(stderr, "stat failed on '%s': errno=%u\n", argv[i], errno); + rc = 1; + } + } + return rc; +} + diff --git a/src/lib/nt/tstkFsCache.c b/src/lib/nt/tstkFsCache.c new file mode 100644 index 0000000..7eb23db --- /dev/null +++ b/src/lib/nt/tstkFsCache.c @@ -0,0 +1,313 @@ +/* $Id: tstkFsCache.c 3381 2020-06-12 11:36:10Z bird $ */ +/** @file + * kFsCache testcase. + */ + +/* + * Copyright (c) 2020 knut st. osmundsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include "kFsCache.h" + +#include + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static unsigned g_cErrors = 0; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define CHECK_RETV(a_Expr) do { \ + if (!(a_Expr)) \ + { \ + g_cErrors++; \ + fprintf(stderr, "error(%u): %s\n", __LINE__, #a_Expr);\ + return; \ + } \ + } while (0) + +#define CHECK(a_Expr) do { \ + if (!(a_Expr)) \ + { \ + g_cErrors++; \ + fprintf(stderr, "error(%u): %s\n", __LINE__, #a_Expr);\ + } \ + } while (0) + +static int myMkDir(const char *pszPath) +{ + if (_mkdir(pszPath) == 0) + return 0; + fprintf(stderr, "_mkdir(%s) -> errno=%d\n", pszPath, errno); + return -1; +} + +static int myCreateFile(const char *pszPath) +{ + FILE *pFile = fopen(pszPath, "w"); + if (pFile) + { + fclose(pFile); + return 0; + } + fprintf(stderr, "fopen(%s,w) -> errno=%d\n", pszPath, errno); + return -1; +} + +static void test1(const char *pszWorkDir) +{ + char szPath[4096]; + size_t cchWorkDir = strlen(pszWorkDir); + PKFSCACHE pCache; + KFSLOOKUPERROR enmLookupError; + PKFSOBJ pFsObj; + + CHECK_RETV(cchWorkDir < sizeof(szPath) - 1024); + memcpy(szPath, pszWorkDir, cchWorkDir); + cchWorkDir += sprintf(&szPath[cchWorkDir], "\\tstkFsCache%u", _getpid()); + CHECK_RETV(myMkDir(szPath) == 0); + + pCache = kFsCacheCreate(KFSCACHE_F_MISSING_OBJECTS | KFSCACHE_F_MISSING_PATHS); + CHECK_RETV(pCache != NULL); + + enmLookupError = (KFSLOOKUPERROR)-1; + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + +#if 0 + /* + * Accidentally left out the '\' in front of the filename, so it ended up in + * a temp dir with almost 1000 files and that triggered a refresh issue. + */ + /* Negative lookup followed by creation of that file. */ + enmLookupError = (KFSLOOKUPERROR)-1; + sprintf(&szPath[cchWorkDir], "file1.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + if (pFsObj) + CHECK(pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + CHECK(myCreateFile(szPath) == 0); + + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + if (pFsObj) + CHECK(pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + kFsCacheInvalidateAll(pCache); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + if (pFsObj) + { + CHECK(pFsObj->bObjType == KFSOBJ_TYPE_FILE); + if (pFsObj->bObjType != KFSOBJ_TYPE_FILE) + fprintf(stderr, "bObjType=%d\n", pFsObj->bObjType); + } +#endif + + /* + * Try emulate the temp issue above. Seem to require several files. + * (The problem was related to long/short filename updating.) + */ + szPath[cchWorkDir++] = '\\'; + sprintf(&szPath[cchWorkDir], "longfilename1.txt"); + CHECK(myCreateFile(szPath) == 0); + sprintf(&szPath[cchWorkDir], "longfilename2.txt"); + CHECK(myCreateFile(szPath) == 0); +#if 1 + /* no file 3 */ + sprintf(&szPath[cchWorkDir], "longfilename4.txt"); + CHECK(myCreateFile(szPath) == 0); + sprintf(&szPath[cchWorkDir], "longfilename5.txt"); + CHECK(myCreateFile(szPath) == 0); + /* no file 6 */ + sprintf(&szPath[cchWorkDir], "longfilename7.txt"); + CHECK(myCreateFile(szPath) == 0); +#endif + + enmLookupError = (KFSLOOKUPERROR)-1; + sprintf(&szPath[cchWorkDir], "longfilename3.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + CHECK(pFsObj && pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + enmLookupError = (KFSLOOKUPERROR)-1; + sprintf(&szPath[cchWorkDir], "longfilename6.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + CHECK(pFsObj && pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + sprintf(&szPath[cchWorkDir], "longfilename3.txt"); + CHECK(myCreateFile(szPath) == 0); + sprintf(&szPath[cchWorkDir], "longfilename6.txt"); + CHECK(myCreateFile(szPath) == 0); + + sprintf(&szPath[cchWorkDir], "longfilename3.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + CHECK(pFsObj && pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + sprintf(&szPath[cchWorkDir], "longfilename6.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + CHECK(pFsObj && pFsObj->bObjType == KFSOBJ_TYPE_MISSING); + + kFsCacheInvalidateAll(pCache); + + sprintf(&szPath[cchWorkDir], "longfilename3.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + if (pFsObj) + { + CHECK(pFsObj->bObjType == KFSOBJ_TYPE_FILE); + if (pFsObj->bObjType != KFSOBJ_TYPE_FILE) + fprintf(stderr, "bObjType=%d\n", pFsObj->bObjType); + } + + sprintf(&szPath[cchWorkDir], "longfilename6.txt"); + CHECK((pFsObj = kFsCacheLookupA(pCache, szPath, &enmLookupError)) != NULL); + if (pFsObj) + { + CHECK(pFsObj->bObjType == KFSOBJ_TYPE_FILE); + if (pFsObj->bObjType != KFSOBJ_TYPE_FILE) + fprintf(stderr, "bObjType=%d\n", pFsObj->bObjType); + } +} + +static int usage(int rcExit) +{ + printf("usage: tstkFsCache [--workdir dir]\n" + "\n" + "Test program of the kFsCache. May leave stuff behind in the work\n" + "directory requiring manual cleanup.\n" + ); + return rcExit; +} + +int main(int argc, char **argv) +{ + const char *pszWorkDir = NULL; + int i; + + /* + * Parse arguments. + */ + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszValue; + const char *psz = &argv[i][1]; + char chOpt; + chOpt = *psz++; + if (chOpt == '-') + { + /* Convert long to short option. */ + if (!strcmp(psz, "workdir")) + chOpt = 'w'; + else if (!strcmp(psz, "help")) + chOpt = '?'; + else if (!strcmp(psz, "version")) + chOpt = 'V'; + else + return usage(2); + psz = ""; + } + + /* + * Requires value? + */ + switch (chOpt) + { + case 'w': + if (*psz) + pszValue = psz; + else if (++i < argc) + pszValue = argv[i]; + else + { + fprintf(stderr, "The '-%c' option takes a value.\n", chOpt); + return 2; + } + break; + + default: + pszValue = NULL; + break; + } + + switch (chOpt) + { + case 'w': + pszWorkDir = pszValue; + break; + + case '?': + return usage(0); + case 'V': + printf("0.0.0\n"); + return 0; + + /* + * Invalid argument. + */ + default: + fprintf(stderr, "syntax error: Invalid option '%s'.\n", argv[i]); + return 2; + } + } + else + { + fprintf(stderr, "syntax error: Invalid argument '%s'.\n", argv[i]); + return 2; + } + } + + /* + * Resolve defaults. + */ + if (!pszWorkDir) + { + pszWorkDir = getenv("TEMP"); + if (!pszWorkDir) + pszWorkDir = "."; + } + + /* + * Do the testing. + */ + test1(pszWorkDir); + + if (!g_cErrors) + printf("Success!\n"); + else + printf("Failed - %u errors!\n", g_cErrors); + return g_cErrors == 0 ? 0 : 1; +} + + -- cgit v1.2.3