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