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