mirror of
https://github.com/NikitaIvanovV/ctpv.git
synced 2024-11-23 21:33:07 +01:00
Initial commit
This commit is contained in:
commit
16bdee675c
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.o
|
||||
*.d
|
||||
/gen/
|
||||
/ctpv
|
||||
/embed/embed
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Nikita Ivanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
53
Makefile
Normal file
53
Makefile
Normal file
@ -0,0 +1,53 @@
|
||||
PREFIX := /usr/local
|
||||
BINPREFIX := $(PREFIX)/bin
|
||||
|
||||
SRC := $(wildcard *.c)
|
||||
OBJ := $(SRC:.c=.o)
|
||||
DEP := $(OBJ:.o=.d)
|
||||
PRE := $(wildcard prev/*.sh)
|
||||
GEN := gen/prev/scripts.h gen/prev/helpers.h
|
||||
|
||||
CFLAGS += -Os -MD -Wall -Wextra -Wno-unused-parameter
|
||||
LDFLAGS += -lmagic
|
||||
|
||||
all: ctpv
|
||||
|
||||
options:
|
||||
@echo "CC = $(CC)"
|
||||
@echo "CFLAGS = $(CFLAGS)"
|
||||
@echo "LDFLAGS = $(LDFLAGS)"
|
||||
|
||||
install: ctpv
|
||||
install -d $(BINPREFIX)
|
||||
install $< $(BINPREFIX)
|
||||
|
||||
uninstall:
|
||||
$(RM) $(BINPREFIX)/ctpv
|
||||
|
||||
clean:
|
||||
$(RM) ctpv $(OBJ) $(DEP) $(GEN)
|
||||
$(MAKE) -C embed clean
|
||||
|
||||
make_embed:
|
||||
$(MAKE) -C embed
|
||||
|
||||
ctpv: $(OBJ)
|
||||
|
||||
ctpv.c: $(GEN)
|
||||
|
||||
gen/prev/scripts.h: embed/embed $(PRE)
|
||||
@mkdir -p $(@D)
|
||||
embed/embed -p prev_scr_ $(PRE) > $@
|
||||
|
||||
gen/prev/helpers.h: embed/embed helpers.sh
|
||||
@mkdir -p $(@D)
|
||||
embed/embed -p prev_ helpers.sh > $@
|
||||
|
||||
embed/embed: make_embed
|
||||
@:
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
.PHONY: all options install uninstall clean make_embed
|
||||
|
||||
.DELETE_ON_ERROR:
|
117
ctpv.c
Normal file
117
ctpv.c
Normal file
@ -0,0 +1,117 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <magic.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "utils.h"
|
||||
#include "previews.h"
|
||||
|
||||
static magic_t magic;
|
||||
|
||||
static struct {
|
||||
int server;
|
||||
} ctpv;
|
||||
|
||||
static void cleanup(void) {
|
||||
cleanup_previews();
|
||||
if (magic != NULL)
|
||||
magic_close(magic);
|
||||
}
|
||||
|
||||
static int init_magic() {
|
||||
magic = magic_open(MAGIC_MIME_TYPE);
|
||||
ERRCHK_RET(!magic, "magic_open() failed");
|
||||
|
||||
ERRCHK_RET(magic_load(magic, NULL) != 0, "magic_load() failed: %s",
|
||||
magic_error(magic));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static const char *get_mimetype(char const *path) {
|
||||
const char *r = magic_file(magic, path);
|
||||
if (!r) {
|
||||
print_errorf("magic_file() failed: %s", magic_error(magic));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static const char *get_ext(char const *path) {
|
||||
const char *r = strrchr(path, '.');
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
return &r[1];
|
||||
}
|
||||
|
||||
static int server(void)
|
||||
{
|
||||
/* TODO */
|
||||
return OK;
|
||||
}
|
||||
|
||||
#define GET_PARG(a, i) (a) = argc > (i) ? argv[i] : NULL
|
||||
|
||||
static int client(int argc, char *argv[])
|
||||
{
|
||||
char *f, *w, *h, *x, *y;
|
||||
GET_PARG(f, 0);
|
||||
GET_PARG(w, 1);
|
||||
GET_PARG(h, 2);
|
||||
GET_PARG(x, 3);
|
||||
GET_PARG(y, 4);
|
||||
|
||||
ERRCHK_RET(!f, "file not given");
|
||||
ERRCHK_RET(access(f, R_OK) != 0, "failed to access '%s': %s", f,
|
||||
strerror(errno));
|
||||
|
||||
ERRCHK_RET_OK(init_magic());
|
||||
|
||||
init_previews(previews, LEN(previews));
|
||||
|
||||
const char *mimetype = get_mimetype(f);
|
||||
ERRCHK_RET(!mimetype);
|
||||
|
||||
Preview *p = find_preview(get_ext(f), mimetype);
|
||||
if (!p) {
|
||||
puts("no preview found");
|
||||
return OK;
|
||||
}
|
||||
|
||||
PreviewArgs args = { .f = f, .w = w, .h = h, .x = x, .y = y };
|
||||
|
||||
ERRCHK_RET_OK(run_preview(p, &args));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
program = argc > 0 ? argv[0] : "ctpv";
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "s")) != -1) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
ctpv.server = 1;
|
||||
break;
|
||||
default:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
int ret;
|
||||
if (ctpv.server)
|
||||
ret = server();
|
||||
else
|
||||
ret = client(argc, &argv[optind]);
|
||||
|
||||
cleanup();
|
||||
|
||||
return ret == OK ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
6
embed/Makefile
Normal file
6
embed/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
embed: embed.c
|
||||
|
||||
clean:
|
||||
$(RM) embed
|
||||
|
||||
.PHONY: clean
|
72
embed/embed.c
Normal file
72
embed/embed.c
Normal file
@ -0,0 +1,72 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
void getvarname(char *res, char *prefix, char *filename)
|
||||
{
|
||||
char *s = strrchr(filename, '/');
|
||||
if (s)
|
||||
s++;
|
||||
else
|
||||
s = filename;
|
||||
|
||||
if (prefix) {
|
||||
size_t prefix_len = strlen(prefix);
|
||||
memcpy(res, prefix, prefix_len);
|
||||
res += prefix_len;
|
||||
}
|
||||
|
||||
for (int c, i = 0; s[i] != 0; i++) {
|
||||
c = s[i];
|
||||
if (!isalnum(c))
|
||||
c = '_';
|
||||
|
||||
res[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
void embed_file(char *prefix, char *filename)
|
||||
{
|
||||
FILE *f = fopen(filename, "r");
|
||||
if (!f) {
|
||||
fprintf(stderr, "failed to open %s: %s\n", filename, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static char varname[FILENAME_MAX];
|
||||
getvarname(varname, prefix, filename);
|
||||
|
||||
printf("char %s[] = { ", varname);
|
||||
|
||||
int c;
|
||||
while ((c = fgetc(f)) != EOF)
|
||||
printf("0x%x, ", c);
|
||||
|
||||
puts("0 };");
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *prefix = NULL;
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "p:")) != -1) {
|
||||
switch (c) {
|
||||
case 'p':
|
||||
prefix = optarg;
|
||||
break;
|
||||
default:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = optind; i < argc; i++)
|
||||
embed_file(prefix, argv[i]);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
16
error.c
Normal file
16
error.c
Normal file
@ -0,0 +1,16 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "utils.h"
|
||||
|
||||
void print_error(char const *error_msg)
|
||||
{
|
||||
fprintf(stderr, "%s: %s\n", program, error_msg);
|
||||
}
|
||||
|
||||
void print_errorf(char const *format, ...)
|
||||
{
|
||||
char s[1024];
|
||||
FORMATTED_STRING(s, format);
|
||||
print_error(s);
|
||||
}
|
33
error.h
Normal file
33
error.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef ERROR_H
|
||||
#define ERROR_H
|
||||
|
||||
#define _ERRCHK_RET_PR(format, ...) \
|
||||
print_error##__VA_OPT__(f)(format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* If cond is true, return ERR. Also call print_error or
|
||||
* print_errorf if error message or format string is given.
|
||||
*/
|
||||
#define ERRCHK_RET(cond, ...) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
__VA_OPT__(_ERRCHK_RET_PR(__VA_ARGS__);) \
|
||||
return ERR; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Shortcut for ERRCHK_RET(expr != OK)
|
||||
*/
|
||||
#define ERRCHK_RET_OK(expr, ...) \
|
||||
ERRCHK_RET((expr) != OK __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
enum {
|
||||
OK,
|
||||
ERR,
|
||||
};
|
||||
|
||||
void print_error(char const *error_msg);
|
||||
void print_errorf(char const *format, ...);
|
||||
|
||||
#endif
|
3
helpers.sh
Normal file
3
helpers.sh
Normal file
@ -0,0 +1,3 @@
|
||||
exists() {
|
||||
command -v "$1" > /dev/null
|
||||
}
|
1
prev/file.sh
Normal file
1
prev/file.sh
Normal file
@ -0,0 +1 @@
|
||||
echo "$f"
|
176
preview.c
Normal file
176
preview.c
Normal file
@ -0,0 +1,176 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "error.h"
|
||||
#include "preview.h"
|
||||
|
||||
#define PREVP_SIZE sizeof(Preview *)
|
||||
|
||||
static char shell[] = "sh";
|
||||
|
||||
static Preview **sorted_by_ext,
|
||||
**sorted_by_mimetype;
|
||||
static size_t prevs_length;
|
||||
|
||||
static int cmp_prev_ext(const void *p1, const void *p2)
|
||||
{
|
||||
Preview *pr1 = *(Preview **)p1;
|
||||
Preview *pr2 = *(Preview **)p2;
|
||||
|
||||
if (!pr1->ext)
|
||||
return 1;
|
||||
|
||||
if (!pr2->ext)
|
||||
return -1;
|
||||
|
||||
return strcmp(pr1->ext, pr2->ext);
|
||||
}
|
||||
|
||||
static int cmp_prev_mimetype(const void *p1, const void *p2)
|
||||
{
|
||||
Preview *pr1 = *(Preview **)p1;
|
||||
Preview *pr2 = *(Preview **)p2;
|
||||
|
||||
if (pr1->type[0] == '\0')
|
||||
return 1;
|
||||
else if (pr2->type[0] == '\0')
|
||||
return -1;
|
||||
|
||||
if (!pr1->subtype && pr2->subtype) {
|
||||
return 1;
|
||||
} else if (pr1->subtype && !pr2->subtype) {
|
||||
return -1;
|
||||
} else {
|
||||
int ret = strcmp(pr1->type, pr2->type);
|
||||
if (ret == 0 && pr1->subtype && pr2->subtype)
|
||||
return strcmp(pr1->subtype, pr2->subtype);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void init_previews(Preview *ps, size_t len)
|
||||
{
|
||||
prevs_length = len;
|
||||
size_t size = len * PREVP_SIZE;
|
||||
|
||||
sorted_by_ext = malloc(size);
|
||||
sorted_by_mimetype = malloc(size);
|
||||
if (!sorted_by_ext || !sorted_by_mimetype) {
|
||||
print_error("malloc() failed");
|
||||
abort();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++)
|
||||
sorted_by_ext[i] = sorted_by_mimetype[i] = &ps[i];
|
||||
|
||||
qsort(sorted_by_ext, len, PREVP_SIZE, cmp_prev_ext);
|
||||
qsort(sorted_by_mimetype, len, PREVP_SIZE, cmp_prev_mimetype);
|
||||
}
|
||||
|
||||
void cleanup_previews(void)
|
||||
{
|
||||
if (sorted_by_ext) {
|
||||
free(sorted_by_ext);
|
||||
sorted_by_ext = NULL;
|
||||
}
|
||||
|
||||
if (sorted_by_mimetype) {
|
||||
free(sorted_by_mimetype);
|
||||
sorted_by_mimetype = NULL;
|
||||
}
|
||||
|
||||
prevs_length = 0;
|
||||
}
|
||||
|
||||
static Preview *find_by_ext(char const *ext)
|
||||
{
|
||||
Preview *key = &(Preview){ .ext = (char *)ext };
|
||||
|
||||
void *p =
|
||||
bsearch(&key, sorted_by_ext, prevs_length, PREVP_SIZE, cmp_prev_ext);
|
||||
|
||||
return p ? *(Preview **)p : NULL;
|
||||
}
|
||||
|
||||
static void break_mimetype(char *mimetype, char **type, char **subtype)
|
||||
{
|
||||
*type = mimetype[0] == '\0' ? NULL : mimetype;
|
||||
*subtype = NULL;
|
||||
|
||||
char *s = strchr(mimetype, '/');
|
||||
if (!s) {
|
||||
print_errorf("invalid mimetype: '%s'", mimetype);
|
||||
abort();
|
||||
}
|
||||
|
||||
*s = '\0';
|
||||
*subtype = &s[1];
|
||||
}
|
||||
|
||||
#define MIMETYPE_MAX 64
|
||||
|
||||
static Preview *find_by_mimetype(char const *mimetype)
|
||||
{
|
||||
Preview *p;
|
||||
char mimetype_c[MIMETYPE_MAX], *t, *s;
|
||||
|
||||
strncpy(mimetype_c, mimetype, MIMETYPE_MAX - 1);
|
||||
break_mimetype(mimetype_c, &t, &s);
|
||||
|
||||
for (size_t i = 0; i < prevs_length; i++) {
|
||||
p = sorted_by_mimetype[i];
|
||||
|
||||
if (strcmp(t, p->type) == 0) {
|
||||
if (p->subtype) {
|
||||
if (strcmp(s, p->subtype) == 0)
|
||||
return p;
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Preview *find_preview(char const *ext, char const *mimetype)
|
||||
{
|
||||
if (!sorted_by_ext || !sorted_by_mimetype) {
|
||||
print_error("init_previews() not called");
|
||||
abort();
|
||||
}
|
||||
|
||||
Preview *ret = NULL;
|
||||
if (mimetype)
|
||||
ret = find_by_mimetype(mimetype);
|
||||
if (!ret)
|
||||
ret = find_by_ext(ext);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define SET_PENV(n, v) \
|
||||
do { \
|
||||
if (v) \
|
||||
ERRCHK_RET(setenv((n), (v), 1) != 0); \
|
||||
} while (0)
|
||||
|
||||
int run_preview(Preview *p, PreviewArgs *pa)
|
||||
{
|
||||
SET_PENV("f", pa->f);
|
||||
SET_PENV("w", pa->w);
|
||||
SET_PENV("h", pa->h);
|
||||
SET_PENV("x", pa->x);
|
||||
SET_PENV("y", pa->y);
|
||||
|
||||
char *args[] = { shell, "-c", p->script, shell, NULL };
|
||||
int ret, exitcode;
|
||||
|
||||
ret = spawn(args, NULL, &exitcode);
|
||||
|
||||
if (exitcode > 0)
|
||||
printf("error: preview exited with code: %d\n", exitcode);
|
||||
|
||||
return ret;
|
||||
}
|
19
preview.h
Normal file
19
preview.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef PREVIEW_H
|
||||
#define PREVIEW_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
char *ext, *type, *subtype, *script;
|
||||
} Preview;
|
||||
|
||||
typedef struct {
|
||||
char *f, *w, *h, *x, *y;
|
||||
} PreviewArgs;
|
||||
|
||||
void init_previews(Preview *ps, size_t len);
|
||||
void cleanup_previews(void);
|
||||
Preview *find_preview(char const *ext, char const *mimetype);
|
||||
int run_preview(Preview *p, PreviewArgs *pa);
|
||||
|
||||
#endif
|
16
previews.h
Normal file
16
previews.h
Normal file
@ -0,0 +1,16 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gen/prev/scripts.h"
|
||||
#include "preview.h"
|
||||
|
||||
/*
|
||||
* This file is supposed to be included in ctpv.c
|
||||
*/
|
||||
|
||||
#define P(e, t, s, f) { e, t, s, f }
|
||||
|
||||
Preview previews[] = {
|
||||
P(NULL, "text", "plain", prev_scr_file_sh),
|
||||
};
|
||||
|
||||
#undef P
|
90
utils.c
Normal file
90
utils.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "utils.h"
|
||||
|
||||
char *program;
|
||||
|
||||
/*
|
||||
* Call command
|
||||
*
|
||||
* If cpid is NULL, wait for the command to finish executing;
|
||||
* otherwise store pid in cpid
|
||||
*/
|
||||
int spawn(char *args[], pid_t *cpid, int *exitcode)
|
||||
{
|
||||
if (exitcode)
|
||||
*exitcode = -1;
|
||||
|
||||
pid_t pid = fork();
|
||||
ERRCHK_RET(pid == -1, "fork() failed");
|
||||
|
||||
/* Child process */
|
||||
if (pid == 0) {
|
||||
execvp(args[0], args);
|
||||
print_errorf("exec() failed: %s", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (cpid) {
|
||||
*cpid = pid;
|
||||
} else {
|
||||
int stat;
|
||||
ERRCHK_RET(waitpid(pid, &stat, 0) == -1, "waitpid() failed: %s",
|
||||
strerror(errno));
|
||||
|
||||
if (exitcode && WIFEXITED(stat))
|
||||
*exitcode = WEXITSTATUS(stat);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
CharVec char_v_new(size_t cap)
|
||||
{
|
||||
CharVec v;
|
||||
v.buf = NULL;
|
||||
v.len = 0;
|
||||
v.cap = cap;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void char_v_free(CharVec *v)
|
||||
{
|
||||
if (v->buf) {
|
||||
free(v->buf);
|
||||
v->buf = NULL;
|
||||
v->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void char_v_append(CharVec *v, char c)
|
||||
{
|
||||
if (!v->buf) {
|
||||
v->buf = malloc(v->cap * sizeof(v->buf[0]));
|
||||
if (!v->buf) {
|
||||
print_error("calloc() failed");
|
||||
abort();
|
||||
}
|
||||
v->buf[0] = '\0';
|
||||
v->len++;
|
||||
}
|
||||
|
||||
if (v->len + 1 >= v->cap) {
|
||||
v->cap *= 2;
|
||||
v->buf = realloc(v->buf, v->cap * sizeof(v->buf[0]));
|
||||
if (!v->buf) {
|
||||
print_error("realloc() failed");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
v->buf[v->len - 1] = c;
|
||||
v->buf[v->len] = '\0';
|
||||
v->len++;
|
||||
}
|
32
utils.h
Normal file
32
utils.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define LEN(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#define FORMATTED_STRING(arr, format) \
|
||||
do { \
|
||||
va_list args; \
|
||||
va_start(args, (format)); \
|
||||
vsnprintf((arr), LEN(arr) - 1, (format), args); \
|
||||
va_end(args); \
|
||||
} while (0)
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
size_t len, cap;
|
||||
} CharVec;
|
||||
|
||||
extern char *program;
|
||||
|
||||
int spawn(char *args[], pid_t *cpid, int *exitcode);
|
||||
|
||||
CharVec char_v_new(size_t cap);
|
||||
void char_v_append(CharVec *v, char c);
|
||||
void char_v_free(CharVec *v);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user