/*- * Copyright (c) 2015, 2017, 2020 * KO Myung-Hun * Copyright (c) 2017, 2020 * mirabilos * * Provided that these terms and disclaimer and all copyright notices * are retained or reproduced in an accompanying document, permission * is granted to deal in this work without restriction, including un- * limited rights to use, publicly perform, distribute, sell, modify, * merge, give away, or sublicence. * * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to * the utmost extent permitted by applicable law, neither express nor * implied; without malicious intent or gross negligence. In no event * may a licensor, author or contributor be held liable for indirect, * direct, other damage, loss, or other issues arising in any way out * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out * of said person's immediate fault when using the work as intended. */ #define INCL_KBD #define INCL_DOS #include #include "sh.h" #include #include #include #include #include __RCSID("$MirOS: src/bin/mksh/os2.c,v 1.11 2020/10/01 21:13:45 tg Exp $"); struct a_s_arg { union { int (*i)(const char *, int); int (*p)(const char *, void *); } fn; union { int i; void *p; } arg; bool isint; }; static void remove_trailing_dots(char *, size_t); static int access_stat_ex(const char *, struct a_s_arg *); static int test_exec_exist(const char *, void *); static void response(int *, const char ***); static char *make_response_file(char * const *); static void add_temp(const char *); static void cleanup_temps(void); static void cleanup(void); #define RPUT(x) do { \ if (new_argc >= new_alloc) { \ new_alloc += 20; \ if (!(new_argv = realloc(new_argv, \ new_alloc * sizeof(char *)))) \ goto exit_out_of_memory; \ } \ new_argv[new_argc++] = (x); \ } while (/* CONSTCOND */ 0) #define KLIBC_ARG_RESPONSE_EXCLUDE \ (__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL) static void response(int *argcp, const char ***argvp) { int i, old_argc, new_argc, new_alloc = 0; const char **old_argv, **new_argv; char *line, *l, *p; FILE *f; old_argc = *argcp; old_argv = *argvp; for (i = 1; i < old_argc; ++i) if (old_argv[i] && !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) && old_argv[i][0] == '@') break; if (i >= old_argc) /* do nothing */ return; new_argv = NULL; new_argc = 0; for (i = 0; i < old_argc; ++i) { if (i == 0 || !old_argv[i] || (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) || old_argv[i][0] != '@' || !(f = fopen(old_argv[i] + 1, "rt"))) RPUT(old_argv[i]); else { long filesize; fseek(f, 0, SEEK_END); filesize = ftell(f); fseek(f, 0, SEEK_SET); line = malloc(filesize + /* type */ 1 + /* NUL */ 1); if (!line) { exit_out_of_memory: fputs("Out of memory while reading response file\n", stderr); exit(255); } line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE; l = line + 1; while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) { p = strchr(l, '\n'); if (p) { /* * if a line ends with a backslash, * concatenate with the next line */ if (p > l && p[-1] == '\\') { char *p1; int count = 0; for (p1 = p - 1; p1 >= l && *p1 == '\\'; p1--) count++; if (count & 1) { l = p + 1; continue; } } *p = 0; } p = strdup(line); if (!p) goto exit_out_of_memory; RPUT(p + 1); l = line + 1; } free(line); if (ferror(f)) { fputs("Cannot read response file\n", stderr); exit(255); } fclose(f); } } RPUT(NULL); --new_argc; *argcp = new_argc; *argvp = new_argv; } static void init_extlibpath(void) { const char *vars[] = { "BEGINLIBPATH", "ENDLIBPATH", "LIBPATHSTRICT", NULL }; char val[512]; int flag; for (flag = 0; vars[flag]; flag++) { DosQueryExtLIBPATH(val, flag + 1); if (val[0]) setenv(vars[flag], val, 1); } } void os2_init(int *argcp, const char ***argvp) { KBDINFO ki; response(argcp, argvp); init_extlibpath(); if (!isatty(STDIN_FILENO)) setmode(STDIN_FILENO, O_BINARY); if (!isatty(STDOUT_FILENO)) setmode(STDOUT_FILENO, O_BINARY); if (!isatty(STDERR_FILENO)) setmode(STDERR_FILENO, O_BINARY); /* ensure ECHO mode is ON so that read command echoes. */ memset(&ki, 0, sizeof(ki)); ki.cb = sizeof(ki); ki.fsMask |= KEYBOARD_ECHO_ON; KbdSetStatus(&ki, 0); atexit(cleanup); } void setextlibpath(const char *name, const char *val) { int flag; char *p, *cp; if (!strcmp(name, "BEGINLIBPATH")) flag = BEGIN_LIBPATH; else if (!strcmp(name, "ENDLIBPATH")) flag = END_LIBPATH; else if (!strcmp(name, "LIBPATHSTRICT")) flag = LIBPATHSTRICT; else return; /* convert slashes to backslashes */ strdupx(cp, val, ATEMP); for (p = cp; *p; p++) { if (*p == '/') *p = '\\'; } DosSetExtLIBPATH(cp, flag); afree(cp, ATEMP); } /* remove trailing dots */ static void remove_trailing_dots(char *name, size_t namelen) { char *p = name + namelen; while (--p > name && *p == '.') /* nothing */; if (*p != '.' && *p != '/' && *p != '\\' && *p != ':') p[1] = '\0'; } /* alias of stat() */ extern int _std_stat(const char *, struct stat *); /* replacement for stat() of kLIBC which fails if there are trailing dots */ int stat(const char *name, struct stat *buffer) { size_t namelen = strlen(name) + 1; char nodots[namelen]; memcpy(nodots, name, namelen); remove_trailing_dots(nodots, namelen); return (_std_stat(nodots, buffer)); } /* alias of access() */ extern int _std_access(const char *, int); /* replacement for access() of kLIBC which fails if there are trailing dots */ int access(const char *name, int mode) { size_t namelen = strlen(name) + 1; char nodots[namelen]; /* * On OS/2 kLIBC, X_OK is set only for executable files. * This prevents scripts from being executed. */ if (mode & X_OK) mode = (mode & ~X_OK) | R_OK; memcpy(nodots, name, namelen); remove_trailing_dots(nodots, namelen); return (_std_access(nodots, mode)); } #define MAX_X_SUFFIX_LEN 4 static const char *x_suffix_list[] = { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL }; /* call fn() by appending executable extensions */ static int access_stat_ex(const char *name, struct a_s_arg *action) { char *x_name; const char **x_suffix; int rc = -1; size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1; /* otherwise, try to append executable suffixes */ x_name = alloc(x_namelen, ATEMP); for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) { strlcpy(x_name, name, x_namelen); strlcat(x_name, *x_suffix, x_namelen); rc = action->isint ? action->fn.i(x_name, action->arg.i) : action->fn.p(x_name, action->arg.p); } afree(x_name, ATEMP); return (rc); } /* access()/search_access() version */ int access_ex(int (*fn)(const char *, int), const char *name, int mode) { struct a_s_arg arg; arg.fn.i = fn; arg.arg.i = mode; arg.isint = true; return (access_stat_ex(name, &arg)); } /* stat()/lstat() version */ int stat_ex(int (*fn)(const char *, struct stat *), const char *name, struct stat *buffer) { struct a_s_arg arg; arg.fn.p = fn; arg.arg.p = buffer; arg.isint = false; return (access_stat_ex(name, &arg)); } static int test_exec_exist(const char *name, void *arg) { struct stat sb; char *real_name; if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode)) return (-1); /*XXX memory leak */ strdupx(real_name, name, ATEMP); *((char **)arg) = real_name; return (0); } const char * real_exec_name(const char *name) { struct a_s_arg arg; char *real_name; arg.fn.p = &test_exec_exist; arg.arg.p = (void *)(&real_name); arg.isint = false; return (access_stat_ex(name, &arg) ? name : real_name); } /* make a response file to pass a very long command line */ static char * make_response_file(char * const *argv) { char rsp_name_arg[] = "@mksh-rsp-XXXXXX"; char *rsp_name = &rsp_name_arg[1]; int i; int fd; char *result; if ((fd = mkstemp(rsp_name)) == -1) return (NULL); /* write all the arguments except a 0th program name */ for (i = 1; argv[i]; i++) { write(fd, argv[i], strlen(argv[i])); write(fd, "\n", 1); } close(fd); add_temp(rsp_name); strdupx(result, rsp_name_arg, ATEMP); return (result); } /* alias of execve() */ extern int _std_execve(const char *, char * const *, char * const *); /* replacement for execve() of kLIBC */ int execve(const char *name, char * const *argv, char * const *envp) { const char *exec_name; FILE *fp; char sign[2]; int pid; int status; int fd; int rc; int saved_mode; int saved_errno; /* * #! /bin/sh : append .exe * extproc sh : search sh.exe in PATH */ exec_name = search_path(name, path, X_OK, NULL); if (!exec_name) { errno = ENOENT; return (-1); } /*- * kLIBC execve() has problems when executing scripts. * 1. it fails to execute a script if a directory whose name * is same as an interpreter exists in a current directory. * 2. it fails to execute a script not starting with sharpbang. * 3. it fails to execute a batch file if COMSPEC is set to a shell * incompatible with cmd.exe, such as /bin/sh. * And ksh process scripts more well, so let ksh process scripts. */ errno = 0; if (!(fp = fopen(exec_name, "rb"))) errno = ENOEXEC; if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign)) errno = ENOEXEC; if (fp && fclose(fp)) errno = ENOEXEC; if (!errno && !((sign[0] == 'M' && sign[1] == 'Z') || (sign[0] == 'N' && sign[1] == 'E') || (sign[0] == 'L' && sign[1] == 'X'))) errno = ENOEXEC; if (errno == ENOEXEC) return (-1); /* * Normal OS/2 programs expect that standard IOs, especially stdin, * are opened in text mode at the startup. By the way, on OS/2 kLIBC * child processes inherit a translation mode of a parent process. * As a result, if stdin is set to binary mode in a parent process, * stdin of child processes is opened in binary mode as well at the * startup. In this case, some programs such as sed suffer from CR. */ saved_mode = setmode(STDIN_FILENO, O_TEXT); pid = spawnve(P_NOWAIT, exec_name, argv, envp); saved_errno = errno; /* arguments too long? */ if (pid == -1 && saved_errno == EINVAL) { /* retry with a response file */ char *rsp_name_arg = make_response_file(argv); if (rsp_name_arg) { char *rsp_argv[3] = { argv[0], rsp_name_arg, NULL }; pid = spawnve(P_NOWAIT, exec_name, rsp_argv, envp); saved_errno = errno; afree(rsp_name_arg, ATEMP); } } /* restore translation mode of stdin */ setmode(STDIN_FILENO, saved_mode); if (pid == -1) { cleanup_temps(); errno = saved_errno; return (-1); } /* close all opened handles */ for (fd = 0; fd < NUFILE; fd++) { if (fcntl(fd, F_GETFD) == -1) continue; close(fd); } while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR) /* nothing */; cleanup_temps(); /* Is this possible? And is this right? */ if (rc == -1) return (-1); if (WIFSIGNALED(status)) _exit(ksh_sigmask(WTERMSIG(status))); _exit(WEXITSTATUS(status)); } static struct temp *templist = NULL; static void add_temp(const char *name) { struct temp *tp; tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM); memcpy(tp->tffn, name, strlen(name) + 1); tp->next = templist; templist = tp; } /* alias of unlink() */ extern int _std_unlink(const char *); /* * Replacement for unlink() of kLIBC not supporting to remove files used by * another processes. */ int unlink(const char *name) { int rc; rc = _std_unlink(name); if (rc == -1 && errno != ENOENT) add_temp(name); return (rc); } static void cleanup_temps(void) { struct temp *tp; struct temp **tpnext; for (tpnext = &templist, tp = templist; tp; tp = *tpnext) { if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) { *tpnext = tp->next; afree(tp, APERM); } else { tpnext = &tp->next; } } } static void cleanup(void) { cleanup_temps(); } int getdrvwd(char **cpp, unsigned int drvltr) { PBYTE cp; ULONG sz; APIRET rc; ULONG drvno; if (DosQuerySysInfo(QSV_MAX_PATH_LENGTH, QSV_MAX_PATH_LENGTH, &sz, sizeof(sz)) != 0) { errno = EDOOFUS; return (-1); } /* allocate 'X:/' plus sz plus NUL */ checkoktoadd((size_t)sz, (size_t)4); cp = aresize(*cpp, (size_t)sz + (size_t)4, ATEMP); cp[0] = ksh_toupper(drvltr); cp[1] = ':'; cp[2] = '/'; drvno = ksh_numuc(cp[0]) + 1; /* NUL is part of space within buffer passed */ ++sz; if ((rc = DosQueryCurrentDir(drvno, cp + 3, &sz)) == 0) { /* success! */ *cpp = cp; return (0); } afree(cp, ATEMP); *cpp = NULL; switch (rc) { case 15: /* invalid drive */ errno = ENOTBLK; break; case 26: /* not dos disk */ errno = ENODEV; break; case 108: /* drive locked */ errno = EDEADLK; break; case 111: /* buffer overflow */ errno = ENAMETOOLONG; break; default: errno = EINVAL; } return (-1); }