Установить pipefail для команд, запускаемых tup
В большом количестве Tupfiles я использую обширную конвейеризацию, например
: input |> < %f command1 | command2 > %o |> output
Проблема в том, что Tup вызывает system
который выполняет эти:-rules в sh
, который не поддерживает set -o pipefail
, В результате, если только command1
терпит неудачу, tup все равно пометит это как успех, потому что у него был код выхода 0. Это очень проблематично.
Я знаю два решения этого, ни одно из которых не является идеальным.
а. Я мог бы отказаться от конвейерной обработки и вместо этого сделать:
: input |> < %f command1 > %o |> intermediate
: intermediate |> < %f command2 > %o |> output
Это будет работать, но потребует утомительного переписывания нескольких правил и, что более важно, будет использовать значительно больше дискового пространства и записи на диск каждый раз, когда происходит обновление.
б) я могу обернуть каждую команду в bash
лайк:
: input |> bash -c 'set -o pipefail && < %f command1 | command2 > %o' |> output
Это кажется немного лучше, так как требует меньше переписываний и избегает ввода-вывода, но все еще очень громоздко. Это также требует побега любого '
по моему:
В идеале должны быть конфиги Tup, которые могут просто указать, какую оболочку / интерпретатор использовать для чтения:-rules. В идеале должна быть конфигурация для общего префикса, поэтому все сценарии можно запускать с set -o pipefail &&
или что-нибудь еще, что я хочу. Насколько я знаю, это не сразу возможно. Обертка вокруг system
нужно будет писать всякий раз, когда tup вызывает правило. Однако, возможно, я пропустил какой-то аспект Tup, который позволил бы сделать что-то более элегантное, чем два предложенных решения.
Редактировать: в то время как вызов системы позволил мне "ввести" pipefail в вызовы системы. Я упустил из виду тот факт, что программы запускаются с использованием системы. С некоторой помощью из списка рассылки выясняется, что они на самом деле запускаются с использованием execle
, Ниже приведен код, который я использовал для вставки на тот случай, если кто-то захочет выполнить то же самое.
Решение
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
int execle(const char* path, const char* arg0, ...) {
/* We're going to interpose this function, modify the arguments if we need
* to, and then convert it into a call to execve. Due to a weirdness in the
* consts of the api, we need to discard a const qualifier on the
* characters in the arguments. The call is `int execve(const char*
* filename, char* const argv[], char* const envp[]);` but it should
* probably be `int execve(const char* filename, const char* const argv[],
* char* const envp[]);` at the very least, e.g. arguments shouldn't be
* modified. These aren't actually modified by the call, so in order to
* avoid the inefficiency of copying the strings into memory we don't need,
* we just do this unsafely and compile with `-Wno-discarded-qualifiers`.
* */
// Count the number of variable arguments for malloc
unsigned int num_args;
va_list ap;
va_start(ap, arg0);
if (arg0) {
num_args = 1;
while(va_arg(ap, const char*)) {
num_args++;
}
} else {
num_args = 0;
}
char* const* env = va_arg(ap, char* const*); // Also grab env
va_end(ap);
// Test for specific tup execle call
va_start(ap, arg0);
int intercept = num_args == 4
&& strcmp(path, "/bin/sh") == 0
&& strcmp(arg0, "/bin/sh") == 0
&& strcmp(va_arg(ap, const char*), "-e") == 0
&& strcmp(va_arg(ap, const char*), "-c") == 0;
va_end(ap);
// Switch on whether to intercept the call, or pass it on
/*const*/ char** args;
if (intercept) { // We want to switch to bash with pipefail enabled
args = malloc(7 * sizeof(args));
path = "/bin/bash";
args[0] = "/bin/bash";
args[1] = "-e";
args[2] = "-o";
args[3] = "pipefail";
args[4] = "-c";
va_start(ap, arg0);
va_arg(ap, const char*);
va_arg(ap, const char*);
args[5] = va_arg(ap, const char*); // command
va_end(ap);
args[6] = NULL;
} else { // Just copy args into a null terminated array for execve
args = malloc((num_args + 1) * sizeof(*args));
char** ref = args;
if (arg0) {
*ref++ = arg0;
const char* arg;
va_start(ap, arg0);
while ((arg = va_arg(ap, const char*))) {
*ref++ = arg;
}
va_end(ap);
}
*ref = NULL;
}
int error_code = execve(path, args, env);
free(args);
return error_code;
}
1 ответ
Вы могли бы реализовать свой собственный system
как
switch(pid = fork()) {
case 0:
// Modify command to prepend "set -o pipefail &&" to it.
execl("/bin/bash", "bash", "-c", command, (char *) 0);
case -1: // handle fork error
default:
waitpid(pid, ...);
}
а также LD_PRELOAD
тот system
внедрение в ваш tup
процесс.
Если вы не хотите заниматься низкоуровневым управлением процессами, вы можете вставить system
просто обернуть команду в bash -c "set -o pipefail && "
и избежать кавычек, а затем вызвать оригинал system
, Смотрите эту статью о вставке библиотеки.