• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*-
2  * Copyright (c) 2015
3  *	KO Myung-Hun <komh@chollian.net>
4  *
5  * Provided that these terms and disclaimer and all copyright notices
6  * are retained or reproduced in an accompanying document, permission
7  * is granted to deal in this work without restriction, including un-
8  * limited rights to use, publicly perform, distribute, sell, modify,
9  * merge, give away, or sublicence.
10  *
11  * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
12  * the utmost extent permitted by applicable law, neither express nor
13  * implied; without malicious intent or gross negligence. In no event
14  * may a licensor, author or contributor be held liable for indirect,
15  * direct, other damage, loss, or other issues arising in any way out
16  * of dealing in the work, even if advised of the possibility of such
17  * damage or existence of a defect, except proven that it results out
18  * of said person's immediate fault when using the work as intended.
19  */
20 
21 #define INCL_DOS
22 #include <os2.h>
23 
24 #include "sh.h"
25 
26 #include <klibc/startup.h>
27 #include <io.h>
28 #include <unistd.h>
29 #include <process.h>
30 
31 __RCSID("$MirOS: src/bin/mksh/os2.c,v 1.1 2017/04/02 15:00:44 tg Exp $");
32 
33 static char *remove_trailing_dots(char *);
34 static int access_stat_ex(int (*)(), const char *, void *);
35 static int test_exec_exist(const char *, char *);
36 static void response(int *, const char ***);
37 static char *make_response_file(char * const *);
38 static void env_slashify(void);
39 static void add_temp(const char *);
40 static void cleanup_temps(void);
41 static void cleanup(void);
42 
43 #define RPUT(x) do {					\
44 	if (new_argc >= new_alloc) {			\
45 		new_alloc += 20;			\
46 		if (!(new_argv = realloc(new_argv,	\
47 		    new_alloc * sizeof(char *))))	\
48 			goto exit_out_of_memory;	\
49 	}						\
50 	new_argv[new_argc++] = (x);			\
51 } while (/* CONSTCOND */ 0)
52 
53 #define KLIBC_ARG_RESPONSE_EXCLUDE	\
54 	(__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL)
55 
56 static void
response(int * argcp,const char *** argvp)57 response(int *argcp, const char ***argvp)
58 {
59 	int i, old_argc, new_argc, new_alloc = 0;
60 	const char **old_argv, **new_argv;
61 	char *line, *l, *p;
62 	FILE *f;
63 
64 	old_argc = *argcp;
65 	old_argv = *argvp;
66 	for (i = 1; i < old_argc; ++i)
67 		if (old_argv[i] &&
68 		    !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) &&
69 		    old_argv[i][0] == '@')
70 			break;
71 
72 	if (i >= old_argc)
73 		/* do nothing */
74 		return;
75 
76 	new_argv = NULL;
77 	new_argc = 0;
78 	for (i = 0; i < old_argc; ++i) {
79 		if (i == 0 || !old_argv[i] ||
80 		    (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) ||
81 		    old_argv[i][0] != '@' ||
82 		    !(f = fopen(old_argv[i] + 1, "rt")))
83 			RPUT(old_argv[i]);
84 		else {
85 			long filesize;
86 
87 			fseek(f, 0, SEEK_END);
88 			filesize = ftell(f);
89 			fseek(f, 0, SEEK_SET);
90 
91 			line = malloc(filesize + /* type */ 1 + /* NUL */ 1);
92 			if (!line) {
93  exit_out_of_memory:
94 				fputs("Out of memory while reading response file\n", stderr);
95 				exit(255);
96 			}
97 
98 			line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE;
99 			l = line + 1;
100 			while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) {
101 				p = strchr(l, '\n');
102 				if (p) {
103 					/*
104 					 * if a line ends with a backslash,
105 					 * concatenate with the next line
106 					 */
107 					if (p > l && p[-1] == '\\') {
108 						char *p1;
109 						int count = 0;
110 
111 						for (p1 = p - 1; p1 >= l &&
112 						    *p1 == '\\'; p1--)
113 							count++;
114 
115 						if (count & 1) {
116 							l = p + 1;
117 
118 							continue;
119 						}
120 					}
121 
122 					*p = 0;
123 				}
124 				p = strdup(line);
125 				if (!p)
126 					goto exit_out_of_memory;
127 
128 				RPUT(p + 1);
129 
130 				l = line + 1;
131 			}
132 
133 			free(line);
134 
135 			if (ferror(f)) {
136 				fputs("Cannot read response file\n", stderr);
137 				exit(255);
138 			}
139 
140 			fclose(f);
141 		}
142 	}
143 
144 	RPUT(NULL);
145 	--new_argc;
146 
147 	*argcp = new_argc;
148 	*argvp = new_argv;
149 }
150 
151 static void
init_extlibpath(void)152 init_extlibpath(void)
153 {
154 	const char *vars[] = {
155 		"BEGINLIBPATH",
156 		"ENDLIBPATH",
157 		"LIBPATHSTRICT",
158 		NULL
159 	};
160 	char val[512];
161 	int flag;
162 
163 	for (flag = 0; vars[flag]; flag++) {
164 		DosQueryExtLIBPATH(val, flag + 1);
165 		if (val[0])
166 			setenv(vars[flag], val, 1);
167 	}
168 }
169 
170 /*
171  * Convert backslashes of environmental variables to forward slahes.
172  * A backslash may be used as an escaped character when doing 'echo'.
173  * This leads to an unexpected behavior.
174  */
175 static void
env_slashify(void)176 env_slashify(void)
177 {
178 	/*
179 	 * PATH and TMPDIR are used by OS/2 as well. That is, they may
180 	 * have backslashes as a directory separator.
181 	 * BEGINLIBPATH and ENDLIBPATH are special variables on OS/2.
182 	 */
183 	const char *var_list[] = {
184 		"PATH",
185 		"TMPDIR",
186 		"BEGINLIBPATH",
187 		"ENDLIBPATH",
188 		NULL
189 	};
190 	const char **var;
191 	char *value;
192 
193 	for (var = var_list; *var; var++) {
194 		value = getenv(*var);
195 
196 		if (value)
197 			_fnslashify(value);
198 	}
199 }
200 
201 void
os2_init(int * argcp,const char *** argvp)202 os2_init(int *argcp, const char ***argvp)
203 {
204 	response(argcp, argvp);
205 
206 	init_extlibpath();
207 	env_slashify();
208 
209 	if (!isatty(STDIN_FILENO))
210 		setmode(STDIN_FILENO, O_BINARY);
211 	if (!isatty(STDOUT_FILENO))
212 		setmode(STDOUT_FILENO, O_BINARY);
213 	if (!isatty(STDERR_FILENO))
214 		setmode(STDERR_FILENO, O_BINARY);
215 
216 	atexit(cleanup);
217 }
218 
219 void
setextlibpath(const char * name,const char * val)220 setextlibpath(const char *name, const char *val)
221 {
222 	int flag;
223 	char *p, *cp;
224 
225 	if (!strcmp(name, "BEGINLIBPATH"))
226 		flag = BEGIN_LIBPATH;
227 	else if (!strcmp(name, "ENDLIBPATH"))
228 		flag = END_LIBPATH;
229 	else if (!strcmp(name, "LIBPATHSTRICT"))
230 		flag = LIBPATHSTRICT;
231 	else
232 		return;
233 
234 	/* convert slashes to backslashes */
235 	strdupx(cp, val, ATEMP);
236 	for (p = cp; *p; p++) {
237 		if (*p == '/')
238 			*p = '\\';
239 	}
240 
241 	DosSetExtLIBPATH(cp, flag);
242 
243 	afree(cp, ATEMP);
244 }
245 
246 /* remove trailing dots */
247 static char *
remove_trailing_dots(char * name)248 remove_trailing_dots(char *name)
249 {
250 	char *p;
251 
252 	for (p = name + strlen(name); --p > name && *p == '.'; )
253 		/* nothing */;
254 
255 	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
256 		p[1] = '\0';
257 
258 	return (name);
259 }
260 
261 #define REMOVE_TRAILING_DOTS(name)	\
262 	remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
263 
264 /* alias of stat() */
265 extern int _std_stat(const char *, struct stat *);
266 
267 /* replacement for stat() of kLIBC which fails if there are trailing dots */
268 int
stat(const char * name,struct stat * buffer)269 stat(const char *name, struct stat *buffer)
270 {
271 	return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
272 }
273 
274 /* alias of access() */
275 extern int _std_access(const char *, int);
276 
277 /* replacement for access() of kLIBC which fails if there are trailing dots */
278 int
access(const char * name,int mode)279 access(const char *name, int mode)
280 {
281 	/*
282 	 * On OS/2 kLIBC, X_OK is set only for executable files.
283 	 * This prevents scripts from being executed.
284 	 */
285 	if (mode & X_OK)
286 		mode = (mode & ~X_OK) | R_OK;
287 
288 	return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
289 }
290 
291 #define MAX_X_SUFFIX_LEN	4
292 
293 static const char *x_suffix_list[] =
294     { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
295 
296 /* call fn() by appending executable extensions */
297 static int
access_stat_ex(int (* fn)(),const char * name,void * arg)298 access_stat_ex(int (*fn)(), const char *name, void *arg)
299 {
300 	char *x_name;
301 	const char **x_suffix;
302 	int rc = -1;
303 	size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
304 
305 	/* otherwise, try to append executable suffixes */
306 	x_name = alloc(x_namelen, ATEMP);
307 
308 	for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
309 		strlcpy(x_name, name, x_namelen);
310 		strlcat(x_name, *x_suffix, x_namelen);
311 
312 		rc = fn(x_name, arg);
313 	}
314 
315 	afree(x_name, ATEMP);
316 
317 	return (rc);
318 }
319 
320 /* access()/search_access() version */
321 int
access_ex(int (* fn)(const char *,int),const char * name,int mode)322 access_ex(int (*fn)(const char *, int), const char *name, int mode)
323 {
324 	/*XXX this smells fishy --mirabilos */
325 	return (access_stat_ex(fn, name, (void *)mode));
326 }
327 
328 /* stat() version */
329 int
stat_ex(const char * name,struct stat * buffer)330 stat_ex(const char *name, struct stat *buffer)
331 {
332 	return (access_stat_ex(stat, name, buffer));
333 }
334 
335 static int
test_exec_exist(const char * name,char * real_name)336 test_exec_exist(const char *name, char *real_name)
337 {
338 	struct stat sb;
339 
340 	if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
341 		return (-1);
342 
343 	/* safe due to calculations in real_exec_name() */
344 	memcpy(real_name, name, strlen(name) + 1);
345 
346 	return (0);
347 }
348 
349 const char *
real_exec_name(const char * name)350 real_exec_name(const char *name)
351 {
352 	char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
353 	const char *real_name = name;
354 
355 	if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
356 		/*XXX memory leak */
357 		strdupx(real_name, x_name, ATEMP);
358 
359 	return (real_name);
360 }
361 
362 /* OS/2 can process a command line up to 32 KiB */
363 #define MAX_CMD_LINE_LEN 32768
364 
365 /* make a response file to pass a very long command line */
366 static char *
make_response_file(char * const * argv)367 make_response_file(char * const *argv)
368 {
369 	char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
370 	char *rsp_name = &rsp_name_arg[1];
371 	int arg_len = 0;
372 	int i;
373 
374 	for (i = 0; argv[i]; i++)
375 		arg_len += strlen(argv[i]) + 1;
376 
377 	/*
378 	 * If a length of command line is longer than MAX_CMD_LINE_LEN, then
379 	 * use a response file. OS/2 cannot process a command line longer
380 	 * than 32K. Of course, a response file cannot be recognised by a
381 	 * normal OS/2 program, that is, neither non-EMX or non-kLIBC. But
382 	 * it cannot accept a command line longer than 32K in itself. So
383 	 * using a response file in this case, is an acceptable solution.
384 	 */
385 	if (arg_len > MAX_CMD_LINE_LEN) {
386 		int fd;
387 		char *result;
388 
389 		if ((fd = mkstemp(rsp_name)) == -1)
390 			return (NULL);
391 
392 		/* write all the arguments except a 0th program name */
393 		for (i = 1; argv[i]; i++) {
394 			write(fd, argv[i], strlen(argv[i]));
395 			write(fd, "\n", 1);
396 		}
397 
398 		close(fd);
399 		add_temp(rsp_name);
400 		strdupx(result, rsp_name_arg, ATEMP);
401 		return (result);
402 	}
403 
404 	return (NULL);
405 }
406 
407 /* alias of execve() */
408 extern int _std_execve(const char *, char * const *, char * const *);
409 
410 /* replacement for execve() of kLIBC */
411 int
execve(const char * name,char * const * argv,char * const * envp)412 execve(const char *name, char * const *argv, char * const *envp)
413 {
414 	const char *exec_name;
415 	FILE *fp;
416 	char sign[2];
417 	char *rsp_argv[3];
418 	char *rsp_name_arg;
419 	int pid;
420 	int status;
421 	int fd;
422 	int rc;
423 
424 	/*
425 	 * #! /bin/sh : append .exe
426 	 * extproc sh : search sh.exe in PATH
427 	 */
428 	exec_name = search_path(name, path, X_OK, NULL);
429 	if (!exec_name) {
430 		errno = ENOENT;
431 		return (-1);
432 	}
433 
434 	/*-
435 	 * kLIBC execve() has problems when executing scripts.
436 	 * 1. it fails to execute a script if a directory whose name
437 	 *    is same as an interpreter exists in a current directory.
438 	 * 2. it fails to execute a script not starting with sharpbang.
439 	 * 3. it fails to execute a batch file if COMSPEC is set to a shell
440 	 *    incompatible with cmd.exe, such as /bin/sh.
441 	 * And ksh process scripts more well, so let ksh process scripts.
442 	 */
443 	errno = 0;
444 	if (!(fp = fopen(exec_name, "rb")))
445 		errno = ENOEXEC;
446 
447 	if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
448 		errno = ENOEXEC;
449 
450 	if (fp && fclose(fp))
451 		errno = ENOEXEC;
452 
453 	if (!errno &&
454 	    !((sign[0] == 'M' && sign[1] == 'Z') ||
455 	      (sign[0] == 'N' && sign[1] == 'E') ||
456 	      (sign[0] == 'L' && sign[1] == 'X')))
457 		errno = ENOEXEC;
458 
459 	if (errno == ENOEXEC)
460 		return (-1);
461 
462 	rsp_name_arg = make_response_file(argv);
463 
464 	if (rsp_name_arg) {
465 		rsp_argv[0] = argv[0];
466 		rsp_argv[1] = rsp_name_arg;
467 		rsp_argv[2] = NULL;
468 
469 		argv = rsp_argv;
470 	}
471 
472 	pid = spawnve(P_NOWAIT, exec_name, argv, envp);
473 
474 	afree(rsp_name_arg, ATEMP);
475 
476 	if (pid == -1) {
477 		cleanup_temps();
478 
479 		return (-1);
480 	}
481 
482 	/* close all opened handles */
483 	for (fd = 0; fd < NUFILE; fd++) {
484 		if (fcntl(fd, F_GETFD) == -1)
485 			continue;
486 
487 		close(fd);
488 	}
489 
490 	while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
491 		/* nothing */;
492 
493 	cleanup_temps();
494 
495 	/* Is this possible? And is this right? */
496 	if (rc == -1)
497 		return (-1);
498 
499 	if (WIFSIGNALED(status))
500 		_exit(ksh_sigmask(WTERMSIG(status)));
501 
502 	_exit(WEXITSTATUS(status));
503 }
504 
505 static struct temp *templist = NULL;
506 
507 static void
add_temp(const char * name)508 add_temp(const char *name)
509 {
510 	struct temp *tp;
511 
512 	tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
513 	memcpy(tp->tffn, name, strlen(name) + 1);
514 	tp->next = templist;
515 	templist = tp;
516 }
517 
518 /* alias of unlink() */
519 extern int _std_unlink(const char *);
520 
521 /*
522  * Replacement for unlink() of kLIBC not supporting to remove files used by
523  * another processes.
524  */
525 int
unlink(const char * name)526 unlink(const char *name)
527 {
528 	int rc;
529 
530 	rc = _std_unlink(name);
531 	if (rc == -1 && errno != ENOENT)
532 		add_temp(name);
533 
534 	return (rc);
535 }
536 
537 static void
cleanup_temps(void)538 cleanup_temps(void)
539 {
540 	struct temp *tp;
541 	struct temp **tpnext;
542 
543 	for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
544 		if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
545 			*tpnext = tp->next;
546 			afree(tp, APERM);
547 		} else {
548 			tpnext = &tp->next;
549 		}
550 	}
551 }
552 
553 static void
cleanup(void)554 cleanup(void)
555 {
556 	cleanup_temps();
557 }
558