/* gawkmisc.c --- miscellaneous gawk routines that are OS specific. -*-C-*- */ /* * Copyright (C) 1986, 1988, 1989, 1991 - 2003, 2012, 2016-2019, 2021, 2022, * the Free Software Foundation, Inc. * * This file is part of GAWK, the GNU implementation of the * AWK Progamming Language. * * GAWK is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GAWK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ const char quote = '\''; const char envsep = ';'; # ifdef DEFPATH const char *defpath = DEFPATH; # else const char *defpath = ".;c:\\lib\\awk;c:\\gnu\\lib\\awk"; # endif /* the Makefile should define DEFLIBPATH */ const char *deflibpath = DEFLIBPATH; #ifdef __MINGW32__ #ifdef HAVE_SOCKETS #include #undef socket #undef setsockopt #undef bind #undef connect #undef listen #undef accept #undef recvfrom #undef shutdown #endif #define WIN32_LEAN_AND_MEAN #include /* gawk_name --- pull out the "gawk" part from how the OS called us */ const char * gawk_name(filespec) const char *filespec; { char *p, *q; p = (char *) filespec; /* Sloppy... */ /* OS/2 allows / for directory separator too */ if ((q = strrchr(p, '\\')) != NULL) p = q + 1; if ((q = strrchr(p, '/')) != NULL && (p == NULL || q > p)) /* support mixed d:\foo/bar\gawk.exe */ p = q + 1; if ((q = strchr(p, '.')) != NULL) *q = '\0'; return strlwr(p); } /* os_arg_fixup --- fixup the command line */ void os_arg_fixup(argcp, argvp) int *argcp; char ***argvp; { return; } /* os_devopen --- open special per-OS devices */ int os_devopen(name, flag) const char *name; int flag; { if (strcmp(name, "/dev/null") == 0) return open("NUL", flag); /* FIXME: */ /* else if (strcmp(name, "/dev/tty") == 0) * return open("???", flag); */ return -1; } /* optimal_bufsize --- determine optimal buffer size */ size_t optimal_bufsize(fd, stb) int fd; struct stat *stb; { /* force all members to zero in case OS doesn't use all of them. */ memset(stb, '\0', sizeof(struct stat)); /* * DOS doesn't have the file system block size in the * stat structure. So we have to make some sort of reasonable * guess. We use stdio's BUFSIZ, since that is what it was * meant for in the first place. */ #define DEFBLKSIZE BUFSIZ if (fstat(fd, stb) == -1) fatal("can't stat fd %d (%s)", fd, strerror(errno)); if (S_ISREG(stb->st_mode) && 0 < stb->st_size && stb->st_size < DEFBLKSIZE) /* small file */ return stb->st_size; return DEFBLKSIZE; } /* ispath --- return true if path has directory components */ int ispath(file) const char *file; { for (; *file; file++) { switch (*file) { case '/': case '\\': case ':': return 1; } } return 0; } /* isdirpunct --- return true if char is a directory separator */ int isdirpunct(c) int c; { return (strchr(":\\/", c) != NULL); } /* os_close_on_exec --- set close on exec flag, print warning if fails */ void os_close_on_exec(fd, name, what, dir) int fd; const char *name, *what, *dir; { HANDLE fh = (HANDLE)_get_osfhandle(fd); if (fh && fh != INVALID_HANDLE_VALUE) SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0); } /* os_isdir --- is this an fd on a directory? */ #if ! defined(S_ISDIR) && defined(S_IFDIR) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif int os_isdir(fd) int fd; { struct stat sbuf; return (fstat(fd, &sbuf) == 0 && S_ISDIR(sbuf.st_mode)); } /* os_isreadable --- fd can be read from */ int os_isreadable(const awk_input_buf_t *iobuf, bool *isdir) { *isdir = false; switch (iobuf->sbuf.st_mode & S_IFMT) { case S_IFREG: case S_IFCHR: /* ttys, /dev/null, .. */ #ifdef S_IFSOCK case S_IFSOCK: #endif #ifdef S_IFIFO case S_IFIFO: #endif return true; case S_IFDIR: *isdir = true; /* fall through */ default: return false; } } /* os_is_setuid --- true if running setuid root */ int os_is_setuid() { return 0; } /* os_setbinmode --- set binary mode on file */ static int orig_tty_mode = -1; int os_setbinmode(fd, mode) int fd, mode; { int prev_mode = setmode(fd, mode); /* Save the original tty mode as we found it. */ if (orig_tty_mode == -1 && fd >= 0 && fd <= 2) orig_tty_mode = prev_mode; return prev_mode; } /* os_restore_mode --- restore the original mode of the console device */ void os_restore_mode (fd) int fd; { if (orig_tty_mode != -1) { setmode(fd, orig_tty_mode); } } /* os_isatty --- return true if fd is a tty */ int os_isatty(int fd) { return (isatty(fd) && lseek(fd, SEEK_CUR, 0) == -1); } /* files_are_same --- return true if files are identical */ int files_are_same(char *path, SRCFILE *src) { struct stat st; size_t pathlen; char *p, *s; if (stat (path, & st) == 0) { /* If they have a working `stat', honor that. */ if (!(st.st_dev == src->sbuf.st_dev && st.st_ino == src->sbuf.st_ino)) return 0; /* Compare modification times. */ if (st.st_mtime != src->mtime) return 0; /* Compare absolute file names case-insensitively, and treat forward- and back-slashes as equal. */ pathlen = strlen(path); for (p = path, s = src->fullpath; p <= path + pathlen; p++, s++) { if (tolower(*p) != tolower(*s) && !((*p == '/' || *p == '\\') && (*s == '/' || *s == '\\'))) return 0; } return 1; } return 0; } extern void *xmalloc (size_t); /* Emulate setenv and unsetenv. Note: this is only actually used in MinGW64 builds, since mingw.org's MinGW has these functions as inlines in stdlib.h since mingwrt-5.0. */ int setenv (const char *name, const char *value, int rewrite) { char *entry; if (*value == '=') ++value; if (getenv (name) && !rewrite) return 0; entry = xmalloc (strlen (name) + 1 + strlen (value) + 1); strcat (strcat (strcpy (entry, name), "="), value); if (putenv (entry) != 0) { free (entry); return -1; } return 0; } int unsetenv (const char *name) { if (!name || !*name || strchr (name, '=') != NULL) return -1; return setenv (name, "", 1); } /* MinGW 3.21 and later defines usleep as an inline function in unistd.h, which conflicts with the version below. */ #if __MINGW32_MAJOR_VERSION + (__MINGW32_MINOR_VERSION > 20) < 4 int usleep(unsigned int usec) { double msecf = usec / 1000.0; Sleep ((DWORD)msecf); return usec - msecf * 1000 < 0 ? 0 : (int)(usec - msecf * 1000); } #endif /* The implementation of wctob in the MS runtime is problematic because it doesn't allow to distinguish between WEOF and 0xff, due to integer sign extension. It also causes failures in dfa.c when characters with the 8th bit set are involved. This replacement version fixes that. */ #include int wctob (wint_t wc) { char buf[64]; if (!(MB_CUR_MAX <= sizeof (buf))) abort (); /* Handle the case where WEOF is a value that does not fit in a wchar_t. */ if (wc == (wchar_t)wc) if (wctomb (buf, (wchar_t)wc) == 1) return (unsigned char) buf[0]; return EOF; } #undef setlocale #include /* On Posix systems, 'setlocale' looks at LC_* variables in the environment, and Gawk users might expect that on Windows as well. The replacement implementation below does that, and also fixes a few additional quirks with locales on Windows. */ static const char * lc_var (int category) { static const char *loc_name[LC_MAX - LC_MIN + 1] = { "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME" }; /* This function assumes LC_* categories are small numbers between 0 and 5, as shown above, so if that changes at some point, complain vociferously. */ if (LC_ALL != 0 || LC_CTYPE != 2 || LC_TIME != 5) abort (); /* Ensured by the caller, so should never happen. */ if (category < LC_MIN || category > LC_MAX) return "????"; return loc_name[category]; } char * w32_setlocale (int category, const char *value) { const char *new_locale = value; if (LC_MIN <= category && category <= LC_MAX && value && *value == '\0') { const char *lc_val = getenv ("LC_ALL"); if (!lc_val) lc_val = getenv (lc_var (category)); if (!lc_val) lc_val = getenv ("LANG"); if (lc_val) new_locale = lc_val; } /* If VALUE includes a codeset, i.e. a Windows codepage number, we must also set the LC_CTYPE locale to the same value, because LC_CTYPE is the only category which is documented to be able to change the codepage. */ if (category != LC_ALL && category != LC_CTYPE) { const char *p = strchr (new_locale, '.'); if (p && isdigit (p[1])) setlocale (LC_CTYPE, new_locale); } return setlocale (category, new_locale); } /* Replacement for the missing nl_langinfo. Only CODESET is currently supported. */ #include char * nl_langinfo (int item) { switch (item) { case CODESET: { /* Shamelessly stolen from Gnulib's nl_langinfo.c. */ static char buf[2 + 10 + 1]; char const *locale = setlocale (LC_CTYPE, NULL); char *codeset = buf; size_t codesetlen; codeset[0] = '\0'; if (locale && locale[0]) { /* If the locale name contains an encoding after the dot, return it. */ char *dot = strchr (locale, '.'); if (dot) { /* Look for the possible @... trailer and remove it, if any. */ char *codeset_start = dot + 1; char const *modifier = strchr (codeset_start, '@'); if (! modifier) codeset = codeset_start; else { codesetlen = modifier - codeset_start; if (codesetlen < sizeof buf) { codeset = memcpy (buf, codeset_start, codesetlen); codeset[codesetlen] = '\0'; } } } } /* If setlocale is successful, it returns the number of the codepage, as a string. Otherwise, fall back on Windows API GetACP, which returns the locale's codepage as a number (although this doesn't change according to what the 'setlocale' call specified). Either way, prepend "CP" to make it a valid codeset name. */ codesetlen = strlen (codeset); if (0 < codesetlen && codesetlen < sizeof buf - 2) memmove (buf + 2, codeset, codesetlen + 1); else sprintf (buf + 2, "%u", GetACP ()); codeset = memcpy (buf, "CP", 2); return codeset; } default: return (char *) ""; } } /* * On MS-Windows with MinGW, execvp causes the shell and the re-exec'ed * dgawk to compete for the keyboard input. * * This will need work if we ever need a real version of execvp. */ int w32_execvp(const char *file, char **argv) { if (_spawnvp(_P_WAIT, file, (const char * const *)argv) != -1) exit(EXIT_SUCCESS); return -1; } #ifdef DYNAMIC #include #include static DWORD last_err; void * dlopen (const char *file, int mode) { char dllfn[MAX_PATH], *p; HANDLE dllhandle; if (mode != RTLD_LAZY) { errno = EINVAL; last_err = ERROR_INVALID_PARAMETER; return NULL; } /* MSDN says to be sure to use backslashes in the DLL file name. */ strcpy (dllfn, file); for (p = dllfn; *p; p++) if (*p == '/') *p = '\\'; dllhandle = LoadLibrary (dllfn); if (!dllhandle) last_err = GetLastError (); return dllhandle; } char * dlerror (void) { static char errbuf[1024]; DWORD ret; if (!last_err) return NULL; ret = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_err, 0, errbuf, sizeof (errbuf), NULL); while (ret > 0 && (errbuf[ret - 1] == '\n' || errbuf[ret - 1] == '\r')) --ret; errbuf[ret] = '\0'; if (!ret) sprintf (errbuf, "Error code %lu", last_err); last_err = 0; return errbuf; } int dlclose (void *handle) { if (!handle || handle == INVALID_HANDLE_VALUE) return -1; if (!FreeLibrary (handle)) return -1; return 0; } void * dlsym (void *handle, const char *name) { FARPROC addr = NULL; if (!handle || handle == INVALID_HANDLE_VALUE) { last_err = ERROR_INVALID_PARAMETER; return NULL; } addr = GetProcAddress (handle, name); if (!addr) last_err = GetLastError (); return (void *)addr; } #endif /* DYNAMIC */ #ifdef HAVE_SOCKETS int socket_to_fd(SOCKET s) { return (s == INVALID_SOCKET ? INVALID_HANDLE : _open_osfhandle (s, O_BINARY | O_NOINHERIT)); } int w32_socket(int family, int type, int protocol) { /* We need to use WSASocket rather than socket, since the latter creates overlapped sockets that cannot be used in file I/O APIs. */ SOCKET s = WSASocket (family, type, protocol, NULL, 0, 0); if (s == INVALID_SOCKET) { switch (WSAGetLastError ()) { case WSAEMFILE: errno = EMFILE; break; case WSANOTINITIALISED: case WSAENETDOWN: errno = EACCES; break; case WSAENOBUFS: errno = ENOMEM; break; case WSAEFAULT: errno = EFAULT; break; default: errno = EINVAL; break; } } return socket_to_fd (s); } int w32_setsockopt (int fd, int level, int optname, const char *optval, int optlen) { SOCKET s = FD_TO_SOCKET (fd); return setsockopt (s, level, optname, optval, optlen); } int w32_bind (int fd, const struct sockaddr *name, int namelen) { SOCKET s = FD_TO_SOCKET (fd); return bind (s, name, namelen); } int w32_connect (int fd, const struct sockaddr *name, int namelen) { SOCKET s = FD_TO_SOCKET (fd); return connect (s, name, namelen); } int w32_listen (int fd, int backlog) { SOCKET s = FD_TO_SOCKET (fd); return listen (s, backlog); } int w32_accept (int fd, struct sockaddr *addr, int *addrlen) { SOCKET s = FD_TO_SOCKET (fd); return socket_to_fd (accept (s, addr, addrlen)); } SOCKET valid_socket (int fd) { SOCKET s = FD_TO_SOCKET (fd); int ov, ol = 4; if (s == INVALID_SOCKET || (getsockopt (s, SOL_SOCKET, SO_TYPE, (char *)&ov, &ol) == SOCKET_ERROR && WSAGetLastError() == WSAENOTSOCK)) return (SOCKET)0; return s; } int w32_closesocket (int fd) { SOCKET s = valid_socket (fd); int res1, res2 = 0; if (!s && fd == FAKE_FD_VALUE) return 0; res1 = close (fd); if (s) res2 = closesocket (s); if (res1 == -1 || res2 == SOCKET_ERROR) return -1; return 0; } int w32_recvfrom (int fd, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) { SOCKET s = FD_TO_SOCKET (fd); return recvfrom (s, buf, len, flags, from, fromlen); } int w32_shutdown (int fd, int how) { SOCKET s = FD_TO_SOCKET (fd); return shutdown (s, how); } #endif /* HAVE_SOCKETS */ /* Translate abnormal exit status of Windows programs into the signal that terminated the program. This is required to support scm_kill and WTERMSIG. */ #include struct signal_and_status { int sig; unsigned status; }; static const struct signal_and_status sigtbl[] = { {SIGSEGV, 0xC0000005}, /* access to invalid address */ {SIGSEGV, 0xC0000008}, /* invalid handle */ {SIGILL, 0xC000001D}, /* illegal instruction */ {SIGILL, 0xC0000025}, /* non-continuable instruction */ {SIGSEGV, 0xC000008C}, /* array bounds exceeded */ {SIGFPE, 0xC000008D}, /* float denormal */ {SIGFPE, 0xC000008E}, /* float divide by zero */ {SIGFPE, 0xC000008F}, /* float inexact */ {SIGFPE, 0xC0000090}, /* float invalid operation */ {SIGFPE, 0xC0000091}, /* float overflow */ {SIGFPE, 0xC0000092}, /* float stack check */ {SIGFPE, 0xC0000093}, /* float underflow */ {SIGFPE, 0xC0000094}, /* integer divide by zero */ {SIGFPE, 0xC0000095}, /* integer overflow */ {SIGILL, 0xC0000096}, /* privileged instruction */ {SIGSEGV, 0xC00000FD}, /* stack overflow */ {SIGTERM, 0xC000013A}, /* Ctrl-C exit */ {SIGINT, 0xC000013A} }; int w32_status_to_termsig (unsigned status) { int i; for (i = 0; i < sizeof (sigtbl) / sizeof (sigtbl[0]); i++) if (status == sigtbl[i].status) return sigtbl[i].sig; return SIGTERM; } /* Emulate the missing strsignal. */ char * strsignal (int signo) { switch (signo) { case SIGINT: return "SIGINT"; case SIGILL: return "SIGILL"; case SIGFPE: return "SIGFPE"; case SIGSEGV: return "SIGSEGV"; case SIGTERM: return "SIGTERM"; case SIGBREAK: return "SIGBREAK"; case SIGABRT: return "SIGABRT"; default: return "Unknown signal"; } } void os_maybe_set_errno (void) { if (errno == 0 || errno == EINVAL) { DWORD w32err = GetLastError (); switch (w32err) { /* When stdout is redirected to a pipe, and the program that reads the pipe (e.g., a pager) exits, Windows doesn't set errno to a useful value. Help it DTRT. */ case ERROR_BAD_PIPE: case ERROR_PIPE_BUSY: case ERROR_NO_DATA: case ERROR_PIPE_NOT_CONNECTED: errno = EPIPE; break; default: errno = EINVAL; break; } } } void init_sockets(void) { #if defined(HAVE_SOCKETS) WSADATA winsockData; int errcode; if ((errcode = WSAStartup (0x101, &winsockData)) != 0 || winsockData.wVersion != 0x101) fatal(_("cannot start Winsock (%d)"), errcode); #endif } #endif /* __MINGW32__ */