source: trunk/src/kash/shfile.c@ 3466

Last change on this file since 3466 was 3466, checked in by bird, 5 years ago

kash: Introduced asynchronous closing of files we're written to on windows because it may take a while on NTFS. We only need to make sure async CloseHandle are done when executing or forking.

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
File size: 79.1 KB
Line 
1/* $Id: shfile.c 3466 2020-09-15 21:28:37Z bird $ */
2/** @file
3 *
4 * File management.
5 *
6 * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
7 *
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27/*******************************************************************************
28* Header Files *
29*******************************************************************************/
30#include "shfile.h"
31#include "shinstance.h" /* TRACE2 */
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <assert.h>
36
37#if K_OS == K_OS_WINDOWS
38# include <limits.h>
39# ifndef PIPE_BUF
40# define PIPE_BUF 512
41# endif
42# include <ntstatus.h>
43# define WIN32_NO_STATUS
44# include <Windows.h>
45# if !defined(_WIN32_WINNT)
46# define _WIN32_WINNT 0x0502 /* Windows Server 2003 */
47# endif
48# include <winternl.h> //NTSTATUS
49#else
50# include <unistd.h>
51# include <fcntl.h>
52# include <dirent.h>
53#endif
54
55
56/*******************************************************************************
57* Defined Constants And Macros *
58*******************************************************************************/
59/** @def SHFILE_IN_USE
60 * Whether the file descriptor table stuff is actually in use or not.
61 */
62#if K_OS == K_OS_WINDOWS \
63 || K_OS == K_OS_OPENBSD /* because of ugly pthread library pipe hacks */ \
64 || !defined(SH_FORKED_MODE)
65# define SHFILE_IN_USE
66#endif
67/** The max file table size. */
68#define SHFILE_MAX 1024
69/** The file table growth rate. */
70#define SHFILE_GROW 64
71/** The min native unix file descriptor. */
72#define SHFILE_UNIX_MIN_FD 32
73/** The path buffer size we use. */
74#define SHFILE_MAX_PATH 4096
75
76/** Set errno and return. Doing a trace in debug build. */
77#define RETURN_ERROR(rc, err, msg) \
78 do { \
79 TRACE2((NULL, "%s: " ## msg ## " - returning %d / %d\n", __FUNCTION__, (rc), (err))); \
80 errno = (err); \
81 return (rc); \
82 } while (0)
83
84#if K_OS == K_OS_WINDOWS
85 /* See msdos.h for description. */
86# define FOPEN 0x01
87# define FEOFLAG 0x02
88# define FCRLF 0x04
89# define FPIPE 0x08
90# define FNOINHERIT 0x10
91# define FAPPEND 0x20
92# define FDEV 0x40
93# define FTEXT 0x80
94
95# define MY_ObjectBasicInformation 0
96# define MY_FileNamesInformation 12
97
98typedef struct
99{
100 ULONG Attributes;
101 ACCESS_MASK GrantedAccess;
102 ULONG HandleCount;
103 ULONG PointerCount;
104 ULONG PagedPoolUsage;
105 ULONG NonPagedPoolUsage;
106 ULONG Reserved[3];
107 ULONG NameInformationLength;
108 ULONG TypeInformationLength;
109 ULONG SecurityDescriptorLength;
110 LARGE_INTEGER CreateTime;
111} MY_OBJECT_BASIC_INFORMATION;
112
113#if 0
114typedef struct
115{
116 union
117 {
118 LONG Status;
119 PVOID Pointer;
120 };
121 ULONG_PTR Information;
122} MY_IO_STATUS_BLOCK;
123#else
124typedef IO_STATUS_BLOCK MY_IO_STATUS_BLOCK;
125#endif
126typedef MY_IO_STATUS_BLOCK *PMY_IO_STATUS_BLOCK;
127
128typedef struct
129{
130 ULONG NextEntryOffset;
131 ULONG FileIndex;
132 ULONG FileNameLength;
133 WCHAR FileName[1];
134} MY_FILE_NAMES_INFORMATION, *PMY_FILE_NAMES_INFORMATION;
135
136typedef NTSTATUS (NTAPI * PFN_NtQueryObject)(HANDLE, int, void *, size_t, size_t *);
137typedef NTSTATUS (NTAPI * PFN_NtQueryDirectoryFile)(HANDLE, HANDLE, void *, void *, PMY_IO_STATUS_BLOCK, void *,
138 ULONG, int, int, PUNICODE_STRING, int);
139typedef NTSTATUS (NTAPI * PFN_RtlUnicodeStringToAnsiString)(PANSI_STRING, PCUNICODE_STRING, int);
140
141
142#endif /* K_OS_WINDOWS */
143
144
145/*********************************************************************************************************************************
146* Global Variables *
147*********************************************************************************************************************************/
148#if K_OS == K_OS_WINDOWS
149static int g_shfile_globals_initialized = 0;
150static PFN_NtQueryObject g_pfnNtQueryObject = NULL;
151static PFN_NtQueryDirectoryFile g_pfnNtQueryDirectoryFile = NULL;
152static PFN_RtlUnicodeStringToAnsiString g_pfnRtlUnicodeStringToAnsiString = NULL;
153# ifdef KASH_ASYNC_CLOSE_HANDLE
154/** Data for the asynchronous CloseHandle machinery. */
155static struct shfileasyncclose
156{
157 /** Mutex protecting the asynchronous CloseHandle stuff. */
158 shmtx mtx;
159 /** Handle to event that the closer-threads are waiting on. */
160 HANDLE evt_workers;
161 /** The ring buffer read index (for closer-threads). */
162 unsigned volatile idx_read;
163 /** The ring buffer write index (for shfile_native_close).
164 * When idx_read and idx_write are the same, the ring buffer is empty. */
165 unsigned volatile idx_write;
166 /** Number of handles currently being pending closure (handles + current
167 * CloseHandle calls). */
168 unsigned volatile num_pending;
169 /** Set if the threads should terminate. */
170 KBOOL volatile terminate_threads;
171 /** Set if one or more shell threads have requested evt_sync to be signalled
172 * when there are no more pending requests. */
173 KBOOL volatile signal_sync;
174 /** Number of threads that have been spawned. */
175 KU8 num_threads;
176 /** Handle to event that the shell threads are waiting on to sync. */
177 HANDLE evt_sync;
178 /** Ring buffer containing handles to be closed. */
179 HANDLE handles[32];
180 /** Worker threads doing the asynchronous closing. */
181 HANDLE threads[8];
182} g_shfile_async_close;
183# endif
184#endif /* K_OS_WINDOWS */
185
186
187/*********************************************************************************************************************************
188* Internal Functions *
189*********************************************************************************************************************************/
190#ifdef SHFILE_IN_USE
191# if K_OS == K_OS_WINDOWS
192static HANDLE shfile_set_inherit_win(shfile *pfd, int set);
193# endif
194#endif
195
196
197#ifdef SHFILE_IN_USE
198
199# ifdef DEBUG
200# if K_OS == K_OS_WINDOWS
201static KU64 shfile_nano_ts(void)
202{
203 static KBOOL volatile s_has_factor = K_FALSE;
204 static double volatile s_factor;
205 double factor;
206 LARGE_INTEGER now;
207 if (s_has_factor)
208 factor = s_factor;
209 else
210 {
211 QueryPerformanceFrequency(&now);
212 s_factor = factor = (double)1000000000.0 / now.QuadPart;
213 s_has_factor = K_TRUE;
214 }
215 QueryPerformanceCounter(&now);
216 return (KU64)(now.QuadPart * factor);
217}
218# endif /* K_OS_WINDOWS */
219# endif /* DEBUG */
220
221# if K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE)
222/**
223 * The closer thread function.
224 */
225static unsigned __stdcall shfile_async_close_handle_thread(void *ignored)
226{
227 KBOOL decrement_pending = K_FALSE;
228 while (!g_shfile_async_close.terminate_threads)
229 {
230 HANDLE toclose;
231 unsigned idx;
232 shmtxtmp tmp;
233
234 /*
235 * Grab a handle if there is one:
236 */
237 shmtx_enter(&g_shfile_async_close.mtx, &tmp);
238
239 if (decrement_pending)
240 {
241 assert(g_shfile_async_close.num_pending > 0);
242 g_shfile_async_close.num_pending -= 1;
243 }
244
245 idx = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
246 if (idx != g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles))
247 {
248 assert(g_shfile_async_close.num_pending > 0);
249 toclose = g_shfile_async_close.handles[idx];
250 assert(toclose);
251 g_shfile_async_close.handles[idx] = NULL;
252 g_shfile_async_close.idx_read = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
253 }
254 else
255 {
256 /* Signal waiters if requested and we've reached zero pending requests. */
257 if (g_shfile_async_close.signal_sync && g_shfile_async_close.num_pending == 0)
258 {
259 BOOL rc = SetEvent(g_shfile_async_close.evt_sync);
260 assert(rc);
261 g_shfile_async_close.signal_sync = FALSE;
262 }
263 toclose = NULL;
264 }
265 shmtx_leave(&g_shfile_async_close.mtx, &tmp);
266
267 /* Do the closing if we have something to close, otherwise wait for work to arrive. */
268 if (toclose != NULL)
269 {
270 BOOL rc = CloseHandle(toclose);
271 assert(rc);
272 decrement_pending = K_TRUE;
273 }
274 else
275 {
276 DWORD dwRet = WaitForSingleObject(g_shfile_async_close.evt_workers, 10000 /*ms*/);
277 assert(dwRet == WAIT_OBJECT_0 || dwRet == WAIT_TIMEOUT);
278 decrement_pending = K_FALSE;
279 }
280 }
281
282 K_NOREF(ignored);
283 return 0;
284}
285
286/**
287 * Try submit a handle for automatic closing.
288 *
289 * @returns K_TRUE if submitted successfully, K_FALSE if not.
290 */
291static KBOOL shfile_async_close_submit(HANDLE toclose)
292{
293 KBOOL ret;
294 unsigned idx;
295 unsigned idx_next;
296 unsigned idx_read;
297 shmtxtmp tmp;
298 shmtx_enter(&g_shfile_async_close.mtx, &tmp);
299
300 /* Get the write index and check that there is a free slot in the buffer: */
301 idx = g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles);
302 idx_next = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
303 idx_read = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
304 if (idx_next != idx_read)
305 {
306 unsigned num_pending, num_threads;
307
308 /* Write the handle to the ring buffer: */
309 assert(g_shfile_async_close.handles[idx] == NULL);
310 g_shfile_async_close.handles[idx] = toclose;
311 g_shfile_async_close.idx_write = idx_next;
312
313 num_pending = g_shfile_async_close.num_pending + 1;
314 g_shfile_async_close.num_pending = num_pending;
315
316 ret = SetEvent(g_shfile_async_close.evt_workers);
317 assert(ret);
318 if (ret)
319 {
320 /* If we have more pending requests than threads, create a new thread. */
321 num_threads = g_shfile_async_close.num_threads;
322 if (num_pending > num_threads && num_threads < K_ELEMENTS(g_shfile_async_close.threads))
323 {
324 unsigned tid = 0;
325 intptr_t hThread = _beginthreadex(NULL /*security*/, 0 /*stack_size*/, shfile_async_close_handle_thread,
326 NULL /*arg*/, 0 /*initflags*/, &tid);
327 assert(hThread != -1);
328 if (hThread != -1)
329 {
330 g_shfile_async_close.threads[num_threads] = (HANDLE)hThread;
331 g_shfile_async_close.num_threads = num_threads + 1;
332 }
333 else if (num_threads == 0)
334 ret = K_FALSE;
335 }
336 }
337
338 /* cleanup on failure. */
339 if (ret)
340 { /* likely */ }
341 else
342 {
343 g_shfile_async_close.handles[idx] = NULL;
344 g_shfile_async_close.idx_write = idx;
345 g_shfile_async_close.num_pending = num_pending - 1;
346 }
347 }
348 else
349 ret = K_FALSE;
350
351 shmtx_leave(&g_shfile_async_close.mtx, &tmp);
352 return ret;
353}
354
355/**
356 * Wait for all pending CloseHandle calls to complete.
357 */
358void shfile_async_close_sync(void)
359{
360 shmtxtmp tmp;
361 shmtx_enter(&g_shfile_async_close.mtx, &tmp);
362
363 if (g_shfile_async_close.num_pending > 0)
364 {
365 DWORD dwRet;
366
367/** @todo help out? */
368 if (!g_shfile_async_close.signal_sync)
369 {
370 BOOL rc = ResetEvent(g_shfile_async_close.evt_sync);
371 assert(rc); K_NOREF(rc);
372
373 g_shfile_async_close.signal_sync = K_TRUE;
374 }
375
376 shmtx_leave(&g_shfile_async_close.mtx, &tmp);
377
378 dwRet = WaitForSingleObject(g_shfile_async_close.evt_sync, 10000 /*ms*/);
379 assert(dwRet == WAIT_OBJECT_0);
380 assert(g_shfile_async_close.num_pending == 0);
381 }
382 else
383 shmtx_leave(&g_shfile_async_close.mtx, &tmp);
384}
385
386# endif /* K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE) */
387
388/**
389 * Close the specified native handle.
390 *
391 * @param native The native file handle.
392 * @param file The file table entry if available.
393 * @param inheritable The inheritability of the handle on windows, K_FALSE elsewhere.
394 */
395static void shfile_native_close(intptr_t native, shfile *file, KBOOL inheritable)
396{
397# if K_OS == K_OS_WINDOWS
398# ifdef KASH_ASYNC_CLOSE_HANDLE
399 /*
400 * CloseHandle may take several milliseconds on NTFS after we've appended
401 * a few bytes to a file. When a script uses lots of 'echo line-text >> file'
402 * we end up executing very slowly, even if 'echo' is builtin and the statement
403 * requires no subshells to be spawned.
404 *
405 * So, detect problematic handles and do CloseHandle asynchronously. When
406 * executing a child process, we probably will have to make sure the CloseHandle
407 * operation has completed before we let do ResumeThread on the child to make
408 * 100% sure we can't have any sharing conflicts. Sharing conflicts are not a
409 * problem for builtin commands.
410 *
411 * If child processes are spawned using handle inheritance and the handle in
412 * question is inheritable, we will have to fix the inheriability before pushing
413 * on the async-close queue. This shouldn't have the CloseHandle issues.
414 */
415 if ( file
416 && (file->shflags & (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_TYPE_MASK))
417 == (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_FILE))
418 {
419 if (inheritable)
420 native = (intptr_t)shfile_set_inherit_win(file, 0);
421 if (shfile_async_close_submit((HANDLE)native))
422 return;
423 }
424
425 /*
426 * Otherwise close it right here:
427 */
428# endif
429 {
430# ifdef DEBUG
431 KU64 ns = shfile_nano_ts();
432 BOOL fRc = CloseHandle((HANDLE)native);
433 assert(fRc); K_NOREF(fRc);
434 ns = shfile_nano_ts() - ns;
435 if (ns > 1000000)
436 TRACE2((NULL, "shfile_native_close: %u ns %p oflags=%#x %s\n",
437 ns, native, file ? file->oflags : 0, file ? file->dbgname : NULL));
438# else
439 BOOL fRc = CloseHandle((HANDLE)native);
440 assert(fRc); K_NOREF(fRc);
441# endif
442 }
443
444# else /* K_OS != K_OS_WINDOWS */
445 int s = errno;
446 close(native);
447 errno = s;
448# endif /* K_OS != K_OS_WINDOWS */
449 K_NOREF(file);
450}
451
452/**
453 * Grows the descriptor table, making sure that it can hold @a fdMin,
454 *
455 * @returns The max(fdMin, fdFirstNew) on success, -1 on failure.
456 * @param pfdtab The table to grow.
457 * @param fdMin Grow to include this index.
458 */
459static int shfile_grow_tab_locked(shfdtab *pfdtab, int fdMin)
460{
461 /*
462 * Grow the descriptor table.
463 */
464 int fdRet = -1;
465 shfile *new_tab;
466 int new_size = pfdtab->size + SHFILE_GROW;
467 while (new_size < fdMin)
468 new_size += SHFILE_GROW;
469 new_tab = sh_realloc(shthread_get_shell(), pfdtab->tab, new_size * sizeof(shfile));
470 if (new_tab)
471 {
472 int i;
473 for (i = pfdtab->size; i < new_size; i++)
474 {
475 new_tab[i].fd = -1;
476 new_tab[i].oflags = 0;
477 new_tab[i].shflags = 0;
478 new_tab[i].native = -1;
479# ifdef DEBUG
480 new_tab[i].dbgname = NULL;
481# endif
482 }
483
484 fdRet = pfdtab->size;
485 if (fdRet < fdMin)
486 fdRet = fdMin;
487
488 pfdtab->tab = new_tab;
489 pfdtab->size = new_size;
490 }
491
492 return fdRet;
493}
494
495/**
496 * Inserts the file into the descriptor table.
497 *
498 * If we're out of memory and cannot extend the table, we'll close the
499 * file, set errno to EMFILE and return -1.
500 *
501 * @returns The file descriptor number. -1 and errno on failure.
502 * @param pfdtab The file descriptor table.
503 * @param native The native file handle.
504 * @param oflags The flags the it was opened/created with.
505 * @param shflags The shell file flags.
506 * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
507 * @param who Who we're doing this for (for logging purposes).
508 * @param dbgname The filename, if applicable/available.
509 */
510static int shfile_insert(shfdtab *pfdtab, intptr_t native, unsigned oflags, unsigned shflags, int fdMin,
511 const char *who, const char *dbgname)
512{
513 shmtxtmp tmp;
514 int fd;
515 int i;
516
517 /*
518 * Fend of bad stuff.
519 */
520 if (fdMin >= SHFILE_MAX)
521 {
522 TRACE2((NULL, "shfile_insert: fdMin=%d is out of bounds; native=%p %s\n", fdMin, native, dbgname));
523 shfile_native_close(native, NULL, K_FALSE);
524 errno = EMFILE;
525 return -1;
526 }
527# if K_OS != K_OS_WINDOWS
528 if (fcntl((int)native, F_SETFD, fcntl((int)native, F_GETFD, 0) | FD_CLOEXEC) == -1)
529 {
530 int e = errno;
531 TRACE2((NULL, "shfile_insert: F_SETFD failed %d; native=%p %s\n", e, native, dbgname));
532 close((int)native);
533 errno = e;
534 return -1;
535 }
536# endif
537
538 shmtx_enter(&pfdtab->mtx, &tmp);
539
540 /*
541 * Search for a fitting unused location.
542 */
543 fd = -1;
544 for (i = fdMin >= 0 ? fdMin : 0; (unsigned)i < pfdtab->size; i++)
545 if (pfdtab->tab[i].fd == -1)
546 {
547 fd = i;
548 break;
549 }
550 if (fd == -1)
551 fd = shfile_grow_tab_locked(pfdtab, fdMin);
552
553 /*
554 * Fill in the entry if we've found one.
555 */
556 if (fd != -1)
557 {
558 pfdtab->tab[fd].fd = fd;
559 pfdtab->tab[fd].oflags = oflags;
560 pfdtab->tab[fd].shflags = shflags;
561 pfdtab->tab[fd].native = native;
562#ifdef DEBUG
563 pfdtab->tab[fd].dbgname = dbgname ? sh_strdup(NULL, dbgname) : NULL;
564#endif
565 TRACE2((NULL, "shfile_insert: #%d: native=%p oflags=%#x shflags=%#x %s\n", fd, native, oflags, shflags, dbgname));
566 }
567 else
568 shfile_native_close(native, NULL, K_FALSE);
569
570 shmtx_leave(&pfdtab->mtx, &tmp);
571
572 if (fd == -1)
573 errno = EMFILE;
574 (void)who;
575 return fd;
576}
577
578# if K_OS != K_OS_WINDOWS
579/**
580 * Makes a copy of the native file, closes the original, and inserts the copy
581 * into the descriptor table.
582 *
583 * If we're out of memory and cannot extend the table, we'll close the
584 * file, set errno to EMFILE and return -1.
585 *
586 * @returns The file descriptor number. -1 and errno on failure.
587 * @param pfdtab The file descriptor table.
588 * @param pnative The native file handle on input, -1 on output.
589 * @param oflags The flags the it was opened/created with.
590 * @param shflags The shell file flags.
591 * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
592 * @param who Who we're doing this for (for logging purposes).
593 * @param dbgname The filename, if applicable/available.
594 */
595static int shfile_copy_insert_and_close(shfdtab *pfdtab, int *pnative, unsigned oflags, unsigned shflags, int fdMin,
596 const char *who, const char *dbgname)
597{
598 int fd = -1;
599 int s = errno;
600 int native_copy = fcntl(*pnative, F_DUPFD, SHFILE_UNIX_MIN_FD);
601 close(*pnative);
602 *pnative = -1;
603 errno = s;
604
605 if (native_copy != -1)
606 fd = shfile_insert(pfdtab, native_copy, oflags, shflags, fdMin, who, dbgname);
607 return fd;
608}
609# endif /* !K_OS_WINDOWS */
610
611/**
612 * Gets a file descriptor and lock the file descriptor table.
613 *
614 * @returns Pointer to the file and table ownership on success,
615 * NULL and errno set to EBADF on failure.
616 * @param pfdtab The file descriptor table.
617 * @param fd The file descriptor number.
618 * @param ptmp See shmtx_enter.
619 */
620static shfile *shfile_get(shfdtab *pfdtab, int fd, shmtxtmp *ptmp)
621{
622 shfile *file = NULL;
623 if ( fd >= 0
624 && (unsigned)fd < pfdtab->size)
625 {
626 shmtx_enter(&pfdtab->mtx, ptmp);
627 if ((unsigned)fd < pfdtab->size
628 && pfdtab->tab[fd].fd != -1)
629 file = &pfdtab->tab[fd];
630 else
631 shmtx_leave(&pfdtab->mtx, ptmp);
632 }
633 if (!file)
634 errno = EBADF;
635 return file;
636}
637
638/**
639 * Puts back a file descriptor and releases the table ownership.
640 *
641 * @param pfdtab The file descriptor table.
642 * @param file The file.
643 * @param ptmp See shmtx_leave.
644 */
645static void shfile_put(shfdtab *pfdtab, shfile *file, shmtxtmp *ptmp)
646{
647 shmtx_leave(&pfdtab->mtx, ptmp);
648 assert(file);
649 (void)file;
650}
651
652/**
653 * Constructs a path from the current directory and the passed in path.
654 *
655 * @returns 0 on success, -1 and errno set to ENAMETOOLONG or EINVAL on failure.
656 *
657 * @param pfdtab The file descriptor table
658 * @param path The path the caller supplied.
659 * @param buf Where to put the path. This is assumed to be SHFILE_MAX_PATH
660 * chars in size.
661 */
662int shfile_make_path(shfdtab *pfdtab, const char *path, char *buf)
663{
664 size_t path_len = strlen(path);
665 if (path_len == 0)
666 {
667 errno = EINVAL;
668 return -1;
669 }
670 if (path_len >= SHFILE_MAX_PATH)
671 {
672 errno = ENAMETOOLONG;
673 return -1;
674 }
675 if ( *path == '/'
676# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
677 || *path == '\\'
678 || ( *path
679 && path[1] == ':'
680 && ( (*path >= 'A' && *path <= 'Z')
681 || (*path >= 'a' && *path <= 'z')))
682# endif
683 )
684 {
685 memcpy(buf, path, path_len + 1);
686 }
687 else
688 {
689 size_t cwd_len;
690 shmtxtmp tmp;
691
692 shmtx_enter(&pfdtab->mtx, &tmp);
693
694 cwd_len = strlen(pfdtab->cwd);
695 memcpy(buf, pfdtab->cwd, cwd_len);
696
697 shmtx_leave(&pfdtab->mtx, &tmp);
698
699 if (cwd_len + path_len + 1 >= SHFILE_MAX_PATH)
700 {
701 errno = ENAMETOOLONG;
702 return -1;
703 }
704 if ( !cwd_len
705 || buf[cwd_len - 1] != '/')
706 buf[cwd_len++] = '/';
707 memcpy(buf + cwd_len, path, path_len + 1);
708 }
709
710# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
711 if (!strcmp(buf, "/dev/null"))
712 strcpy(buf, "NUL");
713# endif
714 return 0;
715}
716
717# if K_OS == K_OS_WINDOWS
718
719/**
720 * Adjusts the file name if it ends with a trailing directory slash.
721 *
722 * Windows APIs doesn't like trailing slashes.
723 *
724 * @returns 1 if it has a directory slash, 0 if not.
725 *
726 * @param abspath The path to adjust (SHFILE_MAX_PATH).
727 */
728static int shfile_trailing_slash_hack(char *abspath)
729{
730 /*
731 * Anything worth adjust here?
732 */
733 size_t path_len = strlen(abspath);
734 if ( path_len == 0
735 || ( abspath[path_len - 1] != '/'
736# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
737 && abspath[path_len - 1] != '\\'
738# endif
739 )
740 )
741 return 0;
742
743 /*
744 * Ok, make the adjustment.
745 */
746 if (path_len + 2 <= SHFILE_MAX_PATH)
747 {
748 /* Add a '.' to the end. */
749 abspath[path_len++] = '.';
750 abspath[path_len] = '\0';
751 }
752 else
753 {
754 /* No space for a dot, remove the slash if it's alone or just remove
755 one and add a dot like above. */
756 if ( abspath[path_len - 2] != '/'
757# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
758 && abspath[path_len - 2] != '\\'
759# endif
760 )
761 abspath[--path_len] = '\0';
762 else
763 abspath[path_len - 1] = '.';
764 }
765
766 return 1;
767}
768
769
770/**
771 * Converts a DOS(/Windows) error code to errno,
772 * assigning it to errno.
773 *
774 * @returns -1
775 * @param err The DOS error.
776 */
777static int shfile_dos2errno(int err)
778{
779 switch (err)
780 {
781 case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break;
782 case ERROR_ACCESS_DENIED: errno = EACCES; break;
783 case ERROR_CURRENT_DIRECTORY: errno = EACCES; break;
784 case ERROR_LOCK_VIOLATION: errno = EACCES; break;
785 case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break;
786 case ERROR_CANNOT_MAKE: errno = EACCES; break;
787 case ERROR_FAIL_I24: errno = EACCES; break;
788 case ERROR_DRIVE_LOCKED: errno = EACCES; break;
789 case ERROR_SEEK_ON_DEVICE: errno = EACCES; break;
790 case ERROR_NOT_LOCKED: errno = EACCES; break;
791 case ERROR_LOCK_FAILED: errno = EACCES; break;
792 case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break;
793 case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break;
794 case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break;
795 case ERROR_INVALID_HANDLE: errno = EBADF; break;
796 case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break;
797 case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break;
798 case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break;
799 case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break;
800 case ERROR_FILE_EXISTS: errno = EEXIST; break;
801 case ERROR_ALREADY_EXISTS: errno = EEXIST; break;
802 case ERROR_INVALID_FUNCTION: errno = EINVAL; break;
803 case ERROR_INVALID_ACCESS: errno = EINVAL; break;
804 case ERROR_INVALID_DATA: errno = EINVAL; break;
805 case ERROR_INVALID_PARAMETER: errno = EINVAL; break;
806 case ERROR_NEGATIVE_SEEK: errno = EINVAL; break;
807 case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break;
808 case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
809 case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
810 case ERROR_INVALID_DRIVE: errno = ENOENT; break;
811 case ERROR_NO_MORE_FILES: errno = ENOENT; break;
812 case ERROR_BAD_NETPATH: errno = ENOENT; break;
813 case ERROR_BAD_NET_NAME: errno = ENOENT; break;
814 case ERROR_BAD_PATHNAME: errno = ENOENT; break;
815 case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break;
816 case ERROR_BAD_FORMAT: errno = ENOEXEC; break;
817 case ERROR_ARENA_TRASHED: errno = ENOMEM; break;
818 case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break;
819 case ERROR_INVALID_BLOCK: errno = ENOMEM; break;
820 case ERROR_NOT_ENOUGH_QUOTA: errno = ENOMEM; break;
821 case ERROR_DISK_FULL: errno = ENOSPC; break;
822 case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break;
823 case ERROR_BROKEN_PIPE: errno = EPIPE; break;
824 case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break;
825 default: errno = EINVAL; break;
826 }
827 return -1;
828}
829
830/**
831 * Converts an NT status code to errno,
832 * assigning it to errno.
833 *
834 * @returns -1
835 * @param rcNt The NT status code.
836 */
837static int shfile_nt2errno(NTSTATUS rcNt)
838{
839 switch (rcNt)
840 {
841 default: errno = EINVAL; break;
842 }
843 return -1;
844}
845
846DWORD shfile_query_handle_access_mask(HANDLE h, PACCESS_MASK pMask)
847{
848 MY_OBJECT_BASIC_INFORMATION BasicInfo;
849 NTSTATUS rcNt;
850
851 if (!g_pfnNtQueryObject)
852 return ERROR_NOT_SUPPORTED;
853
854 rcNt = g_pfnNtQueryObject(h, MY_ObjectBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
855 if (rcNt >= 0)
856 {
857 *pMask = BasicInfo.GrantedAccess;
858 return NO_ERROR;
859 }
860 if (rcNt != STATUS_INVALID_HANDLE)
861 return ERROR_GEN_FAILURE;
862 return ERROR_INVALID_HANDLE;
863}
864
865# endif /* K_OS == K_OS_WINDOWS */
866
867#endif /* SHFILE_IN_USE */
868
869/**
870 * Converts DOS slashes to UNIX slashes if necessary.
871 *
872 * @param pszPath The path to fix.
873 */
874K_INLINE void shfile_fix_slashes(char *pszPath)
875{
876#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
877 while ((pszPath = strchr(pszPath, '\\')))
878 *pszPath++ = '/';
879#else
880 (void)pszPath;
881#endif
882}
883
884/**
885 * Initializes the global variables in this file.
886 */
887static void shfile_init_globals(void)
888{
889#if K_OS == K_OS_WINDOWS
890 if (!g_shfile_globals_initialized)
891 {
892 HMODULE hNtDll = GetModuleHandle("NTDLL");
893 g_pfnNtQueryObject = (PFN_NtQueryObject) GetProcAddress(hNtDll, "NtQueryObject");
894 g_pfnNtQueryDirectoryFile = (PFN_NtQueryDirectoryFile)GetProcAddress(hNtDll, "NtQueryDirectoryFile");
895 g_pfnRtlUnicodeStringToAnsiString = (PFN_RtlUnicodeStringToAnsiString)GetProcAddress(hNtDll, "RtlUnicodeStringToAnsiString");
896 if ( !g_pfnRtlUnicodeStringToAnsiString
897 || !g_pfnNtQueryDirectoryFile)
898 {
899 /* fatal error */
900 }
901
902# ifdef KASH_ASYNC_CLOSE_HANDLE
903 /*
904 * Init the async CloseHandle state.
905 */
906 shmtx_init(&g_shfile_async_close.mtx);
907 g_shfile_async_close.evt_workers = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
908 g_shfile_async_close.evt_sync = CreateEventW(NULL, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
909 if ( !g_shfile_async_close.evt_workers
910 || !g_shfile_async_close.evt_sync)
911 {
912 fprintf(stderr, "fatal error: CreateEventW failed: %u\n", GetLastError());
913 _exit(19);
914 }
915 g_shfile_async_close.idx_read = 0;
916 g_shfile_async_close.idx_write = 0;
917 g_shfile_async_close.num_pending = 0;
918 g_shfile_async_close.terminate_threads = K_FALSE;
919 g_shfile_async_close.signal_sync = K_FALSE;
920 g_shfile_async_close.num_threads = 0;
921 {
922 unsigned i = K_ELEMENTS(g_shfile_async_close.handles);
923 while (i-- > 0)
924 g_shfile_async_close.handles[i] = NULL;
925 i = K_ELEMENTS(g_shfile_async_close.threads);
926 while (i-- > 0)
927 g_shfile_async_close.threads[i] = NULL;
928 }
929# endif
930
931 g_shfile_globals_initialized = 1;
932 }
933#endif
934}
935
936/**
937 * Initializes a file descriptor table.
938 *
939 * @returns 0 on success, -1 and errno on failure.
940 * @param pfdtab The table to initialize.
941 * @param inherit File descriptor table to inherit from. If not specified
942 * we will inherit from the current process as it were.
943 */
944int shfile_init(shfdtab *pfdtab, shfdtab *inherit)
945{
946 int rc;
947
948 shfile_init_globals();
949
950 pfdtab->cwd = NULL;
951 pfdtab->size = 0;
952 pfdtab->tab = NULL;
953 rc = shmtx_init(&pfdtab->mtx);
954 if (!rc)
955 {
956#ifdef SHFILE_IN_USE
957 /* Get CWD with unix slashes. */
958 if (!inherit)
959 {
960 char buf[SHFILE_MAX_PATH];
961 if (getcwd(buf, sizeof(buf)))
962 {
963 shfile_fix_slashes(buf);
964 pfdtab->cwd = sh_strdup(NULL, buf);
965 }
966 if (pfdtab->cwd)
967 {
968# if K_OS == K_OS_WINDOWS
969 static const struct
970 {
971 DWORD dwStdHandle;
972 unsigned fFlags;
973 } aStdHandles[3] =
974 {
975 { STD_INPUT_HANDLE, _O_RDONLY },
976 { STD_OUTPUT_HANDLE, _O_WRONLY },
977 { STD_ERROR_HANDLE, _O_WRONLY }
978 };
979 int i;
980 STARTUPINFO Info;
981 ACCESS_MASK Mask;
982 DWORD dwErr;
983
984 rc = 0;
985
986 /* Try pick up the Visual C++ CRT file descriptor info. */
987 __try {
988 GetStartupInfo(&Info);
989 } __except (EXCEPTION_EXECUTE_HANDLER) {
990 memset(&Info, 0, sizeof(Info));
991 }
992
993 if ( Info.cbReserved2 > sizeof(int)
994 && (uintptr_t)Info.lpReserved2 >= 0x1000
995 && (i = *(int *)Info.lpReserved2) >= 1
996 && i <= 2048
997 && ( Info.cbReserved2 == i * 5 + 4
998 //|| Info.cbReserved2 == i * 5 + 1 - check the cygwin sources.
999 || Info.cbReserved2 == i * 9 + 4))
1000 {
1001 uint8_t *paf = (uint8_t *)Info.lpReserved2 + sizeof(int);
1002 int dwPerH = 1 + (Info.cbReserved2 == i * 9 + 4);
1003 DWORD *ph = (DWORD *)(paf + i) + dwPerH * i;
1004 HANDLE aStdHandles2[3];
1005 int j;
1006
1007 //if (Info.cbReserved2 == i * 5 + 1) - check the cygwin sources.
1008 // i--;
1009
1010 for (j = 0; j < 3; j++)
1011 aStdHandles2[j] = GetStdHandle(aStdHandles[j].dwStdHandle);
1012
1013 while (i-- > 0)
1014 {
1015 ph -= dwPerH;
1016
1017 if ( (paf[i] & (FOPEN | FNOINHERIT)) == FOPEN
1018 && *ph != (uint32_t)INVALID_HANDLE_VALUE)
1019 {
1020 HANDLE h = (HANDLE)(intptr_t)*ph;
1021 int fd2;
1022 int fFlags;
1023 int fFlags2;
1024
1025 if ( h == aStdHandles2[j = 0]
1026 || h == aStdHandles2[j = 1]
1027 || h == aStdHandles2[j = 2])
1028 fFlags = aStdHandles[j].fFlags;
1029 else
1030 {
1031 dwErr = shfile_query_handle_access_mask(h, &Mask);
1032 if (dwErr == ERROR_INVALID_HANDLE)
1033 continue;
1034 else if (dwErr == NO_ERROR)
1035 {
1036 fFlags = 0;
1037 if ( (Mask & (GENERIC_READ | FILE_READ_DATA))
1038 && (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA)))
1039 fFlags |= O_RDWR;
1040 else if (Mask & (GENERIC_READ | FILE_READ_DATA))
1041 fFlags |= O_RDONLY;
1042 else if (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA))
1043 fFlags |= O_WRONLY;
1044 else
1045 fFlags |= O_RDWR;
1046 if ((Mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) == FILE_APPEND_DATA)
1047 fFlags |= O_APPEND;
1048 }
1049 else
1050 fFlags = O_RDWR;
1051 }
1052
1053 if (paf[i] & FPIPE)
1054 fFlags2 = SHFILE_FLAGS_PIPE;
1055 else if (paf[i] & FDEV)
1056 fFlags2 = SHFILE_FLAGS_TTY;
1057 else
1058 fFlags2 = 0;
1059
1060 fd2 = shfile_insert(pfdtab, (intptr_t)h, fFlags, fFlags2, i, "shtab_init", NULL);
1061 assert(fd2 == i); (void)fd2;
1062 if (fd2 != i)
1063 rc = -1;
1064 }
1065 }
1066 }
1067
1068 /* Check the three standard handles. */
1069 for (i = 0; i < 3; i++)
1070 if ( (unsigned)i >= pfdtab->size
1071 || pfdtab->tab[i].fd == -1)
1072 {
1073 HANDLE hFile = GetStdHandle(aStdHandles[i].dwStdHandle);
1074 if (hFile != INVALID_HANDLE_VALUE)
1075 {
1076 DWORD dwType = GetFileType(hFile);
1077 unsigned fFlags = aStdHandles[i].fFlags;
1078 unsigned fFlags2;
1079 int fd2;
1080 if (dwType == FILE_TYPE_CHAR)
1081 fFlags2 = SHFILE_FLAGS_TTY;
1082 else if (dwType == FILE_TYPE_PIPE)
1083 fFlags2 = SHFILE_FLAGS_PIPE;
1084 else
1085 fFlags2 = SHFILE_FLAGS_FILE;
1086 fd2 = shfile_insert(pfdtab, (intptr_t)hFile, fFlags, fFlags2, i, "shtab_init", NULL);
1087 assert(fd2 == i); (void)fd2;
1088 if (fd2 != i)
1089 rc = -1;
1090 }
1091 }
1092# else
1093 /*
1094 * Annoying...
1095 */
1096 int fd;
1097
1098 for (fd = 0; fd < 10; fd++)
1099 {
1100 int oflags = fcntl(fd, F_GETFL, 0);
1101 if (oflags != -1)
1102 {
1103 int cox = fcntl(fd, F_GETFD, 0);
1104 struct stat st;
1105 if ( cox != -1
1106 && fstat(fd, &st) != -1)
1107 {
1108 int native;
1109 int fd2;
1110 int fFlags2 = 0;
1111 if (cox & FD_CLOEXEC)
1112 fFlags2 |= SHFILE_FLAGS_CLOSE_ON_EXEC;
1113 if (S_ISREG(st.st_mode))
1114 fFlags2 |= SHFILE_FLAGS_FILE;
1115 else if (S_ISDIR(st.st_mode))
1116 fFlags2 |= SHFILE_FLAGS_DIR;
1117 else if (S_ISCHR(st.st_mode))
1118 fFlags2 |= SHFILE_FLAGS_TTY;
1119 else if (S_ISFIFO(st.st_mode))
1120 fFlags2 |= SHFILE_FLAGS_PIPE;
1121 else
1122 fFlags2 |= SHFILE_FLAGS_TTY;
1123
1124 native = fcntl(fd, F_DUPFD, SHFILE_UNIX_MIN_FD);
1125 if (native == -1)
1126 native = fd;
1127 fd2 = shfile_insert(pfdtab, native, oflags, fFlags2, fd, "shtab_init", NULL);
1128 assert(fd2 == fd); (void)fd2;
1129 if (fd2 != fd)
1130 rc = -1;
1131 if (native != fd)
1132 close(fd);
1133 }
1134 }
1135 }
1136
1137# endif
1138 }
1139 else
1140 rc = -1;
1141 }
1142 else
1143 {
1144 /*
1145 * Inherit from parent shell's file table.
1146 */
1147 shfile const *src;
1148 shfile *dst;
1149 shmtxtmp tmp;
1150 unsigned fdcount;
1151 unsigned fd;
1152
1153 shmtx_enter(&inherit->mtx, &tmp);
1154
1155 /* allocate table and cwd: */
1156 fdcount = inherit->size;
1157 pfdtab->tab = dst = (shfile *)(fdcount ? sh_calloc(NULL, sizeof(pfdtab->tab[0]), fdcount) : NULL);
1158 pfdtab->cwd = sh_strdup(NULL, inherit->cwd);
1159 if ( pfdtab->cwd
1160 && (pfdtab->tab || fdcount == 0))
1161 {
1162 /* duplicate table entries: */
1163 for (fd = 0, src = inherit->tab; fd < fdcount; fd++, src++, dst++)
1164 if (src->fd == -1)
1165 dst->native = dst->fd = -1;
1166 else
1167 {
1168# if K_OS == K_OS_WINDOWS
1169# ifdef SH_FORKED_MODE
1170 KBOOL const cox = !!(src->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC);
1171# else
1172 KBOOL const cox = K_TRUE;
1173# endif
1174# endif
1175 *dst = *src;
1176# ifdef DEBUG
1177 if (src->dbgname)
1178 dst->dbgname = sh_strdup(NULL, src->dbgname);
1179# endif
1180# if K_OS == K_OS_WINDOWS
1181 if (DuplicateHandle(GetCurrentProcess(),
1182 (HANDLE)src->native,
1183 GetCurrentProcess(),
1184 (HANDLE *)&dst->native,
1185 0,
1186 FALSE /* bInheritHandle */,
1187 DUPLICATE_SAME_ACCESS))
1188 TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p (was %p)\n",
1189 dst->fd, dst->oflags, dst->shflags, dst->native, src->native));
1190 else
1191 {
1192 dst->native = (intptr_t)INVALID_HANDLE_VALUE;
1193 rc = shfile_dos2errno(GetLastError());
1194 TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p - failed %d / %u!\n",
1195 dst->fd, dst->oflags, dst->shflags, src->native, rc, GetLastError()));
1196 break;
1197 }
1198
1199# 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 */
1200 dst->native = dup3(src->native, -1, cox ? O_CLOEXEC : 0);
1201# else
1202 if (cox)
1203 {
1204# ifndef SH_FORKED_MODE
1205 shmtxtmp tmp2;
1206 shmtx_enter(&global_exec_something, &tmp)
1207# endif
1208 dst->native = dup2(src->native, -1);
1209 if (dst->native >= 0)
1210 rc = fcntl(dst->native, F_SETFD, FD_CLOEXEC);
1211# ifndef SH_FORKED_MODE
1212 shmtx_leave(&global_exec_something, &tmp)
1213# endif
1214 if (rc != 0)
1215 break;
1216 }
1217 else
1218 dst->native = dup2(src->native, -1);
1219 if (dst->native < 0)
1220 {
1221 rc = -1;
1222 break;
1223 }
1224# endif
1225 }
1226 }
1227 else
1228 rc = -1;
1229 pfdtab->size = fd;
1230 shmtx_leave(&inherit->mtx, &tmp);
1231 } /* inherit != NULL */
1232#endif
1233 }
1234 return rc;
1235}
1236
1237/**
1238 * Deletes the file descriptor table.
1239 *
1240 * Safe to call more than once.
1241 */
1242void shfile_uninit(shfdtab *pfdtab, int tracefd)
1243{
1244 if (!pfdtab)
1245 return;
1246
1247 if (pfdtab->tab)
1248 {
1249 unsigned left = pfdtab->size;
1250 struct shfile *pfd = pfdtab->tab;
1251 unsigned tracefdfound = 0;
1252 while (left-- > 0)
1253 {
1254 if (pfd->fd != -1)
1255 {
1256 if (pfd->fd != tracefd)
1257 {
1258#if K_OS == K_OS_WINDOWS
1259 BOOL rc = CloseHandle((HANDLE)pfd->native);
1260 assert(rc == TRUE); K_NOREF(rc);
1261#else
1262 int rc = close((int)pfd->native);
1263 assert(rc == 0); K_NOREF(rc);
1264#endif
1265 pfd->fd = -1;
1266 pfd->native = -1;
1267 }
1268 else
1269 tracefdfound++; /* there is only the one */
1270 }
1271 pfd++;
1272 }
1273
1274 if (!tracefdfound)
1275 { /* likely */ }
1276 else
1277 return;
1278
1279 sh_free(NULL, pfdtab->tab);
1280 pfdtab->tab = NULL;
1281 }
1282
1283 shmtx_delete(&pfdtab->mtx);
1284
1285 sh_free(NULL, pfdtab->cwd);
1286 pfdtab->cwd = NULL;
1287}
1288
1289#if K_OS == K_OS_WINDOWS && defined(SHFILE_IN_USE)
1290
1291/**
1292 * Changes the inheritability of a file descriptor, taking console handles into
1293 * account.
1294 *
1295 * @note This MAY change the native handle for the entry.
1296 *
1297 * @returns The native handle.
1298 * @param pfd The file descriptor to change.
1299 * @param set If set, make child processes inherit the handle, if clear
1300 * make them not inherit it.
1301 */
1302static HANDLE shfile_set_inherit_win(shfile *pfd, int set)
1303{
1304 HANDLE hFile = (HANDLE)pfd->native;
1305 if (!SetHandleInformation(hFile, HANDLE_FLAG_INHERIT, set ? HANDLE_FLAG_INHERIT : 0))
1306 {
1307 /* SetHandleInformation doesn't work for console handles,
1308 so we have to duplicate the handle to change the
1309 inheritability. */
1310 DWORD err = GetLastError();
1311 if ( err == ERROR_INVALID_PARAMETER
1312 && DuplicateHandle(GetCurrentProcess(),
1313 hFile,
1314 GetCurrentProcess(),
1315 &hFile,
1316 0,
1317 set ? TRUE : FALSE /* bInheritHandle */,
1318 DUPLICATE_SAME_ACCESS))
1319 {
1320 TRACE2((NULL, "shfile_set_inherit_win: %p -> %p (set=%d)\n", pfd->native, hFile, set));
1321 if (!CloseHandle((HANDLE)pfd->native))
1322 assert(0);
1323 pfd->native = (intptr_t)hFile;
1324 }
1325 else
1326 {
1327 err = GetLastError();
1328 assert(0);
1329 hFile = (HANDLE)pfd->native;
1330 }
1331 }
1332 return hFile;
1333}
1334
1335# ifdef SH_FORKED_MODE
1336/**
1337 * Helper for shfork.
1338 *
1339 * @param pfdtab The file descriptor table.
1340 * @param set Whether to make all handles inheritable (1) or
1341 * to restore them to the rigth state (0).
1342 * @param hndls Where to store the three standard handles.
1343 */
1344void shfile_fork_win(shfdtab *pfdtab, int set, intptr_t *hndls)
1345{
1346 shmtxtmp tmp;
1347 unsigned i;
1348
1349 shmtx_enter(&pfdtab->mtx, &tmp);
1350 TRACE2((NULL, "shfile_fork_win: set=%d\n", set));
1351
1352 i = pfdtab->size;
1353 while (i-- > 0)
1354 {
1355 if (pfdtab->tab[i].fd == i)
1356 {
1357 shfile_set_inherit_win(&pfdtab->tab[i], set);
1358 if (set)
1359 TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
1360 i, pfdtab->tab[i].native, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
1361 }
1362 }
1363
1364 if (hndls)
1365 {
1366 for (i = 0; i < 3; i++)
1367 {
1368 if ( pfdtab->size > i
1369 && pfdtab->tab[i].fd == i)
1370 hndls[i] = pfdtab->tab[i].native;
1371 else
1372 hndls[i] = (intptr_t)INVALID_HANDLE_VALUE;
1373 TRACE2((NULL, "shfile_fork_win: i=%d size=%d fd=%d native=%d hndls[%d]=%p\n",
1374 i, pfdtab->size, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, hndls[i]));
1375 }
1376 }
1377
1378 shmtx_leave(&pfdtab->mtx, &tmp);
1379}
1380# endif /* SH_FORKED_MODE */
1381
1382/** shfile_exec_win helper that make sure there are no _O_APPEND handles. */
1383static KBOOL shfile_exec_win_no_append(shfdtab *pfdtab, unsigned count)
1384{
1385 unsigned i;
1386 for (i = 0; i < count; i++)
1387 if ( (pfdtab->tab[i].oflags & _O_APPEND)
1388 && (pfdtab->tab[i].oflags & (_O_WRONLY | _O_RDWR)))
1389 return K_FALSE;
1390 return K_TRUE;
1391}
1392
1393/**
1394 * Helper for sh_execve.
1395 *
1396 * This is called before and after CreateProcess. On the first call it
1397 * will mark the non-close-on-exec handles as inheritable and produce
1398 * the startup info for the CRT. On the second call, after CreateProcess,
1399 * it will restore the handle inheritability properties.
1400 *
1401 * @returns 0 on success, non-zero on failure.
1402 * @param pfdtab The file descriptor table.
1403 * @param prepare Which call, 1 if before, 0 if after and success, -1 if after on failure.
1404 * @param info The info structure.
1405 */
1406int shfile_exec_win(shfdtab *pfdtab, int prepare, shfdexecwin *info)
1407{
1408 STARTUPINFOA *strtinfo = (STARTUPINFOA *)info->strtinfo;
1409 int rc = 0;
1410 shmtxtmp tmp;
1411 unsigned count;
1412 unsigned i;
1413
1414 shmtx_enter(&pfdtab->mtx, &tmp);
1415 TRACE2((NULL, "shfile_exec_win: prepare=%p\n", prepare));
1416
1417 count = pfdtab->size < (0x10000-4) / (1 + sizeof(HANDLE))
1418 ? pfdtab->size
1419 : (0x10000-4) / (1 + sizeof(HANDLE));
1420 while ( count > 3
1421 && ( pfdtab->tab[count - 1].fd == -1
1422 || (pfdtab->tab[count - 1].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC)))
1423 count--;
1424
1425 if (prepare > 0)
1426 {
1427 if (count <= 3 && shfile_exec_win_no_append(pfdtab, count))
1428 {
1429 info->inherithandles = 0;
1430 info->startsuspended = 1;
1431 strtinfo->cbReserved2 = 0;
1432 strtinfo->lpReserved2 = NULL;
1433 }
1434 else
1435 {
1436 size_t cbData = sizeof(int) + count * (1 + sizeof(HANDLE));
1437 uint8_t *pbData = sh_malloc(shthread_get_shell(), cbData);
1438 uint8_t *paf = pbData + sizeof(int);
1439 HANDLE *pah = (HANDLE *)(paf + count);
1440
1441 info->inherithandles = 1;
1442# ifdef KASH_ASYNC_CLOSE_HANDLE
1443 info->startsuspended = g_shfile_async_close.num_pending > 0;
1444# else
1445 info->startsuspended = 0;
1446# endif
1447 strtinfo->cbReserved2 = (unsigned short)cbData;
1448 strtinfo->lpReserved2 = pbData;
1449
1450# ifndef SH_FORKED_MODE
1451 shmtx_leave(&pfdtab->mtx, &tmp); /* should be harmless as this isn't really necessary at all. */
1452 shmtx_enter(&g_sh_exec_inherit_mtx, &info->tmp);
1453 shmtx_enter(&pfdtab->mtx, &tmp);
1454# endif
1455
1456 *(int *)pbData = count;
1457
1458 i = count;
1459 while (i-- > 0)
1460 {
1461 if ( pfdtab->tab[i].fd == i
1462 && !(pfdtab->tab[i].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
1463 {
1464 HANDLE hFile = shfile_set_inherit_win(&pfdtab->tab[i], 1);
1465 TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
1466 i, hFile, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
1467 paf[i] = FOPEN;
1468 if (pfdtab->tab[i].oflags & _O_APPEND)
1469 paf[i] |= FAPPEND;
1470 if (pfdtab->tab[i].oflags & _O_TEXT)
1471 paf[i] |= FTEXT;
1472 switch (pfdtab->tab[i].shflags & SHFILE_FLAGS_TYPE_MASK)
1473 {
1474 case SHFILE_FLAGS_TTY: paf[i] |= FDEV; break;
1475 case SHFILE_FLAGS_PIPE: paf[i] |= FPIPE; break;
1476 }
1477 pah[i] = hFile;
1478 }
1479 else
1480 {
1481 paf[i] = 0;
1482 pah[i] = INVALID_HANDLE_VALUE;
1483 }
1484 }
1485 }
1486
1487 for (i = 0; i < 3; i++)
1488 {
1489 if ( i < count
1490 && pfdtab->tab[i].fd == i)
1491 {
1492 info->replacehandles[i] = 1;
1493 info->handles[i] = pfdtab->tab[i].native;
1494 }
1495 else
1496 {
1497 info->replacehandles[i] = 0;
1498 info->handles[i] = (intptr_t)INVALID_HANDLE_VALUE;
1499 }
1500 TRACE2((NULL, "shfile_exec_win: i=%d count=%d fd=%d native=%d hndls[%d]=\n",
1501 i, count, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, info->handles[i]));
1502 }
1503 }
1504 else
1505 {
1506 shfile *file = pfdtab->tab;
1507
1508 sh_free(NULL, strtinfo->lpReserved2);
1509 strtinfo->lpReserved2 = NULL;
1510
1511 i = count;
1512 if (prepare == 0)
1513 for (i = 0; i < count; i++, file++)
1514 {
1515 if ( file->fd == i
1516 && !(file->shflags & SHFILE_FLAGS_TRACE))
1517 {
1518 shfile_native_close(file->native, file, info->inherithandles);
1519
1520 file->fd = -1;
1521 file->oflags = 0;
1522 file->shflags = 0;
1523 file->native = -1;
1524# ifdef DEBUG
1525 sh_free(NULL, file->dbgname);
1526 file->dbgname = NULL;
1527# endif
1528 }
1529 }
1530 else if (info->inherithandles)
1531 for (i = 0; i < count; i++, file++)
1532 if ( file->fd == i
1533 && !(file->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
1534 shfile_set_inherit_win(file, 0);
1535
1536# ifndef SH_FORKED_MODE
1537 if (info->inherithandles)
1538 shmtx_leave(&g_sh_exec_inherit_mtx, &info->tmp);
1539# endif
1540 }
1541
1542 shmtx_leave(&pfdtab->mtx, &tmp);
1543 return rc;
1544}
1545
1546#endif /* K_OS_WINDOWS */
1547
1548#if K_OS != K_OS_WINDOWS
1549/**
1550 * Prepare file handles for inherting before a execve call.
1551 *
1552 * This is only used in the normal mode, so we've forked and need not worry
1553 * about cleaning anything up after us. Nor do we need think about locking.
1554 *
1555 * @returns 0 on success, -1 on failure.
1556 */
1557int shfile_exec_unix(shfdtab *pfdtab)
1558{
1559 int rc = 0;
1560# ifdef SHFILE_IN_USE
1561 unsigned fd;
1562
1563 for (fd = 0; fd < pfdtab->size; fd++)
1564 {
1565 if ( pfdtab->tab[fd].fd != -1
1566 && !(pfdtab->tab[fd].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC) )
1567 {
1568 TRACE2((NULL, "shfile_exec_unix: %d => %d\n", pfdtab->tab[fd].native, fd));
1569 if (dup2(pfdtab->tab[fd].native, fd) < 0)
1570 {
1571 /* fatal_error(NULL, "shfile_exec_unix: failed to move %d to %d", pfdtab->tab[fd].fd, fd); */
1572 rc = -1;
1573 }
1574 }
1575 }
1576# endif
1577 return rc;
1578}
1579#endif /* !K_OS_WINDOWS */
1580
1581/**
1582 * open().
1583 */
1584int shfile_open(shfdtab *pfdtab, const char *name, unsigned flags, mode_t mode)
1585{
1586 int fd;
1587#ifdef SHFILE_IN_USE
1588 char absname[SHFILE_MAX_PATH];
1589# if K_OS == K_OS_WINDOWS
1590 HANDLE hFile;
1591 DWORD dwDesiredAccess;
1592 DWORD dwShareMode;
1593 DWORD dwCreationDisposition;
1594 DWORD dwFlagsAndAttributes;
1595 SECURITY_ATTRIBUTES SecurityAttributes;
1596
1597# ifndef _O_ACCMODE
1598# define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
1599# endif
1600 switch (flags & (_O_ACCMODE | _O_APPEND))
1601 {
1602 case _O_RDONLY: dwDesiredAccess = GENERIC_READ; break;
1603 case _O_RDONLY | _O_APPEND: dwDesiredAccess = GENERIC_READ; break;
1604 case _O_WRONLY: dwDesiredAccess = GENERIC_WRITE; break;
1605 case _O_WRONLY | _O_APPEND: dwDesiredAccess = (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
1606 case _O_RDWR: dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; break;
1607 case _O_RDWR | _O_APPEND: dwDesiredAccess = GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
1608
1609 default:
1610 RETURN_ERROR(-1, EINVAL, "invalid mode");
1611 }
1612
1613 dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
1614
1615 SecurityAttributes.nLength = sizeof(SecurityAttributes);
1616 SecurityAttributes.lpSecurityDescriptor = NULL;
1617 SecurityAttributes.bInheritHandle = FALSE;
1618
1619 if (flags & _O_CREAT)
1620 {
1621 if ((flags & (_O_EXCL | _O_TRUNC)) == (_O_EXCL | _O_TRUNC))
1622 RETURN_ERROR(-1, EINVAL, "_O_EXCL | _O_TRUNC");
1623
1624 if (flags & _O_TRUNC)
1625 dwCreationDisposition = CREATE_ALWAYS; /* not 100%, but close enough */
1626 else if (flags & _O_EXCL)
1627 dwCreationDisposition = CREATE_NEW;
1628 else
1629 dwCreationDisposition = OPEN_ALWAYS;
1630 }
1631 else if (flags & _O_TRUNC)
1632 dwCreationDisposition = TRUNCATE_EXISTING;
1633 else
1634 dwCreationDisposition = OPEN_EXISTING;
1635
1636 if (!(flags & _O_CREAT) || (mode & 0222))
1637 dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
1638 else
1639 dwFlagsAndAttributes = FILE_ATTRIBUTE_READONLY;
1640
1641 fd = shfile_make_path(pfdtab, name, &absname[0]);
1642 if (!fd)
1643 {
1644# ifdef DEBUG
1645 KU64 ns = shfile_nano_ts();
1646# endif
1647 SetLastError(0);
1648 hFile = CreateFileA(absname,
1649 dwDesiredAccess,
1650 dwShareMode,
1651 &SecurityAttributes,
1652 dwCreationDisposition,
1653 dwFlagsAndAttributes,
1654 NULL /* hTemplateFile */);
1655# ifdef DEBUG
1656 ns = shfile_nano_ts() - ns;
1657 if (ns > 1000000)
1658 TRACE2((NULL, "shfile_open: %u ns hFile=%p (%d) %s\n", ns, hFile, GetLastError(), absname));
1659# endif
1660 if (hFile != INVALID_HANDLE_VALUE)
1661 fd = shfile_insert(pfdtab, (intptr_t)hFile, flags, 0, -1, "shfile_open", absname);
1662 else
1663 fd = shfile_dos2errno(GetLastError());
1664 }
1665
1666# else /* K_OS != K_OS_WINDOWS */
1667 fd = shfile_make_path(pfdtab, name, &absname[0]);
1668 if (!fd)
1669 {
1670 fd = open(absname, flags, mode);
1671 if (fd != -1)
1672 fd = shfile_copy_insert_and_close(pfdtab, &fd, flags, 0, -1, "shfile_open", absname);
1673 }
1674
1675# endif /* K_OS != K_OS_WINDOWS */
1676
1677#else
1678 fd = open(name, flags, mode);
1679#endif
1680
1681 TRACE2((NULL, "shfile_open(%p:{%s}, %#x, 0%o) -> %d [%d]\n", name, name, flags, mode, fd, errno));
1682 return fd;
1683}
1684
1685int shfile_pipe(shfdtab *pfdtab, int fds[2])
1686{
1687 int rc = -1;
1688#ifdef SHFILE_IN_USE
1689# if K_OS == K_OS_WINDOWS
1690 HANDLE hRead = INVALID_HANDLE_VALUE;
1691 HANDLE hWrite = INVALID_HANDLE_VALUE;
1692 SECURITY_ATTRIBUTES SecurityAttributes;
1693
1694 SecurityAttributes.nLength = sizeof(SecurityAttributes);
1695 SecurityAttributes.lpSecurityDescriptor = NULL;
1696 SecurityAttributes.bInheritHandle = FALSE;
1697
1698 fds[1] = fds[0] = -1;
1699 if (CreatePipe(&hRead, &hWrite, &SecurityAttributes, 4096))
1700 {
1701 fds[0] = shfile_insert(pfdtab, (intptr_t)hRead, O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
1702 if (fds[0] != -1)
1703 {
1704 fds[1] = shfile_insert(pfdtab, (intptr_t)hWrite, O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
1705 if (fds[1] != -1)
1706 rc = 0;
1707 }
1708
1709# else
1710 int native_fds[2];
1711
1712 fds[1] = fds[0] = -1;
1713 if (!pipe(native_fds))
1714 {
1715 fds[0] = shfile_copy_insert_and_close(pfdtab, &native_fds[0], O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
1716 if (fds[0] != -1)
1717 {
1718 fds[1] = shfile_copy_insert_and_close(pfdtab, &native_fds[1], O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
1719 if (fds[1] != -1)
1720 rc = 0;
1721 }
1722# endif
1723 if (fds[1] == -1)
1724 {
1725 int s = errno;
1726 if (fds[0] != -1)
1727 {
1728 shmtxtmp tmp;
1729 shmtx_enter(&pfdtab->mtx, &tmp);
1730 rc = fds[0];
1731 pfdtab->tab[rc].fd = -1;
1732 pfdtab->tab[rc].oflags = 0;
1733 pfdtab->tab[rc].shflags = 0;
1734 pfdtab->tab[rc].native = -1;
1735 shmtx_leave(&pfdtab->mtx, &tmp);
1736 }
1737
1738# if K_OS == K_OS_WINDOWS
1739 CloseHandle(hRead);
1740 CloseHandle(hWrite);
1741# else
1742 close(native_fds[0]);
1743 close(native_fds[1]);
1744# endif
1745 fds[0] = fds[1] = -1;
1746 errno = s;
1747 rc = -1;
1748 }
1749 }
1750 else
1751 {
1752# if K_OS == K_OS_WINDOWS
1753 errno = shfile_dos2errno(GetLastError());
1754# endif
1755 rc = -1;
1756 }
1757
1758#else
1759 rc = pipe(fds);
1760#endif
1761
1762 TRACE2((NULL, "shfile_pipe() -> %d{%d,%d} [%d]\n", rc, fds[0], fds[1], errno));
1763 return rc;
1764}
1765
1766/**
1767 * dup().
1768 */
1769int shfile_dup(shfdtab *pfdtab, int fd)
1770{
1771 return shfile_fcntl(pfdtab,fd, F_DUPFD, 0);
1772}
1773
1774/**
1775 * Move the file descriptor, closing any existing descriptor at @a fdto.
1776 *
1777 * @returns fdto on success, -1 and errno on failure.
1778 * @param pfdtab The file descriptor table.
1779 * @param fdfrom The descriptor to move.
1780 * @param fdto Where to move it.
1781 */
1782int shfile_movefd(shfdtab *pfdtab, int fdfrom, int fdto)
1783{
1784#ifdef SHFILE_IN_USE
1785 int rc;
1786 shmtxtmp tmp;
1787 shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
1788 if (file)
1789 {
1790 /* prepare the new entry */
1791 if ((unsigned)fdto >= pfdtab->size)
1792 shfile_grow_tab_locked(pfdtab, fdto);
1793 if ((unsigned)fdto < pfdtab->size)
1794 {
1795 if (pfdtab->tab[fdto].fd != -1)
1796 shfile_native_close(pfdtab->tab[fdto].native, &pfdtab->tab[fdto], K_FALSE);
1797
1798 /* setup the target. */
1799 pfdtab->tab[fdto].fd = fdto;
1800 pfdtab->tab[fdto].oflags = file->oflags;
1801 pfdtab->tab[fdto].shflags = file->shflags;
1802 pfdtab->tab[fdto].native = file->native;
1803# ifdef DEBUG
1804 pfdtab->tab[fdto].dbgname = file->dbgname;
1805# endif
1806
1807 /* close the source. */
1808 file->fd = -1;
1809 file->oflags = 0;
1810 file->shflags = 0;
1811 file->native = -1;
1812# ifdef DEBUG
1813 file->dbgname = NULL;
1814# endif
1815
1816 rc = fdto;
1817 }
1818 else
1819 {
1820 errno = EMFILE;
1821 rc = -1;
1822 }
1823
1824 shfile_put(pfdtab, file, &tmp);
1825 }
1826 else
1827 rc = -1;
1828 return rc;
1829
1830#else
1831 int fdnew = dup2(fdfrom, fdto);
1832 if (fdnew >= 0)
1833 close(fdfrom);
1834 return fdnew;
1835#endif
1836}
1837
1838/**
1839 * Move the file descriptor to somewhere at @a fdMin or above.
1840 *
1841 * @returns the new file descriptor success, -1 and errno on failure.
1842 * @param pfdtab The file descriptor table.
1843 * @param fdfrom The descriptor to move.
1844 * @param fdMin The minimum descriptor.
1845 */
1846int shfile_movefd_above(shfdtab *pfdtab, int fdfrom, int fdMin)
1847{
1848#ifdef SHFILE_IN_USE
1849 int fdto;
1850 shmtxtmp tmp;
1851 shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
1852 if (file)
1853 {
1854 /* find a new place */
1855 int i;
1856 fdto = -1;
1857 for (i = fdMin; (unsigned)i < pfdtab->size; i++)
1858 if (pfdtab->tab[i].fd == -1)
1859 {
1860 fdto = i;
1861 break;
1862 }
1863 if (fdto == -1)
1864 fdto = shfile_grow_tab_locked(pfdtab, fdMin);
1865 if (fdto != -1)
1866 {
1867 /* setup the target. */
1868 pfdtab->tab[fdto].fd = fdto;
1869 pfdtab->tab[fdto].oflags = file->oflags;
1870 pfdtab->tab[fdto].shflags = file->shflags;
1871 pfdtab->tab[fdto].native = file->native;
1872# ifdef DEBUG
1873 pfdtab->tab[fdto].dbgname = file->dbgname;
1874# endif
1875
1876 /* close the source. */
1877 file->fd = -1;
1878 file->oflags = 0;
1879 file->shflags = 0;
1880 file->native = -1;
1881# ifdef DEBUG
1882 file->dbgname = NULL;
1883# endif
1884 }
1885 else
1886 {
1887 errno = EMFILE;
1888 fdto = -1;
1889 }
1890
1891 shfile_put(pfdtab, file, &tmp);
1892 }
1893 else
1894 fdto = -1;
1895 return fdto;
1896
1897#else
1898 int fdnew = fcntl(fdfrom, F_DUPFD, fdMin);
1899 if (fdnew >= 0)
1900 close(fdfrom);
1901 return fdnew;
1902#endif
1903}
1904
1905/**
1906 * close().
1907 */
1908int shfile_close(shfdtab *pfdtab, unsigned fd)
1909{
1910 int rc;
1911#ifdef SHFILE_IN_USE
1912 shmtxtmp tmp;
1913 shfile *file = shfile_get(pfdtab, fd, &tmp);
1914 if (file)
1915 {
1916 shfile_native_close(file->native, file, K_FALSE);
1917
1918 file->fd = -1;
1919 file->oflags = 0;
1920 file->shflags = 0;
1921 file->native = -1;
1922# ifdef DEBUG
1923 sh_free(NULL, file->dbgname);
1924 file->dbgname = NULL;
1925# endif
1926
1927 shfile_put(pfdtab, file, &tmp);
1928 rc = 0;
1929 }
1930 else
1931 rc = -1;
1932
1933#else
1934 rc = close(fd);
1935#endif
1936
1937 TRACE2((NULL, "shfile_close(%d) -> %d [%d]\n", fd, rc, errno));
1938 return rc;
1939}
1940
1941/**
1942 * read().
1943 */
1944long shfile_read(shfdtab *pfdtab, int fd, void *buf, size_t len)
1945{
1946 long rc;
1947#ifdef SHFILE_IN_USE
1948 shmtxtmp tmp;
1949 shfile *file = shfile_get(pfdtab, fd, &tmp);
1950 if (file)
1951 {
1952# if K_OS == K_OS_WINDOWS
1953 DWORD dwRead = 0;
1954 if (ReadFile((HANDLE)file->native, buf, (DWORD)len, &dwRead, NULL))
1955 rc = dwRead;
1956 else
1957 rc = shfile_dos2errno(GetLastError());
1958# else
1959 rc = read(file->native, buf, len);
1960# endif
1961
1962 shfile_put(pfdtab, file, &tmp);
1963 }
1964 else
1965 rc = -1;
1966
1967#else
1968 rc = read(fd, buf, len);
1969#endif
1970 return rc;
1971}
1972
1973/**
1974 * write().
1975 */
1976long shfile_write(shfdtab *pfdtab, int fd, const void *buf, size_t len)
1977{
1978 long rc;
1979#ifdef SHFILE_IN_USE
1980 shmtxtmp tmp;
1981 shfile *file = shfile_get(pfdtab, fd, &tmp);
1982 if (file)
1983 {
1984# if K_OS == K_OS_WINDOWS
1985 DWORD dwWritten = 0;
1986 if (WriteFile((HANDLE)file->native, buf, (DWORD)len, &dwWritten, NULL))
1987 rc = dwWritten;
1988 else
1989 rc = shfile_dos2errno(GetLastError());
1990# else
1991 rc = write(file->native, buf, len);
1992# endif
1993
1994 file->shflags |= SHFILE_FLAGS_DIRTY; /* there should be no concurrent access, so this is safe. */
1995 shfile_put(pfdtab, file, &tmp);
1996 }
1997 else
1998 rc = -1;
1999
2000# ifdef DEBUG
2001 if (fd != shthread_get_shell()->tracefd)
2002 TRACE2((NULL, "shfile_write(%d,,%d) -> %d [%d]\n", fd, len, rc, errno));
2003# endif
2004
2005#else
2006 if (fd != shthread_get_shell()->tracefd)
2007 {
2008 int iSavedErrno = errno;
2009 struct stat s;
2010 int x;
2011 x = fstat(fd, &s);
2012 TRACE2((NULL, "shfile_write(%d) - %lu bytes (%d) - pos %lu - before; %o\n",
2013 fd, (long)s.st_size, x, (long)lseek(fd, 0, SEEK_CUR), s.st_mode ));
2014 K_NOREF(x);
2015 errno = iSavedErrno;
2016 }
2017
2018 rc = write(fd, buf, len);
2019#endif
2020 return rc;
2021}
2022
2023/**
2024 * lseek().
2025 */
2026long shfile_lseek(shfdtab *pfdtab, int fd, long off, int whench)
2027{
2028 long rc;
2029#ifdef SHFILE_IN_USE
2030 shmtxtmp tmp;
2031 shfile *file = shfile_get(pfdtab, fd, &tmp);
2032 if (file)
2033 {
2034# if K_OS == K_OS_WINDOWS
2035 assert(SEEK_SET == FILE_BEGIN);
2036 assert(SEEK_CUR == FILE_CURRENT);
2037 assert(SEEK_END == FILE_END);
2038 rc = SetFilePointer((HANDLE)file->native, off, NULL, whench);
2039 if (rc == INVALID_SET_FILE_POINTER)
2040 rc = shfile_dos2errno(GetLastError());
2041# else
2042 rc = lseek(file->native, off, whench);
2043# endif
2044
2045 shfile_put(pfdtab, file, &tmp);
2046 }
2047 else
2048 rc = -1;
2049
2050#else
2051 rc = lseek(fd, off, whench);
2052#endif
2053
2054 return rc;
2055}
2056
2057int shfile_fcntl(shfdtab *pfdtab, int fd, int cmd, int arg)
2058{
2059 int rc;
2060#ifdef SHFILE_IN_USE
2061 shmtxtmp tmp;
2062 shfile *file = shfile_get(pfdtab, fd, &tmp);
2063 if (file)
2064 {
2065 switch (cmd)
2066 {
2067 case F_GETFL:
2068 rc = file->oflags;
2069 break;
2070
2071 case F_SETFL:
2072 {
2073 unsigned mask = O_NONBLOCK | O_APPEND | O_BINARY | O_TEXT;
2074# ifdef O_DIRECT
2075 mask |= O_DIRECT;
2076# endif
2077# ifdef O_ASYNC
2078 mask |= O_ASYNC;
2079# endif
2080# ifdef O_SYNC
2081 mask |= O_SYNC;
2082# endif
2083 if ((file->oflags & mask) == (arg & mask))
2084 rc = 0;
2085 else
2086 {
2087# if K_OS == K_OS_WINDOWS
2088 assert(0);
2089 errno = EINVAL;
2090 rc = -1;
2091# else
2092 rc = fcntl(file->native, F_SETFL, arg);
2093 if (rc != -1)
2094 file->oflags = (file->oflags & ~mask) | (arg & mask);
2095# endif
2096 }
2097 break;
2098 }
2099
2100 case F_DUPFD:
2101 {
2102# if K_OS == K_OS_WINDOWS
2103 HANDLE hNew = INVALID_HANDLE_VALUE;
2104 if (DuplicateHandle(GetCurrentProcess(),
2105 (HANDLE)file->native,
2106 GetCurrentProcess(),
2107 &hNew,
2108 0,
2109 FALSE /* bInheritHandle */,
2110 DUPLICATE_SAME_ACCESS))
2111 rc = shfile_insert(pfdtab, (intptr_t)hNew, file->oflags, file->shflags, arg,
2112 "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
2113 else
2114 rc = shfile_dos2errno(GetLastError());
2115# else
2116 int nativeNew = fcntl(file->native, F_DUPFD, SHFILE_UNIX_MIN_FD);
2117 if (nativeNew != -1)
2118 rc = shfile_insert(pfdtab, nativeNew, file->oflags, file->shflags, arg,
2119 "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
2120 else
2121 rc = -1;
2122# endif
2123 break;
2124 }
2125
2126 default:
2127 errno = -EINVAL;
2128 rc = -1;
2129 break;
2130 }
2131
2132 shfile_put(pfdtab, file, &tmp);
2133 }
2134 else
2135 rc = -1;
2136
2137#else
2138 rc = fcntl(fd, cmd, arg);
2139#endif
2140
2141 switch (cmd)
2142 {
2143 case F_GETFL: TRACE2((NULL, "shfile_fcntl(%d,F_GETFL,ignored=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
2144 case F_SETFL: TRACE2((NULL, "shfile_fcntl(%d,F_SETFL,newflags=%#x) -> %d [%d]\n", fd, arg, rc, errno)); break;
2145 case F_DUPFD: TRACE2((NULL, "shfile_fcntl(%d,F_DUPFD,minfd=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
2146 default: TRACE2((NULL, "shfile_fcntl(%d,%d,%d) -> %d [%d]\n", fd, cmd, arg, rc, errno)); break;
2147 }
2148 return rc;
2149}
2150
2151int shfile_stat(shfdtab *pfdtab, const char *path, struct stat *pst)
2152{
2153#ifdef SHFILE_IN_USE
2154 char abspath[SHFILE_MAX_PATH];
2155 int rc;
2156 rc = shfile_make_path(pfdtab, path, &abspath[0]);
2157 if (!rc)
2158 {
2159# if K_OS == K_OS_WINDOWS
2160 int dir_slash = shfile_trailing_slash_hack(abspath);
2161 rc = stat(abspath, pst); /** @todo re-implement stat. */
2162 if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
2163 {
2164 rc = -1;
2165 errno = ENOTDIR;
2166 }
2167# else
2168 rc = stat(abspath, pst);
2169# endif
2170 }
2171 TRACE2((NULL, "shfile_stat(,%s,) -> %d [%d]\n", path, rc, errno));
2172 return rc;
2173#else
2174 return stat(path, pst);
2175#endif
2176}
2177
2178int shfile_lstat(shfdtab *pfdtab, const char *path, struct stat *pst)
2179{
2180 int rc;
2181#ifdef SHFILE_IN_USE
2182 char abspath[SHFILE_MAX_PATH];
2183
2184 rc = shfile_make_path(pfdtab, path, &abspath[0]);
2185 if (!rc)
2186 {
2187# if K_OS == K_OS_WINDOWS
2188 int dir_slash = shfile_trailing_slash_hack(abspath);
2189 rc = stat(abspath, pst); /** @todo re-implement stat. */
2190 if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
2191 {
2192 rc = -1;
2193 errno = ENOTDIR;
2194 }
2195# else
2196 rc = lstat(abspath, pst);
2197# endif
2198 }
2199#else
2200 rc = stat(path, pst);
2201#endif
2202 TRACE2((NULL, "shfile_stat(,%s,) -> %d [%d]\n", path, rc, errno));
2203 return rc;
2204}
2205
2206/**
2207 * chdir().
2208 */
2209int shfile_chdir(shfdtab *pfdtab, const char *path)
2210{
2211 int rc;
2212#ifdef SHFILE_IN_USE
2213 shinstance *psh = shthread_get_shell();
2214 char abspath[SHFILE_MAX_PATH];
2215
2216 rc = shfile_make_path(pfdtab, path, &abspath[0]);
2217 if (!rc)
2218 {
2219 char *abspath_copy = sh_strdup(psh, abspath);
2220 char *free_me = abspath_copy;
2221 rc = chdir(abspath);
2222 if (!rc)
2223 {
2224 shmtxtmp tmp;
2225 shmtx_enter(&pfdtab->mtx, &tmp);
2226
2227 shfile_fix_slashes(abspath_copy);
2228 free_me = pfdtab->cwd;
2229 pfdtab->cwd = abspath_copy;
2230
2231 shmtx_leave(&pfdtab->mtx, &tmp);
2232 }
2233 sh_free(psh, free_me);
2234 }
2235 else
2236 rc = -1;
2237#else
2238 rc = chdir(path);
2239#endif
2240
2241 TRACE2((NULL, "shfile_chdir(,%s) -> %d [%d]\n", path, rc, errno));
2242 return rc;
2243}
2244
2245/**
2246 * getcwd().
2247 */
2248char *shfile_getcwd(shfdtab *pfdtab, char *buf, int size)
2249{
2250 char *ret;
2251#ifdef SHFILE_IN_USE
2252
2253 ret = NULL;
2254 if (buf && !size)
2255 errno = -EINVAL;
2256 else
2257 {
2258 size_t cwd_size;
2259 shmtxtmp tmp;
2260 shmtx_enter(&pfdtab->mtx, &tmp);
2261
2262 cwd_size = strlen(pfdtab->cwd) + 1;
2263 if (buf)
2264 {
2265 if (cwd_size <= (size_t)size)
2266 ret = memcpy(buf, pfdtab->cwd, cwd_size);
2267 else
2268 errno = ERANGE;
2269 }
2270 else
2271 {
2272 if ((size_t)size < cwd_size)
2273 size = (int)cwd_size;
2274 ret = sh_malloc(shthread_get_shell(), size);
2275 if (ret)
2276 ret = memcpy(ret, pfdtab->cwd, cwd_size);
2277 else
2278 errno = ENOMEM;
2279 }
2280
2281 shmtx_leave(&pfdtab->mtx, &tmp);
2282 }
2283#else
2284 ret = getcwd(buf, size);
2285#endif
2286
2287 TRACE2((NULL, "shfile_getcwd(,%p,%d) -> %s [%d]\n", buf, size, ret, errno));
2288 return ret;
2289}
2290
2291/**
2292 * access().
2293 */
2294int shfile_access(shfdtab *pfdtab, const char *path, int type)
2295{
2296 int rc;
2297#ifdef SHFILE_IN_USE
2298 char abspath[SHFILE_MAX_PATH];
2299
2300 rc = shfile_make_path(pfdtab, path, &abspath[0]);
2301 if (!rc)
2302 {
2303# ifdef _MSC_VER
2304 if (type & X_OK)
2305 type = (type & ~X_OK) | R_OK;
2306# endif
2307 rc = access(abspath, type);
2308 }
2309#else
2310# ifdef _MSC_VER
2311 if (type & X_OK)
2312 type = (type & ~X_OK) | R_OK;
2313# endif
2314 rc = access(path, type);
2315#endif
2316
2317 TRACE2((NULL, "shfile_access(,%s,%#x) -> %d [%d]\n", path, type, rc, errno));
2318 return rc;
2319}
2320
2321/**
2322 * isatty()
2323 */
2324int shfile_isatty(shfdtab *pfdtab, int fd)
2325{
2326 int rc;
2327#ifdef SHFILE_IN_USE
2328 shmtxtmp tmp;
2329 shfile *file = shfile_get(pfdtab, fd, &tmp);
2330 if (file)
2331 {
2332# if K_OS == K_OS_WINDOWS
2333 rc = (file->shflags & SHFILE_FLAGS_TYPE_MASK) == SHFILE_FLAGS_TTY;
2334# else
2335 rc = isatty(file->native);
2336# endif
2337 shfile_put(pfdtab, file, &tmp);
2338 }
2339 else
2340 rc = 0;
2341#else
2342 rc = isatty(fd);
2343#endif
2344
2345 TRACE2((NULL, "isatty(%d) -> %d [%d]\n", fd, rc, errno));
2346 return rc;
2347}
2348
2349/**
2350 * fcntl F_SETFD / FD_CLOEXEC.
2351 */
2352int shfile_cloexec(shfdtab *pfdtab, int fd, int closeit)
2353{
2354 int rc;
2355#ifdef SHFILE_IN_USE
2356 shmtxtmp tmp;
2357 shfile *file = shfile_get(pfdtab, fd, &tmp);
2358 if (file)
2359 {
2360 if (closeit)
2361 file->shflags |= SHFILE_FLAGS_CLOSE_ON_EXEC;
2362 else
2363 file->shflags &= ~SHFILE_FLAGS_CLOSE_ON_EXEC;
2364 shfile_put(pfdtab, file, &tmp);
2365 rc = 0;
2366 }
2367 else
2368 rc = -1;
2369#else
2370 rc = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0)
2371 | (closeit ? FD_CLOEXEC : 0));
2372#endif
2373
2374 TRACE2((NULL, "shfile_cloexec(%d, %d) -> %d [%d]\n", fd, closeit, rc, errno));
2375 return rc;
2376}
2377
2378/**
2379 * Sets the SHFILE_FLAGS_TRACE flag.
2380 */
2381int shfile_set_trace(shfdtab *pfdtab, int fd)
2382{
2383 int rc;
2384#ifdef SHFILE_IN_USE
2385 shmtxtmp tmp;
2386 shfile *file = shfile_get(pfdtab, fd, &tmp);
2387 if (file)
2388 {
2389 file->shflags |= SHFILE_FLAGS_TRACE;
2390 shfile_put(pfdtab, file, &tmp);
2391 rc = 0;
2392 }
2393 else
2394 rc = -1;
2395#else
2396 rc = 0;
2397#endif
2398
2399 TRACE2((NULL, "shfile_set_trace(%d) -> %d\n", fd, rc));
2400 return rc;
2401}
2402
2403
2404int shfile_ioctl(shfdtab *pfdtab, int fd, unsigned long request, void *buf)
2405{
2406 int rc;
2407#ifdef SHFILE_IN_USE
2408 shmtxtmp tmp;
2409 shfile *file = shfile_get(pfdtab, fd, &tmp);
2410 if (file)
2411 {
2412# if K_OS == K_OS_WINDOWS
2413 rc = -1;
2414 errno = ENOSYS;
2415# else
2416 rc = ioctl(file->native, request, buf);
2417# endif
2418 shfile_put(pfdtab, file, &tmp);
2419 }
2420 else
2421 rc = -1;
2422#else
2423 rc = ioctl(fd, request, buf);
2424#endif
2425
2426 TRACE2((NULL, "ioctl(%d, %#x, %p) -> %d\n", fd, request, buf, rc));
2427 return rc;
2428}
2429
2430
2431mode_t shfile_get_umask(shfdtab *pfdtab)
2432{
2433 /** @todo */
2434 return 022;
2435}
2436
2437void shfile_set_umask(shfdtab *pfdtab, mode_t mask)
2438{
2439 /** @todo */
2440 (void)mask;
2441}
2442
2443
2444
2445shdir *shfile_opendir(shfdtab *pfdtab, const char *dir)
2446{
2447#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
2448 shdir *pdir = NULL;
2449
2450 TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
2451 shfile_init_globals();
2452 if (g_pfnNtQueryDirectoryFile)
2453 {
2454 char abspath[SHFILE_MAX_PATH];
2455 if (shfile_make_path(pfdtab, dir, &abspath[0]) == 0)
2456 {
2457 HANDLE hFile;
2458 SECURITY_ATTRIBUTES SecurityAttributes;
2459
2460 SecurityAttributes.nLength = sizeof(SecurityAttributes);
2461 SecurityAttributes.lpSecurityDescriptor = NULL;
2462 SecurityAttributes.bInheritHandle = FALSE;
2463
2464 hFile = CreateFileA(abspath,
2465 GENERIC_READ,
2466 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
2467 &SecurityAttributes,
2468 OPEN_EXISTING,
2469 FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS,
2470 NULL /* hTemplateFile */);
2471 if (hFile != INVALID_HANDLE_VALUE)
2472 {
2473 pdir = (shdir *)sh_malloc(shthread_get_shell(), sizeof(*pdir));
2474 if (pdir)
2475 {
2476 pdir->pfdtab = pfdtab;
2477 pdir->native = hFile;
2478 pdir->off = ~(size_t)0;
2479 }
2480 else
2481 CloseHandle(hFile);
2482 }
2483 else
2484 {
2485 errno = shfile_dos2errno(GetLastError());
2486 TRACE2((NULL, "shfile_opendir: CreateFileA(%s) -> %d/%d\n", abspath, GetLastError(), errno));
2487 }
2488 }
2489 }
2490 else
2491 errno = ENOSYS;
2492 return pdir;
2493#else
2494 TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
2495 return (shdir *)opendir(dir);
2496#endif
2497}
2498
2499shdirent *shfile_readdir(struct shdir *pdir)
2500{
2501#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
2502 if (pdir)
2503 {
2504 NTSTATUS rcNt;
2505
2506 if ( pdir->off == ~(size_t)0
2507 || pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) >= pdir->cb)
2508 {
2509 MY_IO_STATUS_BLOCK Ios;
2510
2511 memset(&Ios, 0, sizeof(Ios));
2512 rcNt = g_pfnNtQueryDirectoryFile(pdir->native,
2513 NULL /*Event*/,
2514 NULL /*ApcRoutine*/,
2515 NULL /*ApcContext*/,
2516 &Ios,
2517 &pdir->buf[0],
2518 sizeof(pdir->buf),
2519 MY_FileNamesInformation,
2520 FALSE /*ReturnSingleEntry*/,
2521 NULL /*FileName*/,
2522 pdir->off == ~(size_t)0 /*RestartScan*/);
2523 if (rcNt >= 0 && rcNt != STATUS_PENDING)
2524 {
2525 pdir->cb = Ios.Information;
2526 pdir->off = 0;
2527 }
2528 else if (rcNt == STATUS_NO_MORE_FILES)
2529 errno = 0; /* wrong? */
2530 else
2531 shfile_nt2errno(rcNt);
2532 }
2533
2534 if ( pdir->off != ~(size_t)0
2535 && pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) <= pdir->cb)
2536 {
2537 PMY_FILE_NAMES_INFORMATION pcur = (PMY_FILE_NAMES_INFORMATION)&pdir->buf[pdir->off];
2538 ANSI_STRING astr;
2539 UNICODE_STRING ustr;
2540
2541 astr.Length = astr.MaximumLength = sizeof(pdir->ent.name);
2542 astr.Buffer = &pdir->ent.name[0];
2543
2544 ustr.Length = ustr.MaximumLength = pcur->FileNameLength < ~(USHORT)0 ? (USHORT)pcur->FileNameLength : ~(USHORT)0;
2545 ustr.Buffer = &pcur->FileName[0];
2546
2547 rcNt = g_pfnRtlUnicodeStringToAnsiString(&astr, &ustr, 0/*AllocateDestinationString*/);
2548 if (rcNt < 0)
2549 sprintf(pdir->ent.name, "conversion-failed-%08x-rcNt=%08x-len=%u",
2550 pcur->FileIndex, rcNt, pcur->FileNameLength);
2551 if (pcur->NextEntryOffset)
2552 pdir->off += pcur->NextEntryOffset;
2553 else
2554 pdir->off = pdir->cb;
2555 return &pdir->ent;
2556 }
2557 }
2558 else
2559 errno = EINVAL;
2560 return NULL;
2561#else
2562 struct dirent *pde = readdir((DIR *)pdir);
2563 return pde ? (shdirent *)&pde->d_name[0] : NULL;
2564#endif
2565}
2566
2567void shfile_closedir(struct shdir *pdir)
2568{
2569#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
2570 if (pdir)
2571 {
2572 CloseHandle(pdir->native);
2573 pdir->pfdtab = NULL;
2574 pdir->native = INVALID_HANDLE_VALUE;
2575 sh_free(shthread_get_shell(), pdir);
2576 }
2577 else
2578 errno = EINVAL;
2579#else
2580 closedir((DIR *)pdir);
2581#endif
2582}
2583
Note: See TracBrowser for help on using the repository browser.