From 43e26549f9c58b74d282f203c7d325d00ebb4782 Mon Sep 17 00:00:00 2001 From: Moritz Grimm Date: Mon, 10 Jul 2017 11:39:00 +0200 Subject: [PATCH] Add support for writing PID files --- NEWS | 2 ++ doc/ezstream.1.in.in | 15 +++++++++ src/cfg.c | 15 ++++++++- src/cfg.h | 5 ++- src/cfg_private.h | 3 +- src/cmdline.c | 17 ++++++++-- src/ezstream.c | 5 ++- src/util.c | 75 ++++++++++++++++++++++++++++++++++++++++--- src/util.h | 17 +++++----- tests/check_cfg.c | 9 ++++++ tests/check_cmdline.c | 17 ++++++++++ 11 files changed, 161 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index d2800db..359da32 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ Changes in 1.0.0, released on XXXX-XX-XX: * The configuration file structure has changed. * TagLib (its C wrapper library) is now a mandatory dependency * Support the new '@b@' placeholder for separate album metadata. + * The command line option -p has been added, causing ezstream to write a + locked PID file to a given location Changes in 0.6.0, released on 2015-01-18: diff --git a/doc/ezstream.1.in.in b/doc/ezstream.1.in.in index cf0fb41..ac5d662 100644 --- a/doc/ezstream.1.in.in +++ b/doc/ezstream.1.in.in @@ -20,6 +20,7 @@ .Bk -words .Op Fl hqrVv .Fl c Ar configfile +.Op Fl p Ar pidfile .Ek .Nm .Bk -words @@ -45,6 +46,20 @@ Use the XML configuration in .It Fl h Print a summary of available command line arguments with short descriptions and exit. +.It Fl p Ar pidfile +Write the +.Nm +process ID +.Pq a single number +to +.Ar pidfile . +The file will be written even when it already exists. +A file lock is maintained until the main +.Nm +process terminates. +If the file cannot be written for any reason, +.Nm +will log this, but not consider it a fatal error. .It Fl q Be more quiet. Suppress the output that external programs send to standard error. diff --git a/src/cfg.c b/src/cfg.c index fbce167..7aa2f7f 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Moritz Grimm + * Copyright (c) 2015, 2017 Moritz Grimm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -277,6 +277,13 @@ cfg_set_program_config_file(const char *file, const char **errstrp) return (0); } +int +cfg_set_program_pid_file(const char *file, const char **errstrp) +{ + SET_STRLCPY(cfg.program.pid_file, file, errstrp); + return (0); +} + int cfg_set_program_quiet_stderr(int quiet_stderr, const char **not_used) { @@ -642,6 +649,12 @@ cfg_get_program_config_file(void) return (cfg.program.config_file[0] ? cfg.program.config_file : NULL); } +const char * +cfg_get_program_pid_file(void) +{ + return (cfg.program.pid_file[0] ? cfg.program.pid_file : NULL); +} + int cfg_get_program_quiet_stderr(void) { diff --git a/src/cfg.h b/src/cfg.h index 252d8ac..8a322ec 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Moritz Grimm + * Copyright (c) 2015, 2017 Moritz Grimm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -91,6 +91,7 @@ int cfg_file_check(const char *); int cfg_set_program_name(const char *, const char **); int cfg_set_program_config_type(enum cfg_config_type, const char **); int cfg_set_program_config_file(const char *, const char **); +int cfg_set_program_pid_file(const char *, const char **); int cfg_set_program_quiet_stderr(int, const char **); int cfg_set_program_rtstatus_output(int, const char **); int cfg_set_program_verbosity(unsigned int, const char **); @@ -137,6 +138,8 @@ enum cfg_config_type cfg_get_program_config_type(void); const char * cfg_get_program_config_file(void); +const char * + cfg_get_program_pid_file(void); int cfg_get_program_quiet_stderr(void); int cfg_get_program_rtstatus_output(void); unsigned int diff --git a/src/cfg_private.h b/src/cfg_private.h index fe6ca97..7d00e19 100644 --- a/src/cfg_private.h +++ b/src/cfg_private.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Moritz Grimm + * Copyright (c) 2015, 2017 Moritz Grimm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -37,6 +37,7 @@ struct cfg { char name[PATH_MAX]; enum cfg_config_type config_type; char config_file[PATH_MAX]; + char pid_file[PATH_MAX]; int quiet_stderr; int rtstatus_output; unsigned int verbosity; diff --git a/src/cmdline.c b/src/cmdline.c index e62c3d2..355948c 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Moritz Grimm + * Copyright (c) 2015, 2017 Moritz Grimm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -32,10 +32,11 @@ #include "cmdline.h" #include "playlist.h" -#define OPTSTRING "c:hqrs:Vv" +#define OPTSTRING "c:hp:qrs:Vv" enum opt_vals { OPT_CONFIGFILE = 'c', OPT_HELP = 'h', + OPT_PIDFILE = 'p', OPT_QUIETSTDERR = 'q', OPT_RTSTATUS = 'r', OPT_SHUFFLEFILE = 's', @@ -51,7 +52,7 @@ static void _set_program_name(const char *); static void _usage(void) { - fprintf(stderr, "usage: %s [-hqrVv] -c cfgfile\n", + fprintf(stderr, "usage: %s [-hqrVv] -c cfgfile [-p pidfile]\n", cfg_get_program_name()); fprintf(stderr, " %s -s file\n", cfg_get_program_name()); @@ -63,6 +64,7 @@ _usage_help(void) fprintf(stderr, "\n"); fprintf(stderr, " -c cfgfile use XML configuration in cfgfile\n"); fprintf(stderr, " -h print this help and exit\n"); + fprintf(stderr, " -p pidfile write PID to pidfile\n"); fprintf(stderr, " -q suppress STDERR output from external en-/decoders\n"); fprintf(stderr, " -r show real-time stream information on stdout\n"); fprintf(stderr, " -s file read lines from file, shuffle, print to STDOUT, then exit\n"); @@ -125,6 +127,15 @@ cmdline_parse(int argc, char *argv[], int *ret_p) _usage_help(); *ret_p = 0; return (-1); + case OPT_PIDFILE: + if (0 > cfg_set_program_pid_file(optarg, &err_str)) { + fprintf(stderr, "-%c: argument %s\n", + OPT_PIDFILE, err_str); + _usage(); + *ret_p = 2; + return (-1); + } + break; case OPT_RTSTATUS: cfg_set_program_rtstatus_output(1, NULL); /* FALLTHROUGH */ diff --git a/src/ezstream.c b/src/ezstream.c index d79614e..5eb548b 100644 --- a/src/ezstream.c +++ b/src/ezstream.c @@ -1,7 +1,7 @@ /* * ezstream - source client for Icecast with external en-/decoder support * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski - * Copyright (C) 2007, 2009, 2015 Moritz Grimm + * Copyright (C) 2007, 2009, 2015, 2017 Moritz Grimm * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -801,6 +801,9 @@ main(int argc, char *argv[]) return (ez_shutdown(1)); } + if (0 > writePidfile(cfg_get_program_pid_file())) + log_syserr(WARNING, errno, cfg_get_program_pid_file()); + if (0 > stream_connect(stream)) { log_error("initial server connection failed"); return (ez_shutdown(1)); diff --git a/src/util.c b/src/util.c index c71ee9c..a4887bc 100644 --- a/src/util.c +++ b/src/util.c @@ -1,7 +1,7 @@ /* * ezstream - source client for Icecast with external en-/decoder support * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski - * Copyright (C) 2007, 2009 Moritz Grimm + * Copyright (C) 2007, 2009, 2017 Moritz Grimm * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -24,6 +24,9 @@ #include "compat.h" +#include +#include + #include #include #include @@ -31,12 +34,12 @@ #include #include #include +#include #ifdef HAVE_ICONV # include #endif -#include "cfg.h" #include "log.h" #include "util.h" #include "xalloc.h" @@ -45,7 +48,13 @@ # define BUFSIZ 1024 #endif -char * iconvert(const char *, const char *, const char *, int); +static char *pidfile_path; +static FILE *pidfile_file; +static pid_t pidfile_pid; +static unsigned int pidfile_numlocks; + +static char * iconvert(const char *, const char *, const char *, int); +static void cleanupPidfile(void); int strrcmp(const char *s, const char *sub) @@ -105,7 +114,7 @@ UTF8toCHAR(const char *in_str, int mode) return (iconvert(in_str, "UTF-8", codeset, mode)); } -char * +static char * iconvert(const char *in_str, const char *from, const char *to, int mode) { #ifdef HAVE_ICONV @@ -205,6 +214,64 @@ iconvert(const char *in_str, const char *from, const char *to, int mode) #endif /* HAVE_ICONV */ } +static void +cleanupPidfile(void) +{ + if (NULL != pidfile_path && getpid() == pidfile_pid) { + (void)unlink(pidfile_path); + (void)fclose(pidfile_file); + } +} + +int +writePidfile(const char *path) +{ + int save_errno = 0; + pid_t pid; + + if (NULL == path) + return (0); + + xfree(pidfile_path); + pidfile_path = xstrdup(path); + + if (NULL != pidfile_file) + fclose(pidfile_file); + if (NULL == (pidfile_file = fopen(pidfile_path, "w"))) { + save_errno = errno; + xfree(pidfile_path); + pidfile_path = NULL; + return (-1); + } + + pid = getpid(); + if (0 >= fprintf(pidfile_file, "%ld\n", (long)pid) || + 0 > fflush(pidfile_file) || + 0 > flock(fileno(pidfile_file), LOCK_EX)) + goto error; + + if (0 == pidfile_numlocks) { + pidfile_pid = pid; + if (0 != atexit(cleanupPidfile)) + goto error; + pidfile_numlocks++; + } + + return (0); + +error: + save_errno = errno; + (void)unlink(pidfile_path); + xfree(pidfile_path); + pidfile_path = NULL; + (void)fclose(pidfile_file); + pidfile_file = NULL; + pidfile_pid = 0; + errno = save_errno; + + return (-1); +} + char * replaceString(const char *source, const char *from, const char *to) { diff --git a/src/util.h b/src/util.h index eb18756..a0d6fe5 100644 --- a/src/util.h +++ b/src/util.h @@ -1,7 +1,7 @@ /* * ezstream - source client for Icecast with external en-/decoder support * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski - * Copyright (C) 2007 Moritz Grimm + * Copyright (C) 2007, 2017 Moritz Grimm * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -20,12 +20,13 @@ #define ICONV_TRANSLIT 1 #define ICONV_IGNORE 2 -int strrcmp(const char *, const char *); -int strrcasecmp(const char *, const char *); -char * CHARtoUTF8(const char *, int); -char * UTF8toCHAR(const char *, int); -char * replaceString(const char *, const char *, const char *); -char * shellQuote(const char *); -int urlParse(const char *, char **, unsigned short *, char **); +int writePidfile(const char *); +int strrcmp(const char *, const char *); +int strrcasecmp(const char *, const char *); +char * CHARtoUTF8(const char *, int); +char * UTF8toCHAR(const char *, int); +char * replaceString(const char *, const char *, const char *); +char * shellQuote(const char *); +int urlParse(const char *, char **, unsigned short *, char **); #endif /* __UTIL_H__ */ diff --git a/tests/check_cfg.c b/tests/check_cfg.c index f045170..4ba5820 100644 --- a/tests/check_cfg.c +++ b/tests/check_cfg.c @@ -207,6 +207,14 @@ START_TEST(test_program_config_file) } END_TEST +START_TEST(test_program_pid_file) +{ + ck_assert_ptr_eq(cfg_get_program_pid_file(), NULL); + TEST_STRLCPY(cfg_set_program_pid_file, cfg_get_program_pid_file, + PATH_MAX); +} +END_TEST + START_TEST(test_program_quiet_stderr) { ck_assert_int_eq(cfg_set_program_quiet_stderr(-1, NULL), 0); @@ -808,6 +816,7 @@ cfg_suite(void) tcase_add_test(tc_program, test_program_name); tcase_add_test(tc_program, test_program_config_type); tcase_add_test(tc_program, test_program_config_file); + tcase_add_test(tc_program, test_program_pid_file); tcase_add_test(tc_program, test_program_quiet_stderr); tcase_add_test(tc_program, test_program_rtstatus_output); tcase_add_test(tc_program, test_program_verbosity); diff --git a/tests/check_cmdline.c b/tests/check_cmdline.c index 48865d2..26590c9 100644 --- a/tests/check_cmdline.c +++ b/tests/check_cmdline.c @@ -33,6 +33,22 @@ START_TEST(test_help) } END_TEST +START_TEST(test_pidfile) +{ + char *argv[] = + { + "check_cmdline", "-p", "PIDFILE-TEST" , NULL + }; + int argc = (int)(sizeof(argv) / sizeof(argv[0])) - 1; + int ret; + + ck_assert_ptr_eq(cfg_get_program_pid_file(), NULL); + ck_assert_int_ne(cmdline_parse(argc, argv, &ret), 0); + ck_assert_int_eq(ret, 2); + ck_assert_str_eq(cfg_get_program_pid_file(), "PIDFILE-TEST"); +} +END_TEST + START_TEST(test_quiet_stderr) { char *argv[] = { "check_cmdline", "-q", NULL }; @@ -133,6 +149,7 @@ cmdline_suite(void) tcase_add_checked_fixture(tc_cmdline, setup_checked, teardown_checked); tcase_add_test(tc_cmdline, test_configfile); tcase_add_test(tc_cmdline, test_help); + tcase_add_test(tc_cmdline, test_pidfile); tcase_add_test(tc_cmdline, test_quiet_stderr); tcase_add_test(tc_cmdline, test_rtstatus_output); tcase_add_test(tc_cmdline, test_shuffle);