feat: add better shell-like command support
You can now execute any kind of program in the PATH. You do, however, need to specify "./" if you want to execute a local file, but that shouldn't be too big a problem.
This commit is contained in:
81
src/argv_split.c
Normal file
81
src/argv_split.c
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* sci - a simple ci system
|
||||
Copyright (C) 2024 Asger Gitz-Johansen
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "argv_split.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char* skip_arg(const char* cp) {
|
||||
while(*cp && !isspace(*cp))
|
||||
cp++;
|
||||
return cp;
|
||||
}
|
||||
|
||||
static char* skip_spaces(const char* str) {
|
||||
while(isspace(*str))
|
||||
str++;
|
||||
return(char*)str;
|
||||
}
|
||||
|
||||
static int count_argc(const char* str) {
|
||||
int count = 0;
|
||||
while(*str) {
|
||||
str = skip_spaces(str);
|
||||
if(!*str)
|
||||
continue;
|
||||
count++;
|
||||
str = skip_arg(str);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void argv_free(char** argv) {
|
||||
for(char** p = argv; *p; p++) {
|
||||
free(*p);
|
||||
*p = NULL;
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
char** create_argv_shell(const char* str, int* argc_out) {
|
||||
int argc = count_argc(str);
|
||||
char** result = calloc(argc+1, sizeof(*result));
|
||||
if(result == NULL)
|
||||
return result;
|
||||
if(argc_out)
|
||||
*argc_out = argc+1;
|
||||
char** argvp = result;
|
||||
while(*str) {
|
||||
str = skip_spaces(str);
|
||||
if(!*str)
|
||||
continue;
|
||||
const char* p = str;
|
||||
str = skip_arg(str);
|
||||
char* t = strndup(p, str-p);
|
||||
if(t == NULL) {
|
||||
perror("strndup");
|
||||
argv_free(result);
|
||||
return NULL;
|
||||
}
|
||||
*argvp++ = t;
|
||||
}
|
||||
*argvp = NULL;
|
||||
return result;
|
||||
}
|
||||
// sci-release https://git.gtz.dk/agj/sci/archive/main.tar.gz manual "scripts/wget-and-sci.sh sci"
|
33
src/main.c
33
src/main.c
@ -15,16 +15,20 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "argv_split.h"
|
||||
#include "cli.h"
|
||||
#include "log.h"
|
||||
#include "notify.h"
|
||||
#include "pipeline.h"
|
||||
#include "threadpool.h"
|
||||
#include "util.h"
|
||||
#include "which.h"
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <spawn.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include <wait.h>
|
||||
|
||||
@ -62,8 +66,10 @@ void executor(void* data) {
|
||||
|
||||
// Create logfile path
|
||||
optional_int fd = open_logfile(pipeline_id);
|
||||
if(!fd.has_value)
|
||||
if(!fd.has_value) {
|
||||
log_error("could not open log file - not starting pipeline");
|
||||
return;
|
||||
}
|
||||
|
||||
// spawn the process
|
||||
pid_t pid;
|
||||
@ -72,24 +78,35 @@ void executor(void* data) {
|
||||
posix_spawn_file_actions_adddup2(&actions, fd.value, STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&actions, fd.value, STDERR_FILENO);
|
||||
const pipeline_event* const e = data;
|
||||
char* path = join("PATH=", getenv("PATH"));
|
||||
char* name = join("SCI_PIPELINE_NAME=", e->name);
|
||||
char* url = join("SCI_PIPELINE_URL=", e->url);
|
||||
char* trigger = join("SCI_PIPELINE_TRIGGER=", e->trigger);
|
||||
char* id = join("SCI_PIPELINE_ID=", pipeline_id);
|
||||
char* envp[] = { name, url, trigger, id, NULL };
|
||||
char* argv[] = { "/bin/sh", "-c", e->command, NULL };
|
||||
if(posix_spawn(&pid, "/bin/sh", &actions, NULL, argv, envp) != 0) {
|
||||
char* envp[] = { path, name, url, trigger, id, NULL };
|
||||
int argc;
|
||||
char** argv = create_argv_shell(e->command, &argc);
|
||||
log_trace("executing pipeline %s with argv:", e->name);
|
||||
for(int i = 0; i < argc; i++)
|
||||
log_trace(" \"%s\"", argv[i]);
|
||||
char arg0[PATH_MAX];
|
||||
if(which(argv[0], arg0, PATH_MAX) == -1)
|
||||
goto end;
|
||||
if(posix_spawn(&pid, arg0, &actions, NULL, argv, envp) != 0) {
|
||||
perror("posix_spawn");
|
||||
goto end; // I know. The raptors have picked up the scent. I'll just have to mask it with more poopy code.
|
||||
goto end; // I know. The raptors have picked up the scent. I'll just have to mask it with more stinky code.
|
||||
}
|
||||
log_trace("{%s} (%s) spawned", pipeline_id, e->name);
|
||||
log_info("{%s} (%s) spawned", pipeline_id, e->name);
|
||||
|
||||
// Wait for process to complete
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if(WIFEXITED(status))
|
||||
log_trace("{%s} (%s) exited with status %d", pipeline_id, e->name, WEXITSTATUS(status));
|
||||
log_info("{%s} (%s) exited with status %d", pipeline_id, e->name, status);
|
||||
char buf[32];
|
||||
sprintf(buf, "exited with status %d", status);
|
||||
write(fd.value, buf, strnlen(buf, 32));
|
||||
end:
|
||||
argv_free(argv);
|
||||
close(fd.value);
|
||||
free(pipeline_id);
|
||||
free(name);
|
||||
|
@ -40,7 +40,11 @@ optional_pipeline_conf pipeline_create(const char* config_line) {
|
||||
break;
|
||||
off = pmatch[0].rm_so + (cursor - config_line);
|
||||
len = pmatch[0].rm_eo - pmatch[0].rm_so;
|
||||
opts[i] = strndup(config_line + off, len);
|
||||
// Cut off the "-s if it is string-enclosed
|
||||
if(config_line[off] == '"' && config_line[off+len-1] == '"')
|
||||
opts[i] = strndup(config_line + off+1, len-2);
|
||||
else
|
||||
opts[i] = strndup(config_line + off, len);
|
||||
cursor += pmatch[0].rm_eo;
|
||||
}
|
||||
if(i != 4) {
|
||||
|
54
src/which.c
Normal file
54
src/which.c
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* sci - a simple ci system
|
||||
Copyright (C) 2024 Asger Gitz-Johansen
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "log.h"
|
||||
#include <assert.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int which(const char* program_name, char* out_full_program, int max_path) {
|
||||
assert(out_full_program);
|
||||
assert(max_path > 0);
|
||||
// sanity check - maybe program_name is actually a full-path to begin with
|
||||
if(access(program_name, X_OK) == 0) {
|
||||
snprintf(out_full_program, max_path, "%s", program_name);
|
||||
return 0;
|
||||
}
|
||||
char* path = getenv("PATH");
|
||||
if (path == NULL) {
|
||||
log_error("PATH environment variable not found.");
|
||||
return -1;
|
||||
}
|
||||
char* path_cpy = strdup(path);
|
||||
char* dir = strtok(path_cpy, ":");
|
||||
char full_path[PATH_MAX];
|
||||
while(dir != NULL) {
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir, program_name);
|
||||
if(access(full_path, X_OK) == 0) {
|
||||
snprintf(out_full_program, max_path, "%s", full_path);
|
||||
free(path_cpy);
|
||||
return 0;
|
||||
}
|
||||
dir = strtok(NULL, ":");
|
||||
}
|
||||
log_error("'%s' not found in PATH", program_name);
|
||||
free(path_cpy);
|
||||
return -1;
|
||||
}
|
Reference in New Issue
Block a user