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