summaryrefslogtreecommitdiffstats
path: root/src/lib/nt
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
commit29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch)
tree63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/lib/nt
parentInitial commit. (diff)
downloadkbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz
kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/nt')
-rw-r--r--src/lib/nt/Makefile.kup0
-rw-r--r--src/lib/nt/fts-nt.c1421
-rw-r--r--src/lib/nt/fts-nt.h188
-rw-r--r--src/lib/nt/kFsCache.c4840
-rw-r--r--src/lib/nt/kFsCache.h594
-rw-r--r--src/lib/nt/nt_child_inject_standard_handles.c462
-rw-r--r--src/lib/nt/nt_child_inject_standard_handles.h32
-rw-r--r--src/lib/nt/ntdir.c673
-rw-r--r--src/lib/nt/ntdir.h154
-rw-r--r--src/lib/nt/nthlp.h119
-rw-r--r--src/lib/nt/nthlpcore.c481
-rw-r--r--src/lib/nt/nthlpfs.c636
-rw-r--r--src/lib/nt/ntopenat.c161
-rw-r--r--src/lib/nt/ntopenat.h43
-rw-r--r--src/lib/nt/ntstat.c1065
-rw-r--r--src/lib/nt/ntstat.h144
-rw-r--r--src/lib/nt/ntstuff.h573
-rw-r--r--src/lib/nt/nttypes.h55
-rw-r--r--src/lib/nt/ntunlink.c240
-rw-r--r--src/lib/nt/ntunlink.h54
-rw-r--r--src/lib/nt/ntutimes.c99
-rw-r--r--src/lib/nt/ntutimes.h45
-rw-r--r--src/lib/nt/tstNtFts.c257
-rw-r--r--src/lib/nt/tstNtStat.c157
-rw-r--r--src/lib/nt/tstkFsCache.c313
25 files changed, 12806 insertions, 0 deletions
diff --git a/src/lib/nt/Makefile.kup b/src/lib/nt/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/nt/Makefile.kup
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 <bird-klibc-spam-xiv@anduin.net>
+ * @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 <errno.h>
+#include "fts-nt.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "nthlp.h"
+#include "ntdir.h"
+#include "ntopenat.h" /* for AT_FDCWD */
+#include <stdio.h>//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 <bird-klibc-spam-xiv@anduin.net>
+ * @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 <sys/types.h>
+#include <stdint.h>
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <k/kHlp.h>
+
+#include "nthlp.h"
+#include "ntstat.h"
+
+#include <stdio.h>
+#include <mbstring.h>
+#include <wchar.h>
+#ifdef _MSC_VER
+# include <intrin.h>
+#endif
+//#include <setjmp.h>
+//#include <ctype.h>
+
+
+//#include <Windows.h>
+//#include <winternl.h>
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <k/kHlp.h>
+#include "ntstat.h"
+#ifndef NDEBUG
+# include <stdarg.h>
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Windows.h>
+#include <Winternl.h>
+#include <stdio.h>
+#include <assert.h>
+#include <k/kDefs.h>
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#include <malloc.h>
+#include <assert.h>
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <errno.h>
+#include "nthlp.h"
+#ifndef NDEBUG
+# include <stdio.h>
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <stddef.h>
+#include <string.h>
+#include <wchar.h>
+#include <errno.h>
+
+
+/*******************************************************************************
+* 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <errno.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <io.h>
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#include <malloc.h>
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <sys/stat.h>
+#include <io.h>
+#include <direct.h>
+
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <Windows.h>
+#include <winternl.h>
+#undef WIN32_NO_STATUS
+#include <ntstatus.h>
+#undef timeval
+
+#include <k/kTypes.h>
+
+
+/** @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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <sys/types.h>
+
+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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <wchar.h>
+
+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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+
+static int usage(const char *argv0)
+{
+ printf("usage: %s [options] <dirs & files>\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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#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 <bird-kBuild-spamx@anduin.net>
+ *
+ * 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 <direct.h>
+#include <errno.h>
+#include <process.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "kFsCache.h"
+
+#include <windows.h>
+
+
+/*********************************************************************************************************************************
+* 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;
+}
+
+