1/*- 2 * Copyright (c) 2015, 2017, 2020 3 * KO Myung-Hun <komh@chollian.net> 4 * Copyright (c) 2017, 2020 5 * mirabilos <m@mirbsd.org> 6 * 7 * Provided that these terms and disclaimer and all copyright notices 8 * are retained or reproduced in an accompanying document, permission 9 * is granted to deal in this work without restriction, including un- 10 * limited rights to use, publicly perform, distribute, sell, modify, 11 * merge, give away, or sublicence. 12 * 13 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to 14 * the utmost extent permitted by applicable law, neither express nor 15 * implied; without malicious intent or gross negligence. In no event 16 * may a licensor, author or contributor be held liable for indirect, 17 * direct, other damage, loss, or other issues arising in any way out 18 * of dealing in the work, even if advised of the possibility of such 19 * damage or existence of a defect, except proven that it results out 20 * of said person's immediate fault when using the work as intended. 21 */ 22 23#define INCL_KBD 24#define INCL_DOS 25#include <os2.h> 26 27#include "sh.h" 28 29#include <klibc/startup.h> 30#include <errno.h> 31#include <io.h> 32#include <unistd.h> 33#include <process.h> 34 35__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.11 2020/10/01 21:13:45 tg Exp $"); 36 37struct a_s_arg { 38 union { 39 int (*i)(const char *, int); 40 int (*p)(const char *, void *); 41 } fn; 42 union { 43 int i; 44 void *p; 45 } arg; 46 bool isint; 47}; 48 49static void remove_trailing_dots(char *, size_t); 50static int access_stat_ex(const char *, struct a_s_arg *); 51static int test_exec_exist(const char *, void *); 52static void response(int *, const char ***); 53static char *make_response_file(char * const *); 54static void add_temp(const char *); 55static void cleanup_temps(void); 56static void cleanup(void); 57 58#define RPUT(x) do { \ 59 if (new_argc >= new_alloc) { \ 60 new_alloc += 20; \ 61 if (!(new_argv = realloc(new_argv, \ 62 new_alloc * sizeof(char *)))) \ 63 goto exit_out_of_memory; \ 64 } \ 65 new_argv[new_argc++] = (x); \ 66} while (/* CONSTCOND */ 0) 67 68#define KLIBC_ARG_RESPONSE_EXCLUDE \ 69 (__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL) 70 71static void 72response(int *argcp, const char ***argvp) 73{ 74 int i, old_argc, new_argc, new_alloc = 0; 75 const char **old_argv, **new_argv; 76 char *line, *l, *p; 77 FILE *f; 78 79 old_argc = *argcp; 80 old_argv = *argvp; 81 for (i = 1; i < old_argc; ++i) 82 if (old_argv[i] && 83 !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) && 84 old_argv[i][0] == '@') 85 break; 86 87 if (i >= old_argc) 88 /* do nothing */ 89 return; 90 91 new_argv = NULL; 92 new_argc = 0; 93 for (i = 0; i < old_argc; ++i) { 94 if (i == 0 || !old_argv[i] || 95 (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) || 96 old_argv[i][0] != '@' || 97 !(f = fopen(old_argv[i] + 1, "rt"))) 98 RPUT(old_argv[i]); 99 else { 100 long filesize; 101 102 fseek(f, 0, SEEK_END); 103 filesize = ftell(f); 104 fseek(f, 0, SEEK_SET); 105 106 line = malloc(filesize + /* type */ 1 + /* NUL */ 1); 107 if (!line) { 108 exit_out_of_memory: 109 fputs("Out of memory while reading response file\n", stderr); 110 exit(255); 111 } 112 113 line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE; 114 l = line + 1; 115 while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) { 116 p = strchr(l, '\n'); 117 if (p) { 118 /* 119 * if a line ends with a backslash, 120 * concatenate with the next line 121 */ 122 if (p > l && p[-1] == '\\') { 123 char *p1; 124 int count = 0; 125 126 for (p1 = p - 1; p1 >= l && 127 *p1 == '\\'; p1--) 128 count++; 129 130 if (count & 1) { 131 l = p + 1; 132 133 continue; 134 } 135 } 136 137 *p = 0; 138 } 139 p = strdup(line); 140 if (!p) 141 goto exit_out_of_memory; 142 143 RPUT(p + 1); 144 145 l = line + 1; 146 } 147 148 free(line); 149 150 if (ferror(f)) { 151 fputs("Cannot read response file\n", stderr); 152 exit(255); 153 } 154 155 fclose(f); 156 } 157 } 158 159 RPUT(NULL); 160 --new_argc; 161 162 *argcp = new_argc; 163 *argvp = new_argv; 164} 165 166static void 167init_extlibpath(void) 168{ 169 const char *vars[] = { 170 "BEGINLIBPATH", 171 "ENDLIBPATH", 172 "LIBPATHSTRICT", 173 NULL 174 }; 175 char val[512]; 176 int flag; 177 178 for (flag = 0; vars[flag]; flag++) { 179 DosQueryExtLIBPATH(val, flag + 1); 180 if (val[0]) 181 setenv(vars[flag], val, 1); 182 } 183} 184 185void 186os2_init(int *argcp, const char ***argvp) 187{ 188 KBDINFO ki; 189 190 response(argcp, argvp); 191 192 init_extlibpath(); 193 194 if (!isatty(STDIN_FILENO)) 195 setmode(STDIN_FILENO, O_BINARY); 196 if (!isatty(STDOUT_FILENO)) 197 setmode(STDOUT_FILENO, O_BINARY); 198 if (!isatty(STDERR_FILENO)) 199 setmode(STDERR_FILENO, O_BINARY); 200 201 /* ensure ECHO mode is ON so that read command echoes. */ 202 memset(&ki, 0, sizeof(ki)); 203 ki.cb = sizeof(ki); 204 ki.fsMask |= KEYBOARD_ECHO_ON; 205 KbdSetStatus(&ki, 0); 206 207 atexit(cleanup); 208} 209 210void 211setextlibpath(const char *name, const char *val) 212{ 213 int flag; 214 char *p, *cp; 215 216 if (!strcmp(name, "BEGINLIBPATH")) 217 flag = BEGIN_LIBPATH; 218 else if (!strcmp(name, "ENDLIBPATH")) 219 flag = END_LIBPATH; 220 else if (!strcmp(name, "LIBPATHSTRICT")) 221 flag = LIBPATHSTRICT; 222 else 223 return; 224 225 /* convert slashes to backslashes */ 226 strdupx(cp, val, ATEMP); 227 for (p = cp; *p; p++) { 228 if (*p == '/') 229 *p = '\\'; 230 } 231 232 DosSetExtLIBPATH(cp, flag); 233 234 afree(cp, ATEMP); 235} 236 237/* remove trailing dots */ 238static void 239remove_trailing_dots(char *name, size_t namelen) 240{ 241 char *p = name + namelen; 242 243 while (--p > name && *p == '.') 244 /* nothing */; 245 246 if (*p != '.' && *p != '/' && *p != '\\' && *p != ':') 247 p[1] = '\0'; 248} 249 250/* alias of stat() */ 251extern int _std_stat(const char *, struct stat *); 252 253/* replacement for stat() of kLIBC which fails if there are trailing dots */ 254int 255stat(const char *name, struct stat *buffer) 256{ 257 size_t namelen = strlen(name) + 1; 258 char nodots[namelen]; 259 260 memcpy(nodots, name, namelen); 261 remove_trailing_dots(nodots, namelen); 262 return (_std_stat(nodots, buffer)); 263} 264 265/* alias of access() */ 266extern int _std_access(const char *, int); 267 268/* replacement for access() of kLIBC which fails if there are trailing dots */ 269int 270access(const char *name, int mode) 271{ 272 size_t namelen = strlen(name) + 1; 273 char nodots[namelen]; 274 275 /* 276 * On OS/2 kLIBC, X_OK is set only for executable files. 277 * This prevents scripts from being executed. 278 */ 279 if (mode & X_OK) 280 mode = (mode & ~X_OK) | R_OK; 281 282 memcpy(nodots, name, namelen); 283 remove_trailing_dots(nodots, namelen); 284 return (_std_access(nodots, mode)); 285} 286 287#define MAX_X_SUFFIX_LEN 4 288 289static const char *x_suffix_list[] = 290 { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL }; 291 292/* call fn() by appending executable extensions */ 293static int 294access_stat_ex(const char *name, struct a_s_arg *action) 295{ 296 char *x_name; 297 const char **x_suffix; 298 int rc = -1; 299 size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1; 300 301 /* otherwise, try to append executable suffixes */ 302 x_name = alloc(x_namelen, ATEMP); 303 304 for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) { 305 strlcpy(x_name, name, x_namelen); 306 strlcat(x_name, *x_suffix, x_namelen); 307 308 rc = action->isint ? action->fn.i(x_name, action->arg.i) : 309 action->fn.p(x_name, action->arg.p); 310 } 311 312 afree(x_name, ATEMP); 313 314 return (rc); 315} 316 317/* access()/search_access() version */ 318int 319access_ex(int (*fn)(const char *, int), const char *name, int mode) 320{ 321 struct a_s_arg arg; 322 323 arg.fn.i = fn; 324 arg.arg.i = mode; 325 arg.isint = true; 326 return (access_stat_ex(name, &arg)); 327} 328 329/* stat()/lstat() version */ 330int 331stat_ex(int (*fn)(const char *, struct stat *), 332 const char *name, struct stat *buffer) 333{ 334 struct a_s_arg arg; 335 336 arg.fn.p = fn; 337 arg.arg.p = buffer; 338 arg.isint = false; 339 return (access_stat_ex(name, &arg)); 340} 341 342static int 343test_exec_exist(const char *name, void *arg) 344{ 345 struct stat sb; 346 char *real_name; 347 348 if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode)) 349 return (-1); 350 351 /*XXX memory leak */ 352 strdupx(real_name, name, ATEMP); 353 *((char **)arg) = real_name; 354 return (0); 355} 356 357const char * 358real_exec_name(const char *name) 359{ 360 struct a_s_arg arg; 361 char *real_name; 362 363 arg.fn.p = &test_exec_exist; 364 arg.arg.p = (void *)(&real_name); 365 arg.isint = false; 366 return (access_stat_ex(name, &arg) ? name : real_name); 367} 368 369/* make a response file to pass a very long command line */ 370static char * 371make_response_file(char * const *argv) 372{ 373 char rsp_name_arg[] = "@mksh-rsp-XXXXXX"; 374 char *rsp_name = &rsp_name_arg[1]; 375 int i; 376 int fd; 377 char *result; 378 379 if ((fd = mkstemp(rsp_name)) == -1) 380 return (NULL); 381 382 /* write all the arguments except a 0th program name */ 383 for (i = 1; argv[i]; i++) { 384 write(fd, argv[i], strlen(argv[i])); 385 write(fd, "\n", 1); 386 } 387 388 close(fd); 389 add_temp(rsp_name); 390 strdupx(result, rsp_name_arg, ATEMP); 391 392 return (result); 393} 394 395/* alias of execve() */ 396extern int _std_execve(const char *, char * const *, char * const *); 397 398/* replacement for execve() of kLIBC */ 399int 400execve(const char *name, char * const *argv, char * const *envp) 401{ 402 const char *exec_name; 403 FILE *fp; 404 char sign[2]; 405 int pid; 406 int status; 407 int fd; 408 int rc; 409 int saved_mode; 410 int saved_errno; 411 412 /* 413 * #! /bin/sh : append .exe 414 * extproc sh : search sh.exe in PATH 415 */ 416 exec_name = search_path(name, path, X_OK, NULL); 417 if (!exec_name) { 418 errno = ENOENT; 419 return (-1); 420 } 421 422 /*- 423 * kLIBC execve() has problems when executing scripts. 424 * 1. it fails to execute a script if a directory whose name 425 * is same as an interpreter exists in a current directory. 426 * 2. it fails to execute a script not starting with sharpbang. 427 * 3. it fails to execute a batch file if COMSPEC is set to a shell 428 * incompatible with cmd.exe, such as /bin/sh. 429 * And ksh process scripts more well, so let ksh process scripts. 430 */ 431 errno = 0; 432 if (!(fp = fopen(exec_name, "rb"))) 433 errno = ENOEXEC; 434 435 if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign)) 436 errno = ENOEXEC; 437 438 if (fp && fclose(fp)) 439 errno = ENOEXEC; 440 441 if (!errno && 442 !((sign[0] == 'M' && sign[1] == 'Z') || 443 (sign[0] == 'N' && sign[1] == 'E') || 444 (sign[0] == 'L' && sign[1] == 'X'))) 445 errno = ENOEXEC; 446 447 if (errno == ENOEXEC) 448 return (-1); 449 450 /* 451 * Normal OS/2 programs expect that standard IOs, especially stdin, 452 * are opened in text mode at the startup. By the way, on OS/2 kLIBC 453 * child processes inherit a translation mode of a parent process. 454 * As a result, if stdin is set to binary mode in a parent process, 455 * stdin of child processes is opened in binary mode as well at the 456 * startup. In this case, some programs such as sed suffer from CR. 457 */ 458 saved_mode = setmode(STDIN_FILENO, O_TEXT); 459 460 pid = spawnve(P_NOWAIT, exec_name, argv, envp); 461 saved_errno = errno; 462 463 /* arguments too long? */ 464 if (pid == -1 && saved_errno == EINVAL) { 465 /* retry with a response file */ 466 char *rsp_name_arg = make_response_file(argv); 467 468 if (rsp_name_arg) { 469 char *rsp_argv[3] = { argv[0], rsp_name_arg, NULL }; 470 471 pid = spawnve(P_NOWAIT, exec_name, rsp_argv, envp); 472 saved_errno = errno; 473 474 afree(rsp_name_arg, ATEMP); 475 } 476 } 477 478 /* restore translation mode of stdin */ 479 setmode(STDIN_FILENO, saved_mode); 480 481 if (pid == -1) { 482 cleanup_temps(); 483 484 errno = saved_errno; 485 return (-1); 486 } 487 488 /* close all opened handles */ 489 for (fd = 0; fd < NUFILE; fd++) { 490 if (fcntl(fd, F_GETFD) == -1) 491 continue; 492 493 close(fd); 494 } 495 496 while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR) 497 /* nothing */; 498 499 cleanup_temps(); 500 501 /* Is this possible? And is this right? */ 502 if (rc == -1) 503 return (-1); 504 505 if (WIFSIGNALED(status)) 506 _exit(ksh_sigmask(WTERMSIG(status))); 507 508 _exit(WEXITSTATUS(status)); 509} 510 511static struct temp *templist = NULL; 512 513static void 514add_temp(const char *name) 515{ 516 struct temp *tp; 517 518 tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM); 519 memcpy(tp->tffn, name, strlen(name) + 1); 520 tp->next = templist; 521 templist = tp; 522} 523 524/* alias of unlink() */ 525extern int _std_unlink(const char *); 526 527/* 528 * Replacement for unlink() of kLIBC not supporting to remove files used by 529 * another processes. 530 */ 531int 532unlink(const char *name) 533{ 534 int rc; 535 536 rc = _std_unlink(name); 537 if (rc == -1 && errno != ENOENT) 538 add_temp(name); 539 540 return (rc); 541} 542 543static void 544cleanup_temps(void) 545{ 546 struct temp *tp; 547 struct temp **tpnext; 548 549 for (tpnext = &templist, tp = templist; tp; tp = *tpnext) { 550 if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) { 551 *tpnext = tp->next; 552 afree(tp, APERM); 553 } else { 554 tpnext = &tp->next; 555 } 556 } 557} 558 559static void 560cleanup(void) 561{ 562 cleanup_temps(); 563} 564 565int 566getdrvwd(char **cpp, unsigned int drvltr) 567{ 568 PBYTE cp; 569 ULONG sz; 570 APIRET rc; 571 ULONG drvno; 572 573 if (DosQuerySysInfo(QSV_MAX_PATH_LENGTH, QSV_MAX_PATH_LENGTH, 574 &sz, sizeof(sz)) != 0) { 575 errno = EDOOFUS; 576 return (-1); 577 } 578 579 /* allocate 'X:/' plus sz plus NUL */ 580 checkoktoadd((size_t)sz, (size_t)4); 581 cp = aresize(*cpp, (size_t)sz + (size_t)4, ATEMP); 582 cp[0] = ksh_toupper(drvltr); 583 cp[1] = ':'; 584 cp[2] = '/'; 585 drvno = ksh_numuc(cp[0]) + 1; 586 /* NUL is part of space within buffer passed */ 587 ++sz; 588 if ((rc = DosQueryCurrentDir(drvno, cp + 3, &sz)) == 0) { 589 /* success! */ 590 *cpp = cp; 591 return (0); 592 } 593 afree(cp, ATEMP); 594 *cpp = NULL; 595 switch (rc) { 596 case 15: /* invalid drive */ 597 errno = ENOTBLK; 598 break; 599 case 26: /* not dos disk */ 600 errno = ENODEV; 601 break; 602 case 108: /* drive locked */ 603 errno = EDEADLK; 604 break; 605 case 111: /* buffer overflow */ 606 errno = ENAMETOOLONG; 607 break; 608 default: 609 errno = EINVAL; 610 } 611 return (-1); 612} 613