blob: 7fe3146d276deda8b6dbfdc59bf3d2192254d525 [file] [log] [blame]
jseward2886b0e2004-01-04 03:46:11 +00001
nethercotebb1c9912004-01-04 16:43:23 +00002/*--------------------------------------------------------------------*/
nethercote107e1c02004-10-13 17:55:31 +00003/*--- User-mode execve(), and other stuff shared between stage1 ---*/
4/*--- and stage2. ume.c ---*/
nethercotebb1c9912004-01-04 16:43:23 +00005/*--------------------------------------------------------------------*/
6
jseward2886b0e2004-01-04 03:46:11 +00007/*
8 This file is part of Valgrind, an extensible x86 protected-mode
9 emulator for monitoring program execution on x86-Unixes.
10
11 Copyright (C) 2000-2004 Julian Seward
12 jseward@acm.org
13
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation; either version 2 of the
17 License, or (at your option) any later version.
18
19 This program is distributed in the hope that it will be useful, but
20 WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27 02111-1307, USA.
28
29 The GNU General Public License is contained in the file COPYING.
30*/
31
fitzhardinge7e343cd2003-12-16 02:14:00 +000032
33#define _GNU_SOURCE
34#define _FILE_OFFSET_BITS 64
35
nethercotef1e5e152004-09-01 23:58:16 +000036#include "core.h"
fitzhardinge7e343cd2003-12-16 02:14:00 +000037
fitzhardinge7e343cd2003-12-16 02:14:00 +000038#include <sys/mman.h>
39#include <fcntl.h>
40#include <errno.h>
fitzhardinge7e343cd2003-12-16 02:14:00 +000041#include <stdio.h>
42#include <string.h>
43#include <stdlib.h>
44#include <unistd.h>
fitzhardinge7e343cd2003-12-16 02:14:00 +000045#include <assert.h>
46
47#include "ume.h"
48
nethercote1fe54502004-07-26 15:28:33 +000049struct elfinfo
50{
51 ESZ(Ehdr) e;
52 ESZ(Phdr) *p;
53 int fd;
54};
55
nethercote30d37842004-07-26 10:05:55 +000056static void check_mmap(void* res, void* base, int len)
nethercotebfed1c82004-07-17 12:57:44 +000057{
58 if ((void*)-1 == res) {
nethercote969ecf12004-10-13 17:29:01 +000059 fprintf(stderr, "valgrind: mmap(%p, %d) failed in UME.\n", base, len);
nethercotebfed1c82004-07-17 12:57:44 +000060 exit(1);
61 }
62}
63
nethercote31779c72004-07-30 21:50:15 +000064// 'extra' allows the caller to pass in extra args to 'fn', like free
65// variables to a closure.
66void foreach_map(int (*fn)(char *start, char *end,
fitzhardinge7e343cd2003-12-16 02:14:00 +000067 const char *perm, off_t offset,
nethercote31779c72004-07-30 21:50:15 +000068 int maj, int min, int ino, void* extra),
69 void* extra)
fitzhardinge7e343cd2003-12-16 02:14:00 +000070{
71 static char buf[10240];
72 char *bufptr = buf;
73 int ret, fd;
74
75 fd = open("/proc/self/maps", O_RDONLY);
76
77 if (fd == -1) {
78 perror("open /proc/self/maps");
79 return;
80 }
81
82 ret = read(fd, buf, sizeof(buf));
83
84 if (ret == -1) {
85 perror("read /proc/self/maps");
86 close(fd);
87 return;
88 }
89 close(fd);
90
91 if (ret == sizeof(buf)) {
92 fprintf(stderr, "buf too small\n");
93 return;
94 }
95
96 while(bufptr && bufptr < buf+ret) {
97 char perm[5];
98 off_t offset;
99 int maj, min;
100 int ino;
101 void *segstart, *segend;
102
103 sscanf(bufptr, "%p-%p %s %Lx %x:%x %d",
104 &segstart, &segend, perm, &offset, &maj, &min, &ino);
105 bufptr = strchr(bufptr, '\n');
106 if (bufptr != NULL)
107 bufptr++; /* skip \n */
108
nethercote31779c72004-07-30 21:50:15 +0000109 if (!(*fn)(segstart, segend, perm, offset, maj, min, ino, extra))
fitzhardinge7e343cd2003-12-16 02:14:00 +0000110 break;
111 }
112}
113
nethercote31779c72004-07-30 21:50:15 +0000114/*------------------------------------------------------------*/
115/*--- Finding auxv on the stack ---*/
116/*------------------------------------------------------------*/
fitzhardinge7e343cd2003-12-16 02:14:00 +0000117
118struct ume_auxv *find_auxv(int *esp)
119{
120 esp++; /* skip argc */
121
122 while(*esp != 0) /* skip argv */
123 esp++;
124 esp++;
125
126 while(*esp != 0) /* skip env */
127 esp++;
128 esp++;
129
130 return (struct ume_auxv *)esp;
131}
132
nethercote31779c72004-07-30 21:50:15 +0000133/*------------------------------------------------------------*/
134/*--- Loading ELF files ---*/
135/*------------------------------------------------------------*/
fitzhardinge7e343cd2003-12-16 02:14:00 +0000136
137struct elfinfo *readelf(int fd, const char *filename)
138{
139 struct elfinfo *e = malloc(sizeof(*e));
140 int phsz;
141
nethercote7c018f42004-07-17 16:40:50 +0000142 assert(e);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000143 e->fd = fd;
144
145 if (pread(fd, &e->e, sizeof(e->e), 0) != sizeof(e->e)) {
nethercote08eaff32004-07-22 12:41:12 +0000146 fprintf(stderr, "valgrind: %s: can't read elf header: %s\n",
fitzhardinge7e343cd2003-12-16 02:14:00 +0000147 filename, strerror(errno));
148 return NULL;
149 }
150
151 if (memcmp(&e->e.e_ident[0], ELFMAG, SELFMAG) != 0) {
nethercote08eaff32004-07-22 12:41:12 +0000152 fprintf(stderr, "valgrind: %s: bad ELF magic\n", filename);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000153 return NULL;
154 }
nethercotebdaa89f2004-10-09 19:08:08 +0000155 if (e->e.e_ident[EI_CLASS] != VG_ELF_CLASS) {
156 fprintf(stderr, "valgrind: wrong executable class (eg. 32-bit instead\n"
157 "valgrind: of 64-bit)\n");
fitzhardinge7e343cd2003-12-16 02:14:00 +0000158 return NULL;
159 }
nethercotebdaa89f2004-10-09 19:08:08 +0000160 if (e->e.e_ident[EI_DATA] != VG_ELF_ENDIANNESS) {
161 fprintf(stderr, "valgrind: wrong endian-ness\n");
fitzhardinge7e343cd2003-12-16 02:14:00 +0000162 return NULL;
163 }
164 if (!(e->e.e_type == ET_EXEC || e->e.e_type == ET_DYN)) {
nethercote08eaff32004-07-22 12:41:12 +0000165 fprintf(stderr, "valgrind: need executable\n");
fitzhardinge7e343cd2003-12-16 02:14:00 +0000166 return NULL;
167 }
168
nethercotebdaa89f2004-10-09 19:08:08 +0000169 if (e->e.e_machine != VG_ELF_MACHINE) {
170 fprintf(stderr, "valgrind: wrong architecture\n");
fitzhardinge7e343cd2003-12-16 02:14:00 +0000171 return NULL;
172 }
173
174 if (e->e.e_phentsize != sizeof(ESZ(Phdr))) {
nethercote08eaff32004-07-22 12:41:12 +0000175 fprintf(stderr, "valgrind: sizeof Phdr wrong\n");
fitzhardinge7e343cd2003-12-16 02:14:00 +0000176 return NULL;
177 }
178
179 phsz = sizeof(ESZ(Phdr)) * e->e.e_phnum;
180 e->p = malloc(phsz);
nethercote7c018f42004-07-17 16:40:50 +0000181 assert(e->p);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000182
183 if (pread(fd, e->p, phsz, e->e.e_phoff) != phsz) {
nethercote08eaff32004-07-22 12:41:12 +0000184 fprintf(stderr, "valgrind: can't read phdr: %s\n", strerror(errno));
fitzhardinge7e343cd2003-12-16 02:14:00 +0000185 return NULL;
186 }
187
188 return e;
189}
190
fitzhardinge7e343cd2003-12-16 02:14:00 +0000191/* Map an ELF file. Returns the brk address. */
fitzhardingeb50068f2004-02-24 23:42:55 +0000192ESZ(Addr) mapelf(struct elfinfo *e, ESZ(Addr) base)
fitzhardinge7e343cd2003-12-16 02:14:00 +0000193{
194 int i;
nethercotebfed1c82004-07-17 12:57:44 +0000195 void* res;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000196 ESZ(Addr) elfbrk = 0;
197
198 for(i = 0; i < e->e.e_phnum; i++) {
199 ESZ(Phdr) *ph = &e->p[i];
200 ESZ(Addr) addr, brkaddr;
201 ESZ(Word) memsz;
202
203 if (ph->p_type != PT_LOAD)
204 continue;
205
206 addr = ph->p_vaddr+base;
207 memsz = ph->p_memsz;
208 brkaddr = addr+memsz;
209
210 if (brkaddr > elfbrk)
211 elfbrk = brkaddr;
212 }
213
fitzhardinge7e343cd2003-12-16 02:14:00 +0000214 for(i = 0; i < e->e.e_phnum; i++) {
215 ESZ(Phdr) *ph = &e->p[i];
216 ESZ(Addr) addr, bss, brkaddr;
217 ESZ(Off) off;
218 ESZ(Word) filesz;
219 ESZ(Word) memsz;
220 ESZ(Word) align;
221 unsigned prot = 0;
222
223 if (ph->p_type != PT_LOAD)
224 continue;
225
226 if (ph->p_flags & PF_X)
227 prot |= PROT_EXEC;
228 if (ph->p_flags & PF_W)
229 prot |= PROT_WRITE;
230 if (ph->p_flags & PF_R)
231 prot |= PROT_READ;
232
233 align = ph->p_align;
234
235 addr = ph->p_vaddr+base;
236 off = ph->p_offset;
237 filesz = ph->p_filesz;
238 bss = addr+filesz;
239 memsz = ph->p_memsz;
240 brkaddr = addr+memsz;
241
nethercotebfed1c82004-07-17 12:57:44 +0000242 res = mmap((char *)ROUNDDN(addr, align),
243 ROUNDUP(bss, align)-ROUNDDN(addr, align),
244 prot, MAP_FIXED|MAP_PRIVATE, e->fd, ROUNDDN(off, align));
245 check_mmap(res, (char*)ROUNDDN(addr,align),
246 ROUNDUP(bss, align)-ROUNDDN(addr, align));
fitzhardinge7e343cd2003-12-16 02:14:00 +0000247
248 /* if memsz > filesz, then we need to fill the remainder with zeroed pages */
249 if (memsz > filesz) {
250 UInt bytes;
251
252 bytes = ROUNDUP(brkaddr, align)-ROUNDUP(bss, align);
nethercotebfed1c82004-07-17 12:57:44 +0000253 if (bytes > 0) {
254 res = mmap((char *)ROUNDUP(bss, align), bytes,
255 prot, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
256 check_mmap(res, (char*)ROUNDUP(bss,align), bytes);
257 }
fitzhardinge7e343cd2003-12-16 02:14:00 +0000258
259 bytes = bss & (VKI_BYTES_PER_PAGE - 1);
260 if (bytes > 0) {
261 bytes = VKI_BYTES_PER_PAGE - bytes;
262 memset((char *)bss, 0, bytes);
263 }
264 }
265 }
266
267 return elfbrk;
268}
269
nethercote31779c72004-07-30 21:50:15 +0000270// Forward declaration.
fitzhardinge7e343cd2003-12-16 02:14:00 +0000271static int do_exec_inner(const char *exe, struct exeinfo *info);
272
fitzhardinge7e343cd2003-12-16 02:14:00 +0000273static int match_ELF(const char *hdr, int len)
274{
275 ESZ(Ehdr) *e = (ESZ(Ehdr) *)hdr;
276 return (len > sizeof(*e)) && memcmp(&e->e_ident[0], ELFMAG, SELFMAG) == 0;
277}
278
nethercote31779c72004-07-30 21:50:15 +0000279static int load_ELF(char *hdr, int len, int fd, const char *name,
280 struct exeinfo *info)
fitzhardinge7e343cd2003-12-16 02:14:00 +0000281{
282 struct elfinfo *e;
283 struct elfinfo *interp = NULL;
fitzhardingeca9bd9c2004-09-08 20:05:02 +0000284 ESZ(Addr) exeoff = 0; /* offset between link address and load address */
285 ESZ(Addr) minaddr = ~0; /* lowest mapped address */
286 ESZ(Addr) maxaddr = 0; /* highest mapped address */
287 ESZ(Addr) interp_addr = 0; /* interpreter (ld.so) address */
288 ESZ(Word) interp_size = 0; /* interpreter size */
fitzhardinge7e343cd2003-12-16 02:14:00 +0000289 int i;
290 void *entry;
291
292 e = readelf(fd, name);
293
294 if (e == NULL)
295 return ENOEXEC;
296
297 info->phnum = e->e.e_phnum;
298 info->entry = e->e.e_entry;
299
300 for(i = 0; i < e->e.e_phnum; i++) {
301 ESZ(Phdr) *ph = &e->p[i];
302
303 switch(ph->p_type) {
304 case PT_PHDR:
305 info->phdr = ph->p_vaddr;
306 break;
307
308 case PT_LOAD:
309 if (ph->p_vaddr < minaddr)
310 minaddr = ph->p_vaddr;
311 if (ph->p_vaddr+ph->p_memsz > maxaddr)
312 maxaddr = ph->p_vaddr+ph->p_memsz;
313 break;
314
315 case PT_INTERP: {
316 char *buf = malloc(ph->p_filesz+1);
317 int j;
318 int intfd;
319 int baseaddr_set;
320
nethercote7c018f42004-07-17 16:40:50 +0000321 assert(buf);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000322 pread(fd, buf, ph->p_filesz, ph->p_offset);
323 buf[ph->p_filesz] = '\0';
324
325 intfd = open(buf, O_RDONLY);
326 if (intfd == -1) {
327 perror("open interp");
328 exit(1);
329 }
330
331 interp = readelf(intfd, buf);
332 if (interp == NULL) {
333 fprintf(stderr, "Can't read interpreter\n");
334 return 1;
335 }
336 free(buf);
337
338 baseaddr_set = 0;
339 for(j = 0; j < interp->e.e_phnum; j++) {
340 ESZ(Phdr) *iph = &interp->p[j];
341 ESZ(Addr) end;
342
343 if (iph->p_type != PT_LOAD)
344 continue;
345
346 if (!baseaddr_set) {
347 interp_addr = iph->p_vaddr;
348 baseaddr_set = 1;
349 }
350
351 /* assumes that all segments in the interp are close */
352 end = (iph->p_vaddr - interp_addr) + iph->p_memsz;
353
354 if (end > interp_size)
355 interp_size = end;
356 }
357 break;
nethercoteb24cbc82004-09-03 23:25:33 +0000358
359 default:
360 // do nothing
361 break;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000362 }
363 }
364 }
365
fitzhardingeca9bd9c2004-09-08 20:05:02 +0000366 if (e->e.e_type == ET_DYN) {
367 /* PIE executable */
368 exeoff = info->exe_base - minaddr;
369 }
370
371 minaddr += exeoff;
372 maxaddr += exeoff;
373 info->phdr += exeoff;
374 info->entry += exeoff;
375
fitzhardinge7e343cd2003-12-16 02:14:00 +0000376 if (info->exe_base != info->exe_end) {
377 if (minaddr >= maxaddr ||
378 (minaddr < info->exe_base ||
379 maxaddr > info->exe_end)) {
nethercote2e1cb4c2004-08-05 12:16:13 +0000380 fprintf(stderr, "Executable range %p-%p is outside the\n"
381 "acceptable range %p-%p\n",
382 (void *)minaddr, (void *)maxaddr,
fitzhardinge7e343cd2003-12-16 02:14:00 +0000383 (void *)info->exe_base, (void *)info->exe_end);
384 return ENOMEM;
385 }
386 }
387
fitzhardingeca9bd9c2004-09-08 20:05:02 +0000388 info->brkbase = mapelf(e, exeoff); /* map the executable */
fitzhardinge7e343cd2003-12-16 02:14:00 +0000389
fitzhardinge92360792003-12-24 10:11:11 +0000390 if (info->brkbase == 0)
391 return ENOMEM;
392
fitzhardinge7e343cd2003-12-16 02:14:00 +0000393 if (interp != NULL) {
394 /* reserve a chunk of address space for interpreter */
nethercotebfed1c82004-07-17 12:57:44 +0000395 void* res;
396 char* base = (char *)info->exe_base;
397 char* baseoff;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000398 int flags = MAP_PRIVATE|MAP_ANONYMOUS;
399
400 if (info->map_base != 0) {
401 base = (char *)info->map_base;
402 flags |= MAP_FIXED;
403 }
404
nethercotebfed1c82004-07-17 12:57:44 +0000405 res = mmap(base, interp_size, PROT_NONE, flags, -1, 0);
406 check_mmap(res, base, interp_size);
407 base = res;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000408
409 baseoff = base - interp_addr;
410
fitzhardingeb50068f2004-02-24 23:42:55 +0000411 mapelf(interp, (ESZ(Addr))baseoff);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000412
413 close(interp->fd);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000414
415 entry = baseoff + interp->e.e_entry;
416 info->interp_base = (ESZ(Addr))base;
thughes54d08592004-09-26 14:42:47 +0000417
418 free(interp);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000419 } else
fitzhardingeca9bd9c2004-09-08 20:05:02 +0000420 entry = (void *)e->e.e_entry + exeoff;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000421
422 info->exe_base = minaddr;
423 info->exe_end = maxaddr;
424
425 info->init_eip = (addr_t)entry;
426
427 free(e);
428
429 return 0;
430}
431
432
433static int match_script(const char *hdr, Int len)
434{
435 return (len > 2) && memcmp(hdr, "#!", 2) == 0;
436}
437
nethercote31779c72004-07-30 21:50:15 +0000438static int load_script(char *hdr, int len, int fd, const char *name,
439 struct exeinfo *info)
fitzhardinge7e343cd2003-12-16 02:14:00 +0000440{
441 char *interp;
442 char *const end = hdr+len;
443 char *cp;
444 char *arg = NULL;
445 int eol;
446
447 interp = hdr + 2;
448 while(interp < end && (*interp == ' ' || *interp == '\t'))
449 interp++;
450
451 if (*interp != '/')
452 return ENOEXEC; /* absolute path only for interpreter */
453
454 /* skip over interpreter name */
455 for(cp = interp; cp < end && *cp != ' ' && *cp != '\t' && *cp != '\n'; cp++)
456 ;
457
458 eol = (*cp == '\n');
459
460 *cp++ = '\0';
461
462 if (!eol && cp < end) {
463 /* skip space before arg */
464 while (cp < end && (*cp == '\t' || *cp == ' '))
465 cp++;
466
467 /* arg is from here to eol */
468 arg = cp;
469 while (cp < end && *cp != '\n')
470 cp++;
471 *cp = '\0';
472 }
473
nethercoted6a56872004-07-26 15:32:47 +0000474 info->interp_name = strdup(interp);
475 assert(NULL != info->interp_name);
nethercote71980f02004-01-24 18:18:54 +0000476 if (arg != NULL && *arg != '\0') {
nethercoted6a56872004-07-26 15:32:47 +0000477 info->interp_args = strdup(arg);
478 assert(NULL != info->interp_args);
nethercote71980f02004-01-24 18:18:54 +0000479 }
fitzhardinge7e343cd2003-12-16 02:14:00 +0000480
481 if (info->argv && info->argv[0] != NULL)
482 info->argv[0] = (char *)name;
483
484 if (0)
nethercoted6a56872004-07-26 15:32:47 +0000485 printf("#! script: interp_name=\"%s\" interp_args=\"%s\"\n",
486 info->interp_name, info->interp_args);
fitzhardinge7e343cd2003-12-16 02:14:00 +0000487
488 return do_exec_inner(interp, info);
489}
490
fitzhardingefd7da3a2004-09-08 20:05:29 +0000491/*
492 Emulate the normal Unix permissions checking algorithm.
493
494 If owner matches, then use the owner permissions, else
495 if group matches, then use the group permissions, else
496 use other permissions.
497
498 Note that we can't deal with SUID/SGID, so we refuse to run them
499 (otherwise the executable may misbehave if it doesn't have the
500 permissions it thinks it does).
501*/
502static int check_perms(int fd)
503{
504 struct stat st;
505
506 if (fstat(fd, &st) == -1)
507 return errno;
508
509 if (st.st_mode & (S_ISUID | S_ISGID)) {
510 //fprintf(stderr, "Can't execute suid/sgid executable %s\n", exe);
511 return EACCES;
512 }
513
514 if (geteuid() == st.st_uid) {
515 if (!(st.st_mode & S_IXUSR))
516 return EACCES;
517 } else {
518 int grpmatch = 0;
519
520 if (getegid() == st.st_gid)
521 grpmatch = 1;
522 else {
523 gid_t groups[32];
524 int ngrp = getgroups(32, groups);
525 int i;
526
527 for(i = 0; i < ngrp; i++)
528 if (groups[i] == st.st_gid) {
529 grpmatch = 1;
530 break;
531 }
532 }
533
534 if (grpmatch) {
535 if (!(st.st_mode & S_IXGRP))
536 return EACCES;
537 } else if (!(st.st_mode & S_IXOTH))
538 return EACCES;
539 }
540
541 return 0;
542}
543
fitzhardinge7e343cd2003-12-16 02:14:00 +0000544static int do_exec_inner(const char *exe, struct exeinfo *info)
545{
546 int fd;
fitzhardinged2e37112004-09-09 08:10:42 +0000547 int err;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000548 char buf[VKI_BYTES_PER_PAGE];
549 int bufsz;
550 int i;
551 int ret;
nethercote31779c72004-07-30 21:50:15 +0000552 static const struct {
553 int (*match)(const char *hdr, int len);
554 int (*load) ( char *hdr, int len, int fd2, const char *name,
555 struct exeinfo *);
556 } formats[] = {
557 { match_ELF, load_ELF },
558 { match_script, load_script },
559 };
fitzhardinge7e343cd2003-12-16 02:14:00 +0000560
561 fd = open(exe, O_RDONLY);
562 if (fd == -1) {
563 if (0)
564 fprintf(stderr, "Can't open executable %s: %s\n",
565 exe, strerror(errno));
566 return errno;
567 }
568
fitzhardinged2e37112004-09-09 08:10:42 +0000569 err = check_perms(fd);
fitzhardingefd7da3a2004-09-08 20:05:29 +0000570 if (err != 0) {
571 close(fd);
572 return err;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000573 }
574
575 bufsz = pread(fd, buf, sizeof(buf), 0);
576 if (bufsz < 0) {
577 fprintf(stderr, "Can't read executable header: %s\n",
578 strerror(errno));
579 close(fd);
580 return errno;
581 }
582
583 ret = ENOEXEC;
584 for(i = 0; i < sizeof(formats)/sizeof(*formats); i++) {
585 if ((formats[i].match)(buf, bufsz)) {
586 ret = (formats[i].load)(buf, bufsz, fd, exe, info);
587 break;
588 }
589 }
590
591 close(fd);
592
593 return ret;
594}
595
nethercoteea147e72004-07-26 15:43:57 +0000596// See ume.h for an indication of which entries of 'info' are inputs, which
597// are outputs, and which are both.
fitzhardinge7e343cd2003-12-16 02:14:00 +0000598int do_exec(const char *exe, struct exeinfo *info)
599{
nethercoted6a56872004-07-26 15:32:47 +0000600 info->interp_name = NULL;
601 info->interp_args = NULL;
fitzhardinge7e343cd2003-12-16 02:14:00 +0000602
603 return do_exec_inner(exe, info);
604}
nethercotebb1c9912004-01-04 16:43:23 +0000605
606/*--------------------------------------------------------------------*/
607/*--- end ume.c ---*/
608/*--------------------------------------------------------------------*/