• 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