#include #include #include #include #include #include #include #include #include #include #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] [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; /* UNIX domain socket name lengths are limited to "sizeof(sun_path)", which * varies from one system to another. A partial solution would be to call * "chdir" to trim out the directory part of the socket name and use a * relative path, but the file part of the socket could still be too large * for sockaddr_un. * * It's easier to check the socket length and simply reject names that would * overflow the buffer. */ if (strlen(socketname) > sizeof(addr.sun_path) - 1) { fputs("Cannot create or open 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 [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 \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) { int err; 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"; err = daemon_start(fd, &opts); unlink(socketname); free(socketname); puts("Goodbye."); return err ? EXIT_FAILURE : EXIT_SUCCESS; }