summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorbbergeron <[email protected]>2024-04-03 17:32:01 -0400
committerbbergeron <[email protected]>2024-04-03 17:32:01 -0400
commitc1cb78d574c0429aa5e3ff3a2b3886e4bc153212 (patch)
treebf68806bcbddcafafc015b28c25550ea457eeecc /main.c
Reset Git repo and use a pseudonym to sign commits
I used to sign my commits with my real name and my personal email address, which I wanted scrubbed off the "B." pseudosphere. Re-creating a new git repository was safer than simpler than re-writing the history (although the latter could've also worked but, oh well).
Diffstat (limited to 'main.c')
-rw-r--r--main.c353
1 files changed, 353 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..7899b7b
--- /dev/null
+++ b/main.c
@@ -0,0 +1,353 @@
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+#include "common.h"
+#include "daemon.h"
+
+#define SOCKET_NAME "/cybd.socket"
+
+#define SUN_LEN(ptr) (offsetof (struct sockaddr_un, sun_path) + strlen ((ptr)->sun_path))
+
+static int fd;
+static char *socketname;
+
+static int main_daemon (int fd, int argc, char **argv);
+static int main_push (int fd, int argc, char **argv);
+static int main_pop (int fd, int argc, char **argv);
+static int main_skip (int fd, int argc, char **argv);
+static int main_pause (int fd, int argc, char **argv);
+static int main_resume (int fd, int argc, char **argv);
+static int main_query (int fd, int argc, char **argv);
+static int main_kill (int fd, int argc, char **argv);
+
+static int get_socket_fd (const char *socketname, int do_bind);
+static char *get_default_socketname (void);
+
+
+int main(int argc, char **argv)
+{
+ int opt, err, is_daemon;
+ char *command;
+
+ socketname = NULL;
+ while ((opt = getopt(argc, argv, "s:")) != -1) switch (opt) {
+ case 's':
+ socketname = strdup(optarg);
+ break;
+ default:
+ goto usage;
+ }
+
+ if (socketname == NULL)
+ socketname = get_default_socketname();
+
+ if ((command = argv[optind ++]) == NULL) {
+ fprintf(stderr, "%s: missing command\n", argv[0]);
+ free(socketname);
+ goto usage;
+ }
+
+ is_daemon = strcmp(command, "daemon") == 0;
+ if (is_daemon) unlink(socketname);
+ if ((fd = get_socket_fd(socketname, is_daemon)) == -1) {
+ fprintf(stderr, "%s: Failed to bind socket at '%s': %s\n", argv[0], socketname, strerror(errno));
+ free(socketname);
+ return EXIT_FAILURE;
+ }
+
+ if (is_daemon)
+ err = main_daemon (fd, argc, argv);
+ else {
+ free(socketname);
+ if (strcmp(command, "push" ) == 0) err = main_push (fd, argc, argv);
+ else if (strcmp(command, "pop" ) == 0) err = main_pop (fd, argc, argv);
+ else if (strcmp(command, "skip" ) == 0) err = main_skip (fd, argc, argv);
+ else if (strcmp(command, "pause" ) == 0) err = main_pause (fd, argc, argv);
+ else if (strcmp(command, "resume") == 0) err = main_resume (fd, argc, argv);
+ else if (strcmp(command, "kill" ) == 0) err = main_kill (fd, argc, argv);
+ else if (strcmp(command, "query" ) == 0) err = main_query (fd, argc, argv);
+ else {
+ fprintf(stderr, "%s: %s: Unknown command\n", argv[0], command);
+ close(fd);
+ goto usage;
+ }
+ }
+
+ close(fd);
+ return err;
+
+usage:
+ fprintf(stderr, "Usage: %s [-s socket] <daemon|push|pop|term|list|skip> [args...]\n", argv[0]);
+ return EXIT_FAILURE;
+}
+
+char *get_default_socketname(void)
+{
+ /* Suggest a default socket name for cybd. This function returns either one
+ * of the two following suggestions;
+ *
+ * 1. If the XDG_RUNTIME_DIR environment is set, we assume the user adhere
+ * to the XDG directory specification and behave accordingly.
+ * 2. If not, we use the FHS-specified '/run' directory.
+ *
+ * In either case, the socket names is set to ends with SOCKET_NAME and a
+ * dynamically allocated string is returned. This function may also returns
+ * NULL if cybd has run out of memory, in which case errno will be set to
+ * ENOMEM.
+ */
+ char *name, *xdg;
+
+ if ((xdg = getenv("XDG_RUNTIME_DIR"))) {
+ name = malloc(strlen(xdg) + sizeof(SOCKET_NAME));
+ if (name == NULL) return NULL;
+
+ sprintf(name, "%s" SOCKET_NAME, xdg);
+ return name;
+ }
+
+ return strdup("/run" SOCKET_NAME);
+}
+
+int get_socket_fd (const char *socketname, int as_daemon)
+{
+ int fd;
+ struct sockaddr_un addr;
+
+ // TODO: Re-check the documentation apropos socket name length
+ if (strlen(socketname) > sizeof(addr.sun_path) - 1) {
+ fputs("Failed to create socket: filename too long\n", stderr);
+ return -1;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ perror("Failed to create socket");
+ return -1;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, socketname);
+
+ if ((as_daemon ? bind : connect)(fd, (struct sockaddr *)&addr, SUN_LEN(&addr))) {
+ close(fd);
+ fprintf(stderr, "Failed to open socket at '%s': %s\n", socketname, strerror(errno));
+ return -1;
+ }
+
+ if (as_daemon)
+ listen(fd, SOMAXCONN);
+
+ return fd;
+}
+
+static int main_push(int fd, int argc, char **argv)
+{
+ int read;
+ unsigned int id;
+ char *file;
+
+ if (optind == argc) {
+ fprintf(stderr, "%s: Missing input argument\n", argv[0]);
+ goto usage;
+ }
+ file = argv[optind ++];
+
+ if (argv[optind] != NULL) {
+ sscanf(argv[optind], "%u%n", &id, &read);
+ if (read != strlen(argv[optind])) {
+ fprintf(stderr, "%s: bad index argument: '%u'\n", argv[0], id);
+ goto usage;
+ }
+ optind ++;
+ }
+ else id = PLAYLIST_END;
+
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ goto usage;
+ }
+
+ if (daemon_push(fd, file, id)) {
+ perror("Failed to push entry");
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+
+usage:
+ fprintf(stderr, "Usage: %s [-s socket] push <file> [index]\n", argv[0]);
+ return EXIT_FAILURE;
+}
+
+static int main_pop(int fd, int argc, char **argv)
+{
+ int read;
+ unsigned int id;
+
+ if (argv[optind] != NULL) {
+ sscanf(argv[optind], "%u%n", &id, &read);
+ if (read != strlen(argv[optind])) {
+ fprintf(stderr, "%s: bad index argument: '%u'\n", argv[0], id);
+ goto usage;
+ }
+ optind ++;
+ }
+ else id = PLAYLIST_END;
+
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ goto usage;
+ }
+
+ if (daemon_pop(fd, id)) {
+ perror("Failed to pop entry");
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+
+usage:
+ fprintf(stderr, "Usage: %s [-s socket] pop <id>\n", argv[0]);
+ return EXIT_FAILURE;
+}
+
+static int main_skip(int fd, int argc, char **argv)
+{
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-s socket] skip\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ if (daemon_skip(fd)) {
+ perror("Failed to send skip request");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+static int main_pause(int fd, int argc, char **argv)
+{
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-s socket] pause\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ if (daemon_pause(fd)) {
+ perror("Failed to send pause request");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+static int main_resume(int fd, int argc, char **argv)
+{
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-s socket] resume\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ if (daemon_resume(fd)) {
+ perror("Failed to send resume request");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+static int main_kill(int fd, int argc, char **argv)
+{
+ if (argv[optind] != NULL) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-s socket] kill\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ if (daemon_kill(fd)) {
+ perror("Failed to send kill request");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+static int main_query (int fd, int argc, char **argv)
+{
+ int i;
+ struct Entry *entry;
+ struct StateInfo state_info;
+
+ if (optind != argc) {
+ fprintf(stderr, "%s: Too many arguments\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-s socket] query\n", argv[0]);
+ return -1;
+ }
+
+ if (daemon_query(fd, &state_info)) {
+ perror("Failed to query daemon");
+ return -1;
+ }
+
+ switch (state_info.state) {
+ case STATE_PAUSED:
+ printf("%s (paused)\n", state_info.current_song);
+ break;
+ case STATE_PLAYING:
+ printf("%s (playing)\n", state_info.current_song);
+ break;
+ case STATE_STANDBY:
+ puts("standby");
+ break;
+ }
+
+ for (i = 0; i < state_info.song_count; i ++) {
+ entry = &state_info.next_songs[i];
+ printf("%u\t%s", entry->id, entry->file);
+ free(entry->file);
+ }
+
+ stateinfo_free(&state_info);
+ return 0;
+}
+
+static void close_socket(int sig)
+{
+ puts("Signal received, terminating daemon");
+ shutdown(fd, SHUT_RDWR);
+}
+
+static int main_daemon (int fd, int argc, char **argv)
+{
+ time_t t;
+ int err;
+ struct tm *tm_info;
+ struct StreamerOpt opts;
+ struct sigaction sigact;
+
+ sigact.sa_handler = close_socket;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ // TODO: Read from args
+ opts.filename = "./test/stream.m3u8";
+
+
+ time(&t);
+ tm_info = localtime(&t);
+
+ char buffer[50];
+ strftime(buffer, 50, "Daemon started at %Y-%m-%d %H:%M:%S", tm_info);
+ puts(buffer);
+
+ err = daemon_start(fd, &opts);
+ unlink(socketname);
+ free(socketname);
+
+ puts("Goodbye.");
+ return err ? EXIT_FAILURE : EXIT_SUCCESS;
+}