mirror of
				https://gitlab.com/sortix/sortix.git
				synced 2023-02-13 20:55:38 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1033 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1033 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*******************************************************************************
 | 
						|
 | 
						|
    Copyright(C) Jonas 'Sortie' Termansen 2013.
 | 
						|
 | 
						|
    This file is part of Tix.
 | 
						|
 | 
						|
    Tix is free software: you can redistribute it and/or modify it under the
 | 
						|
    terms of the GNU General Public License as published by the Free Software
 | 
						|
    Foundation, either version 3 of the License, or (at your option) any later
 | 
						|
    version.
 | 
						|
 | 
						|
    Tix is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
						|
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 | 
						|
    details.
 | 
						|
 | 
						|
    You should have received a copy of the GNU General Public License along with
 | 
						|
    Tix. If not, see <https://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
    util.h
 | 
						|
    Shared Utility functions for Tix.
 | 
						|
 | 
						|
*******************************************************************************/
 | 
						|
 | 
						|
#ifndef UTIL_H
 | 
						|
#define UTIL_H
 | 
						|
 | 
						|
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 does_path_contain_dot_or_dotdot(const char* path)
 | 
						|
{
 | 
						|
	size_t index = 0;
 | 
						|
	while ( path[index] )
 | 
						|
	{
 | 
						|
		if ( path[index] == '.' )
 | 
						|
		{
 | 
						|
			index++;
 | 
						|
			if ( path[index] == '.'  )
 | 
						|
				index++;
 | 
						|
			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;
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
{
 | 
						|
	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(*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(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(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(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;
 | 
						|
	ssize_t entry_length;
 | 
						|
	while ( 0 < (entry_length = getline(&entry, &entry_size, fp)) )
 | 
						|
	{
 | 
						|
		if ( entry_length && entry[entry_length-1] == '\n' )
 | 
						|
			entry[entry_length-1] = '\0';
 | 
						|
		string_array_append(sa, entry);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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(string_array_t* sa, const char* key,
 | 
						|
                           const char* def = NULL)
 | 
						|
{
 | 
						|
	size_t keylen = strlen(key);
 | 
						|
	const char* entry = dictionary_get_entry(sa, key);
 | 
						|
	return entry ? entry + keylen + 1 : def;
 | 
						|
}
 | 
						|
 | 
						|
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(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;
 | 
						|
	ssize_t entry_length;
 | 
						|
	while ( 0 < (entry_length = getline(&entry, &entry_size, fp)) )
 | 
						|
	{
 | 
						|
		if ( entry_length && entry[entry_length-1] == '\n' )
 | 
						|
			entry[entry_length-1] = '\0';
 | 
						|
		dictionary_normalize_entry(entry);
 | 
						|
		if ( entry[0] == '#' )
 | 
						|
			continue;
 | 
						|
		string_array_append(sa, entry);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
}
 | 
						|
 | 
						|
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 )
 | 
						|
		return NULL;
 | 
						|
	if ( ret_len && ret[ret_len-1] == '\n' )
 | 
						|
		ret[ret_len-1] = '\0';
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
pid_t fork_or_death()
 | 
						|
{
 | 
						|
	pid_t child_pid = fork();
 | 
						|
	if ( child_pid < 0 )
 | 
						|
		error(1, errno, "fork");
 | 
						|
	return child_pid;
 | 
						|
}
 | 
						|
 | 
						|
void waitpid_or_death(pid_t child_pid, bool die_on_error = true)
 | 
						|
{
 | 
						|
	int status;
 | 
						|
	waitpid(child_pid, &status, 0);
 | 
						|
	if ( die_on_error )
 | 
						|
	{
 | 
						|
		if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 )
 | 
						|
			exit(WEXITSTATUS(status));
 | 
						|
		if ( WIFSIGNALED(status) )
 | 
						|
			error(128 + WTERMSIG(status), 0, "child with pid %ju was killed by "
 | 
						|
			      "signal %i (%s).", (uintmax_t) child_pid, WTERMSIG(status),
 | 
						|
		          strsignal(WTERMSIG(status)));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool fork_and_wait_or_death(bool die_on_error = true)
 | 
						|
{
 | 
						|
	pid_t child_pid = fork_or_death();
 | 
						|
	if ( !child_pid )
 | 
						|
		return true;
 | 
						|
	waitpid_or_death(child_pid, die_on_error);
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
}
 | 
						|
 | 
						|
void CompactArguments(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()
 | 
						|
{
 | 
						|
	const char* env_host = getenv("BUILD");
 | 
						|
	if ( env_host )
 | 
						|
		return strdup(env_host);
 | 
						|
	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* tar, const char* archive, const char* file)
 | 
						|
{
 | 
						|
	int pipes[2];
 | 
						|
	if ( pipe(pipes) )
 | 
						|
		error(1, errno, "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);
 | 
						|
		error(127, errno, "%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_len && 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);
 | 
						|
 | 
						|
	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 )
 | 
						|
	{
 | 
						|
		error(1, 0, "Unable to list contents of `%s'.", archive);
 | 
						|
		exit(WEXITSTATUS(tar_exit_status));
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
FILE* TarOpenFile(const char* tar, const char* archive, const char* file)
 | 
						|
{
 | 
						|
	FILE* fp = tmpfile();
 | 
						|
	if ( !fp )
 | 
						|
		error(1, errno, "tmpfile");
 | 
						|
	pid_t tar_pid = fork_or_death();
 | 
						|
	if ( !tar_pid )
 | 
						|
	{
 | 
						|
		dup2(fileno(fp), 1);
 | 
						|
		fclose(fp);
 | 
						|
		const char* cmd_argv[] =
 | 
						|
		{
 | 
						|
			tar,
 | 
						|
			"--to-stdout",
 | 
						|
			"--extract",
 | 
						|
			"--file", archive,
 | 
						|
			"--", file,
 | 
						|
			NULL
 | 
						|
		};
 | 
						|
		execvp(cmd_argv[0], (char* const*) cmd_argv);
 | 
						|
		error(127, errno, "%s", cmd_argv[0]);
 | 
						|
	}
 | 
						|
	int tar_exit_status;
 | 
						|
	waitpid(tar_pid, &tar_exit_status, 0);
 | 
						|
	if ( !WIFEXITED(tar_exit_status) || WEXITSTATUS(tar_exit_status) != 0 )
 | 
						|
	{
 | 
						|
		error(1, 0, "Unable to extract `%s/%s'", archive, file);
 | 
						|
		exit(WEXITSTATUS(tar_exit_status));
 | 
						|
	}
 | 
						|
	if ( fseeko(fp, 0, SEEK_SET) < 0 )
 | 
						|
		error(1, errno, "fseeko(tmpfile(), 0, SEEK_SET)");
 | 
						|
	return fp;
 | 
						|
}
 | 
						|
 | 
						|
int fopenat_opener(int dirfd, const char* path, const char* mode)
 | 
						|
{
 | 
						|
	int omode = 0;
 | 
						|
	int oflags = 0;
 | 
						|
	char c;
 | 
						|
	// TODO: This is too hacky and a little buggy.
 | 
						|
	while ( (c = *mode++) )
 | 
						|
		switch ( c )
 | 
						|
		{
 | 
						|
			case 'r': omode = O_RDONLY;  break;
 | 
						|
			case 'a': oflags |= O_APPEND; /* fall-through */
 | 
						|
			case 'w': omode = O_WRONLY; oflags |= O_CREAT | O_TRUNC; break;
 | 
						|
			case '+': omode = O_RDWR; break;
 | 
						|
			case 'x': omode = O_EXCL; break;
 | 
						|
			case 'b': break;
 | 
						|
			case 't': break;
 | 
						|
			default:
 | 
						|
				errno = EINVAL;
 | 
						|
				return -1;
 | 
						|
		}
 | 
						|
	return openat(dirfd, path, omode | oflags, 0666);
 | 
						|
}
 | 
						|
 | 
						|
FILE* fopenat(DIR* dir, const char* path, const char* mode)
 | 
						|
{
 | 
						|
	int fd = fopenat_opener(dirfd(dir), path, mode);
 | 
						|
	if ( fd < 0 )
 | 
						|
		return NULL;
 | 
						|
	FILE* ret = fdopen(fd, mode);
 | 
						|
	if ( !ret )
 | 
						|
		return close(fd), (FILE*) NULL;
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
const char* VerifyInfoVariable(string_array_t* info, const char* var,
 | 
						|
                               const char* path)
 | 
						|
{
 | 
						|
	const char* ret = dictionary_get(info, var);
 | 
						|
	if ( !ret )
 | 
						|
		error(1, 0, "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 )
 | 
						|
		error(1, 0, "error: `%s': no `tix.version' variable declared",
 | 
						|
		            tix_path);
 | 
						|
	if ( atoi(tix_version) != 1 )
 | 
						|
		error(1, 0, "error: `%s': tix version `%s' not supported", tix_path,
 | 
						|
		            tix_version);
 | 
						|
	const char* tix_class = dictionary_get(tixinfo, "tix.class");
 | 
						|
	if ( !tix_class )
 | 
						|
		error(1, 0, "error: `%s': no `tix.class' variable declared", tix_path);
 | 
						|
	if ( !strcmp(tix_class, "srctix") )
 | 
						|
		error(1, 0, "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") )
 | 
						|
		error(1, 0, "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")) )
 | 
						|
		error(1, 0, "error: `%s': no `tix.platform' variable declared", tix_path);
 | 
						|
	if ( !(dictionary_get(tixinfo, "pkg.name")) )
 | 
						|
		error(1, 0, "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]) )
 | 
						|
	{
 | 
						|
		free(*collection);
 | 
						|
		*collection = strdup((*argvp)[1]);
 | 
						|
		(*argvp)[1] = NULL;
 | 
						|
		CompactArguments(argcp, argvp);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void VerifyCommandLineCollection(char** collection)
 | 
						|
{
 | 
						|
	if ( !*collection )
 | 
						|
		error(1, 0, "error: you need to specify which tix collection to "
 | 
						|
		            "administer using --collection or TIX_COLLECTION or giving "
 | 
						|
		            "the prefix as the first argument.");
 | 
						|
 | 
						|
	if ( !**collection )
 | 
						|
	{
 | 
						|
		free(*collection);
 | 
						|
		*collection = strdup("/");
 | 
						|
	}
 | 
						|
 | 
						|
	char* collection_rel = *collection;
 | 
						|
	if ( !(*collection = canonicalize_file_name(collection_rel)) )
 | 
						|
		error(1, errno, "canonicalize_file_name(`%s')", collection_rel);
 | 
						|
}
 | 
						|
 | 
						|
void VerifyTixCollectionConfiguration(string_array_t* info, const char* path)
 | 
						|
{
 | 
						|
	const char* tix_version = dictionary_get(info, "tix.version");
 | 
						|
	if ( !tix_version )
 | 
						|
		error(1, 0, "error: `%s': no `tix.version' variable declared", path);
 | 
						|
	if ( atoi(tix_version) != 1 )
 | 
						|
		error(1, 0, "error: `%s': tix version `%s' not supported", path,
 | 
						|
		            tix_version);
 | 
						|
	const char* tix_class = dictionary_get(info, "tix.class");
 | 
						|
	if ( !tix_class )
 | 
						|
		error(1, 0, "error: `%s': no `tix.class' variable declared", path);
 | 
						|
	if ( strcmp(tix_class, "collection") != 0 )
 | 
						|
		error(1, 0, "error: `%s': error: unexpected tix class `%s'.", path,
 | 
						|
		            tix_class);
 | 
						|
	if ( !(dictionary_get(info, "collection.prefix")) )
 | 
						|
		error(1, 0, "error: `%s': no `collection.prefix' variable declared",
 | 
						|
		            path);
 | 
						|
	if ( !(dictionary_get(info, "collection.platform")) )
 | 
						|
		error(1, 0, "error: `%s': no `collection.platform' variable declared",
 | 
						|
		            path);
 | 
						|
}
 | 
						|
 | 
						|
static pid_t original_pid;
 | 
						|
 | 
						|
__attribute__((constructor))
 | 
						|
static void initialize_original_pid()
 | 
						|
{
 | 
						|
	original_pid = getpid();
 | 
						|
}
 | 
						|
 | 
						|
void cleanup_file_or_directory(int, void* path_ptr)
 | 
						|
{
 | 
						|
	if ( original_pid != getpid() )
 | 
						|
		return;
 | 
						|
	const char* path = (const char*) path_ptr;
 | 
						|
	if ( fork_and_wait_or_death(false) )
 | 
						|
	{
 | 
						|
		const char* cmd_argv[] =
 | 
						|
		{
 | 
						|
			"rm",
 | 
						|
			"-rf",
 | 
						|
			"--",
 | 
						|
			path,
 | 
						|
			NULL,
 | 
						|
		};
 | 
						|
		execvp(cmd_argv[0], (char* const*) cmd_argv);
 | 
						|
		error(127, errno, "%s", cmd_argv[0]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
mode_t get_umask_value()
 | 
						|
{
 | 
						|
	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(bool set,
 | 
						|
                         enum recovery_state to_what = RECOVERY_STATE_NONE)
 | 
						|
{
 | 
						|
	static enum recovery_state recovery_state = RECOVERY_STATE_NONE;
 | 
						|
	if ( set )
 | 
						|
		recovery_state = to_what;
 | 
						|
	return recovery_state;
 | 
						|
}
 | 
						|
 | 
						|
bool recovery_print_attempted_execution()
 | 
						|
{
 | 
						|
	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(true, RECOVERY_STATE_PRINT_COMMAND);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	int status;
 | 
						|
	waitpid(child_pid, &status, 0);
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool recovery_run_shell()
 | 
						|
{
 | 
						|
	pid_t child_pid = fork();
 | 
						|
	if ( child_pid < 0 )
 | 
						|
		return false;
 | 
						|
 | 
						|
	if ( !child_pid )
 | 
						|
	{
 | 
						|
		recovery_configure_state(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"),
 | 
						|
		"-i",
 | 
						|
		NULL
 | 
						|
	};
 | 
						|
	execvp(cmd_argv[0], (char* const*) cmd_argv);
 | 
						|
	error(127, errno, "%s", cmd_argv[0]);
 | 
						|
 | 
						|
	__builtin_unreachable();
 | 
						|
}
 | 
						|
 | 
						|
bool fork_and_wait_or_recovery()
 | 
						|
{
 | 
						|
	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) )
 | 
						|
			error(0, 0, "child with pid %ju exited with status %i.",
 | 
						|
			            (uintmax_t) child_pid, WEXITSTATUS(status));
 | 
						|
		else if ( WIFSIGNALED(status) )
 | 
						|
			error(0, 0, "child with pid %ju was killed by signal %i (%s).",
 | 
						|
			            (uintmax_t) child_pid, WTERMSIG(status),
 | 
						|
			            strsignal(WTERMSIG(status)));
 | 
						|
		else
 | 
						|
			error(0, 0, "child with pid %ju exited in an unusual manner (%i).",
 | 
						|
			            (uintmax_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, "\n");
 | 
						|
		fprintf(output, "Please choose one: [%i] ", default_selection);
 | 
						|
		fflush(output);
 | 
						|
 | 
						|
		char* input = read_single_line(stdin);
 | 
						|
		if ( !input )
 | 
						|
		{
 | 
						|
			fprintf(output, "\n");
 | 
						|
			fclose(output);
 | 
						|
			error(0, errno, "can't read line from standard input, aborting.");
 | 
						|
			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 )
 | 
						|
			{
 | 
						|
				error(0, 0, "error: `%s' is not an allowed choice\n", input);
 | 
						|
				goto retry_ask_recovery_method;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( 4 < selection )
 | 
						|
			{
 | 
						|
				error(0, 0, "error: `%i' is not an allowed choice\n", 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;
 | 
						|
		}
 | 
						|
 | 
						|
		default_selection = 2;
 | 
						|
 | 
						|
		goto retry_ask_recovery_method;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#endif
 |