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

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

kash: Added comment about async close and stat. Some more logging in that area.

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