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