xref: /third_party/mksh/os2.c (revision c84f3f3c)
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
37struct 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
49static void remove_trailing_dots(char *, size_t);
50static int access_stat_ex(const char *, struct a_s_arg *);
51static int test_exec_exist(const char *, void *);
52static void response(int *, const char ***);
53static char *make_response_file(char * const *);
54static void add_temp(const char *);
55static void cleanup_temps(void);
56static 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
71static void
72response(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
166static void
167init_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
185void
186os2_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
210void
211setextlibpath(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 */
238static void
239remove_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() */
251extern int _std_stat(const char *, struct stat *);
252
253/* replacement for stat() of kLIBC which fails if there are trailing dots */
254int
255stat(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() */
266extern int _std_access(const char *, int);
267
268/* replacement for access() of kLIBC which fails if there are trailing dots */
269int
270access(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
289static const char *x_suffix_list[] =
290    { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
291
292/* call fn() by appending executable extensions */
293static int
294access_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 */
318int
319access_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 */
330int
331stat_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
342static int
343test_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
357const char *
358real_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 */
370static char *
371make_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() */
396extern int _std_execve(const char *, char * const *, char * const *);
397
398/* replacement for execve() of kLIBC */
399int
400execve(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
511static struct temp *templist = NULL;
512
513static void
514add_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() */
525extern int _std_unlink(const char *);
526
527/*
528 * Replacement for unlink() of kLIBC not supporting to remove files used by
529 * another processes.
530 */
531int
532unlink(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
543static void
544cleanup_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
559static void
560cleanup(void)
561{
562	cleanup_temps();
563}
564
565int
566getdrvwd(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