sortix-mirror/tix/util.h

1314 lines
29 KiB
C

/*
* Copyright (c) 2013, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* util.h
* Shared Utility functions for Tix.
*/
#ifndef UTIL_H
#define UTIL_H
#define DEFAULT_GENERATION "3"
extern char** environ;
bool does_path_contain_dotdot(const char* path)
{
size_t index = 0;
while ( path[index] )
{
if ( path[index] == '.' && path[index+1] == '.' )
{
index += 2;
if ( !path[index] || path[index] == '/' )
return true;
}
while ( path[index] && path[index] != '/' )
index++;
while ( path[index] == '/' )
index++;
}
return false;
}
bool parse_boolean(const char* str)
{
return strcmp(str, "true") == 0;
}
char* strdup_null(const char* src)
{
return src ? strdup(src) : NULL;
}
char* strdup_null_if_content(const char* src)
{
if ( src && !src[0] )
return NULL;
return strdup_null(src);
}
const char* non_modify_basename(const char* path)
{
const char* last_slash = (char*) strrchr((char*) path, '/');
if ( !last_slash )
return path;
return last_slash + 1;
}
typedef struct
{
char** strings;
size_t length;
size_t capacity;
} string_array_t;
string_array_t string_array_make(void)
{
string_array_t sa;
sa.strings = NULL;
sa.length = sa.capacity = 0;
return sa;
}
void string_array_reset(string_array_t* sa)
{
for ( size_t i = 0; i < sa->length; i++ )
free(sa->strings[i]);
free(sa->strings);
*sa = string_array_make();
}
bool string_array_append(string_array_t* sa, const char* str)
{
if ( sa->length == sa->capacity )
{
size_t new_capacity = sa->capacity ? sa->capacity * 2 : 8;
size_t new_size = sizeof(char*) * new_capacity;
char** new_strings = (char**) realloc(sa->strings, new_size);
if ( !new_strings )
return false;
sa->strings = new_strings;
sa->capacity = new_capacity;
}
char* copy = strdup_null(str);
if ( str && !copy )
return false;
sa->strings[sa->length++] = copy;
return true;
}
bool string_array_append_token_string(string_array_t* sa, const char* str)
{
while ( *str )
{
if ( isspace((unsigned char) *str) )
{
str++;
continue;
}
size_t input_length = 0;
size_t output_length = 0;
bool quoted = false;
bool escaped = false;
while ( str[input_length] &&
(escaped || quoted || !isspace((unsigned char) str[input_length])) )
{
if ( !escaped && str[input_length] == '\\' )
escaped = true;
else if ( !escaped && str[input_length] == '"' )
quoted = !quoted;
else
escaped = false, output_length++;
input_length++;
}
if ( quoted || escaped )
return false;
char* output = (char*) malloc(sizeof(char) * (output_length+1));
if ( !output )
return false;
input_length = 0;
output_length = 0;
quoted = false;
escaped = false;
while ( str[input_length] &&
(escaped || quoted || !isspace((unsigned char) str[input_length])) )
{
if ( !escaped && str[input_length] == '\\' )
escaped = true;
else if ( !escaped && str[input_length] == '"' )
quoted = !quoted;
else if ( escaped && str[input_length] == 'a' )
escaped = false, output[output_length++] = '\a';
else if ( escaped && str[input_length] == 'b' )
escaped = false, output[output_length++] = '\b';
else if ( escaped && str[input_length] == 'e' )
escaped = false, output[output_length++] = '\e';
else if ( escaped && str[input_length] == 'f' )
escaped = false, output[output_length++] = '\f';
else if ( escaped && str[input_length] == 'n' )
escaped = false, output[output_length++] = '\n';
else if ( escaped && str[input_length] == 'r' )
escaped = false, output[output_length++] = '\r';
else if ( escaped && str[input_length] == 't' )
escaped = false, output[output_length++] = '\t';
else if ( escaped && str[input_length] == 'v' )
escaped = false, output[output_length++] = '\v';
else
escaped = false, output[output_length++] = str[input_length];
input_length++;
}
output[output_length] = '\0';
if ( !string_array_append(sa, output) )
{
free(output);
return false;
}
free(output);
str += input_length;
}
return true;
}
bool is_token_string_special_character(char c)
{
return isspace((unsigned char) c) || c == '"' || c == '\\';
}
char* token_string_of_string_array(const string_array_t* sa)
{
size_t result_length = 0;
for ( size_t i = 0; i < sa->length; i++ )
{
if ( i )
result_length++;
for ( size_t n = 0; sa->strings[i][n]; n++ )
{
if ( is_token_string_special_character(sa->strings[i][n]) )
result_length++;
result_length++;
}
}
char* result = (char*) malloc(sizeof(char) * (result_length + 1));
if ( !result )
return NULL;
result_length = 0;
for ( size_t i = 0; i < sa->length; i++ )
{
if ( i )
result[result_length++] = ' ';
for ( size_t n = 0; sa->strings[i][n]; n++ )
{
if ( is_token_string_special_character(sa->strings[i][n]) )
result[result_length++] = '\\';
result[result_length++] = sa->strings[i][n];
}
}
result[result_length] = '\0';
return result;
}
void string_array_append_file(string_array_t* sa, FILE* fp)
{
char* entry = NULL;
size_t entry_size = 0;
ssize_t entry_length;
while ( 0 < (entry_length = getline(&entry, &entry_size, fp)) )
{
if ( entry[entry_length-1] == '\n' )
entry[--entry_length] = '\0';
string_array_append(sa, entry);
}
free(entry);
assert(!ferror(fp));
}
bool string_array_append_file_path(string_array_t* sa, const char* path)
{
FILE* fp = fopen(path, "r");
if ( !fp )
return false;
string_array_append_file(sa, fp);
fclose(fp);
return true;
}
size_t string_array_find(string_array_t* sa, const char* str)
{
for ( size_t i = 0; i < sa->length; i++ )
if ( !strcmp(sa->strings[i], str) )
return i;
return SIZE_MAX;
}
bool string_array_contains(string_array_t* sa, const char* str)
{
return string_array_find(sa, str) != SIZE_MAX;
}
size_t dictionary_lookup_index(string_array_t* sa, const char* key)
{
size_t keylen = strlen(key);
for ( size_t i = 0; i < sa->length; i++ )
{
const char* entry = sa->strings[i];
if ( strncmp(key, entry, keylen) != 0 )
continue;
if ( entry[keylen] != '=' )
continue;
return i;
}
return SIZE_MAX;
}
const char* dictionary_get_entry(string_array_t* sa, const char* key)
{
size_t index = dictionary_lookup_index(sa, key);
if ( index == SIZE_MAX )
return NULL;
return sa->strings[index];
}
const char* dictionary_get_def(string_array_t* sa, const char* key,
const char* def)
{
size_t keylen = strlen(key);
const char* entry = dictionary_get_entry(sa, key);
return entry ? entry + keylen + 1 : def;
}
const char* dictionary_get(string_array_t* sa, const char* key)
{
return dictionary_get_def(sa, key, NULL);
}
void dictionary_normalize_entry(char* entry)
{
bool key = true;
size_t input_off, output_off;
for ( input_off = output_off = 0; entry[input_off]; input_off++ )
{
if ( key && isspace((unsigned char) entry[input_off]) )
continue;
if ( key && (entry[input_off] == '=' || entry[input_off] == '#') )
key = false;
entry[output_off++] = entry[input_off];
}
entry[output_off] = '\0';
}
bool is_identifier_char(char c)
{
return ('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
('0' <= c && c <= '9') ||
c == '_';
}
// 0 on success, -1 on error, -2 on syntax error.
int variables_parse_fp(string_array_t* sa, const char* line, FILE* fp)
{
size_t i = 0;
while ( isspace((unsigned char) line[i]) )
i++;
if ( !line[i] || line[i] == '#' )
return 0;
if ( line[i] == '=' )
return -2;
size_t keylen = 0;
while ( line[i + keylen] &&
line[i + keylen] != '=' &&
line[i + keylen] != '#' &&
!isspace((unsigned char) line[i + keylen]) )
{
if ( fputc((unsigned char) line[i + keylen++], fp) == EOF )
return -1;
}
i += keylen;
if ( line[i++] != '=' )
return -2;
fputc('=', fp);
bool escaped = false;
bool singly_quote = false;
bool doubly_quote = false;
while ( true )
{
unsigned char c = (unsigned char) line[i++];
if ( !c )
{
i--;
break;
}
else if ( !escaped && !singly_quote && c == '\\' )
escaped = true;
else if ( !escaped && !doubly_quote && c == '\'' )
singly_quote = !singly_quote;
else if ( !escaped && !singly_quote && c == '"' )
doubly_quote = !doubly_quote;
else if ( !escaped && !singly_quote && c == '$' &&
(is_identifier_char(line[i]) || line[i] == '{') )
{
size_t start = i;
size_t keylen = 0;
if ( line[start] == '{' )
{
start++;
while ( line[start + keylen] && line[start + keylen] != '}' )
keylen++;
if ( !keylen )
return -2;
if ( line[start + keylen] != '}' )
return -2;
i = start + keylen + 1;
}
else
{
while ( line[start + keylen] &&
is_identifier_char(line[start + keylen]) )
keylen++;
i = start + keylen;
}
const char* key = line + start;
const char* value = NULL;
for ( size_t n = 0; !value && n < sa->length; n++ )
{
const char* entry = sa->strings[n];
if ( strncmp(key, entry, keylen) != 0 )
continue;
if ( entry[keylen] != '=' )
continue;
value = entry + keylen + 1;
}
if ( !value )
return -2;
size_t length = strlen(value);
if ( fwrite(value, 1, length, fp) != length )
return -1;
}
else
{
if ( !escaped && !singly_quote && !doubly_quote && isspace(c) )
{
i--;
break;
}
if ( fputc(c, fp) == EOF )
return -1;
escaped = false;
}
}
while ( isspace((unsigned char) line[i]) )
i++;
if ( line[i] && line[i] != '#' )
return -2;
return 1;
}
int variables_parse(string_array_t* sa, const char* line, char** out_ptr)
{
size_t out_size;
FILE* fp = open_memstream(out_ptr, &out_size);
if ( !fp )
return -1;
int result = variables_parse_fp(sa, line, fp);
if ( fclose(fp) == EOF )
result = -1;
if ( result <= 0 )
free(*out_ptr);
return result;
}
int variables_append_file(string_array_t* sa, FILE* fp)
{
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
while ( 0 < (line_length = getline(&line, &line_size, fp)) )
{
if ( line[line_length-1] == '\n' )
line[--line_length] = '\0';
char* entry;
int result = variables_parse(sa, line, &entry);
if ( result == 0 )
continue;
if ( result < 0 )
{
free(line);
return result;
}
if ( !string_array_append(sa, entry) )
{
free(entry);
free(line);
return -1;
}
free(entry);
}
free(line);
if ( ferror(fp) )
return -1;
return 0;
}
int variables_append_file_path(string_array_t* sa, const char* path)
{
FILE* fp = fopen(path, "r");
if ( !fp )
return -1;
int result = variables_append_file(sa, fp);
fclose(fp);
return result;
}
__attribute__((format(printf, 1, 2)))
char* print_string(const char* format, ...)
{
va_list ap;
va_start(ap, format);
char* ret;
if ( vasprintf(&ret, format, ap) < 0 )
ret = NULL;
va_end(ap);
return ret;
}
char* read_single_line(FILE* fp)
{
char* ret = NULL;
size_t ret_size = 0;
ssize_t ret_len = getline(&ret, &ret_size, fp);
if ( ret_len < 0 )
{
free(ret);
return NULL;
}
if ( ret[ret_len-1] == '\n' )
ret[--ret_len] = '\0';
return ret;
}
pid_t fork_or_death(void)
{
pid_t child_pid = fork();
if ( child_pid < 0 )
err(1, "fork");
return child_pid;
}
void waitpid_or_death_def(pid_t child_pid, bool die_on_error)
{
int status;
waitpid(child_pid, &status, 0);
if ( die_on_error )
{
if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 )
exit(WEXITSTATUS(status));
if ( WIFSIGNALED(status) )
errx(128 + WTERMSIG(status), "child with pid %ji was killed by "
"signal %i (%s).", (intmax_t) child_pid, WTERMSIG(status),
strsignal(WTERMSIG(status)));
}
}
void waitpid_or_death(pid_t child_pid)
{
return waitpid_or_death_def(child_pid, true);
}
bool fork_and_wait_or_death_def(bool die_on_error)
{
pid_t child_pid = fork_or_death();
if ( !child_pid )
return true;
waitpid_or_death_def(child_pid, die_on_error);
return false;
}
bool fork_and_wait_or_death(void)
{
return fork_and_wait_or_death_def(true);
}
const char* getenv_def(const char* var, const char* def)
{
const char* ret = getenv(var);
return ret ? ret : def;
}
int mkdir_p(const char* path, mode_t mode)
{
int saved_errno = errno;
if ( !mkdir(path, mode) )
return 0;
if ( errno == ENOENT )
{
char* prev = strdup(path);
if ( !prev )
return -1;
int status = mkdir_p(dirname(prev), mode | 0500);
free(prev);
if ( status < 0 )
return -1;
errno = saved_errno;
if ( !mkdir(path, mode) )
return 0;
}
if ( errno == EEXIST )
return errno = saved_errno, 0;
return -1;
}
static void compact_arguments(int* argc, char*** argv)
{
for ( int i = 0; i < *argc; i++ )
{
while ( i < *argc && !(*argv)[i] )
{
for ( int n = i; n < *argc; n++ )
(*argv)[n] = (*argv)[n+1];
(*argc)--;
}
}
}
char* GetBuildTriplet(void)
{
#if defined(__sortix__) && defined(__i386__)
#if defined(__i686__)
return "i686-sortix";
#elif defined(__i586__)
return "i586-sortix";
#elif defined(__i486__)
return "i486-sortix";
#else
return "i386-sortix";
#endif
#elif defined(__sortix__) && defined(__x86_64__)
return strdup("x86_64-sortix");
#elif defined(__sortix__)
#warning "Add your build triplet here"
#endif
FILE* fp = popen("cc -dumpmachine", "r");
if ( !fp )
return NULL;
char* ret = read_single_line(fp);
pclose(fp);
return ret;
}
bool get_option_variable(const char* option, char** varptr,
const char* arg, int argc, char** argv, int* ip,
const char* argv0)
{
size_t option_len = strlen(option);
if ( strncmp(option, arg, option_len) != 0 )
return false;
if ( arg[option_len] == '=' )
{
*varptr = strdup(arg + option_len + 1);
return true;
}
if ( arg[option_len] != '\0' )
return false;
if ( *ip + 1 == argc )
{
fprintf(stderr, "%s: expected operand after `%s'\n", argv0, option);
exit(1);
}
*varptr = strdup(argv[++*ip]), argv[*ip] = NULL;
return true;
}
char* join_paths(const char* a, const char* b)
{
size_t a_len = strlen(a);
bool has_slash = (a_len && a[a_len-1] == '/') || b[0] == '/';
return has_slash ? print_string("%s%s", a, b) : print_string("%s/%s", a, b);
}
bool IsFile(const char* path)
{
struct stat st;
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
}
bool IsDirectory(const char* path)
{
struct stat st;
return stat(path, &st) == 0 &&
(S_ISDIR(st.st_mode) || (errno = ENOTDIR, false));
}
size_t count_tar_components(const char* path)
{
if ( !*path )
return 0;
size_t slashes = 1;
for ( size_t i = 0; path[i]; i++ )
if ( path[i] == '/' )
slashes++;
return slashes;
}
#define GET_OPTION_VARIABLE(str, varptr) \
get_option_variable(str, varptr, arg, argc, argv, &i, argv0)
// TODO: This is a bit inefficient but doing it otherwise would involve looking
// through the error stream for messages such as "file not found", which
// can be hard to distinguish from the common "oh no, an error occured"
// case in which we need to abort as well.
bool TarContainsFile(const char* archive, const char* file)
{
int pipes[2];
if ( pipe(pipes) )
err(1, "pipe");
pid_t tar_pid = fork_or_death();
if ( !tar_pid )
{
dup2(pipes[1], 1);
close(pipes[1]);
close(pipes[0]);
const char* cmd_argv[] =
{
"tar",
"--list",
"--file", archive,
NULL
};
execvp(cmd_argv[0], (char* const*) cmd_argv);
err(127, "%s", cmd_argv[0]);
}
close(pipes[1]);
FILE* fp = fdopen(pipes[0], "r");
char* line = NULL;
size_t line_size = 0;
ssize_t line_len;
bool ret = false;
while ( 0 < (line_len = getline(&line, &line_size, fp)) )
{
if ( line[line_len-1] == '\n' )
line[--line_len] = '\0';
if ( strcmp(line, file) == 0 )
{
ret = true;
#if !defined(__sortix__)
kill(tar_pid, SIGPIPE);
break;
#endif
}
}
free(line);
if ( ferror(fp) )
err(1, "getline: tar");
fclose(fp);
int tar_exit_status;
waitpid(tar_pid, &tar_exit_status, 0);
bool sigpiped = WIFSIGNALED(tar_exit_status) &&
WTERMSIG(tar_exit_status) == SIGPIPE;
bool errored = !WIFEXITED(tar_exit_status) ||
WEXITSTATUS(tar_exit_status) != 0;
if ( errored && !sigpiped )
{
errx(1, "Unable to list contents of `%s'.", archive);
exit(WEXITSTATUS(tar_exit_status));
}
return ret;
}
void TarExtractFileToFD(const char* archive, const char* file, int fd)
{
pid_t tar_pid = fork_or_death();
if ( !tar_pid )
{
if ( dup2(fd, 1) < 0 )
{
warn("dup2");
_exit(127);
}
close(fd);
const char* cmd_argv[] =
{
"tar",
"--to-stdout",
"--extract",
"--file", archive,
"--", file,
NULL
};
execvp(cmd_argv[0], (char* const*) cmd_argv);
warn("%s", cmd_argv[0]);
_exit(127);
}
int tar_exit_status;
waitpid(tar_pid, &tar_exit_status, 0);
if ( !WIFEXITED(tar_exit_status) || WEXITSTATUS(tar_exit_status) != 0 )
{
errx(1, "Unable to extract `%s/%s'", archive, file);
exit(WEXITSTATUS(tar_exit_status));
}
}
FILE* TarOpenFile(const char* archive, const char* file)
{
FILE* fp = tmpfile();
if ( !fp )
err(1, "tmpfile");
TarExtractFileToFD(archive, file, fileno(fp));
if ( fseeko(fp, 0, SEEK_SET) < 0 )
err(1, "fseeko(tmpfile(), 0, SEEK_SET)");
return fp;
}
void TarIndexToFD(const char* archive, int fd)
{
pid_t tar_pid = fork_or_death();
if ( !tar_pid )
{
if ( dup2(fd, 1) < 0 )
{
warn("dup2");
_exit(127);
}
close(fd);
const char* cmd_argv[] =
{
"tar",
"--list",
"--file", archive,
NULL
};
execvp(cmd_argv[0], (char* const*) cmd_argv);
warn("%s", cmd_argv[0]);
_exit(127);
}
int tar_exit_status;
waitpid(tar_pid, &tar_exit_status, 0);
if ( !WIFEXITED(tar_exit_status) || WEXITSTATUS(tar_exit_status) != 0 )
{
errx(1, "Unable to list contents of `%s'", archive);
exit(WEXITSTATUS(tar_exit_status));
}
}
FILE* TarOpenIndex(const char* archive)
{
FILE* fp = tmpfile();
if ( !fp )
err(1, "tmpfile");
TarIndexToFD(archive, fileno(fp));
if ( fseeko(fp, 0, SEEK_SET) < 0 )
err(1, "fseeko(tmpfile(), 0, SEEK_SET)");
return fp;
}
const char* VerifyInfoVariable(string_array_t* info, const char* var,
const char* path)
{
const char* ret = dictionary_get(info, var);
if ( !ret )
errx(1, "error: `%s': no `%s' variable declared", path, var);
return ret;
}
void VerifyTixInformation(string_array_t* tixinfo, const char* tix_path)
{
const char* tix_version = dictionary_get(tixinfo, "tix.version");
if ( !tix_version )
errx(1, "error: `%s': no `tix.version' variable declared",
tix_path);
if ( atoi(tix_version) != 1 )
errx(1, "error: `%s': tix version `%s' not supported", tix_path,
tix_version);
const char* tix_class = dictionary_get(tixinfo, "tix.class");
if ( !tix_class )
errx(1, "error: `%s': no `tix.class' variable declared", tix_path);
if ( !strcmp(tix_class, "srctix") )
errx(1, "error: `%s': this object is a source tix and needs to be "
"compiled into a binary tix prior to installation.",
tix_path);
if ( strcmp(tix_class, "tix") )
errx(1, "error: `%s': tix class `%s' is not `tix': this object is "
"not suitable for installation.", tix_path, tix_class);
if ( !(dictionary_get(tixinfo, "tix.platform")) )
errx(1, "error: `%s': no `tix.platform' variable declared", tix_path);
if ( !(dictionary_get(tixinfo, "pkg.name")) )
errx(1, "error: `%s': no `pkg.name' variable declared", tix_path);
}
bool IsCollectionPrefixRatherThanCommand(const char* arg)
{
return strchr(arg, '/') || !strcmp(arg, ".") || !strcmp(arg, "..");
}
void ParseOptionalCommandLineCollectionPrefix(char** collection, int* argcp,
char*** argvp)
{
if ( 2 <= *argcp && IsCollectionPrefixRatherThanCommand((*argvp)[1]) )
{
if ( !*collection )
{
free(*collection);
*collection = strdup((*argvp)[1]);
}
(*argvp)[1] = NULL;
compact_arguments(argcp, argvp);
}
else if ( !*collection )
{
*collection = strdup("/");
}
}
void VerifyCommandLineCollection(char** collection)
{
if ( !*collection )
errx(1, "error: you need to specify which tix collection to administer "
"using --collection or giving the prefix as the first "
"argument.");
if ( !**collection )
{
free(*collection);
*collection = strdup("/");
}
char* collection_rel = *collection;
if ( !(*collection = realpath(collection_rel, NULL)) )
err(1, "realpath: %s", collection_rel);
free(collection_rel);
}
void VerifyTixCollectionConfiguration(string_array_t* info, const char* path)
{
// TODO: After releasing Sortix 1.1, remove generation 2 compatibility.
const char* tix_version = dictionary_get(info, "tix.version");
if ( tix_version )
{
if ( !tix_version )
errx(1, "error: `%s': no `tix.version' variable declared", path);
if ( atoi(tix_version) != 1 )
errx(1, "error: `%s': tix version `%s' not supported", path,
tix_version);
const char* tix_class = dictionary_get(info, "tix.class");
if ( !tix_class )
errx(1, "error: `%s': no `tix.class' variable declared", path);
if ( strcmp(tix_class, "collection") != 0 )
errx(1, "error: `%s': error: unexpected tix class `%s'.", path,
tix_class);
if ( !(dictionary_get(info, "collection.prefix")) )
errx(1, "error: `%s': no `collection.prefix' variable declared",
path);
if ( !(dictionary_get(info, "collection.platform")) )
errx(1, "error: `%s': no `collection.platform' variable declared",
path);
return;
}
const char* version = dictionary_get(info, "TIX_COLLECTION_VERSION");
if ( !version )
errx(1, "%s: Mandatory TIX_COLLECTION_VERSION was not set", path);
if ( atoi(version) != 3 )
errx(1, "%s: Unsupported: TIX_COLLECTION_VERSION: %s", path, version);
if ( !(dictionary_get(info, "PREFIX")) )
errx(1, "%s: Mandatory PREFIX was not set", path);
if ( !(dictionary_get(info, "PLATFORM")) )
errx(1, "%s: Mandatory PLATFORM was not set", path);
}
static pid_t original_pid;
char* tmp_root = NULL;
static void cleanup_tmp(void)
{
if ( original_pid != getpid() )
return;
if ( !tmp_root )
return;
pid_t pid = fork();
if ( pid < 0 )
{
warn("fork");
return;
}
if ( pid == 0 )
{
const char* cmd_argv[] =
{
"rm",
"-rf",
"--",
(const char*) tmp_root,
NULL,
};
execvp(cmd_argv[0], (char* const*) cmd_argv);
warn("%s", cmd_argv[0]);
_exit(127);
}
int code;
waitpid(pid, &code, 0);
free(tmp_root);
tmp_root = NULL;
}
void initialize_tmp(const char* tmp, const char* purpose)
{
if ( tmp_root )
errx(1, "error: initialize_tmp called twice");
if ( asprintf(&tmp_root, "%s/%s.XXXXXX", tmp, purpose) < 0 )
err(1, "error: asprintf");
if ( !mkdtemp(tmp_root) )
err(1, "mkdtemp: `%s'", tmp_root);
original_pid = getpid();
if ( atexit(cleanup_tmp) != 0 )
{
int errnum = errno;
cleanup_tmp();
errno = errnum;
err(1, "atexit");
}
}
mode_t get_umask_value(void)
{
mode_t result = umask(0);
umask(result);
return result;
}
int fchmod_plus_x(int fd)
{
struct stat st;
if ( fstat(fd, &st) != 0 )
return -1;
mode_t new_mode = st.st_mode | (0111 & ~get_umask_value());
if ( fchmod(fd, new_mode) != 0 )
return -1;
return 0;
}
void fprint_shell_variable_assignment(FILE* fp, const char* variable, const char* value)
{
if ( value )
{
fprintf(fp, "export %s='", variable);
for ( size_t i = 0; value[i]; i++ )
if ( value[i] == '\'' )
fprintf(fp, "'\\''");
else
fputc(value[i], fp);
fprintf(fp, "'\n");
}
else
{
fprintf(fp, "unset %s\n", variable);
}
}
bool needs_single_quote(const char* string)
{
for ( size_t i = 0; string[i]; i++ )
{
char c = string[i];
if ( !(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
('0' <= c && c <= '9') ||
c == '/' || c == '_' || c == '.' || c == '+' || c == ':' ||
c == '%' || c == '$' || c == '{' || c == '}' || c == '-') )
return true;
}
return false;
}
void fwrite_variable(FILE* fp, const char* key, const char* value)
{
fprintf(fp, "%s=", key);
if ( !needs_single_quote(value) )
fprintf(fp, "%s\n", value);
else
{
fputc('\'', fp);
for ( size_t i = 0; value[i]; i++ )
if ( value[i] == '\'' )
fprintf(fp, "'\\''");
else
fputc(value[i], fp);
fputs("'\n", fp);
}
}
bool is_success_exit_status(int status)
{
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
__attribute__((noreturn))
bool exit_like_exit_status(int status)
{
if ( WIFEXITED(status) )
exit(WEXITSTATUS(status));
if ( WIFSIGNALED(status) )
exit(128 + WTERMSIG(status));
exit(1);
}
enum recovery_state
{
RECOVERY_STATE_NONE,
RECOVERY_STATE_PRINT_COMMAND,
RECOVERY_STATE_RUN_SHELL,
};
enum recovery_state
recovery_configure_state_def(bool set, enum recovery_state to_what)
{
static enum recovery_state recovery_state = RECOVERY_STATE_NONE;
if ( set )
recovery_state = to_what;
return recovery_state;
}
enum recovery_state
recovery_configure_state(bool set)
{
return recovery_configure_state_def(set, RECOVERY_STATE_NONE);
}
bool recovery_print_attempted_execution(void)
{
pid_t child_pid = fork();
if ( child_pid < 0 )
return false;
if ( !child_pid )
{
// Redirect stdout and stderr to /dev/null to prevent duplicate errors.
// The recovery_execvp function below will automatically re-open the
// terminal device when it needs to talk to the terminal.
int dev_null = open("/dev/null", O_WRONLY);
if ( 0 <= dev_null )
{
dup2(dev_null, 1);
dup2(dev_null, 2);
close(dev_null);
}
recovery_configure_state_def(true, RECOVERY_STATE_PRINT_COMMAND);
return true;
}
int status;
waitpid(child_pid, &status, 0);
return false;
}
bool recovery_run_shell(void)
{
pid_t child_pid = fork();
if ( child_pid < 0 )
return false;
if ( !child_pid )
{
recovery_configure_state_def(true, RECOVERY_STATE_RUN_SHELL);
return true;
}
int status;
waitpid(child_pid, &status, 0);
return false;
}
int recovery_execvp(const char* path, char* const* argv)
{
if ( recovery_configure_state(false) == RECOVERY_STATE_NONE )
return execvp(path, argv);
// Make sure that stdout and stderr go to an interactive terminal.
if ( !isatty(1) )
{
int dev_tty = open("/dev/tty", O_WRONLY);
if ( 0 <= dev_tty )
{
dup2(dev_tty, 1);
dup2(dev_tty, 2);
close(dev_tty);
}
}
printf("Attempted command was: ");
for ( int i = 0; argv[i]; i++ )
{
if ( i )
putchar(' ');
putchar('\'');
for ( size_t n = 0; argv[i][n]; n++ )
if ( argv[i][n] == '\'' )
printf("'\\''");
else
putchar(argv[i][n]);
putchar('\'');
}
printf("\n");
fflush(stdout);
if ( recovery_configure_state(false) == RECOVERY_STATE_PRINT_COMMAND )
_exit(0);
const char* cmd_argv[] =
{
getenv_def("SHELL", "sh"),
NULL
};
execvp(cmd_argv[0], (char* const*) cmd_argv);
err(127, "%s", cmd_argv[0]);
}
bool fork_and_wait_or_recovery(void)
{
int default_selection = 1;
while ( true )
{
pid_t child_pid = fork_or_death();
if ( !child_pid )
return true;
int status;
waitpid(child_pid, &status, 0);
if ( is_success_exit_status(status) )
return false;
if ( WIFEXITED(status) )
warnx("child with pid %ju exited with status %i.",
(intmax_t) child_pid, WEXITSTATUS(status));
else if ( WIFSIGNALED(status) )
warnx("child with pid %ji was killed by signal %i (%s).",
(intmax_t) child_pid, WTERMSIG(status),
strsignal(WTERMSIG(status)));
else
warnx("child with pid %ji exited in an unusual manner (%i).",
(intmax_t) child_pid, status);
if ( recovery_print_attempted_execution() )
return true;
if ( !isatty(0) )
exit_like_exit_status(status);
FILE* output = fopen("/dev/tty", "we");
if ( !output )
exit_like_exit_status(status);
retry_ask_recovery_method:
fprintf(output, "\n");
fprintf(output, "1. Abort\n");
fprintf(output, "2. Try again\n");
fprintf(output, "3. Pretend command was successful\n");
fprintf(output, "4. Run $SHELL -i to investigate\n");
fprintf(output, "5. Dump environment\n");
fprintf(output, "\n");
fprintf(output, "Please choose one: [%i] ", default_selection);
fflush(output);
char* input = read_single_line(stdin);
if ( !input )
{
fprintf(output, "\n");
fclose(output);
warn("stdin");
exit_like_exit_status(status);
}
int selection = default_selection;
if ( input[0] )
{
char* input_end;
selection = (int) strtol(input, &input_end, 0);
if ( *input_end )
{
warnx("error: `%s' is not an allowed choice", input);
goto retry_ask_recovery_method;
}
if ( 5 < selection )
{
warnx("error: `%i' is not an allowed choice", selection);
goto retry_ask_recovery_method;
}
}
if ( selection == 1 )
exit_like_exit_status(status);
if ( selection == 2 )
{
fprintf(output, "\nTrying to execute command again.\n\n");
fclose(output);
continue;
}
if ( selection == 3 )
{
fprintf(output, "\nPretending the command executed successfully.\n\n");
fclose(output);
return false;
}
if ( selection == 4 )
{
fprintf(output, "\nDropping you to a recovery shell, type `exit' "
"when you are done.\n\n");
if ( recovery_run_shell() )
return true;
}
if ( selection == 5 )
{
for ( size_t i = 0; environ[i]; i++ )
fprintf(output, "%s\n", environ[i]);
goto retry_ask_recovery_method;
}
default_selection = 2;
goto retry_ask_recovery_method;
}
}
#endif