~ chicken-core (master) /posixwin.scm


  1;;;; posixwin.scm - Miscellaneous file- and process-handling routines, available on Windows
  2;
  3; Copyright (c) 2008-2022, The CHICKEN Team
  4; Copyright (c) 2000-2007, Felix L. Winkelmann
  5; All rights reserved.
  6;
  7; Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
  8; conditions are met:
  9;
 10;   Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 11;     disclaimer.
 12;   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 13;     disclaimer in the documentation and/or other materials provided with the distribution.
 14;   Neither the name of the author nor the names of its contributors may be used to endorse or promote
 15;     products derived from this software without specific prior written permission.
 16;
 17; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 18; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 19; AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 20; CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 21; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 22; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 23; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 24; OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 25; POSSIBILITY OF SUCH DAMAGE.
 26
 27
 28; Not implemented:
 29;
 30; open/noctty  open/nonblock  open/fsync  open/sync
 31; perm/isvtx  perm/isuid  perm/isgid
 32; file-select
 33; set-signal-mask!  signal-mask	 signal-masked?	 signal-mask!  signal-unmask!
 34; user-information
 35; change-file-owner
 36; current-user-id  current-group-id  current-effective-user-id	current-effective-group-id
 37; current-effective-user-name
 38; set-user-id!	set-group-id!
 39; create-session
 40; process-group-id  set-process-group-id!
 41; create-symbolic-link	read-symbolic-link
 42; file-truncate
 43; file-lock  file-lock/blocking	 file-unlock  file-test-lock
 44; create-fifo
 45; prot/...
 46; map/...
 47; set-alarm!
 48; process-fork	process-wait
 49; parent-process-id
 50; process-signal
 51
 52
 53; Issues
 54;
 55; - Use of a UTF8 encoded string will not work properly. Windows uses a
 56; 16-bit UNICODE character string encoding and specialized system calls
 57; and/or structure settings for the use of such strings.
 58
 59
 60(declare
 61  (uses data-structures))
 62
 63(define-foreign-variable _stat_st_blksize scheme-object "C_SCHEME_UNDEFINED")
 64(define-foreign-variable _stat_st_blocks scheme-object "C_SCHEME_UNDEFINED")
 65
 66(include "posix-common.scm")
 67
 68#>
 69
 70#ifndef WIN32_LEAN_AND_MEAN
 71# define WIN32_LEAN_AND_MEAN
 72#endif
 73
 74#include <direct.h>
 75#include <errno.h>
 76#include <fcntl.h>
 77#include <io.h>
 78#include <process.h>
 79#include <signal.h>
 80#include <stdio.h>
 81#include <utime.h>
 82#include <windows.h>
 83#include <winsock2.h>
 84
 85#define PIPE_BUF	512
 86
 87#ifndef EWOULDBLOCK
 88# define EWOULDBLOCK 0
 89#endif
 90
 91static int C_pipefds[ 2 ];
 92static time_t C_secs;
 93
 94/* pipe handles */
 95static HANDLE C_rd0, C_wr0, C_wr0_, C_rd1, C_wr1, C_rd1_;
 96static HANDLE C_save0, C_save1; /* saved I/O handles */
 97static char C_rdbuf; /* one-char buffer for read */
 98static int C_exstatus;
 99static HANDLE C_pid;
100
101/* platform information; initialized for cached testing */
102static char C_shlcmd[255 + 1] = "";
103
104/* Current user name */
105static C_char C_username[255 + 1] = "";
106
107#define open_binary_input_pipe(a, n, name) C_mpointer(a, _wpopen(C_OS_FILENAME(name, 0), L"r"))
108#define open_text_input_pipe(a, n, name)     open_binary_input_pipe(a, n, name)
109#define open_binary_output_pipe(a, n, name)  C_mpointer(a, _wpopen(C_OS_FILENAME(name, 0), L"w"))
110#define open_text_output_pipe(a, n, name)    open_binary_output_pipe(a, n, name)
111#define close_pipe(p)                        C_fix(_pclose(C_port_file(p)))
112
113#define C_chmod(fn, m)      C_fix(_wchmod(C_OS_FILENAME(fn, 0), C_unfix(m)))
114#define C_pipe(d, m)        C_fix(_pipe(C_pipefds, PIPE_BUF, C_unfix(m)))
115#define C_close(fd)         C_fix(close(C_unfix(fd)))
116
117#define C_u_i_lstat(fn)     C_u_i_stat(fn)
118
119#define C_open(fn, fl, m)   C_fix(_wopen(C_OS_FILENAME(fn, 0), C_unfix(fl), C_unfix(m)))
120#define C_read(fd, b, n)    C_fix(read(C_unfix(fd), C_data_pointer(b), C_unfix(n)))
121#define C_write(fd, b, n)   C_fix(write(C_unfix(fd), C_data_pointer(b), C_unfix(n)))
122
123#define C_flushall()        C_fix(_flushall())
124
125#define C_umask(m)          C_fix(_umask(C_unfix(m)))
126
127#define C_ctime(n)          (C_secs = (n), ctime(&C_secs))
128
129#define TIME_STRING_MAXLENGTH 255
130static char C_time_string [TIME_STRING_MAXLENGTH + 1];
131#undef TIME_STRING_MAXLENGTH
132
133/*
134  mapping from Win32 error codes to errno
135*/
136
137typedef struct
138{
139    DWORD   win32;
140    int	    libc;
141} errmap_t;
142
143static errmap_t errmap[] =
144{
145    {ERROR_INVALID_FUNCTION,	  EINVAL},
146    {ERROR_FILE_NOT_FOUND,	  ENOENT},
147    {ERROR_PATH_NOT_FOUND,	  ENOENT},
148    {ERROR_TOO_MANY_OPEN_FILES,	  EMFILE},
149    {ERROR_ACCESS_DENIED,	  EACCES},
150    {ERROR_INVALID_HANDLE,	  EBADF},
151    {ERROR_ARENA_TRASHED,	  ENOMEM},
152    {ERROR_NOT_ENOUGH_MEMORY,	  ENOMEM},
153    {ERROR_INVALID_BLOCK,	  ENOMEM},
154    {ERROR_BAD_ENVIRONMENT,	  E2BIG},
155    {ERROR_BAD_FORMAT,		  ENOEXEC},
156    {ERROR_INVALID_ACCESS,	  EINVAL},
157    {ERROR_INVALID_DATA,	  EINVAL},
158    {ERROR_INVALID_DRIVE,	  ENOENT},
159    {ERROR_CURRENT_DIRECTORY,	  EACCES},
160    {ERROR_NOT_SAME_DEVICE,	  EXDEV},
161    {ERROR_NO_MORE_FILES,	  ENOENT},
162    {ERROR_LOCK_VIOLATION,	  EACCES},
163    {ERROR_BAD_NETPATH,		  ENOENT},
164    {ERROR_NETWORK_ACCESS_DENIED, EACCES},
165    {ERROR_BAD_NET_NAME,	  ENOENT},
166    {ERROR_FILE_EXISTS,		  EEXIST},
167    {ERROR_CANNOT_MAKE,		  EACCES},
168    {ERROR_FAIL_I24,		  EACCES},
169    {ERROR_INVALID_PARAMETER,	  EINVAL},
170    {ERROR_NO_PROC_SLOTS,	  EAGAIN},
171    {ERROR_DRIVE_LOCKED,	  EACCES},
172    {ERROR_BROKEN_PIPE,		  EPIPE},
173    {ERROR_DISK_FULL,		  ENOSPC},
174    {ERROR_INVALID_TARGET_HANDLE, EBADF},
175    {ERROR_INVALID_HANDLE,	  EINVAL},
176    {ERROR_WAIT_NO_CHILDREN,	  ECHILD},
177    {ERROR_CHILD_NOT_COMPLETE,	  ECHILD},
178    {ERROR_DIRECT_ACCESS_HANDLE,  EBADF},
179    {ERROR_NEGATIVE_SEEK,	  EINVAL},
180    {ERROR_SEEK_ON_DEVICE,	  EACCES},
181    {ERROR_DIR_NOT_EMPTY,	  ENOTEMPTY},
182    {ERROR_NOT_LOCKED,		  EACCES},
183    {ERROR_BAD_PATHNAME,	  ENOENT},
184    {ERROR_MAX_THRDS_REACHED,	  EAGAIN},
185    {ERROR_LOCK_FAILED,		  EACCES},
186    {ERROR_ALREADY_EXISTS,	  EEXIST},
187    {ERROR_FILENAME_EXCED_RANGE,  ENOENT},
188    {ERROR_NESTING_NOT_ALLOWED,	  EAGAIN},
189    {ERROR_NOT_ENOUGH_QUOTA,	  ENOMEM},
190    {0, 0}
191};
192
193static void
194set_errno(DWORD w32err)
195{
196    errmap_t *map;
197    for (map = errmap; map->win32; ++map)
198    {
199	if (map->win32 == w32err)
200	{
201	    errno = map->libc;
202	    return;
203	}
204    }
205    errno = ENOSYS; /* For lack of anything better */
206}
207
208static int
209set_last_errno()
210{
211    set_errno(GetLastError());
212    return 0;
213}
214
215static int fd_to_path(C_word fd, C_WCHAR path[])
216{
217  DWORD result;
218  HANDLE fh = (HANDLE)_get_osfhandle(C_unfix(fd));
219
220  if (fh == INVALID_HANDLE_VALUE) {
221    set_last_errno();
222    return -1;
223  }
224
225	/* XXX wchar_t */
226  result = GetFinalPathNameByHandleW(fh, path, MAX_PATH, VOLUME_NAME_DOS);
227  if (result == 0) {
228    set_last_errno();
229    return -1;
230  } else if (result >= MAX_PATH) { /* Shouldn't happen */
231    errno = ENOMEM; /* For lack of anything better */
232    return -1;
233  } else {
234    return 0;
235  }
236}
237
238static C_word C_fchmod(C_word fd, C_word m)
239{
240  C_WCHAR path[MAX_PATH];
241  if (fd_to_path(fd, path) == -1) return C_fix(-1);
242  else return C_fix(_wchmod(path, C_unfix(m)));
243}
244
245static C_word C_fchdir(C_word fd)
246{
247  C_WCHAR path[MAX_PATH];
248  if (fd_to_path(fd, path) == -1) return C_fix(-1);
249  else return C_fix(_wchdir(path));
250}
251
252static int
253process_wait(C_word h, C_word t)
254{
255    DWORD wait_result = WaitForSingleObject((HANDLE)h, (t ? 0 : INFINITE));
256    DWORD ret;
257    switch (wait_result)
258    {
259        case WAIT_OBJECT_0:
260            if (GetExitCodeProcess((HANDLE)h, &ret))
261            {
262                CloseHandle((HANDLE)h);
263                C_exstatus = ret;
264                C_pid = (HANDLE)h;
265                return 1;
266            }
267            break;
268        case WAIT_TIMEOUT:
269            C_pid = 0;
270            return 1;
271    }
272    return set_last_errno();
273}
274
275#define C_process_wait(p, t) (process_wait(C_unfix(p), C_truep(t)) ? C_SCHEME_TRUE : C_SCHEME_FALSE)
276
277
278static int C_isNT = 0;
279
280
281static int
282C_windows_nt()
283{
284  static int has_info = 0;
285
286  if(!has_info) {
287    OSVERSIONINFO ovf;
288    ZeroMemory(&ovf, sizeof(ovf));
289    ovf.dwOSVersionInfoSize = sizeof(ovf);
290    has_info = 1;
291
292    if(GetVersionEx(&ovf)) {
293      SYSTEM_INFO si;
294
295      switch (ovf.dwPlatformId) {
296      case VER_PLATFORM_WIN32_NT:
297        return C_isNT = 1;
298      }
299    }
300  }
301
302  return C_isNT;
303}
304
305
306static int
307get_shlcmd()
308{
309    static wchar_t buf[ 255 ];
310    /* Do we need to build the shell command pathname? */
311    if (!strlen(C_shlcmd))
312    {
313      char *cmdnam = C_windows_nt() ? "\\cmd.exe" : "\\command.com";
314      UINT len = GetSystemDirectoryW(buf, sizeof(buf));
315      if (len)
316        C_strlcpy(C_shlcmd + len, C_utf8(buf), sizeof(C_shlcmd));
317      else
318        return set_last_errno();
319    }
320
321    return 1;
322}
323
324#define C_sysinfo() (sysinfo() ? C_SCHEME_TRUE : C_SCHEME_FALSE)
325#define C_get_shlcmd() (get_shlcmd() ? C_SCHEME_TRUE : C_SCHEME_FALSE)
326
327/* GetUserName */
328
329static int
330get_user_name()
331{
332    static wchar_t buf[ 255 ];
333    if (!C_strlen(C_username))
334    {
335        DWORD bufCharCount = sizeof(buf) / sizeof(buf[0]);
336        if (!GetUserNameW(buf, &bufCharCount))
337            return set_last_errno();
338        C_strlcpy(C_username, C_utf8(buf), sizeof(C_username));
339    }
340    return 1;
341}
342
343#define C_get_user_name() (get_user_name() ? C_SCHEME_TRUE : C_SCHEME_FALSE)
344
345/*
346    Spawn a process directly.
347    Params:
348    app         Command to execute.
349    cmdlin      Command line (arguments).
350    env         Environment for the new process (may be NULL).
351    handle, stdin, stdout, stderr
352                Spawned process info are returned in integers.
353                When spawned process shares standard io stream with the parent
354                process the respective value in handle, stdin, stdout, stderr
355                is -1.
356    params      A bitmask controling operation.
357                Bit 1: Child & parent share standard input if this bit is set.
358                Bit 2: Share standard output if bit is set.
359                Bit 3: Share standard error if bit is set.
360
361    Returns: pid, zero return value indicates failure.
362*/
363static DWORD
364C_process(const char *app, C_word cmdlin, const char **env,
365          int *phandle, int *pstdin_fd, int *pstdout_fd, int *pstderr_fd,
366          int params)
367{
368    int i;
369    int success = TRUE;
370    DWORD pid;
371    const int f_share_io[3] = { params & 1, params & 2, params & 4};
372    int io_fds[3] = { -1, -1, -1 };
373    HANDLE
374        child_io_handles[3] = { NULL, NULL, NULL },
375        standard_io_handles[3] = {
376            GetStdHandle(STD_INPUT_HANDLE),
377            GetStdHandle(STD_OUTPUT_HANDLE),
378            GetStdHandle(STD_ERROR_HANDLE)};
379    const char modes[3] = "rww";
380    HANDLE cur_process = GetCurrentProcess(), child_process = NULL;
381    void* envblk = NULL;
382
383    /****** create io handles & fds ***/
384
385    for (i=0; i<3 && success; ++i)
386    {
387	if (f_share_io[i])
388	{
389	    success = DuplicateHandle(
390		cur_process, standard_io_handles[i],
391		cur_process, &child_io_handles[i],
392		0, FALSE, DUPLICATE_SAME_ACCESS);
393	}
394	else
395	{
396	    HANDLE a, b;
397	    success = CreatePipe(&a,&b,NULL,0);
398	    if(success)
399	    {
400		HANDLE parent_end;
401		if (modes[i]=='r') { child_io_handles[i]=a; parent_end=b; }
402		else		   { parent_end=a; child_io_handles[i]=b; }
403		success = (io_fds[i] = _open_osfhandle((C_word)parent_end,0)) >= 0;
404                /* Make new handle inheritable */
405		if (success)
406		  success = SetHandleInformation(child_io_handles[i], HANDLE_FLAG_INHERIT, -1);
407	    }
408	}
409    }
410
411#if 0 /* Requires a sorted list by key! */
412    /****** create environment block if necessary ****/
413
414    if (env && success)
415    {
416        char** p;
417        int len = 0;
418
419        for (p = env; *p; ++p) len += strlen(*p) + 1;
420
421        if (envblk = C_malloc((len + 1) * sizeof(wchar_t));
422        {
423            wchar_t* pb = (wchar_t*)envblk;
424            for (p = env; *p; ++p)
425            {
426            	wchar_t *u = C_utf16(*p, 0);  /* BOGUS! */
427            	int n = wcslen(*u);
428                C_memcpy(pb, *u, n + 1);
429                pb += n + 1;
430            }
431            *pb = '\0';
432            /* This _should_ already have been checked for embedded NUL bytes */
433        }
434        else
435            success = FALSE;
436    }
437#endif
438
439    /****** finally spawn process ****/
440
441    if (success)
442    {
443        PROCESS_INFORMATION pi;
444        STARTUPINFOW si;
445
446        ZeroMemory(&pi,sizeof pi);
447        ZeroMemory(&si,sizeof si);
448        si.cb = sizeof si;
449        si.dwFlags = STARTF_USESTDHANDLES;
450        si.hStdInput = child_io_handles[0];
451        si.hStdOutput = child_io_handles[1];
452        si.hStdError = child_io_handles[2];
453
454        /* FIXME passing 'app' param causes failure & possible stack corruption */
455        success = CreateProcessW(
456            NULL, C_utf16(cmdlin, 0), NULL, NULL, TRUE, 0, envblk, NULL, &si, &pi);
457
458        if (success)
459        {
460            child_process=pi.hProcess;
461            CloseHandle(pi.hThread);
462            pid = pi.dwProcessId;
463        }
464        else
465            set_last_errno();
466    }
467    else
468        set_last_errno();
469
470    /****** cleanup & return *********/
471
472    /* parent must close child end */
473    for (i=0; i<3; ++i) {
474	if (child_io_handles[i] != NULL)
475	    CloseHandle(child_io_handles[i]);
476    }
477
478    if (success)
479    {
480	*phandle = (C_word)child_process;
481	*pstdin_fd = io_fds[0];
482	*pstdout_fd = io_fds[1];
483	*pstderr_fd = io_fds[2];
484    }
485    else
486    {
487	for (i=0; i<3; ++i) {
488	    if (io_fds[i] != -1)
489		_close(io_fds[i]);
490	}
491    }
492
493    return success;
494}
495
496static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
497{
498  struct _stat64i32 sb;
499  struct _utimbuf tb;
500  C_word bv = C_block_item(filename, 0);
501  C_WCHAR *fn = C_OS_FILENAME(bv, 0);
502
503  /* Only stat if needed */
504  if (atime == C_SCHEME_FALSE || mtime == C_SCHEME_FALSE) {
505    if (C_stat(fn, &sb) == -1) return -1;
506  }
507
508  if (atime == C_SCHEME_FALSE) {
509    tb.actime = sb.st_atime;
510  } else {
511    tb.actime = C_num_to_int64(atime);
512  }
513  if (mtime == C_SCHEME_FALSE) {
514    tb.modtime = sb.st_mtime;
515  } else {
516    tb.modtime = C_num_to_int64(mtime);
517  }
518  return _wutime(fn, &tb);
519}
520
521#define C_u_i_execvp(f, a) C_fix(_wexecvp(C_utf16(f, 0), (void *)C_c_pointer_vector_or_null(a)))
522#define C_u_i_execve(f,a,e) C_fix(_wexecve(C_utf16(f, 0), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
523
524/* MS replacement for the fork-exec pair */
525#define C_u_i_spawnvp(m,f,a)    C_fix(_wspawnvp(C_unfix(m), C_utf16(f, 0), (void *)C_c_pointer_vector_or_null(a)))
526#define C_u_i_spawnvpe(m,f,a,e) C_fix(_wspawnvpe(C_unfix(m), C_utf16(f, 0), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
527
528<#
529
530(import (only chicken.string string-intersperse))
531
532;;; Lo-level I/O:
533
534(define-foreign-variable _o_noinherit int "O_NOINHERIT")
535(set! chicken.file.posix#open/noinherit _o_noinherit)
536
537(set! chicken.file.posix#file-open
538  (let ((defmode (bitwise-ior _s_irusr _s_iwusr _s_irgrp _s_iwgrp _s_iroth _s_iwoth)))
539    (lambda (filename flags . mode)
540      (let ([mode (if (pair? mode) (car mode) defmode)])
541	(##sys#check-string filename 'file-open)
542	(##sys#check-fixnum flags 'file-open)
543	(##sys#check-fixnum mode 'file-open)
544	(let ([fd (##core#inline "C_open" (##sys#make-c-string filename 'file-open) flags mode)])
545	  (when (eq? -1 fd)
546            (##sys#signal-hook/errno
547             #:file-error (##sys#update-errno) 'file-open "cannot open file" filename flags mode))
548	  fd) ) ) ) )
549
550(set! chicken.file.posix#file-close
551  (lambda (fd)
552    (##sys#check-fixnum fd 'file-close)
553    (let loop ()
554      (when (fx< (##core#inline "C_close" fd) 0)
555	(cond
556	  ((fx= _errno _eintr) (##sys#dispatch-interrupt loop))
557	  (else
558	   (posix-error #:file-error 'file-close "cannot close file" fd)))))))
559
560(set! chicken.file.posix#file-read
561  (lambda (fd size . buffer)
562    (##sys#check-fixnum fd 'file-read)
563    (##sys#check-fixnum size 'file-read)
564    (let ([buf (if (pair? buffer) (car buffer) (##sys#make-bytevector size))])
565      (unless (##core#inline "C_byteblockp" buf)
566	(##sys#signal-hook #:type-error 'file-read "bad argument type - not a bytevector" buf) )
567      (let ([n (##core#inline "C_read" fd buf size)])
568	(when (eq? -1 n)
569          (##sys#signal-hook/errno
570           #:file-error (##sys#update-errno) 'file-read "cannot read from file" fd size))
571	(list buf n) ) ) ) )
572
573(set! chicken.file.posix#file-write
574  (lambda (fd buffer #!optional size)
575    (##sys#check-fixnum fd 'file-write)
576    (when (string? buffer)
577      (set! buffer (##sys#slot buffer 0))
578      (unless size (set! size (fx- (##sys#size buffer) 1))))
579    (unless (##core#inline "C_byteblockp" buffer)
580      (##sys#signal-hook #:type-error 'file-write "bad argument type - not a string or bytevector" buffer) )
581    (let ((size (or size (##sys#size buffer))))
582      (##sys#check-fixnum size 'file-write)
583      (let ([n (##core#inline "C_write" fd buffer size)])
584	(when (eq? -1 n)
585          (##sys#signal-hook/errno
586           #:file-error (##sys#update-errno) 'file-write "cannot write to file" fd size))
587	n) ) ) )
588
589(set! chicken.file.posix#file-mkstemp
590  (lambda (template)
591    (##sys#check-string template 'file-mkstemp)
592    (let* ((diz "0123456789abcdefghijklmnopqrstuvwxyz")
593	   (diz-len (string-length diz))
594	   (max-attempts (* diz-len diz-len diz-len))
595	   (tmpl (string-copy template)) ; We'll overwrite this later
596	   (tmpl-len (string-length tmpl))
597	   (first-x (let loop ((i (fx- tmpl-len 1)))
598		      (if (and (fx>= i 0)
599			       (eq? (string-ref tmpl i) #\X))
600			  (loop (fx- i 1))
601			  (fx+ i 1)))))
602      (cond ((not (##sys#file-exists? (or (pathname-directory template) ".") #f #t 'file-mkstemp))
603	     ;; Quit early instead of looping needlessly with C_open
604	     ;; failing every time.  This is a race condition, but not
605	     ;; a security-critical one.
606	     (##sys#signal-hook #:file-error 'file-mkstemp "non-existent directory" template))
607	    ((fx= first-x tmpl-len)
608	     (##sys#signal-hook #:file-error 'file-mkstemp "invalid template" template)))
609      (let loop ((count 1))
610	(let suffix-loop ((index (fx- tmpl-len 1)))
611	  (when (fx>= index first-x)
612	    (string-set! tmpl index
613  		  (string-ref diz (##core#inline "C_rand" diz-len)))
614	    (suffix-loop (fx- index 1))))
615	(let ((fd (##core#inline "C_open"
616				 (##sys#make-c-string tmpl 'file-open)
617				 (bitwise-ior chicken.file.posix#open/rdwr
618					      chicken.file.posix#open/creat
619					      chicken.file.posix#open/excl)
620				 (fxior _s_irusr _s_iwusr))))
621	  (if (eq? -1 fd)
622	      (if (fx< count max-attempts)
623		  (loop (fx+ count 1))
624		  (posix-error #:file-error 'file-mkstemp "cannot create temporary file" template))
625	      (values fd tmpl)))))))
626
627;;; Pipe primitive:
628
629(define-foreign-variable _pipefd0 int "C_pipefds[ 0 ]")
630(define-foreign-variable _pipefd1 int "C_pipefds[ 1 ]")
631
632(set! chicken.process#create-pipe
633  (lambda (#!optional (mode (fxior chicken.file.posix#open/binary
634                                   chicken.file.posix#open/noinherit)))
635    (when (fx< (##core#inline "C_pipe" #f mode) 0)
636      (##sys#signal-hook/errno
637       #:file-error (##sys#update-errno) 'create-pipe "cannot create pipe"))
638    (values _pipefd0 _pipefd1) ) )
639
640;;; Signal processing:
641
642(define-foreign-variable _nsig int "NSIG")
643(define-foreign-variable _sigterm int "SIGTERM")
644(define-foreign-variable _sigint int "SIGINT")
645(define-foreign-variable _sigfpe int "SIGFPE")
646(define-foreign-variable _sigill int "SIGILL")
647(define-foreign-variable _sigsegv int "SIGSEGV")
648(define-foreign-variable _sigabrt int "SIGABRT")
649(define-foreign-variable _sigbreak int "SIGBREAK")
650
651(set! chicken.process.signal#signal/term _sigterm)
652(set! chicken.process.signal#signal/int _sigint)
653(set! chicken.process.signal#signal/fpe _sigfpe)
654(set! chicken.process.signal#signal/ill _sigill)
655(set! chicken.process.signal#signal/segv _sigsegv)
656(set! chicken.process.signal#signal/abrt _sigabrt)
657(set! chicken.process.signal#signal/break _sigbreak)
658(set! chicken.process.signal#signal/alrm 0)
659(set! chicken.process.signal#signal/bus 0)
660(set! chicken.process.signal#signal/chld 0)
661(set! chicken.process.signal#signal/cont 0)
662(set! chicken.process.signal#signal/hup 0)
663(set! chicken.process.signal#signal/io 0)
664(set! chicken.process.signal#signal/kill 0)
665(set! chicken.process.signal#signal/pipe 0)
666(set! chicken.process.signal#signal/prof 0)
667(set! chicken.process.signal#signal/quit 0)
668(set! chicken.process.signal#signal/stop 0)
669(set! chicken.process.signal#signal/trap 0)
670(set! chicken.process.signal#signal/tstp 0)
671(set! chicken.process.signal#signal/urg 0)
672(set! chicken.process.signal#signal/usr1 0)
673(set! chicken.process.signal#signal/usr2 0)
674(set! chicken.process.signal#signal/vtalrm 0)
675(set! chicken.process.signal#signal/winch 0)
676(set! chicken.process.signal#signal/xcpu 0)
677(set! chicken.process.signal#signal/xfsz 0)
678
679(set! chicken.process.signal#signals-list
680  (list
681   chicken.process.signal#signal/term
682   chicken.process.signal#signal/int
683   chicken.process.signal#signal/fpe
684   chicken.process.signal#signal/ill
685   chicken.process.signal#signal/segv
686   chicken.process.signal#signal/abrt
687   chicken.process.signal#signal/break))
688
689;;; Using file-descriptors:
690
691(define duplicate-fileno
692  (lambda (old . new)
693    (##sys#check-fixnum old duplicate-fileno)
694    (let ([fd (if (null? new)
695		  (##core#inline "C_dup" old)
696		  (let ([n (car new)])
697		    (##sys#check-fixnum n 'duplicate-fileno)
698		    (##core#inline "C_dup2" old n) ) ) ] )
699      (when (fx< fd 0)
700        (##sys#signal-hook/errno
701         #:file-error (##sys#update-errno) 'duplicate-fileno "cannot duplicate file descriptor" old))
702      fd) ) )
703
704
705;;; Time related things:
706
707(set! chicken.time.posix#local-timezone-abbreviation
708  (foreign-lambda* c-string ()
709   "char *z = (_daylight ? _tzname[1] : _tzname[0]);\n"
710   "C_return(z);") )
711
712
713;;; Process handling:
714
715(define-foreign-variable _p_overlay int "P_OVERLAY")
716(define-foreign-variable _p_wait int "P_WAIT")
717(define-foreign-variable _p_nowait int "P_NOWAIT")
718(define-foreign-variable _p_nowaito int "P_NOWAITO")
719(define-foreign-variable _p_detach int "P_DETACH")
720
721(set! chicken.process#spawn/overlay _p_overlay)
722(set! chicken.process#spawn/wait _p_wait)
723(set! chicken.process#spawn/nowait _p_nowait)
724(set! chicken.process#spawn/nowaito _p_nowaito)
725(set! chicken.process#spawn/detach _p_detach)
726
727; Windows uses a commandline style for process arguments. Thus any
728; arguments with embedded whitespace will parse incorrectly. Must
729; string-quote such arguments.
730(define quote-arg-string
731  (let ((needs-quoting?
732         ;; This is essentially (string-any char-whitespace? s) but we
733         ;; don't want a SRFI-13 dependency. (Do we?)
734         (lambda (s)
735           (let ((len (string-length s)))
736             (let loop ((i 0))
737               (cond
738                ((fx= i len) #f)
739                ((char-whitespace? (string-ref s i)))
740                ((char=? #\' (string-ref s i)))
741                (else (loop (fx+ i 1)))))))))
742    (lambda (str)
743      (if (needs-quoting? str) (string-append "\"" str "\"") str))))
744
745(define c-string->allocated-pointer
746  (foreign-lambda* c-pointer ((scheme-object o))
747     ;; includes 0 byte at end
748     "int len = C_header_size(o) * sizeof(C_WCHAR); \n"
749     "char *ptr = C_malloc(len); \n"
750     "if (ptr != NULL) {\n"
751     "  C_WCHAR *u = C_utf16(o, 0); \n"
752     "  C_memcpy(ptr, u, len); \n"
753     "}\n"
754     "C_return(ptr);"))
755
756(set! chicken.process#process-execute
757  (lambda (filename #!optional (arglist '()) envlist exactf)
758    (let ((conv (if exactf (lambda (x) x) quote-arg-string)))
759     (call-with-exec-args
760       'process-execute filename conv arglist envlist
761       (lambda (prg argbuf envbuf)
762         (##core#inline "C_flushall")
763         (let ((r (if envbuf
764                      (##core#inline "C_u_i_execve" prg argbuf envbuf)
765                      (##core#inline "C_u_i_execvp" prg argbuf))))
766           (when (fx= r -1)
767             (posix-error #:process-error 'process-execute "cannot execute process" filename))))))))
768
769(set! chicken.process#process-spawn
770  (lambda (mode filename #!optional (arglist '()) envlist exactf)
771    (let ((conv (if exactf (lambda (x) x) quote-arg-string)))
772      (##sys#check-fixnum mode 'process-spawn)
773      (call-with-exec-args
774       'process-spawn filename conv arglist envlist
775       (lambda (prg argbuf envbuf)
776         (##core#inline "C_flushall")
777         (let ((r (if envbuf
778                      (##core#inline "C_u_i_spawnvpe" mode prg argbuf envbuf)
779                      (##core#inline "C_u_i_spawnvp" mode prg argbuf))))
780           (if (fx= r -1)
781               (posix-error #:process-error 'process-spawn
782                            "cannot spawn process" filename)
783               (register-pid r))))))))
784
785(define-foreign-variable _shlcmd c-string "C_shlcmd")
786
787(define (shell-command loc)
788  (or (get-environment-variable "COMSPEC")
789      (if (##core#inline "C_get_shlcmd")
790          _shlcmd
791          (##sys#error/errno
792           (##sys#update-errno) loc "cannot retrieve system directory"))))
793
794(define (shell-command-arguments cmdlin)
795  (list "/c" cmdlin) )
796
797(set! chicken.process#process-run
798  (lambda (f . args)
799    (let ((args (if (pair? args) (car args) #f)))
800      (if args
801          (chicken.process#process-spawn
802           chicken.process#spawn/nowait f args)
803          (chicken.process#process-spawn
804           chicken.process#spawn/nowait
805           (shell-command 'process-run)
806           (shell-command-arguments f)) ) ) ) )
807
808;;; Run subprocess connected with pipes:
809(define-foreign-variable _rdbuf char "C_rdbuf")
810(define-foreign-variable _wr0 int "C_wr0_")
811(define-foreign-variable _rd1 int "C_rd1_")
812
813; from original by Mejedi
814;; process-impl
815; loc            caller procedure symbol
816; cmd            pathname or commandline
817; args           string-list or '()
818; env            string-list or #f (currently ignored)
819; stdoutf        #f then share, or #t then create
820; stdinf         #f then share, or #t then create
821; stderrf        #f then share, or #t then create
822;
823; (values stdin-input-port? stdout-output-port? pid stderr-input-port?)
824; where stdin-input-port?, etc. is a port or #f, indicating no port created.
825
826(define process-impl
827  ;; XXX TODO: When environment is implemented, check for embedded NUL bytes!
828  (let ([c-process
829          (foreign-lambda bool "C_process" c-string scheme-object c-pointer
830            (c-pointer int) (c-pointer int) (c-pointer int) (c-pointer int) int)])
831    ; The environment list must be sorted & include current directory
832    ; information for the system drives. i.e !C:=...
833    ; For now any environment is ignored.
834    (lambda (loc cmd args env stdoutf stdinf stderrf exactf enc)
835      (let* ((arglist (cons cmd args))
836             (cmdlin (string-intersperse
837                      (if exactf
838                          arglist
839                          (map quote-arg-string arglist)))))
840        (let-location ([handle int -1]
841                       [stdin_fd int -1] [stdout_fd int -1] [stderr_fd int -1])
842          (let ([res
843                  (c-process cmd (##sys#slot cmdlin 0) #f
844                    (location handle)
845                    (location stdin_fd) (location stdout_fd) (location stderr_fd)
846                    (+ (if stdinf 0 1) (if stdoutf 0 2) (if stderrf 0 4)))])
847            (if res
848              (make-process
849               handle #f
850               (and stdinf (chicken.file.posix#open-output-file*
851                            stdin_fd))  ;Parent stdout
852               (and stdoutf (chicken.file.posix#open-input-file*
853                             stdout_fd)) ;Parent stdin
854               (and stderrf (chicken.file.posix#open-input-file*
855                             stderr_fd))
856               #f)
857              (##sys#signal-hook/errno
858               #:process-error (##sys#update-errno) loc "cannot execute process" cmdlin))))))))
859
860;; TODO: See if this can be moved to posix-common
861(let ((%process
862        (lambda (loc err? cmd args env exactf enc)
863          (let ((chkstrlst
864                 (lambda (lst)
865                   (##sys#check-list lst loc)
866                   (for-each (cut ##sys#check-string <> loc) lst) )))
867            (##sys#check-string cmd loc)
868            (if args
869              (chkstrlst args)
870              (begin
871                (set! exactf #t)
872                (set! args (shell-command-arguments cmd))
873                (set! cmd (shell-command loc)) ) )
874            (when env (check-environment-list env loc))
875            (process-impl loc cmd args env #t #t err? exactf enc)))))
876  (set! chicken.process#process
877    (lambda (cmd #!optional args env (enc 'utf-8) exactf)
878      (%process 'process #f cmd args env exactf enc) ))
879  (set! chicken.process#process*
880    (lambda (cmd #!optional args env (enc 'utf-8) exactf)
881      (%process 'process* #t cmd args env exactf enc) )) )
882
883(define-foreign-variable _exstatus int "C_exstatus")
884(define-foreign-variable _pid int "C_pid")
885
886(define (process-wait-impl pid nohang)
887  (cond ((##core#inline "C_process_wait" pid nohang)
888          (values _pid #t _exstatus))
889        (else (values -1 #f #f) ) ))
890
891
892;;; Getting group- and user-information:
893
894(define-foreign-variable _username c-string "C_username")
895
896(set! chicken.process-context.posix#current-user-name
897  (lambda ()
898    (if (##core#inline "C_get_user_name")
899        _username
900        (##sys#error/errno
901         (##sys#update-errno) 'current-user-name "cannot retrieve current user-name"))))
902
903
904;;; unimplemented stuff:
905
906(define-unimplemented chown) ; covers set-file-group! and set-file-owner!
907(set!-unimplemented chicken.file.posix#create-fifo)
908(set!-unimplemented chicken.process-context.posix#create-session)
909(set!-unimplemented chicken.file.posix#create-symbolic-link)
910(set!-unimplemented chicken.process-context.posix#current-effective-group-id)
911(set!-unimplemented chicken.process-context.posix#current-effective-user-id)
912(set!-unimplemented chicken.process-context.posix#current-effective-user-name)
913(set!-unimplemented chicken.process-context.posix#current-group-id)
914(set!-unimplemented chicken.process-context.posix#current-user-id)
915(set!-unimplemented chicken.process-context.posix#user-information)
916(set!-unimplemented chicken.file.posix#file-control)
917(set!-unimplemented chicken.file.posix#file-link)
918(set!-unimplemented chicken.file.posix#file-lock)
919(set!-unimplemented chicken.file.posix#file-lock/blocking)
920(set!-unimplemented chicken.file.posix#file-select)
921(set!-unimplemented chicken.file.posix#file-test-lock)
922(set!-unimplemented chicken.file.posix#file-truncate)
923(set!-unimplemented chicken.file.posix#file-unlock)
924(set!-unimplemented chicken.process-context.posix#parent-process-id)
925(set!-unimplemented chicken.process#process-fork)
926(set!-unimplemented chicken.process-context.posix#process-group-id)
927(set!-unimplemented chicken.process#process-signal)
928(set!-unimplemented chicken.file.posix#read-symbolic-link)
929(set!-unimplemented chicken.process.signal#set-alarm!)
930(set!-unimplemented chicken.process-context.posix#set-root-directory!)
931(set!-unimplemented chicken.process.signal#set-signal-mask!)
932(set!-unimplemented chicken.process.signal#signal-mask)
933(set!-unimplemented chicken.process.signal#signal-mask!)
934(set!-unimplemented chicken.process.signal#signal-masked?)
935(set!-unimplemented chicken.process.signal#signal-unmask!)
936(set!-unimplemented chicken.process-context.posix#user-information)
937(set!-unimplemented chicken.time.posix#utc-time->seconds)
938(set!-unimplemented chicken.time.posix#string->time)
939
940;; Unix-only definitions
941(set! chicken.file.posix#fcntl/dupfd 0)
942(set! chicken.file.posix#fcntl/getfd 0)
943(set! chicken.file.posix#fcntl/setfd 0)
944(set! chicken.file.posix#fcntl/getfl 0)
945(set! chicken.file.posix#fcntl/setfl 0)
946(set! chicken.file.posix#open/noctty 0)
947(set! chicken.file.posix#open/nonblock 0)
948(set! chicken.file.posix#open/fsync 0)
949(set! chicken.file.posix#open/sync 0)
950(set! chicken.file.posix#perm/isgid 0)
951(set! chicken.file.posix#perm/isuid 0)
952(set! chicken.file.posix#perm/isvtx 0)
Trap