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
37 struct 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
49 static void remove_trailing_dots(char *, size_t);
50 static int access_stat_ex(const char *, struct a_s_arg *);
51 static int test_exec_exist(const char *, void *);
52 static void response(int *, const char ***);
53 static char *make_response_file(char * const *);
54 static void add_temp(const char *);
55 static void cleanup_temps(void);
56 static 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
71 static void
response(int * argcp,const char *** argvp)72 response(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
166 static void
init_extlibpath(void)167 init_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
185 void
os2_init(int * argcp,const char *** argvp)186 os2_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
210 void
setextlibpath(const char * name,const char * val)211 setextlibpath(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 */
238 static void
remove_trailing_dots(char * name,size_t namelen)239 remove_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() */
251 extern int _std_stat(const char *, struct stat *);
252
253 /* replacement for stat() of kLIBC which fails if there are trailing dots */
254 int
stat(const char * name,struct stat * buffer)255 stat(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() */
266 extern int _std_access(const char *, int);
267
268 /* replacement for access() of kLIBC which fails if there are trailing dots */
269 int
access(const char * name,int mode)270 access(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
289 static const char *x_suffix_list[] =
290 { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
291
292 /* call fn() by appending executable extensions */
293 static int
access_stat_ex(const char * name,struct a_s_arg * action)294 access_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 */
318 int
access_ex(int (* fn)(const char *,int),const char * name,int mode)319 access_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 */
330 int
stat_ex(int (* fn)(const char *,struct stat *),const char * name,struct stat * buffer)331 stat_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
342 static int
test_exec_exist(const char * name,void * arg)343 test_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
357 const char *
real_exec_name(const char * name)358 real_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 */
370 static char *
make_response_file(char * const * argv)371 make_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() */
396 extern int _std_execve(const char *, char * const *, char * const *);
397
398 /* replacement for execve() of kLIBC */
399 int
execve(const char * name,char * const * argv,char * const * envp)400 execve(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
511 static struct temp *templist = NULL;
512
513 static void
add_temp(const char * name)514 add_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() */
525 extern int _std_unlink(const char *);
526
527 /*
528 * Replacement for unlink() of kLIBC not supporting to remove files used by
529 * another processes.
530 */
531 int
unlink(const char * name)532 unlink(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
543 static void
cleanup_temps(void)544 cleanup_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
559 static void
cleanup(void)560 cleanup(void)
561 {
562 cleanup_temps();
563 }
564
565 int
getdrvwd(char ** cpp,unsigned int drvltr)566 getdrvwd(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