From cbbe31f11e1787df56ef18eaa861e36a5729ed49 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Wed, 10 May 2017 21:00:08 +0200 Subject: [PATCH] aws iot sample: add program options for application usage and daemonization https://jira.digi.com/browse/DEL-4149 Signed-off-by: Tatiana Leon --- awsiot-sample/src/daemonize.c | 280 ++++++++++++++++++++++++++++++++++ awsiot-sample/src/daemonize.h | 28 ++++ awsiot-sample/src/main.c | 89 +++++++++++ 3 files changed, 397 insertions(+) create mode 100644 awsiot-sample/src/daemonize.c create mode 100644 awsiot-sample/src/daemonize.h diff --git a/awsiot-sample/src/daemonize.c b/awsiot-sample/src/daemonize.c new file mode 100644 index 0000000..2804e22 --- /dev/null +++ b/awsiot-sample/src/daemonize.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2017 Digi International Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + * ======================================================================= + * + * From http://www.itp.uzh.ch/~dpotter/howto/daemonize + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*------------------------------------------------------------------------------ + F U N C T I O N D E C L A R A T I O N S +------------------------------------------------------------------------------*/ +static void daemonize(char const *const daemon_name); +static void signal_handler(int signum); +static int get_lock(char const *const file_name); +static void release_lock(int const fd); + +/*------------------------------------------------------------------------------ + G L O B A L V A R I A B L E S +------------------------------------------------------------------------------*/ +static volatile sig_atomic_t signal_from_child = 0; + +/*------------------------------------------------------------------------------ + F U N C T I O N D E F I N I T I O N S +------------------------------------------------------------------------------*/ +/** + * start_daemon() - Start a new daemon. + * + * @name: Daemon name. + * + * Return: 0 on success, 1 otherwise. + */ +int start_daemon(const char *name) +{ + int result = EXIT_SUCCESS; + int lock_fd = -1; + + /* Daemonize if requested. */ + lock_fd = get_lock(name); + if (lock_fd < 0) { + IOT_ERROR("Unable to start %s. It may be currently running", name); + result = EXIT_FAILURE; + goto done; + } + daemonize(name); + +done: + /* Clean up. */ + IOT_INFO("Daemon terminated"); + release_lock(lock_fd); + + return result; +} + +/** + * daemonize() - Daemonize the current process using the given name. + * + * @name: Daemon name. + */ +static void daemonize(char const *const name) +{ + pid_t pid, sid, parent; + FILE *pid_fp = NULL; + char pid_filename[PATH_MAX]; + + /* Already a daemon. */ + if (getppid() == 1) { + return; + } + + IOT_DEBUG("Start daemon %s", name); + + /* Open .pid file while root. We can write it once we know child PID. */ + umask(0022); + snprintf(pid_filename, sizeof(pid_filename), "/var/run/%s.pid", name); + pid_fp = fopen(pid_filename, "w+"); + if (pid_fp == NULL) { + IOT_ERROR("Unable to open pid file (errno: %d)", errno); + exit(EXIT_FAILURE); + } + + /* Trap signals that we expect to receive. */ + signal(SIGCHLD, signal_handler); + signal(SIGUSR1, signal_handler); + signal(SIGALRM, signal_handler); + + /* Fork off the parent process. */ + pid = fork(); + if (pid < 0) { + IOT_ERROR("Unable to fork daemon (errno: %d, %s)", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + /* If we got a good PID, then we can exit the parent process. */ + if (pid > 0) { + int fork_succeeded; + int exit_code; + + /* Write the PID of the newly created child process into the file. */ + if (fprintf(pid_fp, "%d\n", pid) <= 0) { + IOT_ERROR("Unable to write pid file (errno: %d)", errno); + fclose(pid_fp); + /* Do we want to do anything about the child that has just started + * at this point. */ + exit(EXIT_FAILURE); + } + + fclose(pid_fp); + + fork_succeeded = 0; + /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or + * for two seconds to elapse (SIGALRM). pause() should return after two + * seconds at most. */ + alarm(2); + if (signal_from_child) + fork_succeeded = 1; + pause(); + if (signal_from_child) + fork_succeeded = 1; + + exit_code = fork_succeeded ? EXIT_SUCCESS : EXIT_FAILURE; + exit(exit_code); + } + + /* At this point we are executing as the child process. */ + parent = getppid(); + + /* Cancel certain signals. */ + signal(SIGCHLD, SIG_DFL); /* A child process dies */ + signal(SIGTSTP, SIG_IGN); /* Various TTY signals */ + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */ + signal(SIGTERM, SIG_DFL); /* Die on SIGTERM */ + + /* Change the file mode mask. */ + umask(0); + + /* Create a new SID for the child process. */ + sid = setsid(); + if (sid < 0) { + IOT_ERROR("Unable to create a new session (errno %d, %s)", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Change the current working directory. This prevents the current + directory from being locked; hence not being able to remove it. */ + if (chdir("/") < 0) { + IOT_ERROR("Unable to change directory to '%s', (errno %d, %s)", "/", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Redirect standard files to /dev/null. + * Log an error, but otherwise ignore it. */ + if (freopen("/dev/null", "r", stdin) == NULL) + IOT_ERROR("Failed to redirect stdin to /dev/null"); + if (freopen("/dev/null", "w", stdout) == NULL) + IOT_ERROR("Failed to redirect stdout to /dev/null"); + if (freopen("/dev/null", "w", stderr) == NULL) + IOT_ERROR("Failed to redirect stderr to /dev/null"); + + /* Tell the parent process that we are A-okay. */ + kill(parent, SIGUSR1); +} + +/** + * signal_handler() - Manage signal received. + * + * @signum: Received signal. + */ +static void signal_handler(int signum) +{ + switch (signum) { + case SIGALRM: + /* Ignore this signal.*/ + break; + case SIGUSR1: + signal_from_child = 1; + break; + case SIGCHLD: + exit(EXIT_FAILURE); + break; + default: + break; + } +} + +/** + * get_lock() - Try to get lock + * + * @file_name: Name of the file used as lock inside '/var/lock'. + * + * Return: File descriptor of lock file, or -1 on error. + */ +static int get_lock(char const *const file_name) +{ + static char const path[] = "/var/lock/"; + char full_path[sizeof(path) + strlen(file_name)]; + int fd; + + snprintf(full_path, sizeof(full_path), "%s%s", path, file_name); + + fd = open(full_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd == -1) { + IOT_ERROR("Could not open PID file '%s'", full_path); + goto error; + } + + if (flock(fd, LOCK_EX | LOCK_NB) != 0) { + IOT_ERROR("Could not open lock PID file '%s'", full_path); + goto error; + } + + if (ftruncate(fd, 0) != 0) { + IOT_ERROR("Could not truncate PID file '%s'", full_path); + goto error; + } + + { + char buf[50]; + int len = snprintf(buf, sizeof(buf), "%ld", (long) getpid()); + + if (write(fd, buf, len) != len) { + IOT_ERROR("Error writing to PID file '%s'", full_path); + goto error; + } + } + goto done; + +error: + release_lock(fd); + fd = -1; + +done: + return fd; +} + +/** + * release_lock() - Release the lock obtained with 'get_lock(file_name)' + * + * @fd: File descriptor of lock file. + */ +static void release_lock(int const fd) +{ + if (fd < 0) + return; + + if (ftruncate(fd, 0) == -1) + IOT_ERROR("Could not truncate PID file"); + + if (lockf(fd, F_ULOCK, 0) == -1) + IOT_ERROR("Unable to unlock"); + + close(fd); +} diff --git a/awsiot-sample/src/daemonize.h b/awsiot-sample/src/daemonize.h new file mode 100644 index 0000000..5b892b8 --- /dev/null +++ b/awsiot-sample/src/daemonize.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2017 Digi International Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + * ======================================================================= + */ + +#ifndef DAEMONIZE_H_ +#define DAEMONIZE_H_ + +/*------------------------------------------------------------------------------ + F U N C T I O N D E C L A R A T I O N S +------------------------------------------------------------------------------*/ +int start_daemon(const char *name); + +#endif /* DAEMONIZE_H_ */ diff --git a/awsiot-sample/src/main.c b/awsiot-sample/src/main.c index b21729b..738f274 100644 --- a/awsiot-sample/src/main.c +++ b/awsiot-sample/src/main.c @@ -18,16 +18,38 @@ */ #include +#include +#include #include #include +#include "daemonize.h" + +/*------------------------------------------------------------------------------ + D E F I N I T I O N S +------------------------------------------------------------------------------*/ +#define VERSION "0.1" GIT_REVISION + +#define USAGE \ + "AWS IoT Device SDK demo.\n" \ + "Copyright(c) Digi International Inc.\n" \ + "\n" \ + "Version: %s\n" \ + "\n" \ + "Usage: %s [options]\n\n" \ + " -d --daemon Daemonize the process\n" \ + " -h --help Print help and exit\n" \ + "\n" + /*------------------------------------------------------------------------------ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ +static int start_aws_iot(const char *config_file); static int check_stop(void); static void add_sigkill_signal(void); static void graceful_shutdown(void); static void sigint_handler(int signum); +static void usage(char const *const name); /*------------------------------------------------------------------------------ G L O B A L V A R I A B L E S @@ -38,6 +60,63 @@ static volatile int stop = 0; F U N C T I O N D E F I N I T I O N S ------------------------------------------------------------------------------*/ int main(int argc, char **argv) +{ + int result = EXIT_SUCCESS; + char *name = basename(argv[0]); + static int opt, opt_index; + int create_daemon = 0; + char *config_file = NULL; + static const char *short_options = "dh"; + static const struct option long_options[] = { + {"daemon", no_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + while (1) { + opt = getopt_long(argc, argv, short_options, long_options, + &opt_index); + if (opt == -1) + break; + + switch (opt) { + case 'd': + create_daemon = 1; + break; + case 'h': + usage(name); + goto done; + default: + usage(name); + result = EXIT_FAILURE; + goto done; + } + } + + /* Daemonize if requested. */ + if (create_daemon) { + if (start_daemon(name) != 0) { + result = EXIT_FAILURE; + goto done; + } + } + + /* Do the real work. */ + start_aws_iot(config_file); + +done: + return result; +} + +/* + * start_aws_iot() - Start AWS IoT Device SDK + * + * @config_file: Absolute path of the configuration file to use. + * NULL to use the default one (/etc/awsiotsdk.conf). + * + * Return: 0 on success, 1 otherwise. + */ +static int start_aws_iot(const char *config_file) { int result = EXIT_SUCCESS; @@ -97,3 +176,13 @@ static void sigint_handler(int signum) IOT_DEBUG("Received signal %d to close Cloud connection.", signum); exit(0); } + +/** + * usage() - Print usage information + * + * @name: Application name. + */ +static void usage(char const *const name) +{ + printf(USAGE, VERSION, name); +}