sortix-mirror/tix/util.h
Jonas 'Sortie' Termansen 9588b0d3db Add ports to the Sortix repository.
This change imports the ports collection from the former porttix and srctix
repositories and converts them to port(5) files with metadata pointing to
the upstream release tarballs with patches checked into this repository.
Ports are now developed and versioned along with the operating system and
are automatically built per the PACKAGES environment variable. The patches
are licensed under the same license as the relevant ports.

Tix has gained support for the new port(5) format. tix-port(8) is the new
high level ports build even point that handles downloading pstream releases
into the new mirror cache directory, applying the patches, building the port
with the lower-level tix-build(8), and finally installing the binary
package. The new tix-vars(8) program parses port(5) files and the new
tix-rmdiff(8) program produces input for tix-rmpatch(8).

The old doc/ directory is discontinued in favor of manual pages documenting
the new ports system.

The obsolete porttix-create(8) and srctix-create(8) programs are removed.
2022-06-13 22:29:53 +02:00

1280 lines
28 KiB
C

/*
* Copyright (c) 2013, 2015, 2016, 2022 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 "2"
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';
}
void dictionary_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';
dictionary_normalize_entry(entry);
if ( entry[0] == '#' )
continue;
string_array_append(sa, entry);
}
free(entry);
assert(!ferror(fp));
}
bool dictionary_append_file_path(string_array_t* sa, const char* path)
{
FILE* fp = fopen(path, "r");
if ( !fp )
return false;
dictionary_append_file(sa, fp);
fclose(fp);
return true;
}
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) != 0 && errno != EEXIST )
return -1;
errno = saved_errno;
return 0;
}
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)
{
const char* tix_version = dictionary_get(info, "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);
}
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 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");
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