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