OSDN Git Service

Merge "Upgrade to mksh R56b." am: a1899ee519 am: e1c2b662fd
[android-x86/external-mksh.git] / src / os2.c
1 /*-
2  * Copyright (c) 2015
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_DOS
24 #include <os2.h>
25
26 #include "sh.h"
27
28 #include <klibc/startup.h>
29 #include <io.h>
30 #include <unistd.h>
31 #include <process.h>
32
33 __RCSID("$MirOS: src/bin/mksh/os2.c,v 1.2 2017/04/29 22:04:29 tg Exp $");
34
35 static char *remove_trailing_dots(char *);
36 static int access_stat_ex(int (*)(), const char *, void *);
37 static int test_exec_exist(const char *, char *);
38 static void response(int *, const char ***);
39 static char *make_response_file(char * const *);
40 static void env_slashify(void);
41 static void add_temp(const char *);
42 static void cleanup_temps(void);
43 static 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
58 static void
59 response(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
153 static void
154 init_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
172 /*
173  * Convert backslashes of environmental variables to forward slahes.
174  * A backslash may be used as an escaped character when doing 'echo'.
175  * This leads to an unexpected behavior.
176  */
177 static void
178 env_slashify(void)
179 {
180         /*
181          * PATH and TMPDIR are used by OS/2 as well. That is, they may
182          * have backslashes as a directory separator.
183          * BEGINLIBPATH and ENDLIBPATH are special variables on OS/2.
184          */
185         const char *var_list[] = {
186                 "PATH",
187                 "TMPDIR",
188                 "BEGINLIBPATH",
189                 "ENDLIBPATH",
190                 NULL
191         };
192         const char **var;
193         char *value;
194
195         for (var = var_list; *var; var++) {
196                 value = getenv(*var);
197
198                 if (value)
199                         _fnslashify(value);
200         }
201 }
202
203 void
204 os2_init(int *argcp, const char ***argvp)
205 {
206         response(argcp, argvp);
207
208         init_extlibpath();
209         env_slashify();
210
211         if (!isatty(STDIN_FILENO))
212                 setmode(STDIN_FILENO, O_BINARY);
213         if (!isatty(STDOUT_FILENO))
214                 setmode(STDOUT_FILENO, O_BINARY);
215         if (!isatty(STDERR_FILENO))
216                 setmode(STDERR_FILENO, O_BINARY);
217
218         atexit(cleanup);
219 }
220
221 void
222 setextlibpath(const char *name, const char *val)
223 {
224         int flag;
225         char *p, *cp;
226
227         if (!strcmp(name, "BEGINLIBPATH"))
228                 flag = BEGIN_LIBPATH;
229         else if (!strcmp(name, "ENDLIBPATH"))
230                 flag = END_LIBPATH;
231         else if (!strcmp(name, "LIBPATHSTRICT"))
232                 flag = LIBPATHSTRICT;
233         else
234                 return;
235
236         /* convert slashes to backslashes */
237         strdupx(cp, val, ATEMP);
238         for (p = cp; *p; p++) {
239                 if (*p == '/')
240                         *p = '\\';
241         }
242
243         DosSetExtLIBPATH(cp, flag);
244
245         afree(cp, ATEMP);
246 }
247
248 /* remove trailing dots */
249 static char *
250 remove_trailing_dots(char *name)
251 {
252         char *p = strnul(name);
253
254         while (--p > name && *p == '.')
255                 /* nothing */;
256
257         if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
258                 p[1] = '\0';
259
260         return (name);
261 }
262
263 #define REMOVE_TRAILING_DOTS(name)      \
264         remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
265
266 /* alias of stat() */
267 extern int _std_stat(const char *, struct stat *);
268
269 /* replacement for stat() of kLIBC which fails if there are trailing dots */
270 int
271 stat(const char *name, struct stat *buffer)
272 {
273         return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
274 }
275
276 /* alias of access() */
277 extern int _std_access(const char *, int);
278
279 /* replacement for access() of kLIBC which fails if there are trailing dots */
280 int
281 access(const char *name, int mode)
282 {
283         /*
284          * On OS/2 kLIBC, X_OK is set only for executable files.
285          * This prevents scripts from being executed.
286          */
287         if (mode & X_OK)
288                 mode = (mode & ~X_OK) | R_OK;
289
290         return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
291 }
292
293 #define MAX_X_SUFFIX_LEN        4
294
295 static const char *x_suffix_list[] =
296     { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
297
298 /* call fn() by appending executable extensions */
299 static int
300 access_stat_ex(int (*fn)(), const char *name, void *arg)
301 {
302         char *x_name;
303         const char **x_suffix;
304         int rc = -1;
305         size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
306
307         /* otherwise, try to append executable suffixes */
308         x_name = alloc(x_namelen, ATEMP);
309
310         for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
311                 strlcpy(x_name, name, x_namelen);
312                 strlcat(x_name, *x_suffix, x_namelen);
313
314                 rc = fn(x_name, arg);
315         }
316
317         afree(x_name, ATEMP);
318
319         return (rc);
320 }
321
322 /* access()/search_access() version */
323 int
324 access_ex(int (*fn)(const char *, int), const char *name, int mode)
325 {
326         /*XXX this smells fishy --mirabilos */
327         return (access_stat_ex(fn, name, (void *)mode));
328 }
329
330 /* stat() version */
331 int
332 stat_ex(const char *name, struct stat *buffer)
333 {
334         return (access_stat_ex(stat, name, buffer));
335 }
336
337 static int
338 test_exec_exist(const char *name, char *real_name)
339 {
340         struct stat sb;
341
342         if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
343                 return (-1);
344
345         /* safe due to calculations in real_exec_name() */
346         memcpy(real_name, name, strlen(name) + 1);
347
348         return (0);
349 }
350
351 const char *
352 real_exec_name(const char *name)
353 {
354         char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
355         const char *real_name = name;
356
357         if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
358                 /*XXX memory leak */
359                 strdupx(real_name, x_name, ATEMP);
360
361         return (real_name);
362 }
363
364 /* OS/2 can process a command line up to 32 KiB */
365 #define MAX_CMD_LINE_LEN 32768
366
367 /* make a response file to pass a very long command line */
368 static char *
369 make_response_file(char * const *argv)
370 {
371         char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
372         char *rsp_name = &rsp_name_arg[1];
373         int arg_len = 0;
374         int i;
375
376         for (i = 0; argv[i]; i++)
377                 arg_len += strlen(argv[i]) + 1;
378
379         /*
380          * If a length of command line is longer than MAX_CMD_LINE_LEN, then
381          * use a response file. OS/2 cannot process a command line longer
382          * than 32K. Of course, a response file cannot be recognised by a
383          * normal OS/2 program, that is, neither non-EMX or non-kLIBC. But
384          * it cannot accept a command line longer than 32K in itself. So
385          * using a response file in this case, is an acceptable solution.
386          */
387         if (arg_len > MAX_CMD_LINE_LEN) {
388                 int fd;
389                 char *result;
390
391                 if ((fd = mkstemp(rsp_name)) == -1)
392                         return (NULL);
393
394                 /* write all the arguments except a 0th program name */
395                 for (i = 1; argv[i]; i++) {
396                         write(fd, argv[i], strlen(argv[i]));
397                         write(fd, "\n", 1);
398                 }
399
400                 close(fd);
401                 add_temp(rsp_name);
402                 strdupx(result, rsp_name_arg, ATEMP);
403                 return (result);
404         }
405
406         return (NULL);
407 }
408
409 /* alias of execve() */
410 extern int _std_execve(const char *, char * const *, char * const *);
411
412 /* replacement for execve() of kLIBC */
413 int
414 execve(const char *name, char * const *argv, char * const *envp)
415 {
416         const char *exec_name;
417         FILE *fp;
418         char sign[2];
419         char *rsp_argv[3];
420         char *rsp_name_arg;
421         int pid;
422         int status;
423         int fd;
424         int rc;
425
426         /*
427          * #! /bin/sh : append .exe
428          * extproc sh : search sh.exe in PATH
429          */
430         exec_name = search_path(name, path, X_OK, NULL);
431         if (!exec_name) {
432                 errno = ENOENT;
433                 return (-1);
434         }
435
436         /*-
437          * kLIBC execve() has problems when executing scripts.
438          * 1. it fails to execute a script if a directory whose name
439          *    is same as an interpreter exists in a current directory.
440          * 2. it fails to execute a script not starting with sharpbang.
441          * 3. it fails to execute a batch file if COMSPEC is set to a shell
442          *    incompatible with cmd.exe, such as /bin/sh.
443          * And ksh process scripts more well, so let ksh process scripts.
444          */
445         errno = 0;
446         if (!(fp = fopen(exec_name, "rb")))
447                 errno = ENOEXEC;
448
449         if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
450                 errno = ENOEXEC;
451
452         if (fp && fclose(fp))
453                 errno = ENOEXEC;
454
455         if (!errno &&
456             !((sign[0] == 'M' && sign[1] == 'Z') ||
457               (sign[0] == 'N' && sign[1] == 'E') ||
458               (sign[0] == 'L' && sign[1] == 'X')))
459                 errno = ENOEXEC;
460
461         if (errno == ENOEXEC)
462                 return (-1);
463
464         rsp_name_arg = make_response_file(argv);
465
466         if (rsp_name_arg) {
467                 rsp_argv[0] = argv[0];
468                 rsp_argv[1] = rsp_name_arg;
469                 rsp_argv[2] = NULL;
470
471                 argv = rsp_argv;
472         }
473
474         pid = spawnve(P_NOWAIT, exec_name, argv, envp);
475
476         afree(rsp_name_arg, ATEMP);
477
478         if (pid == -1) {
479                 cleanup_temps();
480
481                 return (-1);
482         }
483
484         /* close all opened handles */
485         for (fd = 0; fd < NUFILE; fd++) {
486                 if (fcntl(fd, F_GETFD) == -1)
487                         continue;
488
489                 close(fd);
490         }
491
492         while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
493                 /* nothing */;
494
495         cleanup_temps();
496
497         /* Is this possible? And is this right? */
498         if (rc == -1)
499                 return (-1);
500
501         if (WIFSIGNALED(status))
502                 _exit(ksh_sigmask(WTERMSIG(status)));
503
504         _exit(WEXITSTATUS(status));
505 }
506
507 static struct temp *templist = NULL;
508
509 static void
510 add_temp(const char *name)
511 {
512         struct temp *tp;
513
514         tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
515         memcpy(tp->tffn, name, strlen(name) + 1);
516         tp->next = templist;
517         templist = tp;
518 }
519
520 /* alias of unlink() */
521 extern int _std_unlink(const char *);
522
523 /*
524  * Replacement for unlink() of kLIBC not supporting to remove files used by
525  * another processes.
526  */
527 int
528 unlink(const char *name)
529 {
530         int rc;
531
532         rc = _std_unlink(name);
533         if (rc == -1 && errno != ENOENT)
534                 add_temp(name);
535
536         return (rc);
537 }
538
539 static void
540 cleanup_temps(void)
541 {
542         struct temp *tp;
543         struct temp **tpnext;
544
545         for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
546                 if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
547                         *tpnext = tp->next;
548                         afree(tp, APERM);
549                 } else {
550                         tpnext = &tp->next;
551                 }
552         }
553 }
554
555 static void
556 cleanup(void)
557 {
558         cleanup_temps();
559 }