1
0
mirror of https://github.com/adtools/clib2.git synced 2025-12-08 14:59:05 +00:00
Files
amiga-clib2/library/unistd_execve.c
Olaf Barthel 42acda07f3 - Rewrote execve() from scratch and documented it.
git-svn-id: file:///Users/olsen/Code/migration-svn-zu-git/logical-line-staging/clib2/trunk@15113 87f5fb63-7c3d-0410-a384-fd976d0f7a62
2006-08-01 19:01:17 +00:00

724 lines
17 KiB
C

/*
* $Id: unistd_execve.c,v 1.2 2006-08-01 19:01:17 obarthel Exp $
*
* :ts=4
*
* Portable ISO 'C' (1994) runtime library for the Amiga computer
* Copyright (c) 2002-2006 by Olaf Barthel <olsen (at) sourcery.han.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Neither the name of Olaf Barthel nor the names of contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _UNISTD_HEADERS_H
#include "unistd_headers.h"
#endif /* _UNISTD_HEADERS_H */
/****************************************************************************/
/* The following is not part of the ISO 'C' (1994) standard. */
/****************************************************************************/
/* This gets handed around when trying to locate a program or a script
interpreter which knows how to do the job. */
struct program_info
{
struct Segment * resident_command; /* If not NULL, points to a valid
resident command */
BPTR home_dir; /* If not ZERO refers to the directory
in which the command to be executed
can be found */
BPTR segment_list; /* If not ZERO refers to a command
loaded into memory */
char * program_name; /* Points to the name of the command */
char * interpreter_name; /* If not NULL the name of the command
interpreter to use */
char * interpreter_args; /* If not NULL these are additional
arguments to be passedto the command
interpreter */
};
/****************************************************************************/
/* Try to find a resident command by name; returns a pointer to the Segment
data structure ready to use, or NULL if none could be found */
static struct Segment *
find_resident_command(const char * command_name)
{
struct Segment * seg;
/* This must be done under Forbid() since dos.library does not have
a more sophisticated arbitration method for this yet... */
Forbid();
seg = FindSegment((STRPTR)name,NULL,0);
if(seg != NULL)
{
/* Check if that's a disable command or something else. */
if((seg->seg_UC < 0) && ((seg->seg_UC > CMD_INTERNAL) || (seg->seg_UC <= CMD_DISABLED)))
{
seg = NULL;
}
else
{
/* Unless it's a built-in command, mark it as having another user. */
if(seg->seg_UC >= 0)
seg->seg_UC++;
}
}
Permit();
return(seg);
}
/****************************************************************************/
/* Try to read the first line of a script file */
static int
get_first_script_line(const char * path,char ** line_ptr)
{
BPTR script_file;
int result = -1;
char * script_line = NULL;
size_t script_line_size = 0;
LONG c;
(*line_ptr) = NULL;
script_file = Open(path,MODE_OLDFILE);
if(script_file == ZERO)
{
__set_errno(__translate_io_error_to_errno(IoErr()));
goto out;
}
/* Make file access a little more robust. */
SetVBuf(script_file,NULL,BUF_LINE,1024);
while((c = FGetC(script_file)) != -1)
{
/* Still enough room in the buffer? We always reserve
enough memory for the next character and a NUL
to terminate the string with. */
if(script_line_length + 2 > script_line_size)
{
char * new_script_line;
/* Always reserve a little more memory than needed,
and one extra byte to allow us to to NUL-terminate
the string. */
new_script_line = realloc(script_line,script_line_length + 10);
if(new_script_line == NULL)
{
__set_error(ENOMEM);
goto out;
}
script_line = new_script_line;
script_line_size = script_line_length + 10;
}
script_line[script_line_length++] = c;
/* Stop when we hit a line feed or unprintable character */
if(c == '\n' || c < ' ' || (c >= 128 && c < 160))
break;
}
/* Check for read error */
if(c == -1 && IoErr() != 0)
{
__set_errno(__translate_io_error_to_errno(IoErr()));
goto out;
}
/* Provide for NUL-termination. */
if(script_line_size > 0)
{
/* Also strip all trailing blank spaces; that includes
line feed and carriage return characters. */
while(script_line_length > 0 && isspace(script_line[script_line_length-1]))
script_line_length--;
script_line[script_line_length] = '\0';
}
(*line_ptr) = script_line;
result = 0;
out:
if(script_file != ZERO)
Close(script_file);
if(script_line != NULL)
free(script_line);
return(result);
}
/****************************************************************************/
/* Release all the resources allocate for the program information, as produced
by the find_command() function */
static void
free_program_info(struct program_info * pi)
{
if(pi != NULL)
{
if(pi->resident_command != NULL)
{
Forbid();
if(pi->resident_command->seg_UC > 0)
pi->resident_command->seg_UC--;
Permit();
}
if(pi->interpreter_name != NULL)
free(pi->interpreter_name);
if(pi->interpreter_args != NULL)
free(pi->interpreter_args);
if(pi->program_name != NULL)
free(pi->program_name);
if(pi->home_dir != ZERO)
UnLock(pi->home_dir);
if(pi->segment_list)
UnLoadSeg(pi->segment_list);
free(pi);
}
}
/****************************************************************************/
/* Try to find a command by name; if the name does not include any path
information, try the dos.library resident command list */
static int
find_command(char * path,struct program_info ** result_ptr)
{
struct name_translation_info nti;
char * script_line = NULL;
struct program_info * pi;
APTR old_window_ptr;
int result = -1;
BPTR file_lock;
BPTR old_dir;
int error;
(*result_ptr) = NULL;
/* We don't want to show any dos.library requesters while we
are looking */
old_window_ptr = __set_process_window((APTR)-1);
pi = malloc(sizeof(*pi));
if(pi == NULL)
{
__set_errno(ENOMEM);
goto out;
}
memset(pi,0,sizeof(*pi));
error = __translate_unix_to_amiga_path_name(&path,&nti);
if(error != 0)
{
__set_errno(error);
goto out;
}
/* No relative or absolute path given? */
if(FilePart(path) == (STRPTR)path)
{
/* Try to find the command on the resident list */
pi->resident_command = find_resident_command(path);
if(pi->resident_command == NULL)
{
__set_errno(ENOENT);
goto out;
}
}
else
{
struct MsgPort * file_system;
struct DevProc * dvp = NULL;
BOOL done = FALSE;
LONG io_err;
int error = 0;
/* Now for the simple stuff. Find a command or command script file
under the path name given. Handle multi-volume assignments, such as
referring to "C:" gracefully */
file_system = GetFileSysTask();
do
{
dvp = GetDeviceProc(path,dvp);
if(dvp != NULL)
{
SetFileSysTask(dvp->dvp_Port);
old_dir = CurrentDir(dvp->dvp_Lock);
/* First try: let's assume that that the file is
executable */
pi->segment_list = LoadSeg(path);
if(pi->segment_list != ZERO)
{
/* Remember where that file came from so that
"PROGDIR:" will work */
pi->home_dir = DupLock(dvp->dvp_Lock);
if(pi->home_dir != ZERO)
{
/* Also remember the name of the command */
pi->program_name = strdup(path);
if(pi->program_name != NULL)
done = TRUE;
else
error = ENOMEM;
}
else
{
error = __translate_io_error_to_errno(IoErr());
}
}
io_err = IoErr();
/* If that didn't work and we might be dealing with a script
file, have a closer look at it. */
if(error == 0 && !done && (io_err == ERROR_OBJECT_NOT_FOUND || io_err == ERROR_OBJECT_WRONG_TYPE || io_err == ERROR_BAD_HUNK))
{
/* Could that be an ARexx or shell script? */
if(get_first_script_line(path,&script_line) == 0)
{
if(strncmp(script_line,"/*",2) == SAME)
{
/* That's an ARexx script */
pi->interpreter_name = strdup("RX");
if(pi->interpreter_name != NULL)
done = TRUE;
else
error = ENOMEM;
}
else if (strncmp(script_line,"#!",2) == SAME)
{
char * name;
char * args;
/* That's probably a shell script */
name = &script_line[2];
while((*name) != '\0' && isspace(*name))
name++;
/* Do we have a command name? */
if((*name) != '\0')
{
/* Find out if there are any script parameters */
args = name;
while((*args) != '\0' && !isspace(*args))
args++;
if((*args) != '\0')
{
(*args++) = '\0';
while((*args) != '\0' && isspace(*args))
args++;
}
/* Remember the parameters, if any */
if((*args) != '\0')
{
pi->interpreter_args = strdup(args);
if(pi->interpreter_args == NULL)
error = ENOMEM;
}
/* And remember the interpreter name. */
if(error == 0)
{
pi->interpreter_name = strdup(name);
if(pi->interpreter_name != NULL)
done = TRUE;
else
error = ENOMEM;
}
}
}
free(script_line);
script_line = NULL;
}
/* If that still didn't work, check if the file has
the script bit set */
if(error == 0 && !done)
{
BPTR file_lock;
file_lock = Lock(path,SHARED_LOCK);
if(file_lock != ZERO)
{
D_S(struct FileInfoBlock,fib);
if(Examine(file_lock,fib))
{
if(fib->fib_Protection & FIBF_SCRIPT)
{
/* If it's an AmigaDOS script, remember
to run it through the Execute command */
pi->interpreter_name = strdup("Execute");
if(pi->interpreter_name != NULL)
done = TRUE;
else
error = ENOMEM;
}
}
else
{
error = __translate_io_error_to_errno(IoErr());
}
UnLock(file_lock);
}
}
}
CurrentDir(old_dir);
}
}
while(!done && error == 0 && dvp != NULL && (dvp->dvp_Flags & DVPF_ASSIGN));
SetFileSysTask(file_system);
if(error != 0)
{
__set_errno(error);
goto out;
}
}
(*result_ptr) = pi;
out:
if(script_line != NULL)
free(script_line);
if(pi != NULL)
free_program_info(pi);
__set_process_window(old_window_ptr);
return(result);
}
/****************************************************************************/
/* Scan the string, looking for characters which need to be
escape with a '*' if that string is to be quoted and the
contents should remain in the same form */
static size_t
count_extra_escape_chars(const char * string,size_t len)
{
size_t count = 0;
size_t i;
char c;
for(i = 0 ; i < len ; i++)
{
c = (*s++);
if(c == '\"' || c == '*' || c == '\n')
count++;
}
return(count);
}
/****************************************************************************/
/* Scan a string for characters which may require that the string
should be quoted */
STATIC BOOL
string_needs_quoting(const char * string,size_t len)
{
BOOL result = FALSE;
size_t i;
char c;
for(i = 0 ; i < len ; i++)
{
c = (*s++);
if(c == ' ' || ((unsigned char)c) == 0xA0 || c == '\t' || c == '\n' || c == '\"')
{
result = TRUE;
break;
}
}
return(result);
}
/****************************************************************************/
/* Figure out how many characters would go into a string composed of
individual arguments. This takes into account the lengths of
the individual argument strings, the separator characters, the
quote characters and any escape characters. */
static size_t
get_arg_string_length(char *const argv[])
{
size_t result = 0;
size_t i,len;
char * s;
for(i = 0 ; argv[i] != NULL ; i++)
{
s = (char *)argv[i];
len = strlen(s);
if(len > 0)
{
if((*s) != '\"')
{
if(string_needs_quoting(s,len))
len += 1 + count_extra_escape_chars(s,len) + 1;
}
if(result == 0)
result = len;
else
result = result + 1 + len;
}
}
return(result);
}
/****************************************************************************/
/* Put together an argument string from a list of individual
components, quoting characters, escape characters and
separator characters. You're supposed to have enough memory
reserved for the whole string to fit */
static void
build_arg_string(char *const argv[],char * arg_string)
{
BOOL first_char = FALSE;
size_t i,j,len;
char * s;
for(i = 0 ; argv[i] != NULL ; i++)
{
s = (char *)argv[i];
len = strlen(s);
if(len > 0)
{
if(first_char)
first_char = FALSE;
else
(*arg_string++) = ' ';
if((*s) != '\"' && string_needs_quoting(s,len))
{
(*arg_string++) = '\"';
for(j = 0 ; j < len ; j++)
{
if(s[j] == '\"' || s[j] == '*' || s[j] == '\n')
(*arg_string++) = '*';
(*arg_string++) = s[j];
}
(*arg_string++) = '\"';
}
else
{
memcpy(arg_string,s,len);
arg_string += len;
}
}
}
}
/****************************************************************************/
int
execve(const char *path, char *const argv[], char *const envp[])
{
struct Process * this_process = (struct Process *)FindTask(NULL);
char old_program_name[256]
int result = -1;
struct program_info * pi;
char * arg_string = NULL;
size_t arg_string_len = 0;
size_t parameter_string_len;
BOOL success = FALSE;
int error;
LONG rc;
LONG c;
/* We begin by trying to find the command to execute */
if(find_command((char *)path,&pi) != 0)
goto out;
/* We'll need to know how much memory to reserve for the
parameters anyway */
parameter_string_len = get_arg_string_length(argv);
/* Do we have to use a script interpreter? */
if(pi->interpreter_name != NULL)
{
struct program_info * pi_interpreter;
/* Now try to find the command corresponding to the
interpreter given */
if(find_command(pi->interpreter_name,&pi_interpreter) != 0)
goto out;
/* We only try to resolve the name once. If this is still
not a command we can launch, we chicken out */
if(pi_interpreter->interpreter_name != NULL)
{
free_program_info(pi_interpreter);
goto out;
}
/* Just remember the arguments that need to be passed
to the interpreter */
pi_interpreter->interpreter_args = pi->interpreter_args;
pi->interpreter_args = NULL;
free_program_info(pi);
pi = pi_interpreter;
/* Reserve as much memory as is required for the
interpreter's parameters and the command's
arguments */
if(pi->interpreter_args != NULL)
{
arg_string_len = strlen(pi->interpreter_args);
arg_string = malloc(arg_string_len + 1 + parameter_string_len + 1 + 1);
if(arg_string == NULL)
{
__set_errno(ENOMEM);
goto out;
}
memcpy(arg_string,pi->interpreter_args,arg_string_len);
if(parameter_string_len > 0)
arg_string[arg_string_len++] = ' ';
}
else
{
arg_string = malloc(parameter_string_len + 1 + 1);
}
}
else
{
arg_string = malloc(parameter_string_len + 1 + 1);
}
/* If that didn't work, we quit here */
if(arg_string == NULL)
{
__set_errno(ENOMEM);
goto out;
}
/* Any command parameters to take care of? */
if(parameter_string_len > 0)
{
build_arg_string(argv,&arg_string[arg_string_len]);
arg_string_len += parameter_string_len;
}
/* Add the terminating new line character and a NUL,
to be nice... */
arg_string[arg_string_len++] = '\n';
arg_string[arg_string_len] = '\0';
/* Change the shell's program name */
GetProgramName(old_program_name,sizeof(old_program_name));
SetProgramName(pi->program_name);
/* Change the command's home directory, so that "PROGDIR:"
can be used */
old_dir = ThisProcess->pr_HomeDir;
ThisProcess->pr_HomeDir = pi->home_dir;
/* Reset the break signal before the program starts */
SetSignal(0,SIGBREAKF_CTRL_C);
/* Now try to run the program with the accumulated parameters */
rc = RunCommand((pi->resident_command != NULL) ? pi->resident_command->seg_Seg : pi->segment_list,Cli()->cli_DefaultStack * sizeof(LONG),arg_string,arg_string_len);
/* Restore the home directory */
ThisProcess->pr_HomeDir = old_dir;
/* Restore the program name */
SetProgramName(old_program_name);
/* Did we launch the program? */
if(rc == -1)
{
__set_errno(__translate_io_error_to_errno(IoErr()));
goto out;
}
/* Looks good, doesn't it? */
success = TRUE;
out:
/* Clean up... */
if(pi != NULL)
free_program_info(pi);
if(arg_string != NULL)
free(arg_string);
/* If things went well, we can actually quit now. */
if(success)
exit(result);
/* This function only returns control to the caller
if something went wrong... */
return(-1);
}