sortix-mirror/tix/util.h
Jonas 'Sortie' Termansen d189183900 Third generation Tix.
The .tix.tar.xz binary package format now stores the contents in the root
rather than the data/ subdirectory and the tix metadata now has the same
layout as the loose files in /tix, such that a .tix.tar.xz package can
simply be directly extracted into the filesystem. The /tix/manifest/ is now
included in the binary package rather than being generated on installation.

The /tix/collection.conf and /tix/tixinfo metadata files are now in the
tix-vars(1) format in the style of port(5).

The /tix/installed.list file has been removed since it isn't loose file
compatible and one can list the /tix/tixinfo directory instead.

The /tix/repository.list file has been removed since the feature is unused
and doesn't match the future direction of tix.

The kernel support for tix binary packages has been removed since it will
simply install by extracting the tar archive into the root filesystem.

Add the post-install sha256sum to the port version stamp.
2023-07-15 16:43:27 +02:00

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