• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*-
2  * Copyright (c) 2015, 2017
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_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
response(int * argcp,const char *** argvp)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
init_extlibpath(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
os2_init(int * argcp,const char *** argvp)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
setextlibpath(const char * name,const char * val)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 *
remove_trailing_dots(char * name)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
stat(const char * name,struct stat * buffer)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
access(const char * name,int mode)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
access_stat_ex(int (* fn)(),const char * name,void * arg)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
access_ex(int (* fn)(const char *,int),const char * name,int mode)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
stat_ex(const char * name,struct stat * buffer)300 stat_ex(const char *name, struct stat *buffer)
301 {
302 	return (access_stat_ex(stat, name, buffer));
303 }
304 
305 static int
test_exec_exist(const char * name,char * real_name)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 *
real_exec_name(const char * name)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 *
make_response_file(char * const * argv)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
execve(const char * name,char * const * argv,char * const * envp)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
add_temp(const char * name)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
unlink(const char * name)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
cleanup_temps(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
cleanup(void)523 cleanup(void)
524 {
525 	cleanup_temps();
526 }
527 
528 int
getdrvwd(char ** cpp,unsigned int drvltr)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