Home | History | Annotate | Download | only in src
      1 /*-
      2  * Copyright (c) 2015
      3  *	KO Myung-Hun <komh (at) 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
     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
    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
    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
    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
    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 *
    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
    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
    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
    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
    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
    330 stat_ex(const char *name, struct stat *buffer)
    331 {
    332 	return (access_stat_ex(stat, name, buffer));
    333 }
    334 
    335 static int
    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 *
    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 *
    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
    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
    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
    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
    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
    554 cleanup(void)
    555 {
    556 	cleanup_temps();
    557 }
    558