summaryrefslogtreecommitdiffstats
path: root/src/kash/shfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kash/shfile.c')
-rw-r--r--src/kash/shfile.c2656
1 files changed, 2656 insertions, 0 deletions
diff --git a/src/kash/shfile.c b/src/kash/shfile.c
new file mode 100644
index 0000000..78f5332
--- /dev/null
+++ b/src/kash/shfile.c
@@ -0,0 +1,2656 @@
+/* $Id: shfile.c 3542 2022-01-29 01:36:00Z bird $ */
+/** @file
+ *
+ * File management.
+ *
+ * Copyright (c) 2007-2010 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "shfile.h"
+#include "shinstance.h" /* TRACE2 */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#if K_OS == K_OS_WINDOWS
+# include <ntstatus.h>
+# define WIN32_NO_STATUS
+# include <Windows.h>
+# if !defined(_WIN32_WINNT)
+# define _WIN32_WINNT 0x0502 /* Windows Server 2003 */
+# endif
+# include <winternl.h> //NTSTATUS
+#else
+# include <unistd.h>
+# include <fcntl.h>
+# include <dirent.h>
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** @def SHFILE_IN_USE
+ * Whether the file descriptor table stuff is actually in use or not.
+ */
+#if K_OS == K_OS_WINDOWS \
+ || K_OS == K_OS_OPENBSD /* because of ugly pthread library pipe hacks */ \
+ || !defined(SH_FORKED_MODE)
+# define SHFILE_IN_USE
+#endif
+/** The max file table size. */
+#define SHFILE_MAX 1024
+/** The file table growth rate. */
+#define SHFILE_GROW 64
+/** The min native unix file descriptor. */
+#define SHFILE_UNIX_MIN_FD 32
+/** The path buffer size we use. */
+#define SHFILE_MAX_PATH 4096
+
+/** Set errno and return. Doing a trace in debug build. */
+#define RETURN_ERROR(rc, err, msg) \
+ do { \
+ TRACE2((NULL, "%s: " ## msg ## " - returning %d / %d\n", __FUNCTION__, (rc), (err))); \
+ errno = (err); \
+ return (rc); \
+ } while (0)
+
+#if K_OS == K_OS_WINDOWS
+ /* See msdos.h for description. */
+# define FOPEN 0x01
+# define FEOFLAG 0x02
+# define FCRLF 0x04
+# define FPIPE 0x08
+# define FNOINHERIT 0x10
+# define FAPPEND 0x20
+# define FDEV 0x40
+# define FTEXT 0x80
+
+# define MY_ObjectBasicInformation 0
+# define MY_FileNamesInformation 12
+
+typedef struct
+{
+ ULONG Attributes;
+ ACCESS_MASK GrantedAccess;
+ ULONG HandleCount;
+ ULONG PointerCount;
+ ULONG PagedPoolUsage;
+ ULONG NonPagedPoolUsage;
+ ULONG Reserved[3];
+ ULONG NameInformationLength;
+ ULONG TypeInformationLength;
+ ULONG SecurityDescriptorLength;
+ LARGE_INTEGER CreateTime;
+} MY_OBJECT_BASIC_INFORMATION;
+
+#if 0
+typedef struct
+{
+ union
+ {
+ LONG Status;
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+} MY_IO_STATUS_BLOCK;
+#else
+typedef IO_STATUS_BLOCK MY_IO_STATUS_BLOCK;
+#endif
+typedef MY_IO_STATUS_BLOCK *PMY_IO_STATUS_BLOCK;
+
+typedef struct
+{
+ ULONG NextEntryOffset;
+ ULONG FileIndex;
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+} MY_FILE_NAMES_INFORMATION, *PMY_FILE_NAMES_INFORMATION;
+
+typedef NTSTATUS (NTAPI * PFN_NtQueryObject)(HANDLE, int, void *, size_t, size_t *);
+typedef NTSTATUS (NTAPI * PFN_NtQueryDirectoryFile)(HANDLE, HANDLE, void *, void *, PMY_IO_STATUS_BLOCK, void *,
+ ULONG, int, int, PUNICODE_STRING, int);
+typedef NTSTATUS (NTAPI * PFN_RtlUnicodeStringToAnsiString)(PANSI_STRING, PCUNICODE_STRING, int);
+
+
+#endif /* K_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if K_OS == K_OS_WINDOWS
+static int g_shfile_globals_initialized = 0;
+static PFN_NtQueryObject g_pfnNtQueryObject = NULL;
+static PFN_NtQueryDirectoryFile g_pfnNtQueryDirectoryFile = NULL;
+static PFN_RtlUnicodeStringToAnsiString g_pfnRtlUnicodeStringToAnsiString = NULL;
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+/** Data for the asynchronous CloseHandle machinery. */
+static struct shfileasyncclose
+{
+ /** Mutex protecting the asynchronous CloseHandle stuff. */
+ shmtx mtx;
+ /** Handle to event that the closer-threads are waiting on. */
+ HANDLE evt_workers;
+ /** The ring buffer read index (for closer-threads). */
+ unsigned volatile idx_read;
+ /** The ring buffer write index (for shfile_native_close).
+ * When idx_read and idx_write are the same, the ring buffer is empty. */
+ unsigned volatile idx_write;
+ /** Number of handles currently being pending closure (handles + current
+ * CloseHandle calls). */
+ unsigned volatile num_pending;
+ /** Set if the threads should terminate. */
+ KBOOL volatile terminate_threads;
+ /** Set if one or more shell threads have requested evt_sync to be signalled
+ * when there are no more pending requests. */
+ KBOOL volatile signal_sync;
+ /** Number of threads that have been spawned. */
+ KU8 num_threads;
+ /** Handle to event that the shell threads are waiting on to sync. */
+ HANDLE evt_sync;
+ /** Ring buffer containing handles to be closed. */
+ HANDLE handles[32];
+ /** Worker threads doing the asynchronous closing. */
+ HANDLE threads[8];
+} g_shfile_async_close;
+# endif
+#endif /* K_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef SHFILE_IN_USE
+# if K_OS == K_OS_WINDOWS
+static HANDLE shfile_set_inherit_win(shfile *pfd, int set);
+# endif
+#endif
+
+
+#ifdef SHFILE_IN_USE
+
+# ifdef DEBUG
+# if K_OS == K_OS_WINDOWS
+static KU64 shfile_nano_ts(void)
+{
+ static KBOOL volatile s_has_factor = K_FALSE;
+ static double volatile s_factor;
+ double factor;
+ LARGE_INTEGER now;
+ if (s_has_factor)
+ factor = s_factor;
+ else
+ {
+ QueryPerformanceFrequency(&now);
+ s_factor = factor = (double)1000000000.0 / now.QuadPart;
+ s_has_factor = K_TRUE;
+ }
+ QueryPerformanceCounter(&now);
+ return (KU64)(now.QuadPart * factor);
+}
+# endif /* K_OS_WINDOWS */
+# endif /* DEBUG */
+
+# if K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE)
+/**
+ * The closer thread function.
+ */
+static unsigned __stdcall shfile_async_close_handle_thread(void *ignored)
+{
+ KBOOL decrement_pending = K_FALSE;
+ shthread_set_name("Async CloseHandle");
+ while (!g_shfile_async_close.terminate_threads)
+ {
+ HANDLE toclose;
+ unsigned idx;
+ shmtxtmp tmp;
+
+ /*
+ * Grab a handle if there is one:
+ */
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ if (decrement_pending)
+ {
+ kHlpAssert(g_shfile_async_close.num_pending > 0);
+ g_shfile_async_close.num_pending -= 1;
+ }
+
+ idx = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
+ if (idx != g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles))
+ {
+ kHlpAssert(g_shfile_async_close.num_pending > 0);
+ toclose = g_shfile_async_close.handles[idx];
+ kHlpAssert(toclose);
+ g_shfile_async_close.handles[idx] = NULL;
+ g_shfile_async_close.idx_read = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
+ }
+ else
+ {
+ /* Signal waiters if requested and we've reached zero pending requests. */
+ if (g_shfile_async_close.signal_sync && g_shfile_async_close.num_pending == 0)
+ {
+ BOOL rc = SetEvent(g_shfile_async_close.evt_sync);
+ kHlpAssert(rc);
+ g_shfile_async_close.signal_sync = FALSE;
+ }
+ toclose = NULL;
+ }
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+
+ /* Do the closing if we have something to close, otherwise wait for work to arrive. */
+ if (toclose != NULL)
+ {
+ BOOL rc = CloseHandle(toclose);
+ kHlpAssert(rc);
+ decrement_pending = K_TRUE;
+ }
+ else
+ {
+ DWORD dwRet = WaitForSingleObject(g_shfile_async_close.evt_workers, 10000 /*ms*/);
+ kHlpAssert(dwRet == WAIT_OBJECT_0 || dwRet == WAIT_TIMEOUT);
+ decrement_pending = K_FALSE;
+ }
+ }
+
+ K_NOREF(ignored);
+ return 0;
+}
+
+/**
+ * Try submit a handle for automatic closing.
+ *
+ * @returns K_TRUE if submitted successfully, K_FALSE if not.
+ */
+static KBOOL shfile_async_close_submit(HANDLE toclose)
+{
+ KBOOL ret;
+ unsigned idx;
+ unsigned idx_next;
+ unsigned idx_read;
+ unsigned num_pending = 0;
+ unsigned num_threads = 0;
+ shmtxtmp tmp;
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ /* Get the write index and check that there is a free slot in the buffer: */
+ idx = g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles);
+ idx_next = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
+ idx_read = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
+ if (idx_next != idx_read)
+ {
+ /* Write the handle to the ring buffer: */
+ kHlpAssert(g_shfile_async_close.handles[idx] == NULL);
+ g_shfile_async_close.handles[idx] = toclose;
+ g_shfile_async_close.idx_write = idx_next;
+
+ num_pending = g_shfile_async_close.num_pending + 1;
+ g_shfile_async_close.num_pending = num_pending;
+
+ ret = SetEvent(g_shfile_async_close.evt_workers);
+ kHlpAssert(ret);
+ if (ret)
+ {
+ /* If we have more pending requests than threads, create a new thread. */
+ num_threads = g_shfile_async_close.num_threads;
+ if (num_pending > num_threads && num_threads < K_ELEMENTS(g_shfile_async_close.threads))
+ {
+ int const savederrno = errno;
+ unsigned tid = 0;
+ intptr_t hThread = _beginthreadex(NULL /*security*/, 0 /*stack_size*/, shfile_async_close_handle_thread,
+ NULL /*arg*/, 0 /*initflags*/, &tid);
+ kHlpAssert(hThread != -1);
+ if (hThread != -1)
+ {
+ g_shfile_async_close.threads[num_threads] = (HANDLE)hThread;
+ g_shfile_async_close.num_threads = ++num_threads;
+ }
+ else
+ {
+ TRACE2((NULL, "shfile_async_close_submit: _beginthreadex failed: %d\n", errno));
+ if (num_threads == 0)
+ ret = K_FALSE;
+ }
+ errno = savederrno;
+ }
+ }
+ else
+ TRACE2((NULL, "shfile_async_close_submit: SetEvent(%p) failed: %u\n", g_shfile_async_close.evt_workers, GetLastError()));
+
+ /* cleanup on failure. */
+ if (ret)
+ { /* likely */ }
+ else
+ {
+ g_shfile_async_close.handles[idx] = NULL;
+ g_shfile_async_close.idx_write = idx;
+ g_shfile_async_close.num_pending = num_pending - 1;
+ }
+ }
+ else
+ ret = K_FALSE;
+
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+ TRACE2((NULL, "shfile_async_close_submit: toclose=%p idx=%d #pending=%u #thread=%u -> %d\n",
+ toclose, idx, num_pending, num_threads, ret));
+ return ret;
+}
+
+/**
+ * Wait for all pending CloseHandle calls to complete.
+ */
+void shfile_async_close_sync(void)
+{
+ shmtxtmp tmp;
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ if (g_shfile_async_close.num_pending > 0)
+ {
+ DWORD dwRet;
+
+/** @todo help out? */
+ if (!g_shfile_async_close.signal_sync)
+ {
+ BOOL rc = ResetEvent(g_shfile_async_close.evt_sync);
+ kHlpAssert(rc); K_NOREF(rc);
+
+ g_shfile_async_close.signal_sync = K_TRUE;
+ }
+
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+
+ TRACE2((NULL, "shfile_async_close_sync: Calling WaitForSingleObject...\n"));
+ dwRet = WaitForSingleObject(g_shfile_async_close.evt_sync, 10000 /*ms*/);
+ kHlpAssert(dwRet == WAIT_OBJECT_0);
+ kHlpAssert(g_shfile_async_close.num_pending == 0);
+ TRACE2((NULL, "shfile_async_close_sync: WaitForSingleObject returned %u...\n", dwRet));
+ }
+ else
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+}
+
+# endif /* K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE) */
+
+/**
+ * Close the specified native handle.
+ *
+ * @param native The native file handle.
+ * @param file The file table entry if available.
+ * @param inheritable The inheritability of the handle on windows, K_FALSE elsewhere.
+ */
+static void shfile_native_close(intptr_t native, shfile *file, KBOOL inheritable)
+{
+# if K_OS == K_OS_WINDOWS
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ /*
+ * CloseHandle may take several milliseconds on NTFS after we've appended
+ * a few bytes to a file. When a script uses lots of 'echo line-text >> file'
+ * we end up executing very slowly, even if 'echo' is builtin and the statement
+ * requires no subshells to be spawned.
+ *
+ * So, detect problematic handles and do CloseHandle asynchronously. When
+ * executing a child process, we probably will have to make sure the CloseHandle
+ * operation has completed before we do ResumeThread on the child to make 100%
+ * sure we can't have any sharing conflicts or end up with incorrect CRT stat()
+ * results. Sharing conflicts are not a problem for builtin commands, and for
+ * stat we do not use the CRT code but ntstat.c/h and it seems to work fine
+ * (might be a tiny bit slower, so (TODO) might be worth reducing what we ask for).
+ *
+ * If child processes are spawned using handle inheritance and the handle in
+ * question is inheritable, we will have to fix the inheriability before pushing
+ * on the async-close queue. This shouldn't have the CloseHandle issues.
+ */
+ if ( file
+ && (file->shflags & (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_TYPE_MASK))
+ == (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_FILE))
+ {
+ if (inheritable)
+ native = (intptr_t)shfile_set_inherit_win(file, 0);
+ if (shfile_async_close_submit((HANDLE)native))
+ return;
+ }
+
+ /*
+ * Otherwise close it right here:
+ */
+# endif
+ {
+# ifdef DEBUG
+ KU64 ns = shfile_nano_ts();
+ BOOL fRc = CloseHandle((HANDLE)native);
+ kHlpAssert(fRc); K_NOREF(fRc);
+ ns = shfile_nano_ts() - ns;
+ if (ns > 1000000)
+ TRACE2((NULL, "shfile_native_close: %u ns %p oflags=%#x %s\n",
+ ns, native, file ? file->oflags : 0, file ? file->dbgname : NULL));
+# else
+ BOOL fRc = CloseHandle((HANDLE)native);
+ kHlpAssert(fRc); K_NOREF(fRc);
+# endif
+ }
+
+# else /* K_OS != K_OS_WINDOWS */
+ int s = errno;
+ close(native);
+ errno = s;
+# endif /* K_OS != K_OS_WINDOWS */
+ K_NOREF(file);
+}
+
+/**
+ * Grows the descriptor table, making sure that it can hold @a fdMin,
+ *
+ * @returns The max(fdMin, fdFirstNew) on success, -1 on failure.
+ * @param pfdtab The table to grow.
+ * @param fdMin Grow to include this index.
+ */
+static int shfile_grow_tab_locked(shfdtab *pfdtab, int fdMin)
+{
+ /*
+ * Grow the descriptor table.
+ */
+ int fdRet = -1;
+ shfile *new_tab;
+ int new_size = pfdtab->size + SHFILE_GROW;
+ while (new_size < fdMin)
+ new_size += SHFILE_GROW;
+ TRACE2((NULL, "shfile_grow_tab_locked: old %p / %d entries; new size: %d\n", pfdtab->tab, pfdtab->size, new_size));
+ new_tab = sh_realloc(shthread_get_shell(), pfdtab->tab, new_size * sizeof(shfile));
+ if (new_tab)
+ {
+ int i;
+ for (i = pfdtab->size; i < new_size; i++)
+ {
+ new_tab[i].fd = -1;
+ new_tab[i].oflags = 0;
+ new_tab[i].shflags = 0;
+ new_tab[i].native = -1;
+# ifdef DEBUG
+ new_tab[i].dbgname = NULL;
+# endif
+ }
+
+ fdRet = pfdtab->size;
+ if (fdRet < fdMin)
+ fdRet = fdMin;
+
+ pfdtab->tab = new_tab;
+ pfdtab->size = new_size;
+
+ TRACE2((NULL, "shfile_grow_tab_locked: new %p / %d entries\n", pfdtab->tab, pfdtab->size));
+ }
+
+ return fdRet;
+}
+
+/**
+ * Inserts the file into the descriptor table.
+ *
+ * If we're out of memory and cannot extend the table, we'll close the
+ * file, set errno to EMFILE and return -1.
+ *
+ * @returns The file descriptor number. -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param native The native file handle.
+ * @param oflags The flags the it was opened/created with.
+ * @param shflags The shell file flags.
+ * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
+ * @param who Who we're doing this for (for logging purposes).
+ * @param dbgname The filename, if applicable/available.
+ */
+static int shfile_insert(shfdtab *pfdtab, intptr_t native, unsigned oflags, unsigned shflags, int fdMin,
+ const char *who, const char *dbgname)
+{
+ shmtxtmp tmp;
+ int fd;
+ int i;
+
+ /*
+ * Fend of bad stuff.
+ */
+ if (fdMin >= SHFILE_MAX)
+ {
+ TRACE2((NULL, "shfile_insert: fdMin=%d is out of bounds; native=%p %s\n", fdMin, native, dbgname));
+ shfile_native_close(native, NULL, K_FALSE);
+ errno = EMFILE;
+ return -1;
+ }
+# if K_OS != K_OS_WINDOWS
+ if (fcntl((int)native, F_SETFD, fcntl((int)native, F_GETFD, 0) | FD_CLOEXEC) == -1)
+ {
+ int e = errno;
+ TRACE2((NULL, "shfile_insert: F_SETFD failed %d; native=%p %s\n", e, native, dbgname));
+ close((int)native);
+ errno = e;
+ return -1;
+ }
+# endif
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ /*
+ * Search for a fitting unused location.
+ */
+ fd = -1;
+ for (i = fdMin >= 0 ? fdMin : 0; (unsigned)i < pfdtab->size; i++)
+ if (pfdtab->tab[i].fd == -1)
+ {
+ fd = i;
+ break;
+ }
+ if (fd == -1)
+ fd = shfile_grow_tab_locked(pfdtab, fdMin);
+
+ /*
+ * Fill in the entry if we've found one.
+ */
+ if (fd != -1)
+ {
+ pfdtab->tab[fd].fd = fd;
+ pfdtab->tab[fd].oflags = oflags;
+ pfdtab->tab[fd].shflags = shflags;
+ pfdtab->tab[fd].native = native;
+#ifdef DEBUG
+ pfdtab->tab[fd].dbgname = dbgname ? sh_strdup(NULL, dbgname) : NULL;
+#endif
+ TRACE2((NULL, "shfile_insert: #%d: native=%p oflags=%#x shflags=%#x %s\n", fd, native, oflags, shflags, dbgname));
+ }
+ else
+ shfile_native_close(native, NULL, K_FALSE);
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+
+ if (fd == -1)
+ errno = EMFILE;
+ (void)who;
+ return fd;
+}
+
+# if K_OS != K_OS_WINDOWS
+/**
+ * Makes a copy of the native file, closes the original, and inserts the copy
+ * into the descriptor table.
+ *
+ * If we're out of memory and cannot extend the table, we'll close the
+ * file, set errno to EMFILE and return -1.
+ *
+ * @returns The file descriptor number. -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param pnative The native file handle on input, -1 on output.
+ * @param oflags The flags the it was opened/created with.
+ * @param shflags The shell file flags.
+ * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
+ * @param who Who we're doing this for (for logging purposes).
+ * @param dbgname The filename, if applicable/available.
+ */
+static int shfile_copy_insert_and_close(shfdtab *pfdtab, int *pnative, unsigned oflags, unsigned shflags, int fdMin,
+ const char *who, const char *dbgname)
+{
+ int fd = -1;
+ int s = errno;
+ int native_copy = fcntl(*pnative, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ close(*pnative);
+ *pnative = -1;
+ errno = s;
+
+ if (native_copy != -1)
+ fd = shfile_insert(pfdtab, native_copy, oflags, shflags, fdMin, who, dbgname);
+ return fd;
+}
+# endif /* !K_OS_WINDOWS */
+
+/**
+ * Gets a file descriptor and lock the file descriptor table.
+ *
+ * @returns Pointer to the file and table ownership on success,
+ * NULL and errno set to EBADF on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fd The file descriptor number.
+ * @param ptmp See shmtx_enter.
+ */
+static shfile *shfile_get(shfdtab *pfdtab, int fd, shmtxtmp *ptmp)
+{
+ shfile *file = NULL;
+ if ( fd >= 0
+ && (unsigned)fd < pfdtab->size)
+ {
+ shmtx_enter(&pfdtab->mtx, ptmp);
+ if ((unsigned)fd < pfdtab->size
+ && pfdtab->tab[fd].fd != -1)
+ file = &pfdtab->tab[fd];
+ else
+ shmtx_leave(&pfdtab->mtx, ptmp);
+ }
+ if (!file)
+ errno = EBADF;
+ return file;
+}
+
+/**
+ * Puts back a file descriptor and releases the table ownership.
+ *
+ * @param pfdtab The file descriptor table.
+ * @param file The file.
+ * @param ptmp See shmtx_leave.
+ */
+static void shfile_put(shfdtab *pfdtab, shfile *file, shmtxtmp *ptmp)
+{
+ shmtx_leave(&pfdtab->mtx, ptmp);
+ kHlpAssert(file);
+ (void)file;
+}
+
+/**
+ * Constructs a path from the current directory and the passed in path.
+ *
+ * @returns 0 on success, -1 and errno set to ENAMETOOLONG or EINVAL on failure.
+ *
+ * @param pfdtab The file descriptor table
+ * @param path The path the caller supplied.
+ * @param buf Where to put the path. This is assumed to be SHFILE_MAX_PATH
+ * chars in size.
+ */
+int shfile_make_path(shfdtab *pfdtab, const char *path, char *buf)
+{
+ size_t path_len = strlen(path);
+ if (path_len == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (path_len >= SHFILE_MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if ( *path == '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ || *path == '\\'
+ || ( *path
+ && path[1] == ':'
+ && ( (*path >= 'A' && *path <= 'Z')
+ || (*path >= 'a' && *path <= 'z')))
+# endif
+ )
+ {
+ memcpy(buf, path, path_len + 1);
+ }
+ else
+ {
+ size_t cwd_len;
+ shmtxtmp tmp;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ cwd_len = strlen(pfdtab->cwd);
+ memcpy(buf, pfdtab->cwd, cwd_len);
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+
+ if (cwd_len + path_len + 1 >= SHFILE_MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if ( !cwd_len
+ || buf[cwd_len - 1] != '/')
+ buf[cwd_len++] = '/';
+ memcpy(buf + cwd_len, path, path_len + 1);
+ }
+
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ if (!strcmp(buf, "/dev/null"))
+ strcpy(buf, "NUL");
+# endif
+ return 0;
+}
+
+# if K_OS == K_OS_WINDOWS
+
+/**
+ * Adjusts the file name if it ends with a trailing directory slash.
+ *
+ * Windows APIs doesn't like trailing slashes.
+ *
+ * @returns 1 if it has a directory slash, 0 if not.
+ *
+ * @param abspath The path to adjust (SHFILE_MAX_PATH).
+ */
+static int shfile_trailing_slash_hack(char *abspath)
+{
+ /*
+ * Anything worth adjust here?
+ */
+ size_t path_len = strlen(abspath);
+ if ( path_len == 0
+ || ( abspath[path_len - 1] != '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ && abspath[path_len - 1] != '\\'
+# endif
+ )
+ )
+ return 0;
+
+ /*
+ * Ok, make the adjustment.
+ */
+ if (path_len + 2 <= SHFILE_MAX_PATH)
+ {
+ /* Add a '.' to the end. */
+ abspath[path_len++] = '.';
+ abspath[path_len] = '\0';
+ }
+ else
+ {
+ /* No space for a dot, remove the slash if it's alone or just remove
+ one and add a dot like above. */
+ if ( abspath[path_len - 2] != '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ && abspath[path_len - 2] != '\\'
+# endif
+ )
+ abspath[--path_len] = '\0';
+ else
+ abspath[path_len - 1] = '.';
+ }
+
+ return 1;
+}
+
+
+/**
+ * Converts a DOS(/Windows) error code to errno,
+ * assigning it to errno.
+ *
+ * @returns -1
+ * @param err The DOS error.
+ */
+static int shfile_dos2errno(int err)
+{
+ switch (err)
+ {
+ case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break;
+ case ERROR_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_CURRENT_DIRECTORY: errno = EACCES; break;
+ case ERROR_LOCK_VIOLATION: errno = EACCES; break;
+ case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_CANNOT_MAKE: errno = EACCES; break;
+ case ERROR_FAIL_I24: errno = EACCES; break;
+ case ERROR_DRIVE_LOCKED: errno = EACCES; break;
+ case ERROR_SEEK_ON_DEVICE: errno = EACCES; break;
+ case ERROR_NOT_LOCKED: errno = EACCES; break;
+ case ERROR_LOCK_FAILED: errno = EACCES; break;
+ case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break;
+ case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break;
+ case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break;
+ case ERROR_INVALID_HANDLE: errno = EBADF; break;
+ case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break;
+ case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break;
+ case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break;
+ case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break;
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_ALREADY_EXISTS: errno = EEXIST; break;
+ case ERROR_INVALID_FUNCTION: errno = EINVAL; break;
+ case ERROR_INVALID_ACCESS: errno = EINVAL; break;
+ case ERROR_INVALID_DATA: errno = EINVAL; break;
+ case ERROR_INVALID_PARAMETER: errno = EINVAL; break;
+ case ERROR_NEGATIVE_SEEK: errno = EINVAL; break;
+ case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break;
+ case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_INVALID_DRIVE: errno = ENOENT; break;
+ case ERROR_NO_MORE_FILES: errno = ENOENT; break;
+ case ERROR_BAD_NETPATH: errno = ENOENT; break;
+ case ERROR_BAD_NET_NAME: errno = ENOENT; break;
+ case ERROR_BAD_PATHNAME: errno = ENOENT; break;
+ case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break;
+ case ERROR_BAD_FORMAT: errno = ENOEXEC; 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_NOT_ENOUGH_QUOTA: errno = ENOMEM; break;
+ case ERROR_DISK_FULL: errno = ENOSPC; break;
+ case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break;
+ case ERROR_BROKEN_PIPE: errno = EPIPE; break;
+ case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break;
+ default: errno = EINVAL; break;
+ }
+ return -1;
+}
+
+/**
+ * Converts an NT status code to errno,
+ * assigning it to errno.
+ *
+ * @returns -1
+ * @param rcNt The NT status code.
+ */
+static int shfile_nt2errno(NTSTATUS rcNt)
+{
+ switch (rcNt)
+ {
+ default: errno = EINVAL; break;
+ }
+ return -1;
+}
+
+DWORD shfile_query_handle_access_mask(HANDLE h, PACCESS_MASK pMask)
+{
+ MY_OBJECT_BASIC_INFORMATION BasicInfo;
+ NTSTATUS rcNt;
+
+ if (!g_pfnNtQueryObject)
+ return ERROR_NOT_SUPPORTED;
+
+ rcNt = g_pfnNtQueryObject(h, MY_ObjectBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
+ if (rcNt >= 0)
+ {
+ *pMask = BasicInfo.GrantedAccess;
+ return NO_ERROR;
+ }
+ if (rcNt != STATUS_INVALID_HANDLE)
+ return ERROR_GEN_FAILURE;
+ return ERROR_INVALID_HANDLE;
+}
+
+# endif /* K_OS == K_OS_WINDOWS */
+
+#endif /* SHFILE_IN_USE */
+
+/**
+ * Converts DOS slashes to UNIX slashes if necessary.
+ *
+ * @param pszPath The path to fix.
+ */
+K_INLINE void shfile_fix_slashes(char *pszPath)
+{
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ while ((pszPath = strchr(pszPath, '\\')))
+ *pszPath++ = '/';
+#else
+ (void)pszPath;
+#endif
+}
+
+/**
+ * Initializes the global variables in this file.
+ */
+static void shfile_init_globals(void)
+{
+#if K_OS == K_OS_WINDOWS
+ if (!g_shfile_globals_initialized)
+ {
+ HMODULE hNtDll = GetModuleHandle("NTDLL");
+ g_pfnNtQueryObject = (PFN_NtQueryObject) GetProcAddress(hNtDll, "NtQueryObject");
+ g_pfnNtQueryDirectoryFile = (PFN_NtQueryDirectoryFile)GetProcAddress(hNtDll, "NtQueryDirectoryFile");
+ g_pfnRtlUnicodeStringToAnsiString = (PFN_RtlUnicodeStringToAnsiString)GetProcAddress(hNtDll, "RtlUnicodeStringToAnsiString");
+ if ( !g_pfnRtlUnicodeStringToAnsiString
+ || !g_pfnNtQueryDirectoryFile)
+ {
+ /* fatal error */
+ }
+
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ /*
+ * Init the async CloseHandle state.
+ */
+ shmtx_init(&g_shfile_async_close.mtx);
+ g_shfile_async_close.evt_workers = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
+ g_shfile_async_close.evt_sync = CreateEventW(NULL, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
+ if ( !g_shfile_async_close.evt_workers
+ || !g_shfile_async_close.evt_sync)
+ {
+ fprintf(stderr, "fatal error: CreateEventW failed: %u\n", GetLastError());
+ _exit(19);
+ }
+ g_shfile_async_close.idx_read = 0;
+ g_shfile_async_close.idx_write = 0;
+ g_shfile_async_close.num_pending = 0;
+ g_shfile_async_close.terminate_threads = K_FALSE;
+ g_shfile_async_close.signal_sync = K_FALSE;
+ g_shfile_async_close.num_threads = 0;
+ {
+ unsigned i = K_ELEMENTS(g_shfile_async_close.handles);
+ while (i-- > 0)
+ g_shfile_async_close.handles[i] = NULL;
+ i = K_ELEMENTS(g_shfile_async_close.threads);
+ while (i-- > 0)
+ g_shfile_async_close.threads[i] = NULL;
+ }
+# endif
+
+ g_shfile_globals_initialized = 1;
+ }
+#endif
+}
+
+/**
+ * Initializes a file descriptor table.
+ *
+ * @returns 0 on success, -1 and errno on failure.
+ * @param pfdtab The table to initialize.
+ * @param inherit File descriptor table to inherit from. If not specified
+ * we will inherit from the current process as it were.
+ */
+int shfile_init(shfdtab *pfdtab, shfdtab *inherit)
+{
+ int rc;
+
+ shfile_init_globals();
+
+ pfdtab->cwd = NULL;
+ pfdtab->size = 0;
+ pfdtab->tab = NULL;
+ rc = shmtx_init(&pfdtab->mtx);
+ if (!rc)
+ {
+#ifdef SHFILE_IN_USE
+ /* Get CWD with unix slashes. */
+ if (!inherit)
+ {
+ char buf[SHFILE_MAX_PATH];
+ if (getcwd(buf, sizeof(buf)))
+ {
+ shfile_fix_slashes(buf);
+ pfdtab->cwd = sh_strdup(NULL, buf);
+ }
+ if (pfdtab->cwd)
+ {
+# if K_OS == K_OS_WINDOWS
+ static const struct
+ {
+ DWORD dwStdHandle;
+ unsigned fFlags;
+ } aStdHandles[3] =
+ {
+ { STD_INPUT_HANDLE, _O_RDONLY },
+ { STD_OUTPUT_HANDLE, _O_WRONLY },
+ { STD_ERROR_HANDLE, _O_WRONLY }
+ };
+ int i;
+ STARTUPINFO Info;
+ ACCESS_MASK Mask;
+ DWORD dwErr;
+
+ rc = 0;
+
+ /* Try pick up the Visual C++ CRT file descriptor info. */
+ __try {
+ GetStartupInfo(&Info);
+ } __except (EXCEPTION_EXECUTE_HANDLER) {
+ memset(&Info, 0, sizeof(Info));
+ }
+
+ if ( Info.cbReserved2 > sizeof(int)
+ && (uintptr_t)Info.lpReserved2 >= 0x1000
+ && (i = *(int *)Info.lpReserved2) >= 1
+ && i <= 2048
+ && ( Info.cbReserved2 == i * 5 + 4
+ //|| Info.cbReserved2 == i * 5 + 1 - check the cygwin sources.
+ || Info.cbReserved2 == i * 9 + 4))
+ {
+ uint8_t *paf = (uint8_t *)Info.lpReserved2 + sizeof(int);
+ int dwPerH = 1 + (Info.cbReserved2 == i * 9 + 4);
+ DWORD *ph = (DWORD *)(paf + i) + dwPerH * i;
+ HANDLE aStdHandles2[3];
+ int j;
+
+ //if (Info.cbReserved2 == i * 5 + 1) - check the cygwin sources.
+ // i--;
+
+ for (j = 0; j < 3; j++)
+ aStdHandles2[j] = GetStdHandle(aStdHandles[j].dwStdHandle);
+
+ while (i-- > 0)
+ {
+ ph -= dwPerH;
+
+ if ( (paf[i] & (FOPEN | FNOINHERIT)) == FOPEN
+ && *ph != (uint32_t)INVALID_HANDLE_VALUE
+ && *ph != 0)
+ {
+ HANDLE h = (HANDLE)(intptr_t)*ph;
+ int fd2;
+ int fFlags;
+ int fFlags2;
+
+ if ( h == aStdHandles2[j = 0]
+ || h == aStdHandles2[j = 1]
+ || h == aStdHandles2[j = 2])
+ fFlags = aStdHandles[j].fFlags;
+ else
+ {
+ dwErr = shfile_query_handle_access_mask(h, &Mask);
+ if (dwErr == ERROR_INVALID_HANDLE)
+ continue;
+ if (dwErr == NO_ERROR)
+ {
+ fFlags = 0;
+ if ( (Mask & (GENERIC_READ | FILE_READ_DATA))
+ && (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA)))
+ fFlags |= O_RDWR;
+ else if (Mask & (GENERIC_READ | FILE_READ_DATA))
+ fFlags |= O_RDONLY;
+ else if (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA))
+ fFlags |= O_WRONLY;
+ else
+ fFlags |= O_RDWR;
+ if ((Mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) == FILE_APPEND_DATA)
+ fFlags |= O_APPEND;
+ }
+ else
+ fFlags = O_RDWR;
+ }
+
+ if (paf[i] & FPIPE)
+ fFlags2 = SHFILE_FLAGS_PIPE;
+ else if (paf[i] & FDEV)
+ fFlags2 = SHFILE_FLAGS_TTY;
+ else
+ fFlags2 = 0;
+
+ fd2 = shfile_insert(pfdtab, (intptr_t)h, fFlags, fFlags2, i, "shtab_init", NULL);
+ kHlpAssert(fd2 == i); (void)fd2;
+ if (fd2 != i)
+ rc = -1;
+ }
+ }
+ }
+
+ /* Check the three standard handles. */
+ for (i = 0; i < 3; i++)
+ if ( (unsigned)i >= pfdtab->size
+ || pfdtab->tab[i].fd == -1)
+ {
+ HANDLE hFile = GetStdHandle(aStdHandles[i].dwStdHandle);
+ if ( hFile != INVALID_HANDLE_VALUE
+ && hFile != NULL)
+ {
+ DWORD dwType = GetFileType(hFile);
+ unsigned fFlags = aStdHandles[i].fFlags;
+ unsigned fFlags2;
+ int fd2;
+ if (dwType == FILE_TYPE_CHAR)
+ fFlags2 = SHFILE_FLAGS_TTY;
+ else if (dwType == FILE_TYPE_PIPE)
+ fFlags2 = SHFILE_FLAGS_PIPE;
+ else
+ fFlags2 = SHFILE_FLAGS_FILE;
+ fd2 = shfile_insert(pfdtab, (intptr_t)hFile, fFlags, fFlags2, i, "shtab_init", NULL);
+ kHlpAssert(fd2 == i); (void)fd2;
+ if (fd2 != i)
+ rc = -1;
+ }
+ }
+# else
+ /*
+ * Annoying...
+ */
+ int fd;
+
+ for (fd = 0; fd < 10; fd++)
+ {
+ int oflags = fcntl(fd, F_GETFL, 0);
+ if (oflags != -1)
+ {
+ int cox = fcntl(fd, F_GETFD, 0);
+ struct stat st;
+ if ( cox != -1
+ && fstat(fd, &st) != -1)
+ {
+ int native;
+ int fd2;
+ int fFlags2 = 0;
+ if (cox & FD_CLOEXEC)
+ fFlags2 |= SHFILE_FLAGS_CLOSE_ON_EXEC;
+ if (S_ISREG(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_FILE;
+ else if (S_ISDIR(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_DIR;
+ else if (S_ISCHR(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_TTY;
+ else if (S_ISFIFO(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_PIPE;
+ else
+ fFlags2 |= SHFILE_FLAGS_TTY;
+
+ native = fcntl(fd, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ if (native == -1)
+ native = fd;
+ fd2 = shfile_insert(pfdtab, native, oflags, fFlags2, fd, "shtab_init", NULL);
+ kHlpAssert(fd2 == fd); (void)fd2;
+ if (fd2 != fd)
+ rc = -1;
+ if (native != fd)
+ close(fd);
+ }
+ }
+ }
+
+# endif
+ }
+ else
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Inherit from parent shell's file table.
+ */
+ shfile const *src;
+ shfile *dst;
+ shmtxtmp tmp;
+ unsigned fdcount;
+ unsigned fd;
+
+ shmtx_enter(&inherit->mtx, &tmp);
+
+ /* allocate table and cwd: */
+ fdcount = inherit->size;
+ pfdtab->tab = dst = (shfile *)(fdcount ? sh_calloc(NULL, sizeof(pfdtab->tab[0]), fdcount) : NULL);
+ pfdtab->cwd = sh_strdup(NULL, inherit->cwd);
+ if ( pfdtab->cwd
+ && (pfdtab->tab || fdcount == 0))
+ {
+ /* duplicate table entries: */
+ for (fd = 0, src = inherit->tab; fd < fdcount; fd++, src++, dst++)
+ if (src->fd == -1)
+ dst->native = dst->fd = -1;
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+# ifdef SH_FORKED_MODE
+ KBOOL const cox = !!(src->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC);
+# else
+ KBOOL const cox = K_TRUE;
+# endif
+# endif
+ *dst = *src;
+# ifdef DEBUG
+ if (src->dbgname)
+ dst->dbgname = sh_strdup(NULL, src->dbgname);
+# endif
+# if K_OS == K_OS_WINDOWS
+ if (DuplicateHandle(GetCurrentProcess(),
+ (HANDLE)src->native,
+ GetCurrentProcess(),
+ (HANDLE *)&dst->native,
+ 0,
+ FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p (was %p)\n",
+ dst->fd, dst->oflags, dst->shflags, dst->native, src->native));
+ else
+ {
+ dst->native = (intptr_t)INVALID_HANDLE_VALUE;
+ rc = shfile_dos2errno(GetLastError());
+ TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p - failed %d / %u!\n",
+ dst->fd, dst->oflags, dst->shflags, src->native, rc, GetLastError()));
+ break;
+ }
+
+# elif K_OS == K_OS_LINUX /* 2.6.27 / glibc 2.9 */ || K_OS == K_OS_FREEBSD /* 10.0 */ || K_OS == K_OS_NETBSD /* 6.0 */
+ dst->native = dup3(src->native, -1, cox ? O_CLOEXEC : 0);
+# else
+ if (cox)
+ {
+# ifndef SH_FORKED_MODE
+ shmtxtmp tmp2;
+ shmtx_enter(&global_exec_something, &tmp)
+# endif
+ dst->native = dup2(src->native, -1);
+ if (dst->native >= 0)
+ rc = fcntl(dst->native, F_SETFD, FD_CLOEXEC);
+# ifndef SH_FORKED_MODE
+ shmtx_leave(&global_exec_something, &tmp)
+# endif
+ if (rc != 0)
+ break;
+ }
+ else
+ dst->native = dup2(src->native, -1);
+ if (dst->native < 0)
+ {
+ rc = -1;
+ break;
+ }
+# endif
+ }
+ }
+ else
+ rc = -1;
+ pfdtab->size = fd;
+ shmtx_leave(&inherit->mtx, &tmp);
+ } /* inherit != NULL */
+#endif
+ }
+ return rc;
+}
+
+/**
+ * Deletes the file descriptor table.
+ *
+ * Safe to call more than once.
+ */
+void shfile_uninit(shfdtab *pfdtab, int tracefd)
+{
+ if (!pfdtab)
+ return;
+
+ if (pfdtab->tab)
+ {
+ unsigned left = pfdtab->size;
+ struct shfile *pfd = pfdtab->tab;
+ unsigned tracefdfound = 0;
+ while (left-- > 0)
+ {
+ if (pfd->fd != -1)
+ {
+ if (pfd->fd != tracefd)
+ {
+#if K_OS == K_OS_WINDOWS
+ BOOL rc = CloseHandle((HANDLE)pfd->native);
+ kHlpAssert(rc == TRUE); K_NOREF(rc);
+#else
+ int rc = close((int)pfd->native);
+ kHlpAssert(rc == 0); K_NOREF(rc);
+#endif
+ pfd->fd = -1;
+ pfd->native = -1;
+ }
+ else
+ tracefdfound++; /* there is only the one */
+ }
+ pfd++;
+ }
+
+ if (!tracefdfound)
+ { /* likely */ }
+ else
+ return;
+
+ sh_free(NULL, pfdtab->tab);
+ pfdtab->tab = NULL;
+ }
+
+ shmtx_delete(&pfdtab->mtx);
+
+ sh_free(NULL, pfdtab->cwd);
+ pfdtab->cwd = NULL;
+}
+
+#if K_OS == K_OS_WINDOWS && defined(SHFILE_IN_USE)
+
+/**
+ * Changes the inheritability of a file descriptor, taking console handles into
+ * account.
+ *
+ * @note This MAY change the native handle for the entry.
+ *
+ * @returns The native handle.
+ * @param pfd The file descriptor to change.
+ * @param set If set, make child processes inherit the handle, if clear
+ * make them not inherit it.
+ */
+static HANDLE shfile_set_inherit_win(shfile *pfd, int set)
+{
+ HANDLE hFile = (HANDLE)pfd->native;
+ if (!SetHandleInformation(hFile, HANDLE_FLAG_INHERIT, set ? HANDLE_FLAG_INHERIT : 0))
+ {
+ /* SetHandleInformation doesn't work for console handles,
+ so we have to duplicate the handle to change the
+ inheritability. */
+ DWORD err = GetLastError();
+ if ( err == ERROR_INVALID_PARAMETER
+ && DuplicateHandle(GetCurrentProcess(),
+ hFile,
+ GetCurrentProcess(),
+ &hFile,
+ 0,
+ set ? TRUE : FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ {
+ TRACE2((NULL, "shfile_set_inherit_win: %p -> %p (set=%d)\n", pfd->native, hFile, set));
+ if (!CloseHandle((HANDLE)pfd->native))
+ kHlpAssert(0);
+ pfd->native = (intptr_t)hFile;
+ }
+ else
+ {
+ err = GetLastError();
+ kHlpAssert(0);
+ hFile = (HANDLE)pfd->native;
+ }
+ }
+ return hFile;
+}
+
+# ifdef SH_FORKED_MODE
+/**
+ * Helper for shfork.
+ *
+ * @param pfdtab The file descriptor table.
+ * @param set Whether to make all handles inheritable (1) or
+ * to restore them to the rigth state (0).
+ * @param hndls Where to store the three standard handles.
+ */
+void shfile_fork_win(shfdtab *pfdtab, int set, intptr_t *hndls)
+{
+ shmtxtmp tmp;
+ unsigned i;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ TRACE2((NULL, "shfile_fork_win: set=%d\n", set));
+
+ i = pfdtab->size;
+ while (i-- > 0)
+ {
+ if (pfdtab->tab[i].fd == i)
+ {
+ shfile_set_inherit_win(&pfdtab->tab[i], set);
+ if (set)
+ TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
+ i, pfdtab->tab[i].native, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
+ }
+ }
+
+ if (hndls)
+ {
+ for (i = 0; i < 3; i++)
+ {
+ if ( pfdtab->size > i
+ && pfdtab->tab[i].fd == i)
+ hndls[i] = pfdtab->tab[i].native;
+ else
+ hndls[i] = (intptr_t)INVALID_HANDLE_VALUE;
+ TRACE2((NULL, "shfile_fork_win: i=%d size=%d fd=%d native=%d hndls[%d]=%p\n",
+ i, pfdtab->size, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, hndls[i]));
+ }
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+}
+# endif /* SH_FORKED_MODE */
+
+/** shfile_exec_win helper that make sure there are no _O_APPEND handles. */
+static KBOOL shfile_exec_win_no_append(shfdtab *pfdtab, unsigned count)
+{
+ unsigned i;
+ for (i = 0; i < count; i++)
+ if ( (pfdtab->tab[i].oflags & _O_APPEND)
+ && (pfdtab->tab[i].oflags & (_O_WRONLY | _O_RDWR)))
+ return K_FALSE;
+ return K_TRUE;
+}
+
+/**
+ * Helper for sh_execve.
+ *
+ * This is called before and after CreateProcess. On the first call it
+ * will mark the non-close-on-exec handles as inheritable and produce
+ * the startup info for the CRT. On the second call, after CreateProcess,
+ * it will restore the handle inheritability properties.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pfdtab The file descriptor table.
+ * @param prepare Which call, 1 if before, 0 if after and success, -1 if after on failure.
+ * @param info The info structure.
+ */
+int shfile_exec_win(shfdtab *pfdtab, int prepare, shfdexecwin *info)
+{
+ STARTUPINFOA *strtinfo = (STARTUPINFOA *)info->strtinfo;
+ int rc = 0;
+ shmtxtmp tmp;
+ unsigned count;
+ unsigned i;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ TRACE2((NULL, "shfile_exec_win: prepare=%p\n", prepare));
+
+ count = pfdtab->size < (0x10000-4) / (1 + sizeof(HANDLE))
+ ? pfdtab->size
+ : (0x10000-4) / (1 + sizeof(HANDLE));
+ while ( count > 3
+ && ( pfdtab->tab[count - 1].fd == -1
+ || (pfdtab->tab[count - 1].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC)))
+ count--;
+
+ if (prepare > 0)
+ {
+ if (count <= 3 && shfile_exec_win_no_append(pfdtab, count))
+ {
+ info->inherithandles = 0;
+ info->startsuspended = 1;
+ strtinfo->cbReserved2 = 0;
+ strtinfo->lpReserved2 = NULL;
+ }
+ else
+ {
+ size_t cbData = sizeof(int) + count * (1 + sizeof(HANDLE));
+ uint8_t *pbData = sh_malloc(shthread_get_shell(), cbData);
+ uint8_t *paf = pbData + sizeof(int);
+ HANDLE *pah = (HANDLE *)(paf + count);
+
+ info->inherithandles = 1;
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ info->startsuspended = g_shfile_async_close.num_pending > 0;
+# else
+ info->startsuspended = 0;
+# endif
+ strtinfo->cbReserved2 = (unsigned short)cbData;
+ strtinfo->lpReserved2 = pbData;
+
+# ifndef SH_FORKED_MODE
+ shmtx_leave(&pfdtab->mtx, &tmp); /* should be harmless as this isn't really necessary at all. */
+ shmtx_enter(&g_sh_exec_inherit_mtx, &info->tmp);
+ shmtx_enter(&pfdtab->mtx, &tmp);
+# endif
+
+ *(int *)pbData = count;
+
+ i = count;
+ while (i-- > 0)
+ {
+ if ( pfdtab->tab[i].fd == i
+ && !(pfdtab->tab[i].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
+ {
+ HANDLE hFile = shfile_set_inherit_win(&pfdtab->tab[i], 1);
+ TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
+ i, hFile, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
+ paf[i] = FOPEN;
+ if (pfdtab->tab[i].oflags & _O_APPEND)
+ paf[i] |= FAPPEND;
+ if (pfdtab->tab[i].oflags & _O_TEXT)
+ paf[i] |= FTEXT;
+ switch (pfdtab->tab[i].shflags & SHFILE_FLAGS_TYPE_MASK)
+ {
+ case SHFILE_FLAGS_TTY: paf[i] |= FDEV; break;
+ case SHFILE_FLAGS_PIPE: paf[i] |= FPIPE; break;
+ }
+ pah[i] = hFile;
+ }
+ else
+ {
+ paf[i] = 0;
+ pah[i] = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ if ( i < count
+ && pfdtab->tab[i].fd == i)
+ {
+ info->replacehandles[i] = 1;
+ info->handles[i] = pfdtab->tab[i].native;
+ }
+ else
+ {
+ info->replacehandles[i] = 0;
+ info->handles[i] = (intptr_t)INVALID_HANDLE_VALUE;
+ }
+ TRACE2((NULL, "shfile_exec_win: i=%d count=%d fd=%d native=%d hndls[%d]=\n",
+ i, count, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, info->handles[i]));
+ }
+ }
+ else
+ {
+ shfile *file = pfdtab->tab;
+
+ sh_free(NULL, strtinfo->lpReserved2);
+ strtinfo->lpReserved2 = NULL;
+
+ i = count;
+ if (prepare == 0)
+ for (i = 0; i < count; i++, file++)
+ {
+ if ( file->fd == i
+ && !(file->shflags & SHFILE_FLAGS_TRACE))
+ {
+ shfile_native_close(file->native, file, info->inherithandles);
+
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ sh_free(NULL, file->dbgname);
+ file->dbgname = NULL;
+# endif
+ }
+ }
+ else if (info->inherithandles)
+ for (i = 0; i < count; i++, file++)
+ if ( file->fd == i
+ && !(file->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
+ shfile_set_inherit_win(file, 0);
+
+# ifndef SH_FORKED_MODE
+ if (info->inherithandles)
+ shmtx_leave(&g_sh_exec_inherit_mtx, &info->tmp);
+# endif
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ return rc;
+}
+
+#endif /* K_OS_WINDOWS */
+
+#if K_OS != K_OS_WINDOWS
+/**
+ * Prepare file handles for inherting before a execve call.
+ *
+ * This is only used in the normal mode, so we've forked and need not worry
+ * about cleaning anything up after us. Nor do we need think about locking.
+ *
+ * @returns 0 on success, -1 on failure.
+ */
+int shfile_exec_unix(shfdtab *pfdtab)
+{
+ int rc = 0;
+# ifdef SHFILE_IN_USE
+ unsigned fd;
+
+ for (fd = 0; fd < pfdtab->size; fd++)
+ {
+ if ( pfdtab->tab[fd].fd != -1
+ && !(pfdtab->tab[fd].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC) )
+ {
+ TRACE2((NULL, "shfile_exec_unix: %d => %d\n", pfdtab->tab[fd].native, fd));
+ if (dup2(pfdtab->tab[fd].native, fd) < 0)
+ {
+ /* fatal_error(NULL, "shfile_exec_unix: failed to move %d to %d", pfdtab->tab[fd].fd, fd); */
+ rc = -1;
+ }
+ }
+ }
+# endif
+ return rc;
+}
+#endif /* !K_OS_WINDOWS */
+
+/**
+ * open().
+ */
+int shfile_open(shfdtab *pfdtab, const char *name, unsigned flags, mode_t mode)
+{
+ int fd;
+#ifdef SHFILE_IN_USE
+ char absname[SHFILE_MAX_PATH];
+# if K_OS == K_OS_WINDOWS
+ HANDLE hFile;
+ DWORD dwDesiredAccess;
+ DWORD dwShareMode;
+ DWORD dwCreationDisposition;
+ DWORD dwFlagsAndAttributes;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+# ifndef _O_ACCMODE
+# define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
+# endif
+ switch (flags & (_O_ACCMODE | _O_APPEND))
+ {
+ case _O_RDONLY: dwDesiredAccess = GENERIC_READ; break;
+ case _O_RDONLY | _O_APPEND: dwDesiredAccess = GENERIC_READ; break;
+ case _O_WRONLY: dwDesiredAccess = GENERIC_WRITE; break;
+ case _O_WRONLY | _O_APPEND: dwDesiredAccess = (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
+ case _O_RDWR: dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; break;
+ case _O_RDWR | _O_APPEND: dwDesiredAccess = GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
+
+ default:
+ RETURN_ERROR(-1, EINVAL, "invalid mode");
+ }
+
+ dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ if (flags & _O_CREAT)
+ {
+ if ((flags & (_O_EXCL | _O_TRUNC)) == (_O_EXCL | _O_TRUNC))
+ RETURN_ERROR(-1, EINVAL, "_O_EXCL | _O_TRUNC");
+
+ if (flags & _O_TRUNC)
+ dwCreationDisposition = CREATE_ALWAYS; /* not 100%, but close enough */
+ else if (flags & _O_EXCL)
+ dwCreationDisposition = CREATE_NEW;
+ else
+ dwCreationDisposition = OPEN_ALWAYS;
+ }
+ else if (flags & _O_TRUNC)
+ dwCreationDisposition = TRUNCATE_EXISTING;
+ else
+ dwCreationDisposition = OPEN_EXISTING;
+
+ if (!(flags & _O_CREAT) || (mode & 0222))
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+ else
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_READONLY;
+
+ fd = shfile_make_path(pfdtab, name, &absname[0]);
+ if (!fd)
+ {
+# ifdef DEBUG
+ KU64 ns = shfile_nano_ts();
+# endif
+ SetLastError(0);
+ hFile = CreateFileA(absname,
+ dwDesiredAccess,
+ dwShareMode,
+ &SecurityAttributes,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL /* hTemplateFile */);
+# ifdef DEBUG
+ ns = shfile_nano_ts() - ns;
+ if (ns > 1000000)
+ TRACE2((NULL, "shfile_open: %u ns hFile=%p (%d) %s\n", ns, hFile, GetLastError(), absname));
+# endif
+ if (hFile != INVALID_HANDLE_VALUE)
+ fd = shfile_insert(pfdtab, (intptr_t)hFile, flags, 0, -1, "shfile_open", absname);
+ else
+ fd = shfile_dos2errno(GetLastError());
+ }
+
+# else /* K_OS != K_OS_WINDOWS */
+ fd = shfile_make_path(pfdtab, name, &absname[0]);
+ if (!fd)
+ {
+ fd = open(absname, flags, mode);
+ if (fd != -1)
+ fd = shfile_copy_insert_and_close(pfdtab, &fd, flags, 0, -1, "shfile_open", absname);
+ }
+
+# endif /* K_OS != K_OS_WINDOWS */
+
+#else
+ fd = open(name, flags, mode);
+#endif
+
+ TRACE2((NULL, "shfile_open(%p:{%s}, %#x, 0%o) -> %d [%d]\n", name, name, flags, mode, fd, errno));
+ return fd;
+}
+
+int shfile_pipe(shfdtab *pfdtab, int fds[2])
+{
+ int rc = -1;
+#ifdef SHFILE_IN_USE
+# if K_OS == K_OS_WINDOWS
+ HANDLE hRead = INVALID_HANDLE_VALUE;
+ HANDLE hWrite = INVALID_HANDLE_VALUE;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ fds[1] = fds[0] = -1;
+ if (CreatePipe(&hRead, &hWrite, &SecurityAttributes, SHFILE_PIPE_SIZE))
+ {
+ fds[0] = shfile_insert(pfdtab, (intptr_t)hRead, O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
+ if (fds[0] != -1)
+ {
+ fds[1] = shfile_insert(pfdtab, (intptr_t)hWrite, O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
+ if (fds[1] != -1)
+ rc = 0;
+ }
+
+# else
+ int native_fds[2];
+
+ fds[1] = fds[0] = -1;
+ if (!pipe(native_fds))
+ {
+ fds[0] = shfile_copy_insert_and_close(pfdtab, &native_fds[0], O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
+ if (fds[0] != -1)
+ {
+ fds[1] = shfile_copy_insert_and_close(pfdtab, &native_fds[1], O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
+ if (fds[1] != -1)
+ rc = 0;
+ }
+# endif
+ if (fds[1] == -1)
+ {
+ int s = errno;
+ if (fds[0] != -1)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ rc = fds[0];
+ pfdtab->tab[rc].fd = -1;
+ pfdtab->tab[rc].oflags = 0;
+ pfdtab->tab[rc].shflags = 0;
+ pfdtab->tab[rc].native = -1;
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+
+# if K_OS == K_OS_WINDOWS
+ CloseHandle(hRead);
+ CloseHandle(hWrite);
+# else
+ close(native_fds[0]);
+ close(native_fds[1]);
+# endif
+ fds[0] = fds[1] = -1;
+ errno = s;
+ rc = -1;
+ }
+ }
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+ errno = shfile_dos2errno(GetLastError());
+# endif
+ rc = -1;
+ }
+
+#else
+ rc = pipe(fds);
+#endif
+
+ TRACE2((NULL, "shfile_pipe() -> %d{%d,%d} [%d]\n", rc, fds[0], fds[1], errno));
+ return rc;
+}
+
+/**
+ * dup().
+ */
+int shfile_dup(shfdtab *pfdtab, int fd)
+{
+ return shfile_fcntl(pfdtab,fd, F_DUPFD, 0);
+}
+
+/**
+ * Move the file descriptor, closing any existing descriptor at @a fdto.
+ *
+ * @returns fdto on success, -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fdfrom The descriptor to move.
+ * @param fdto Where to move it.
+ */
+int shfile_movefd(shfdtab *pfdtab, int fdfrom, int fdto)
+{
+#ifdef SHFILE_IN_USE
+ int rc;
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
+ if (file)
+ {
+ /* prepare the new entry */
+ if ((unsigned)fdto >= pfdtab->size)
+ shfile_grow_tab_locked(pfdtab, fdto);
+ if ((unsigned)fdto < pfdtab->size)
+ {
+ if (pfdtab->tab[fdto].fd != -1)
+ shfile_native_close(pfdtab->tab[fdto].native, &pfdtab->tab[fdto], K_FALSE);
+
+ /* setup the target. */
+ pfdtab->tab[fdto].fd = fdto;
+ pfdtab->tab[fdto].oflags = file->oflags;
+ pfdtab->tab[fdto].shflags = file->shflags;
+ pfdtab->tab[fdto].native = file->native;
+# ifdef DEBUG
+ pfdtab->tab[fdto].dbgname = file->dbgname;
+# endif
+
+ /* close the source. */
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ file->dbgname = NULL;
+# endif
+
+ rc = fdto;
+ }
+ else
+ {
+ errno = EMFILE;
+ rc = -1;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+ return rc;
+
+#else
+ int fdnew = dup2(fdfrom, fdto);
+ if (fdnew >= 0)
+ close(fdfrom);
+ return fdnew;
+#endif
+}
+
+/**
+ * Move the file descriptor to somewhere at @a fdMin or above.
+ *
+ * @returns the new file descriptor success, -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fdfrom The descriptor to move.
+ * @param fdMin The minimum descriptor.
+ */
+int shfile_movefd_above(shfdtab *pfdtab, int fdfrom, int fdMin)
+{
+#ifdef SHFILE_IN_USE
+ int fdto;
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
+ if (file)
+ {
+ /* find a new place */
+ int i;
+ fdto = -1;
+ for (i = fdMin; (unsigned)i < pfdtab->size; i++)
+ if (pfdtab->tab[i].fd == -1)
+ {
+ fdto = i;
+ break;
+ }
+ if (fdto == -1)
+ fdto = shfile_grow_tab_locked(pfdtab, fdMin);
+ if (fdto != -1)
+ {
+ /* setup the target. */
+ pfdtab->tab[fdto].fd = fdto;
+ pfdtab->tab[fdto].oflags = file->oflags;
+ pfdtab->tab[fdto].shflags = file->shflags;
+ pfdtab->tab[fdto].native = file->native;
+# ifdef DEBUG
+ pfdtab->tab[fdto].dbgname = file->dbgname;
+# endif
+
+ /* close the source. */
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ file->dbgname = NULL;
+# endif
+ }
+ else
+ {
+ errno = EMFILE;
+ fdto = -1;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ fdto = -1;
+ return fdto;
+
+#else
+ int fdnew = fcntl(fdfrom, F_DUPFD, fdMin);
+ if (fdnew >= 0)
+ close(fdfrom);
+ return fdnew;
+#endif
+}
+
+/**
+ * close().
+ */
+int shfile_close(shfdtab *pfdtab, unsigned fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ shfile_native_close(file->native, file, K_FALSE);
+
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ sh_free(NULL, file->dbgname);
+ file->dbgname = NULL;
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+
+#else
+ rc = close(fd);
+#endif
+
+ TRACE2((NULL, "shfile_close(%d) -> %d [%d]\n", fd, rc, errno));
+ return rc;
+}
+
+/**
+ * read().
+ */
+long shfile_read(shfdtab *pfdtab, int fd, void *buf, size_t len)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ DWORD dwRead = 0;
+ if (ReadFile((HANDLE)file->native, buf, (DWORD)len, &dwRead, NULL))
+ rc = dwRead;
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = read(file->native, buf, len);
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = read(fd, buf, len);
+#endif
+ return rc;
+}
+
+/**
+ * write().
+ */
+long shfile_write(shfdtab *pfdtab, int fd, const void *buf, size_t len)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ DWORD dwWritten = 0;
+ if (WriteFile((HANDLE)file->native, buf, (DWORD)len, &dwWritten, NULL))
+ rc = dwWritten;
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = write(file->native, buf, len);
+# endif
+
+ file->shflags |= SHFILE_FLAGS_DIRTY; /* there should be no concurrent access, so this is safe. */
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+# ifdef DEBUG
+ if (fd != shthread_get_shell()->tracefd)
+ TRACE2((NULL, "shfile_write(%d,,%d) -> %d [%d]\n", fd, len, rc, errno));
+# endif
+
+#else
+ if (fd != shthread_get_shell()->tracefd)
+ {
+ int iSavedErrno = errno;
+ struct stat s;
+ int x;
+ x = fstat(fd, &s);
+ TRACE2((NULL, "shfile_write(%d) - %lu bytes (%d) - pos %lu - before; %o\n",
+ fd, (long)s.st_size, x, (long)lseek(fd, 0, SEEK_CUR), s.st_mode ));
+ K_NOREF(x);
+ errno = iSavedErrno;
+ }
+
+ rc = write(fd, buf, len);
+#endif
+ return rc;
+}
+
+/**
+ * lseek().
+ */
+long shfile_lseek(shfdtab *pfdtab, int fd, long off, int whench)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ kHlpAssert(SEEK_SET == FILE_BEGIN);
+ kHlpAssert(SEEK_CUR == FILE_CURRENT);
+ kHlpAssert(SEEK_END == FILE_END);
+ rc = SetFilePointer((HANDLE)file->native, off, NULL, whench);
+ if (rc == INVALID_SET_FILE_POINTER)
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = lseek(file->native, off, whench);
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = lseek(fd, off, whench);
+#endif
+
+ return rc;
+}
+
+int shfile_fcntl(shfdtab *pfdtab, int fd, int cmd, int arg)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ switch (cmd)
+ {
+ case F_GETFL:
+ rc = file->oflags;
+ break;
+
+ case F_SETFL:
+ {
+ unsigned mask = O_NONBLOCK | O_APPEND | O_BINARY | O_TEXT;
+# ifdef O_DIRECT
+ mask |= O_DIRECT;
+# endif
+# ifdef O_ASYNC
+ mask |= O_ASYNC;
+# endif
+# ifdef O_SYNC
+ mask |= O_SYNC;
+# endif
+ if ((file->oflags & mask) == (arg & mask))
+ rc = 0;
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+ kHlpAssert(0);
+ errno = EINVAL;
+ rc = -1;
+# else
+ rc = fcntl(file->native, F_SETFL, arg);
+ if (rc != -1)
+ file->oflags = (file->oflags & ~mask) | (arg & mask);
+# endif
+ }
+ break;
+ }
+
+ case F_DUPFD:
+ {
+# if K_OS == K_OS_WINDOWS
+ HANDLE hNew = INVALID_HANDLE_VALUE;
+ if (DuplicateHandle(GetCurrentProcess(),
+ (HANDLE)file->native,
+ GetCurrentProcess(),
+ &hNew,
+ 0,
+ FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ rc = shfile_insert(pfdtab, (intptr_t)hNew, file->oflags, file->shflags, arg,
+ "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ int nativeNew = fcntl(file->native, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ if (nativeNew != -1)
+ rc = shfile_insert(pfdtab, nativeNew, file->oflags, file->shflags, arg,
+ "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
+ else
+ rc = -1;
+# endif
+ break;
+ }
+
+ default:
+ errno = -EINVAL;
+ rc = -1;
+ break;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = fcntl(fd, cmd, arg);
+#endif
+
+ switch (cmd)
+ {
+ case F_GETFL: TRACE2((NULL, "shfile_fcntl(%d,F_GETFL,ignored=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ case F_SETFL: TRACE2((NULL, "shfile_fcntl(%d,F_SETFL,newflags=%#x) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ case F_DUPFD: TRACE2((NULL, "shfile_fcntl(%d,F_DUPFD,minfd=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ default: TRACE2((NULL, "shfile_fcntl(%d,%d,%d) -> %d [%d]\n", fd, cmd, arg, rc, errno)); break;
+ }
+ return rc;
+}
+
+int shfile_stat(shfdtab *pfdtab, const char *path, struct stat *pst)
+{
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+ int rc;
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# if K_OS == K_OS_WINDOWS
+# if 1
+ rc = birdStatFollowLink(abspath, pst);
+# else
+ int dir_slash = shfile_trailing_slash_hack(abspath);
+ rc = stat(abspath, pst); /** @todo re-implement stat. */
+ if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
+ {
+ rc = -1;
+ errno = ENOTDIR;
+
+ }
+# endif
+# else
+ rc = stat(abspath, pst);
+# endif
+ }
+ TRACE2((NULL, "shfile_stat(,%s,) -> %d [%d] st_size=%llu st_mode=%o\n",
+ path, rc, errno, (unsigned long long)pst->st_size, pst->st_mode));
+ return rc;
+#else
+ return stat(path, pst);
+#endif
+}
+
+/**
+ * @retval 1 if regular file.
+ * @retval 0 if found but not a regular file.
+ * @retval -1 and errno on failure
+ */
+int shfile_stat_isreg(shfdtab *pfdtab, const char *path)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ char abspath[SHFILE_MAX_PATH];
+ KU16 mode = 0;
+ int rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+ rc = birdStatModeOnly(abspath, &mode, 0 /*fFollowLink*/);
+ if (rc >= 0)
+ rc = S_ISREG(mode) ? 1 : 0;
+ }
+ TRACE2((NULL, "shfile_stat_isreg(,%s,) -> %d [%d] st_mode=%o\n", path, rc, errno, mode));
+ return rc;
+#else
+ struct stat st;
+ int rc = shfile_stat(pfdtab, path, &st);
+ if (rc >= 0)
+ rc = S_ISREG(st.st_mode) ? 1 : 0;
+ return rc;
+#endif
+}
+
+/**
+ * Same as shfile_stat, but without the data structure.
+ */
+int shfile_stat_exists(shfdtab *pfdtab, const char *path)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ char abspath[SHFILE_MAX_PATH];
+ KU16 mode = 0;
+ int rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ rc = birdStatModeOnly(abspath, &mode, 0 /*fFollowLink*/);
+ TRACE2((NULL, "shfile_stat_exists(,%s,) -> %d [%d] st_mode=%o\n", path, rc, errno, mode));
+ return rc;
+#else
+ struct stat ignored;
+ return shfile_stat(pfdtab, path, &ignored);
+#endif
+}
+
+int shfile_lstat(shfdtab *pfdtab, const char *path, struct stat *pst)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# if K_OS == K_OS_WINDOWS
+# if 1
+ rc = birdStatOnLink(abspath, pst);
+# else
+ int dir_slash = shfile_trailing_slash_hack(abspath);
+ rc = stat(abspath, pst); /** @todo re-implement stat. */
+ if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
+ {
+ rc = -1;
+ errno = ENOTDIR;
+ }
+# endif
+# else
+ rc = lstat(abspath, pst);
+# endif
+ }
+#else
+ rc = stat(path, pst);
+#endif
+ TRACE2((NULL, "shfile_lstat(,%s,) -> %d [%d] st_size=%llu st_mode=%o\n",
+ path, rc, errno, (unsigned long long)pst->st_size, pst->st_mode));
+ return rc;
+}
+
+/**
+ * chdir().
+ */
+int shfile_chdir(shfdtab *pfdtab, const char *path)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shinstance *psh = shthread_get_shell();
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+ char *abspath_copy = sh_strdup(psh, abspath);
+ char *free_me = abspath_copy;
+ rc = chdir(abspath);
+ if (!rc)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ shfile_fix_slashes(abspath_copy);
+ free_me = pfdtab->cwd;
+ pfdtab->cwd = abspath_copy;
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+ sh_free(psh, free_me);
+ }
+ else
+ rc = -1;
+#else
+ rc = chdir(path);
+#endif
+
+ TRACE2((NULL, "shfile_chdir(,%s) -> %d [%d]\n", path, rc, errno));
+ return rc;
+}
+
+/**
+ * getcwd().
+ */
+char *shfile_getcwd(shfdtab *pfdtab, char *buf, int size)
+{
+ char *ret;
+#ifdef SHFILE_IN_USE
+
+ ret = NULL;
+ if (buf && !size)
+ errno = -EINVAL;
+ else
+ {
+ size_t cwd_size;
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ cwd_size = strlen(pfdtab->cwd) + 1;
+ if (buf)
+ {
+ if (cwd_size <= (size_t)size)
+ ret = memcpy(buf, pfdtab->cwd, cwd_size);
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ if ((size_t)size < cwd_size)
+ size = (int)cwd_size;
+ ret = sh_malloc(shthread_get_shell(), size);
+ if (ret)
+ ret = memcpy(ret, pfdtab->cwd, cwd_size);
+ else
+ errno = ENOMEM;
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+#else
+ ret = getcwd(buf, size);
+#endif
+
+ TRACE2((NULL, "shfile_getcwd(,%p,%d) -> %s [%d]\n", buf, size, ret, errno));
+ return ret;
+}
+
+/**
+ * access().
+ */
+int shfile_access(shfdtab *pfdtab, const char *path, int type)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# ifdef _MSC_VER
+ if (type & X_OK)
+ type = (type & ~X_OK) | R_OK;
+# endif
+ rc = access(abspath, type);
+ }
+#else
+# ifdef _MSC_VER
+ if (type & X_OK)
+ type = (type & ~X_OK) | R_OK;
+# endif
+ rc = access(path, type);
+#endif
+
+ TRACE2((NULL, "shfile_access(,%s,%#x) -> %d [%d]\n", path, type, rc, errno));
+ return rc;
+}
+
+/**
+ * isatty()
+ */
+int shfile_isatty(shfdtab *pfdtab, int fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ rc = (file->shflags & SHFILE_FLAGS_TYPE_MASK) == SHFILE_FLAGS_TTY;
+# else
+ rc = isatty(file->native);
+# endif
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = 0;
+#else
+ rc = isatty(fd);
+#endif
+
+ TRACE2((NULL, "isatty(%d) -> %d [%d]\n", fd, rc, errno));
+ return rc;
+}
+
+/**
+ * fcntl F_SETFD / FD_CLOEXEC.
+ */
+int shfile_cloexec(shfdtab *pfdtab, int fd, int closeit)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ if (closeit)
+ file->shflags |= SHFILE_FLAGS_CLOSE_ON_EXEC;
+ else
+ file->shflags &= ~SHFILE_FLAGS_CLOSE_ON_EXEC;
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+#else
+ rc = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0)
+ | (closeit ? FD_CLOEXEC : 0));
+#endif
+
+ TRACE2((NULL, "shfile_cloexec(%d, %d) -> %d [%d]\n", fd, closeit, rc, errno));
+ return rc;
+}
+
+/**
+ * Sets the SHFILE_FLAGS_TRACE flag.
+ */
+int shfile_set_trace(shfdtab *pfdtab, int fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ file->shflags |= SHFILE_FLAGS_TRACE;
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+#else
+ rc = 0;
+#endif
+
+ TRACE2((NULL, "shfile_set_trace(%d) -> %d\n", fd, rc));
+ return rc;
+}
+
+
+int shfile_ioctl(shfdtab *pfdtab, int fd, unsigned long request, void *buf)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ rc = -1;
+ errno = ENOSYS;
+# else
+ rc = ioctl(file->native, request, buf);
+# endif
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+#else
+ rc = ioctl(fd, request, buf);
+#endif
+
+ TRACE2((NULL, "ioctl(%d, %#x, %p) -> %d\n", fd, request, buf, rc));
+ return rc;
+}
+
+
+mode_t shfile_get_umask(shfdtab *pfdtab)
+{
+ /** @todo */
+ return 022;
+}
+
+void shfile_set_umask(shfdtab *pfdtab, mode_t mask)
+{
+ /** @todo */
+ (void)mask;
+}
+
+
+
+shdir *shfile_opendir(shfdtab *pfdtab, const char *dir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ shdir *pdir = NULL;
+
+ TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
+ shfile_init_globals();
+ if (g_pfnNtQueryDirectoryFile)
+ {
+ char abspath[SHFILE_MAX_PATH];
+ if (shfile_make_path(pfdtab, dir, &abspath[0]) == 0)
+ {
+ HANDLE hFile;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ hFile = CreateFileA(abspath,
+ GENERIC_READ,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &SecurityAttributes,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS,
+ NULL /* hTemplateFile */);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ pdir = (shdir *)sh_malloc(shthread_get_shell(), sizeof(*pdir));
+ if (pdir)
+ {
+ pdir->pfdtab = pfdtab;
+ pdir->native = hFile;
+ pdir->off = ~(size_t)0;
+ }
+ else
+ CloseHandle(hFile);
+ }
+ else
+ {
+ errno = shfile_dos2errno(GetLastError());
+ TRACE2((NULL, "shfile_opendir: CreateFileA(%s) -> %d/%d\n", abspath, GetLastError(), errno));
+ }
+ }
+ }
+ else
+ errno = ENOSYS;
+ return pdir;
+#else
+ TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
+ return (shdir *)opendir(dir);
+#endif
+}
+
+shdirent *shfile_readdir(struct shdir *pdir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ if (pdir)
+ {
+ NTSTATUS rcNt;
+
+ if ( pdir->off == ~(size_t)0
+ || pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) >= pdir->cb)
+ {
+ MY_IO_STATUS_BLOCK Ios;
+
+ memset(&Ios, 0, sizeof(Ios));
+ rcNt = g_pfnNtQueryDirectoryFile(pdir->native,
+ NULL /*Event*/,
+ NULL /*ApcRoutine*/,
+ NULL /*ApcContext*/,
+ &Ios,
+ &pdir->buf[0],
+ sizeof(pdir->buf),
+ MY_FileNamesInformation,
+ FALSE /*ReturnSingleEntry*/,
+ NULL /*FileName*/,
+ pdir->off == ~(size_t)0 /*RestartScan*/);
+ if (rcNt >= 0 && rcNt != STATUS_PENDING)
+ {
+ pdir->cb = Ios.Information;
+ pdir->off = 0;
+ }
+ else if (rcNt == STATUS_NO_MORE_FILES)
+ errno = 0; /* wrong? */
+ else
+ shfile_nt2errno(rcNt);
+ }
+
+ if ( pdir->off != ~(size_t)0
+ && pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) <= pdir->cb)
+ {
+ PMY_FILE_NAMES_INFORMATION pcur = (PMY_FILE_NAMES_INFORMATION)&pdir->buf[pdir->off];
+ ANSI_STRING astr;
+ UNICODE_STRING ustr;
+
+ astr.Length = astr.MaximumLength = sizeof(pdir->ent.name);
+ astr.Buffer = &pdir->ent.name[0];
+
+ ustr.Length = ustr.MaximumLength = pcur->FileNameLength < ~(USHORT)0 ? (USHORT)pcur->FileNameLength : ~(USHORT)0;
+ ustr.Buffer = &pcur->FileName[0];
+
+ rcNt = g_pfnRtlUnicodeStringToAnsiString(&astr, &ustr, 0/*AllocateDestinationString*/);
+ if (rcNt < 0)
+ sprintf(pdir->ent.name, "conversion-failed-%08x-rcNt=%08x-len=%u",
+ pcur->FileIndex, rcNt, pcur->FileNameLength);
+ if (pcur->NextEntryOffset)
+ pdir->off += pcur->NextEntryOffset;
+ else
+ pdir->off = pdir->cb;
+ return &pdir->ent;
+ }
+ }
+ else
+ errno = EINVAL;
+ return NULL;
+#else
+ struct dirent *pde = readdir((DIR *)pdir);
+ return pde ? (shdirent *)&pde->d_name[0] : NULL;
+#endif
+}
+
+void shfile_closedir(struct shdir *pdir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ if (pdir)
+ {
+ CloseHandle(pdir->native);
+ pdir->pfdtab = NULL;
+ pdir->native = INVALID_HANDLE_VALUE;
+ sh_free(shthread_get_shell(), pdir);
+ }
+ else
+ errno = EINVAL;
+#else
+ closedir((DIR *)pdir);
+#endif
+}
+