1 /*-
2  * Copyright (c) 2015, 2017, 2020
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_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.10 2020/04/07 11:13:45 tg Exp $");
36 
37 static char *remove_trailing_dots(char *);
38 static int access_stat_ex(int (*)(), const char *, void *);
39 static int test_exec_exist(const char *, char *);
40 static void response(int *, const char ***);
41 static char *make_response_file(char * const *);
42 static void add_temp(const char *);
43 static void cleanup_temps(void);
44 static void cleanup(void);
45 
46 #define RPUT(x) do {					\
47 	if (new_argc >= new_alloc) {			\
48 		new_alloc += 20;			\
49 		if (!(new_argv = realloc(new_argv,	\
50 		    new_alloc * sizeof(char *))))	\
51 			goto exit_out_of_memory;	\
52 	}						\
53 	new_argv[new_argc++] = (x);			\
54 } while (/* CONSTCOND */ 0)
55 
56 #define KLIBC_ARG_RESPONSE_EXCLUDE	\
57 	(__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL)
58 
59 static void
response(int * argcp,const char *** argvp)60 response(int *argcp, const char ***argvp)
61 {
62 	int i, old_argc, new_argc, new_alloc = 0;
63 	const char **old_argv, **new_argv;
64 	char *line, *l, *p;
65 	FILE *f;
66 
67 	old_argc = *argcp;
68 	old_argv = *argvp;
69 	for (i = 1; i < old_argc; ++i)
70 		if (old_argv[i] &&
71 		    !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) &&
72 		    old_argv[i][0] == '@')
73 			break;
74 
75 	if (i >= old_argc)
76 		/* do nothing */
77 		return;
78 
79 	new_argv = NULL;
80 	new_argc = 0;
81 	for (i = 0; i < old_argc; ++i) {
82 		if (i == 0 || !old_argv[i] ||
83 		    (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) ||
84 		    old_argv[i][0] != '@' ||
85 		    !(f = fopen(old_argv[i] + 1, "rt")))
86 			RPUT(old_argv[i]);
87 		else {
88 			long filesize;
89 
90 			fseek(f, 0, SEEK_END);
91 			filesize = ftell(f);
92 			fseek(f, 0, SEEK_SET);
93 
94 			line = malloc(filesize + /* type */ 1 + /* NUL */ 1);
95 			if (!line) {
96  exit_out_of_memory:
97 				fputs("Out of memory while reading response file\n", stderr);
98 				exit(255);
99 			}
100 
101 			line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE;
102 			l = line + 1;
103 			while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) {
104 				p = strchr(l, '\n');
105 				if (p) {
106 					/*
107 					 * if a line ends with a backslash,
108 					 * concatenate with the next line
109 					 */
110 					if (p > l && p[-1] == '\\') {
111 						char *p1;
112 						int count = 0;
113 
114 						for (p1 = p - 1; p1 >= l &&
115 						    *p1 == '\\'; p1--)
116 							count++;
117 
118 						if (count & 1) {
119 							l = p + 1;
120 
121 							continue;
122 						}
123 					}
124 
125 					*p = 0;
126 				}
127 				p = strdup(line);
128 				if (!p)
129 					goto exit_out_of_memory;
130 
131 				RPUT(p + 1);
132 
133 				l = line + 1;
134 			}
135 
136 			free(line);
137 
138 			if (ferror(f)) {
139 				fputs("Cannot read response file\n", stderr);
140 				exit(255);
141 			}
142 
143 			fclose(f);
144 		}
145 	}
146 
147 	RPUT(NULL);
148 	--new_argc;
149 
150 	*argcp = new_argc;
151 	*argvp = new_argv;
152 }
153 
154 static void
init_extlibpath(void)155 init_extlibpath(void)
156 {
157 	const char *vars[] = {
158 		"BEGINLIBPATH",
159 		"ENDLIBPATH",
160 		"LIBPATHSTRICT",
161 		NULL
162 	};
163 	char val[512];
164 	int flag;
165 
166 	for (flag = 0; vars[flag]; flag++) {
167 		DosQueryExtLIBPATH(val, flag + 1);
168 		if (val[0])
169 			setenv(vars[flag], val, 1);
170 	}
171 }
172 
173 void
os2_init(int * argcp,const char *** argvp)174 os2_init(int *argcp, const char ***argvp)
175 {
176 	KBDINFO ki;
177 
178 	response(argcp, argvp);
179 
180 	init_extlibpath();
181 
182 	if (!isatty(STDIN_FILENO))
183 		setmode(STDIN_FILENO, O_BINARY);
184 	if (!isatty(STDOUT_FILENO))
185 		setmode(STDOUT_FILENO, O_BINARY);
186 	if (!isatty(STDERR_FILENO))
187 		setmode(STDERR_FILENO, O_BINARY);
188 
189 	/* ensure ECHO mode is ON so that read command echoes. */
190 	memset(&ki, 0, sizeof(ki));
191 	ki.cb = sizeof(ki);
192 	ki.fsMask |= KEYBOARD_ECHO_ON;
193 	KbdSetStatus(&ki, 0);
194 
195 	atexit(cleanup);
196 }
197 
198 void
setextlibpath(const char * name,const char * val)199 setextlibpath(const char *name, const char *val)
200 {
201 	int flag;
202 	char *p, *cp;
203 
204 	if (!strcmp(name, "BEGINLIBPATH"))
205 		flag = BEGIN_LIBPATH;
206 	else if (!strcmp(name, "ENDLIBPATH"))
207 		flag = END_LIBPATH;
208 	else if (!strcmp(name, "LIBPATHSTRICT"))
209 		flag = LIBPATHSTRICT;
210 	else
211 		return;
212 
213 	/* convert slashes to backslashes */
214 	strdupx(cp, val, ATEMP);
215 	for (p = cp; *p; p++) {
216 		if (*p == '/')
217 			*p = '\\';
218 	}
219 
220 	DosSetExtLIBPATH(cp, flag);
221 
222 	afree(cp, ATEMP);
223 }
224 
225 /* remove trailing dots */
226 static char *
remove_trailing_dots(char * name)227 remove_trailing_dots(char *name)
228 {
229 	char *p = strnul(name);
230 
231 	while (--p > name && *p == '.')
232 		/* nothing */;
233 
234 	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
235 		p[1] = '\0';
236 
237 	return (name);
238 }
239 
240 #define REMOVE_TRAILING_DOTS(name)	\
241 	remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
242 
243 /* alias of stat() */
244 extern int _std_stat(const char *, struct stat *);
245 
246 /* replacement for stat() of kLIBC which fails if there are trailing dots */
247 int
stat(const char * name,struct stat * buffer)248 stat(const char *name, struct stat *buffer)
249 {
250 	return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
251 }
252 
253 /* alias of access() */
254 extern int _std_access(const char *, int);
255 
256 /* replacement for access() of kLIBC which fails if there are trailing dots */
257 int
access(const char * name,int mode)258 access(const char *name, int mode)
259 {
260 	/*
261 	 * On OS/2 kLIBC, X_OK is set only for executable files.
262 	 * This prevents scripts from being executed.
263 	 */
264 	if (mode & X_OK)
265 		mode = (mode & ~X_OK) | R_OK;
266 
267 	return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
268 }
269 
270 #define MAX_X_SUFFIX_LEN	4
271 
272 static const char *x_suffix_list[] =
273     { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
274 
275 /* call fn() by appending executable extensions */
276 static int
access_stat_ex(int (* fn)(),const char * name,void * arg)277 access_stat_ex(int (*fn)(), const char *name, void *arg)
278 {
279 	char *x_name;
280 	const char **x_suffix;
281 	int rc = -1;
282 	size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
283 
284 	/* otherwise, try to append executable suffixes */
285 	x_name = alloc(x_namelen, ATEMP);
286 
287 	for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
288 		strlcpy(x_name, name, x_namelen);
289 		strlcat(x_name, *x_suffix, x_namelen);
290 
291 		rc = fn(x_name, arg);
292 	}
293 
294 	afree(x_name, ATEMP);
295 
296 	return (rc);
297 }
298 
299 /* access()/search_access() version */
300 int
access_ex(int (* fn)(const char *,int),const char * name,int mode)301 access_ex(int (*fn)(const char *, int), const char *name, int mode)
302 {
303 	/*XXX this smells fishy --mirabilos */
304 	return (access_stat_ex(fn, name, (void *)mode));
305 }
306 
307 /* stat()/lstat() version */
308 int
stat_ex(int (* fn)(const char *,struct stat *),const char * name,struct stat * buffer)309 stat_ex(int (*fn)(const char *, struct stat *),
310     const char *name, struct stat *buffer)
311 {
312 	return (access_stat_ex(fn, name, buffer));
313 }
314 
315 static int
test_exec_exist(const char * name,char * real_name)316 test_exec_exist(const char *name, char *real_name)
317 {
318 	struct stat sb;
319 
320 	if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
321 		return (-1);
322 
323 	/* safe due to calculations in real_exec_name() */
324 	memcpy(real_name, name, strlen(name) + 1);
325 
326 	return (0);
327 }
328 
329 const char *
real_exec_name(const char * name)330 real_exec_name(const char *name)
331 {
332 	char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
333 	const char *real_name = name;
334 
335 	if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
336 		/*XXX memory leak */
337 		strdupx(real_name, x_name, ATEMP);
338 
339 	return (real_name);
340 }
341 
342 /* make a response file to pass a very long command line */
343 static char *
make_response_file(char * const * argv)344 make_response_file(char * const *argv)
345 {
346 	char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
347 	char *rsp_name = &rsp_name_arg[1];
348 	int i;
349 	int fd;
350 	char *result;
351 
352 	if ((fd = mkstemp(rsp_name)) == -1)
353 		return (NULL);
354 
355 	/* write all the arguments except a 0th program name */
356 	for (i = 1; argv[i]; i++) {
357 		write(fd, argv[i], strlen(argv[i]));
358 		write(fd, "\n", 1);
359 	}
360 
361 	close(fd);
362 	add_temp(rsp_name);
363 	strdupx(result, rsp_name_arg, ATEMP);
364 
365 	return (result);
366 }
367 
368 /* alias of execve() */
369 extern int _std_execve(const char *, char * const *, char * const *);
370 
371 /* replacement for execve() of kLIBC */
372 int
execve(const char * name,char * const * argv,char * const * envp)373 execve(const char *name, char * const *argv, char * const *envp)
374 {
375 	const char *exec_name;
376 	FILE *fp;
377 	char sign[2];
378 	int pid;
379 	int status;
380 	int fd;
381 	int rc;
382 	int saved_mode;
383 	int saved_errno;
384 
385 	/*
386 	 * #! /bin/sh : append .exe
387 	 * extproc sh : search sh.exe in PATH
388 	 */
389 	exec_name = search_path(name, path, X_OK, NULL);
390 	if (!exec_name) {
391 		errno = ENOENT;
392 		return (-1);
393 	}
394 
395 	/*-
396 	 * kLIBC execve() has problems when executing scripts.
397 	 * 1. it fails to execute a script if a directory whose name
398 	 *    is same as an interpreter exists in a current directory.
399 	 * 2. it fails to execute a script not starting with sharpbang.
400 	 * 3. it fails to execute a batch file if COMSPEC is set to a shell
401 	 *    incompatible with cmd.exe, such as /bin/sh.
402 	 * And ksh process scripts more well, so let ksh process scripts.
403 	 */
404 	errno = 0;
405 	if (!(fp = fopen(exec_name, "rb")))
406 		errno = ENOEXEC;
407 
408 	if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
409 		errno = ENOEXEC;
410 
411 	if (fp && fclose(fp))
412 		errno = ENOEXEC;
413 
414 	if (!errno &&
415 	    !((sign[0] == 'M' && sign[1] == 'Z') ||
416 	      (sign[0] == 'N' && sign[1] == 'E') ||
417 	      (sign[0] == 'L' && sign[1] == 'X')))
418 		errno = ENOEXEC;
419 
420 	if (errno == ENOEXEC)
421 		return (-1);
422 
423 	/*
424 	 * Normal OS/2 programs expect that standard IOs, especially stdin,
425 	 * are opened in text mode at the startup. By the way, on OS/2 kLIBC
426 	 * child processes inherit a translation mode of a parent process.
427 	 * As a result, if stdin is set to binary mode in a parent process,
428 	 * stdin of child processes is opened in binary mode as well at the
429 	 * startup. In this case, some programs such as sed suffer from CR.
430 	 */
431 	saved_mode = setmode(STDIN_FILENO, O_TEXT);
432 
433 	pid = spawnve(P_NOWAIT, exec_name, argv, envp);
434 	saved_errno = errno;
435 
436 	/* arguments too long? */
437 	if (pid == -1 && saved_errno == EINVAL) {
438 		/* retry with a response file */
439 		char *rsp_name_arg = make_response_file(argv);
440 
441 		if (rsp_name_arg) {
442 			char *rsp_argv[3] = { argv[0], rsp_name_arg, NULL };
443 
444 			pid = spawnve(P_NOWAIT, exec_name, rsp_argv, envp);
445 			saved_errno = errno;
446 
447 			afree(rsp_name_arg, ATEMP);
448 		}
449 	}
450 
451 	/* restore translation mode of stdin */
452 	setmode(STDIN_FILENO, saved_mode);
453 
454 	if (pid == -1) {
455 		cleanup_temps();
456 
457 		errno = saved_errno;
458 		return (-1);
459 	}
460 
461 	/* close all opened handles */
462 	for (fd = 0; fd < NUFILE; fd++) {
463 		if (fcntl(fd, F_GETFD) == -1)
464 			continue;
465 
466 		close(fd);
467 	}
468 
469 	while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
470 		/* nothing */;
471 
472 	cleanup_temps();
473 
474 	/* Is this possible? And is this right? */
475 	if (rc == -1)
476 		return (-1);
477 
478 	if (WIFSIGNALED(status))
479 		_exit(ksh_sigmask(WTERMSIG(status)));
480 
481 	_exit(WEXITSTATUS(status));
482 }
483 
484 static struct temp *templist = NULL;
485 
486 static void
add_temp(const char * name)487 add_temp(const char *name)
488 {
489 	struct temp *tp;
490 
491 	tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
492 	memcpy(tp->tffn, name, strlen(name) + 1);
493 	tp->next = templist;
494 	templist = tp;
495 }
496 
497 /* alias of unlink() */
498 extern int _std_unlink(const char *);
499 
500 /*
501  * Replacement for unlink() of kLIBC not supporting to remove files used by
502  * another processes.
503  */
504 int
unlink(const char * name)505 unlink(const char *name)
506 {
507 	int rc;
508 
509 	rc = _std_unlink(name);
510 	if (rc == -1 && errno != ENOENT)
511 		add_temp(name);
512 
513 	return (rc);
514 }
515 
516 static void
cleanup_temps(void)517 cleanup_temps(void)
518 {
519 	struct temp *tp;
520 	struct temp **tpnext;
521 
522 	for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
523 		if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
524 			*tpnext = tp->next;
525 			afree(tp, APERM);
526 		} else {
527 			tpnext = &tp->next;
528 		}
529 	}
530 }
531 
532 static void
cleanup(void)533 cleanup(void)
534 {
535 	cleanup_temps();
536 }
537 
538 int
getdrvwd(char ** cpp,unsigned int drvltr)539 getdrvwd(char **cpp, unsigned int drvltr)
540 {
541 	PBYTE cp;
542 	ULONG sz;
543 	APIRET rc;
544 	ULONG drvno;
545 
546 	if (DosQuerySysInfo(QSV_MAX_PATH_LENGTH, QSV_MAX_PATH_LENGTH,
547 	    &sz, sizeof(sz)) != 0) {
548 		errno = EDOOFUS;
549 		return (-1);
550 	}
551 
552 	/* allocate 'X:/' plus sz plus NUL */
553 	checkoktoadd((size_t)sz, (size_t)4);
554 	cp = aresize(*cpp, (size_t)sz + (size_t)4, ATEMP);
555 	cp[0] = ksh_toupper(drvltr);
556 	cp[1] = ':';
557 	cp[2] = '/';
558 	drvno = ksh_numuc(cp[0]) + 1;
559 	/* NUL is part of space within buffer passed */
560 	++sz;
561 	if ((rc = DosQueryCurrentDir(drvno, cp + 3, &sz)) == 0) {
562 		/* success! */
563 		*cpp = cp;
564 		return (0);
565 	}
566 	afree(cp, ATEMP);
567 	*cpp = NULL;
568 	switch (rc) {
569 	case 15: /* invalid drive */
570 		errno = ENOTBLK;
571 		break;
572 	case 26: /* not dos disk */
573 		errno = ENODEV;
574 		break;
575 	case 108: /* drive locked */
576 		errno = EDEADLK;
577 		break;
578 	case 111: /* buffer overflow */
579 		errno = ENAMETOOLONG;
580 		break;
581 	default:
582 		errno = EINVAL;
583 	}
584 	return (-1);
585 }
586