OSDN Git Service

Upgrade to mksh R56c.
[android-x86/external-mksh.git] / src / os2.c
1 /*-
2  * Copyright (c) 2015, 2017
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 <errno.h>
30 #include <io.h>
31 #include <unistd.h>
32 #include <process.h>
33
34 __RCSID("$MirOS: src/bin/mksh/os2.c,v 1.8 2017/12/22 16:41:42 tg Exp $");
35
36 static char *remove_trailing_dots(char *);
37 static int access_stat_ex(int (*)(), const char *, void *);
38 static int test_exec_exist(const char *, char *);
39 static void response(int *, const char ***);
40 static char *make_response_file(char * const *);
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 void
173 os2_init(int *argcp, const char ***argvp)
174 {
175         response(argcp, argvp);
176
177         init_extlibpath();
178
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
189 void
190 setextlibpath(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 */
217 static char *
218 remove_trailing_dots(char *name)
219 {
220         char *p = strnul(name);
221
222         while (--p > name && *p == '.')
223                 /* 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() */
235 extern int _std_stat(const char *, struct stat *);
236
237 /* replacement for stat() of kLIBC which fails if there are trailing dots */
238 int
239 stat(const char *name, struct stat *buffer)
240 {
241         return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
242 }
243
244 /* alias of access() */
245 extern int _std_access(const char *, int);
246
247 /* replacement for access() of kLIBC which fails if there are trailing dots */
248 int
249 access(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
263 static const char *x_suffix_list[] =
264     { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
265
266 /* call fn() by appending executable extensions */
267 static int
268 access_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 */
291 int
292 access_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 */
299 int
300 stat_ex(const char *name, struct stat *buffer)
301 {
302         return (access_stat_ex(stat, name, buffer));
303 }
304
305 static int
306 test_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
319 const char *
320 real_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
332 /* make a response file to pass a very long command line */
333 static char *
334 make_response_file(char * const *argv)
335 {
336         char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
337         char *rsp_name = &rsp_name_arg[1];
338         int i;
339         int fd;
340         char *result;
341
342         if ((fd = mkstemp(rsp_name)) == -1)
343                 return (NULL);
344
345         /* 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);
349         }
350
351         close(fd);
352         add_temp(rsp_name);
353         strdupx(result, rsp_name_arg, ATEMP);
354
355         return (result);
356 }
357
358 /* alias of execve() */
359 extern int _std_execve(const char *, char * const *, char * const *);
360
361 /* replacement for execve() of kLIBC */
362 int
363 execve(const char *name, char * const *argv, char * const *envp)
364 {
365         const char *exec_name;
366         FILE *fp;
367         char sign[2];
368         int pid;
369         int status;
370         int fd;
371         int rc;
372         int saved_mode;
373         int saved_errno;
374
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
413         /*
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);
422
423         pid = spawnve(P_NOWAIT, exec_name, argv, envp);
424         saved_errno = errno;
425
426         /* 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);
443
444         if (pid == -1) {
445                 cleanup_temps();
446
447                 errno = saved_errno;
448                 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
474 static struct temp *templist = NULL;
475
476 static void
477 add_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() */
488 extern int _std_unlink(const char *);
489
490 /*
491  * Replacement for unlink() of kLIBC not supporting to remove files used by
492  * another processes.
493  */
494 int
495 unlink(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
506 static void
507 cleanup_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
522 static void
523 cleanup(void)
524 {
525         cleanup_temps();
526 }
527
528 int
529 getdrvwd(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 }