diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | alloc.c | 20 | ||||
-rw-r--r-- | alloc.h | 10 | ||||
-rwxr-xr-x | automedia | 4 | ||||
-rw-r--r-- | buffer.c | 54 | ||||
-rw-r--r-- | buffer.h | 30 | ||||
-rwxr-xr-x | build.sh | 8 | ||||
-rw-r--r-- | main.c | 60 | ||||
-rw-r--r-- | program.c | 102 | ||||
-rw-r--r-- | program.h | 13 |
10 files changed, 299 insertions, 5 deletions
@@ -1,2 +1,3 @@ .vscode/ -__pycache__
\ No newline at end of file +__pycache__ +automedia @@ -0,0 +1,20 @@ +#include "alloc.h" +#include <stdio.h> + +void* alloc_or_crash(size_t size) { + void *mem = malloc(size); + if(!mem) { + fprintf(stderr, "Error: failed to allocate %zu bytes\n", size); + abort(); + } + return mem; +} + +void* realloc_or_crash(void *mem, size_t new_size) { + void *new_mem = realloc(mem, new_size); + if(!new_mem) { + fprintf(stderr, "Error: failed to reallocate %p to size %zu\n", mem, new_size); + abort(); + } + return new_mem; +} @@ -0,0 +1,10 @@ +#ifndef ALLOC_H +#define ALLOC_H + +#include <stddef.h> +#include <stdlib.h> + +void* alloc_or_crash(size_t size); +void* realloc_or_crash(void *mem, size_t new_size); + +#endif diff --git a/automedia b/automedia deleted file mode 100755 index e6e35a1..0000000 --- a/automedia +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -cd /usr/share/automedia -./automedia.py "$@" diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..879a62b --- /dev/null +++ b/buffer.c @@ -0,0 +1,54 @@ +#include "buffer.h" +#include "alloc.h" +#include <stdio.h> +#include <string.h> +#include <assert.h> + +void buffer_init(Buffer *self) { + self->data = NULL; + self->size = 0; + self->capacity = 0; +} + +void buffer_deinit(Buffer *self) { + free(self->data); + self->size = 0; + self->capacity = 0; +} + +static void buffer_ensure_capacity(Buffer *self, size_t new_size) { + size_t new_capacity = self->capacity; + if(new_size <= self->capacity) + return; + + if(new_capacity == 0) + new_capacity = 8; + + while(new_capacity < new_size) { + /* new_capacity *= 1.5 */ + new_capacity += (new_capacity >> 1); + } + + self->data = realloc_or_crash(self->data, new_capacity); + self->capacity = new_capacity; +} + +void buffer_append(Buffer *self, const void *data, size_t size) { + buffer_ensure_capacity(self, self->size + size); + memcpy((char*)self->data + self->size, data, size); + self->size += size; +} + +void* buffer_pop(Buffer *self, size_t size) { + assert(self->size >= size); + self->size -= size; + return (char*)self->data + self->size; +} + +void* buffer_begin(Buffer *self) { + return self->data; +} + +void* buffer_end(Buffer *self) { + return (char*)self->data + self->size; +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..a8c1940 --- /dev/null +++ b/buffer.h @@ -0,0 +1,30 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include <stddef.h> + +/* + TODO: Optimize small size buffers by using data and size members (16 bytes on x86) + instead of heap allocation +*/ +typedef struct { + void *data; + size_t size; + size_t capacity; +} Buffer; + +void buffer_init(Buffer *self); +void buffer_deinit(Buffer *self); + +void buffer_append(Buffer *self, const void *data, size_t size); +/* + This function changes the size of the buffer without changing the capacity + and returns the value at the back of the buffer, which is valid until @tsl_buffer_append or + @tsl_buffer_deinit is called. + The buffer size has to be larger or equal to @size. +*/ +void* buffer_pop(Buffer *self, size_t size); +void* buffer_begin(Buffer *self); +void* buffer_end(Buffer *self); + +#endif diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..575c08b --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +CFLAGS="-O0 -g3 -Wall -Wextra -Werror" +#CFLAGS="-O3 -s" +#LIBS="-lcurl" +LIBS="" +#gcc +musl-gcc -static main.c program.c alloc.c buffer.c -o automedia $CFLAGS $LIBS @@ -0,0 +1,60 @@ +#include <stdio.h> +#include <stdlib.h> +#include "program.h" +#include "buffer.h" + +static int program_buffer_write_callback(char *data, int size, void *userdata) { + Buffer *buffer = userdata; + buffer_append(buffer, data, size); + return 0; +} + +static void usage() { + fprintf(stderr, "usage: automedia COMMAND\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "COMMANDS\n"); + fprintf(stderr, " add Add media to track\n"); + fprintf(stderr, " sync Start syncing tracked media\n"); + fprintf(stderr, " downloaded List downloaded media\n"); + exit(1); +} + +static void usage_add() { + fprintf(stderr, "usage: automedia add <type> <url|filename> [--name name] [--start-after start_after]\n"); + fprintf(stderr, "OPTIONS\n"); + fprintf(stderr, " type The type should be either rss or html\n"); + fprintf(stderr, " url The url to the rss or html\n"); + fprintf(stderr, " filename The filename of an episode of an existing serie to start track. Currently only works with rss on https://nyaa.si\n"); + fprintf(stderr, " --name The display name to be used for the media. Optional for rss, in which case the name will be retries from rss TITLE, required for html\n"); + fprintf(stderr, " --start-after The sync should start downloading media after this item. This --start-after value should be the title of the episode/chapter (Optional, default is to start from the first item)\n"); + fprintf(stderr, "EXAMPLES\n"); + fprintf(stderr, " automedia.py add rss 'https://nyaa.si/?page=rss&q=Tejina-senpai+1080p&c=0_0&f=0&u=HorribleSubs'\n"); + fprintf(stderr, " automedia.py add html 'https://manganelo.com/manga/read_naruto_manga_online_free3' --name Naruto\n"); + fprintf(stderr, " automedia.py add rss '[Erai-raws] Saiki Kusuo no Psi Nan - Kanketsu-hen - 01 [1080p][Multiple Subtitle].mkv'\n"); + exit(1); +} + +static void usage_sync() { + fprintf(stderr, "usage: automedia sync <download_dir>\n"); + fprintf(stderr, "OPTIONS\n"); + fprintf(stderr, " download_dir The path where media should be downloaded to\n"); + fprintf(stderr, "EXAMPLES\n"); + fprintf(stderr, " automedia.py sync /home/adam/Downloads/automedia\n"); + exit(1); +} + +int main(int argc, char **argv) { + if(argc < 3) + usage(); + + Buffer buffer; + buffer_init(&buffer); + + const char *args[] = { "curl", "-s", "-L", "-f", "https://google.com", NULL }; + program_exec(args, program_buffer_write_callback, &buffer); + printf("program output: %.*s\n", (int)buffer.size, (char*)buffer.data); + + buffer_deinit(&buffer); + + return 0; +} diff --git a/program.c b/program.c new file mode 100644 index 0000000..6004ccb --- /dev/null +++ b/program.c @@ -0,0 +1,102 @@ +#include "program.h" +#include <unistd.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#define READ_END 0 +#define WRITE_END 1 + +int program_exec(const char **args, ProgramOutputCallback output_callback, void *userdata) { + /* 1 arguments */ + if(args[0] == NULL) + return -1; + + int fd[2]; + if(pipe(fd) == -1) { + perror("Failed to open pipe"); + return -2; + } + + pid_t parent_pid = getpid(); + + pid_t pid = fork(); + if(pid == -1) { + perror("Failed to fork"); + return -3; + } else if(pid == 0) { /* child */ + if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { + perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed"); + exit(127); + } + + /* Test if the parent died before the above call to prctl */ + if(getppid() != parent_pid) + exit(127); + + dup2(fd[WRITE_END], STDOUT_FILENO); + close(fd[READ_END]); + close(fd[WRITE_END]); + + execvp(args[0], (char* const*)args); + perror("execvp"); + exit(127); + } else { /* parent */ + close(fd[WRITE_END]); + + int result = 0; + int status; + + char buffer[4097]; + + for(;;) { + ssize_t bytes_read = read(fd[READ_END], buffer, sizeof(buffer) - 1); + if(bytes_read == 0) { + break; + } else if(bytes_read == -1) { + int err = errno; + fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(err)); + result = -err; + goto cleanup; + } + + buffer[bytes_read] = '\0'; + if(output_callback && output_callback(buffer, bytes_read, userdata) != 0) + break; + } + + if(waitpid(pid, &status, 0) == -1) { + perror("waitpid failed"); + result = -5; + goto cleanup; + } + + if(!WIFEXITED(status)) { + result = -4; + goto cleanup; + } + + int exit_status = WEXITSTATUS(status); + if(exit_status != 0) { + fprintf(stderr, "Failed to execute program ("); + const char **arg = args; + while(*arg) { + if(arg != args) + fputc(' ', stderr); + fprintf(stderr, "'%s'", *arg); + ++arg; + } + fprintf(stderr, "), exit status %d\n", exit_status); + result = -exit_status; + goto cleanup; + } + + cleanup: + close(fd[READ_END]); + return result; + } +} diff --git a/program.h b/program.h new file mode 100644 index 0000000..4c93066 --- /dev/null +++ b/program.h @@ -0,0 +1,13 @@ +#ifndef PROGRAM_H +#define PROGRAM_H + +/* Return 0 if you want to continue reading. @data is null-terminated */ +typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); + +/* + @args need to have at least 2 arguments. The first which is the program name + and the last which is NULL, which indicates end of args +*/ +int program_exec(const char **args, ProgramOutputCallback output_callback, void *userdata); + +#endif |