blob: 5d39630a9314a70e8078fab4e6ce5b4b3551be20 [file] [log] [blame]
Elliott Hughesa3c3f962017-04-12 16:52:30 -07001/*-
2 * Copyright (c) 2015
3 * KO Myung-Hun <komh@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
33static char *remove_trailing_dots(char *);
34static int access_stat_ex(int (*)(), const char *, void *);
35static int test_exec_exist(const char *, char *);
36static void response(int *, const char ***);
37static char *make_response_file(char * const *);
38static void env_slashify(void);
39static void add_temp(const char *);
40static void cleanup_temps(void);
41static 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
56static void
57response(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
151static void
152init_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 */
175static void
176env_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
201void
202os2_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
219void
220setextlibpath(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 */
247static char *
248remove_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() */
265extern int _std_stat(const char *, struct stat *);
266
267/* replacement for stat() of kLIBC which fails if there are trailing dots */
268int
269stat(const char *name, struct stat *buffer)
270{
271 return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
272}
273
274/* alias of access() */
275extern int _std_access(const char *, int);
276
277/* replacement for access() of kLIBC which fails if there are trailing dots */
278int
279access(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
293static const char *x_suffix_list[] =
294 { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
295
296/* call fn() by appending executable extensions */
297static int
298access_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 */
321int
322access_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 */
329int
330stat_ex(const char *name, struct stat *buffer)
331{
332 return (access_stat_ex(stat, name, buffer));
333}
334
335static int
336test_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
349const char *
350real_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 */
366static char *
367make_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() */
408extern int _std_execve(const char *, char * const *, char * const *);
409
410/* replacement for execve() of kLIBC */
411int
412execve(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
505static struct temp *templist = NULL;
506
507static void
508add_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() */
519extern int _std_unlink(const char *);
520
521/*
522 * Replacement for unlink() of kLIBC not supporting to remove files used by
523 * another processes.
524 */
525int
526unlink(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
537static void
538cleanup_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
553static void
554cleanup(void)
555{
556 cleanup_temps();
557}