blob: 2bc63ed1ffa27c2d34defef00f58f2095aa82cbf [file] [log] [blame]
Elliott Hughesa3c3f962017-04-12 16:52:30 -07001/*-
Elliott Hughesdd4abe02018-02-05 15:55:19 -08002 * Copyright (c) 2015, 2017
Elliott Hughesa3c3f962017-04-12 16:52:30 -07003 * KO Myung-Hun <komh@chollian.net>
Elliott Hughes23925bb2017-09-22 16:04:20 -07004 * Copyright (c) 2017
5 * mirabilos <m@mirbsd.org>
Elliott Hughesa3c3f962017-04-12 16:52:30 -07006 *
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>
Elliott Hughesdd4abe02018-02-05 15:55:19 -080029#include <errno.h>
Elliott Hughesa3c3f962017-04-12 16:52:30 -070030#include <io.h>
31#include <unistd.h>
32#include <process.h>
33
Elliott Hughesdd4abe02018-02-05 15:55:19 -080034__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.8 2017/12/22 16:41:42 tg Exp $");
Elliott Hughesa3c3f962017-04-12 16:52:30 -070035
36static char *remove_trailing_dots(char *);
37static int access_stat_ex(int (*)(), const char *, void *);
38static int test_exec_exist(const char *, char *);
39static void response(int *, const char ***);
40static char *make_response_file(char * const *);
Elliott Hughesa3c3f962017-04-12 16:52:30 -070041static void add_temp(const char *);
42static void cleanup_temps(void);
43static 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
58static void
59response(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
153static void
154init_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
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700172void
173os2_init(int *argcp, const char ***argvp)
174{
175 response(argcp, argvp);
176
177 init_extlibpath();
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700178
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
189void
190setextlibpath(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 */
217static char *
218remove_trailing_dots(char *name)
219{
Elliott Hughes23925bb2017-09-22 16:04:20 -0700220 char *p = strnul(name);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700221
Elliott Hughes23925bb2017-09-22 16:04:20 -0700222 while (--p > name && *p == '.')
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700223 /* 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() */
235extern int _std_stat(const char *, struct stat *);
236
237/* replacement for stat() of kLIBC which fails if there are trailing dots */
238int
239stat(const char *name, struct stat *buffer)
240{
241 return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
242}
243
244/* alias of access() */
245extern int _std_access(const char *, int);
246
247/* replacement for access() of kLIBC which fails if there are trailing dots */
248int
249access(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
263static const char *x_suffix_list[] =
264 { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
265
266/* call fn() by appending executable extensions */
267static int
268access_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 */
291int
292access_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 */
299int
300stat_ex(const char *name, struct stat *buffer)
301{
302 return (access_stat_ex(stat, name, buffer));
303}
304
305static int
306test_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
319const char *
320real_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
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700332/* make a response file to pass a very long command line */
333static char *
334make_response_file(char * const *argv)
335{
336 char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
337 char *rsp_name = &rsp_name_arg[1];
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700338 int i;
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800339 int fd;
340 char *result;
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700341
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800342 if ((fd = mkstemp(rsp_name)) == -1)
343 return (NULL);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700344
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800345 /* 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);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700349 }
350
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800351 close(fd);
352 add_temp(rsp_name);
353 strdupx(result, rsp_name_arg, ATEMP);
354
355 return (result);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700356}
357
358/* alias of execve() */
359extern int _std_execve(const char *, char * const *, char * const *);
360
361/* replacement for execve() of kLIBC */
362int
363execve(const char *name, char * const *argv, char * const *envp)
364{
365 const char *exec_name;
366 FILE *fp;
367 char sign[2];
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700368 int pid;
369 int status;
370 int fd;
371 int rc;
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800372 int saved_mode;
373 int saved_errno;
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700374
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
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800413 /*
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);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700422
423 pid = spawnve(P_NOWAIT, exec_name, argv, envp);
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800424 saved_errno = errno;
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700425
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800426 /* 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);
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700443
444 if (pid == -1) {
445 cleanup_temps();
446
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800447 errno = saved_errno;
Elliott Hughesa3c3f962017-04-12 16:52:30 -0700448 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
474static struct temp *templist = NULL;
475
476static void
477add_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() */
488extern int _std_unlink(const char *);
489
490/*
491 * Replacement for unlink() of kLIBC not supporting to remove files used by
492 * another processes.
493 */
494int
495unlink(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
506static void
507cleanup_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
522static void
523cleanup(void)
524{
525 cleanup_temps();
526}
Elliott Hughesdd4abe02018-02-05 15:55:19 -0800527
528int
529getdrvwd(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}