
/*
   vdev: a virtual device manager for *nix
   Copyright (C) 2015  Jude Nelson

   This program is dual-licensed: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 3 or later as 
   published by the Free Software Foundation. For the terms of this 
   license, see LICENSE.GPLv3+ or <http://www.gnu.org/licenses/>.

   You are free to use this program under the terms of the GNU General
   Public License, but WITHOUT ANY WARRANTY; without even the implied 
   warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   See the GNU General Public License for more details.

   Alternatively, you are free to use this program under the terms of the 
   Internet Software Consortium License, but WITHOUT ANY WARRANTY; without
   even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   For the terms of this license, see LICENSE.ISC or 
   <http://www.isc.org/downloads/software-support-policy/isc-license/>.
*/

#include "main.h"
#include "libvdev/misc.h"
#include "libvdev/sbuf.h"

#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

/* global vdev state */
static struct vdev_state vdev;

// run! 
int main(int argc, char **argv)
{
    int rc = 0;
    int coldplug_quiesce_pipe[2];
    bool is_child = false;
    bool is_parent = false;
    ssize_t nr = 0;
    
    /* FD to write to once we finish flushing the initial device requests */
    int coldplug_finished_fd = -1;

    memset(&vdev, 0, sizeof(struct vdev_state));
   
    /* ignore SIGPIPE from daemonlets */ 
    signal(SIGPIPE, SIG_IGN);
   
    /* set up global vdev state */
    rc = vdev_init(&vdev, argc, argv);
    if (rc != 0) {
        vdev_error("vdev_init rc = %d\n", rc);
        vdev_shutdown(&vdev, false);
        exit(1);
    }
      
    /* run the preseed command */
    rc = vdev_preseed_run(&vdev);
    if (rc != 0) {
        vdev_error("vdev_preseed_run rc = %d\n", rc);
        vdev_shutdown(&vdev, false);
        exit(1);
    }

    /* if we're going to daemonize, then redirect logging to the logfile */
    if (!vdev.config->foreground) {
        /* sanity check */
        if (vdev.config->logfile_path == NULL) {
            fprintf(stderr, "No logfile specified\n");
            vdev_shutdown(&vdev, false);
            exit(2);
        }
        
        /* do we need to connect to syslog? */
        if (strcmp(vdev.config->logfile_path, "syslog") == 0) {
            vdev_debug("%s", "Switching to syslog for messages\n");
            vdev_enable_syslog();
        } else {
            /* send to a specific logfile */ 
            rc = vdev_log_redirect(vdev.config->logfile_path);
            if (rc != 0) {
                vdev_error("vdev_log_redirect('%s') rc = %d\n", 
                           vdev.config->logfile_path, rc);
                vdev_shutdown(&vdev, false);
                exit(2);
            }
        }
      
        if (!vdev.config->coldplug_only) {
            /* will become a daemon after handling coldplug.
             * set up a pipe between parent and child, so the child can
             * signal the parent when it's device queue has been emptied
             * (meaning the parent can safely exit).
             * (coldplug_only uses vdev_detach_session() later instead — no pipe.)
             */
            rc = pipe(coldplug_quiesce_pipe);
            if (rc != 0) {
                vdev_error("pipe rc = %d\n", rc);
                vdev_shutdown(&vdev, false);
                exit(3);
            }
     
            /* become a daemon: parent (rc==1) stays alive to read coldplug_quiesce_pipe */
            rc = vdev_daemonize();
            if (rc < 0) {
                vdev_error("vdev_daemonize rc = %d\n", rc);
                vdev_shutdown(&vdev, false);
                exit(3);
            }
     
            if (rc == 0) {
                /* child */
                close(coldplug_quiesce_pipe[0]);
                coldplug_finished_fd = coldplug_quiesce_pipe[1];
        
                /* write a pidfile */
                if (vdev.config->pidfile_path != NULL) {
                    rc = vdev_pidfile_write(vdev.config->pidfile_path);
                    if (rc != 0) {
                        vdev_error("vdev_pidfile_write('%s') rc = %d\n", 
                                   vdev.config->pidfile_path, rc);
                        vdev_shutdown(&vdev, false);
                        exit(4);
                    }
                }

                is_child = true;
            } else {
                /* parent */
                is_parent = true;
                close(coldplug_quiesce_pipe[1]);
            }
        }
    }
   
    if (!is_parent || vdev.config->foreground || vdev.config->coldplug_only) {
        
        if (vdev.config->coldplug_only && !vdev.config->foreground) {
            /*
             * coldplug_only skipped the block above (pipe + vdev_daemonize): there is
             * no parent process that must stay in main() to read coldplug_quiesce_pipe.
             * vdev_detach_session() double-forks, setsid, and exits intermediate PIDs so
             * only the final child returns here — unlike vdev_daemonize(), which returns
             * 1 in the original parent so this same main() can wait on the pipe (see the
             * final `else` / is_parent branch). Helpers use the same detach when they must
             * leave the terminal.
             */
            vdev_detach_session();

            /*
             * detach closes every inherited fd (including log redirect). Reopen
             * logging and write the pidfile in the surviving child.
             */
            if (vdev.config->logfile_path != NULL) {
                if (strcmp(vdev.config->logfile_path, "syslog") == 0) {
                    vdev_enable_syslog();
                } else {
                    rc = vdev_log_redirect(vdev.config->logfile_path);
                    if (rc != 0) {
                        vdev_error("vdev_log_redirect('%s') after detach rc = %d\n",
                                   vdev.config->logfile_path, rc);
                        vdev_shutdown(&vdev, false);
                        exit(2);
                    }
                }
            }

            if (vdev.config->pidfile_path != NULL) {
                rc = vdev_pidfile_write(vdev.config->pidfile_path);
                if (rc != 0) {
                    vdev_error("vdev_pidfile_write('%s') after detach rc = %d\n",
                               vdev.config->pidfile_path, rc);
                    vdev_shutdown(&vdev, false);
                    exit(4);
                }
            }

        } else if (vdev.config->foreground) {
            
            if (!vdev.config->coldplug_only) {
                /* do we need to connect to syslog? */
                if (strcmp(vdev.config->logfile_path, "syslog") == 0) {
                    vdev_debug("%s", "Switching to syslog for messages\n");
                    vdev_enable_syslog();
                } else {
                    /* send to a specific logfile */
                    rc = vdev_log_redirect(vdev.config->logfile_path);
                    if (rc != 0) {
                        vdev_error("vdev_log_redirect('%s') rc = %d\n", 
                                   vdev.config->logfile_path, rc);
                        vdev_shutdown(&vdev, false);
                        exit(2);
                    }
                }
        
                /* write a pidfile */
                if (vdev.config->pidfile_path != NULL) {
                    rc = vdev_pidfile_write(vdev.config->pidfile_path);
                    if (rc != 0) {
                        vdev_error("vdev_pidfile_write('%s') rc = %d\n", 
                                   vdev.config->pidfile_path, rc);
                        vdev_shutdown(&vdev, false);
                        exit(4);
                    }
                }
            }
        }

        /* child, or foreground, or coldplug only.  start handling (coldplug) device events */
        rc = vdev_start(&vdev);
        if (rc != 0) {
            vdev_error("vdev_backend_init rc = %d\n", rc);
            vdev_stop(&vdev);
            vdev_shutdown(&vdev, false);
     
            /* if child, and we're connected to the parent, then tell the parent to exit failure */
            if (is_child && !vdev.config->foreground && !vdev.config->coldplug_only) {
                ssize_t _dummy = write(coldplug_quiesce_pipe[1], &rc, sizeof(rc));
                (void)_dummy;
                close(coldplug_quiesce_pipe[1]);
            }
            exit(5);
        }
     
        /* main loop: get events from the OS and process them.
         * wake up the parent once we finish the coldplugged devices
         */
        rc = vdev_main(&vdev, coldplug_finished_fd);
        if (rc == SIGTERM || rc == SIGALRM || rc == SIGINT) {
            /* Do not clear vdev.running here — vdev_stop() requires it true to drain
             * the work queue and stop daemonlets before shutdown. */
            vdev_error("vdev_main (signal tells to die) rc = %d\n", rc);
        }
      
        /* if only doing coldplug... */
        if (rc == 0 && vdev.config->coldplug_only && vdev.config->preseed_path) {
            /* find and remove all stale coldplug devices.
             * use the metadata directory to figure this out
             */
            rc = vdev_remove_unplugged_devices(&vdev);
            if (rc != 0)
                vdev_error("vdev_remove_unplugged_devices() rc = %d\n", rc);
        }

        /* quiesce requests */
        vdev_stop(&vdev);
   
        /* print benchmarks... */
        vdev_debug("%s", "Action benchmarks:\n");
        for (unsigned int i = 0; i < vdev.num_acts; i++)
            vdev_action_log_benchmarks(&vdev.acts[i]);
      
        /* Drop pidfile on intentional signal exit (e.g. os-queue-tracking flag 2). */
        {
            bool drop_pidfile = !vdev.running;

            if (rc == SIGTERM || rc == SIGALRM || rc == SIGINT)
                drop_pidfile = true;
            vdev_shutdown(&vdev, drop_pidfile);
        }

        return rc;

    } else {
      
        /* clean up, but keep the pidfile */
        vdev_shutdown(&vdev, false);
        close(coldplug_quiesce_pipe[0]);

        return 0;
    }
}
