示例#1
0
文件: roll.c 项目: richievos/roll
int main(int argc, char *argv[]) {
    options_t options;
    int exit_code = 0;
    int pid_file_fd = -1;
    int try_failsafe = 0;
    int failsafe_mode = 0;
    int prune_packages = 0;
    int report_errors = 0;
    FILE *hostclass_file = NULL,
         *host_file = NULL;
    FILE *fp = NULL;
    host_config_t host_config;
    hostclass_config_t hostclass_config;
    struct stat st;
    char hostclass_file_tmpname[PATH_MAX],  /* "/tmp/hostclass.yml" */
         hostclass_file_name[PATH_MAX],     /* "/usr/local/etc/hostclass.yml" */
         host_file_tmpname[PATH_MAX],       /* "/tmp/host.yml" */
         host_file_name[PATH_MAX],          /* "/usr/local/etc/host.yml" */
         hostclass_config_url[PATH_MAX],
         host_config_url[PATH_MAX],
         download_url_format[PATH_MAX],
         temp_package_link_dir[PATH_MAX],
         previous_package_link_dir[PATH_MAX],
         package_download_dir[PATH_MAX],
         package_temp_dir[PATH_MAX],
         package_stow_dir[PATH_MAX],
         package_target_dir[PATH_MAX],
         package_link_dir[PATH_MAX],
         local_profiled_file_copy[PATH_MAX], *local_profiled_dir,
         pathbuf[PATH_MAX];
    char hostname[HOST_NAME_MAX];
    const char *base_groups[] = {"production", NULL};
    const char *failsafe_groups[] = {"failsafe", NULL};
    const char *download_groups[] = {"production", "failsafe", NULL};

    #define SNPRINTF_OR_ERROR(label, str, size, fmt, ...) \
        { int n = snprintf((str), (size), (fmt), __VA_ARGS__); \
          if(n == (size)) { log_error("%s is too long for buffer", (label)); \
              goto error; } }
    #define MKPATH_OR_ERROR(label, path) \
        { strlcpy(pathbuf, (path), sizeof(pathbuf)); \
          if(0 != mkpath((pathbuf)) && EEXIST != errno) { \
              log_error("Cannot make %s directory %s", (label), (pathbuf)); \
              goto error; } \
          if(0 != chmod((path), 0755)) { \
              log_error("Cannot chmod %s directory %s", (label), (path)); \
              goto error; } }
    #define RMRF_OR_ERROR(label, path) \
        { if(dir_exists(path) && !rmrf(path)) { \
              log_error("Cannot remove %s directory %s", (label), (path)); \
              goto error; } }
    #define CP_OR_ERROR(label, source, dest, dest_mode)       \
        { if(0 != cp(source, dest, dest_mode)) { \
              log_error ("Cannot copy %s to destination %s", (label), (dest)); \
              goto error; } }

    memset(&host_config, 0, sizeof(host_config_t));
    memset(&hostclass_config, 0, sizeof(hostclass_config_t));
    memset(&options, 0, sizeof(options_t));
    memset(hostclass_file_tmpname, 0, sizeof(hostclass_file_tmpname));
    memset(host_file_tmpname, 0, sizeof(host_file_tmpname));
    memset(previous_package_link_dir, 0, sizeof(previous_package_link_dir));

    options.base_url = BASE_URL;
    options.package_dir = PACKAGE_DIR;
    options.local_initd_file = LOCAL_INITD_FILENAME;
    options.local_profiled_file = LOCAL_PROFILED_FILENAME;
    options.config_dir = CONFIG_DIR;
    options.log_file = NULL;
    options.pid_file = PID_FILE;
    options.proxy = NULL;

    /* === Start ====================================================== */
    if(!parse_commandline(argc, argv, &options)) {
        goto error;
    }

    if(options.failsafe) {
        failsafe_mode = 1;
    }

    if(options.prune) {
      prune_packages = 1;
    }

    if(!(options.dryrun || has_root_privileges())) {
        goto error;
    }

    if(!open_pid_file(&pid_file_fd, options.pid_file)) {
        goto error;
    }

    if(!log_init(options.log_file, NULL)) {
        goto error;
    }

    report_errors = 1;
    log_header("Initializing", failsafe_mode);
    log_message("Roll starting with pid %ld\n", (long)getpid());
    log_message("Logging to %s\n", get_log_filename());

    if(!sanitize_environment()) {
        goto error;
    }

    if(!get_hostname(hostname)) {
        goto error;
    }
    log_message("Hostname is %s\n", hostname);

    /* === Fetch configuration ======================================== */
    log_header("Fetching config files", failsafe_mode);

    /* fetch host file, a versioned snapshot of a host file */
    if(options.host_file) {
        log_info("Using user specified host file %s", options.host_file);
        strlcpy(host_file_tmpname, options.host_file, sizeof(host_file_tmpname));
    } else {
        /* fetch the host config file */
        SNPRINTF_OR_ERROR(
            "Host configuration URL",
            host_config_url, PATH_MAX, HOST_CONFIG_URL_FORMAT,
            options.base_url, hostname
        );
        SNPRINTF_OR_ERROR(
            "Temporary host filename",
            host_file_tmpname, PATH_MAX, "/var/tmp/roll_host.%ld",
            (long)getpid()
        );
        unlink(host_file_tmpname); /* ignore error */
        log_info("Downloading host config from %s", host_config_url);
        if(!download(host_config_url, host_file_tmpname, options.proxy)) {
            goto error;
        }
        log_info("Saved host file to %s", host_file_tmpname);
    }

    host_file = fopen(host_file_tmpname, "rb");
    if(!host_file) {
        log_error("Unable to open %s: %s", host_file_tmpname, strerror(errno));
        goto error;
    }

    /* parse the host file, figure out which hostclass file to fetch */
    log_info("Parsing host config file");
    if(!parse_host_config(&host_config, host_file)) {
        goto error;
    }
    if(!host_config.hostclass_tag) {
        log_error("The host configuration does not specify a hostclass tag");
        goto error;
    }

    /* fetch hostclass file, a verisioned snapshot of a hostclass file */
    if(options.hostclass_file) {
        log_info("Using user specified hostclass file %s", options.hostclass_file);
        strlcpy(hostclass_file_tmpname, options.hostclass_file, sizeof(hostclass_file_tmpname));
    } else {
        /* fetch the hostclass config file */
        SNPRINTF_OR_ERROR(
            "Hostclass configuration URL",
            hostclass_config_url, PATH_MAX, HOSTCLASS_CONFIG_URL_FORMAT,
            options.base_url, host_config.hostclass_tag
        );
        SNPRINTF_OR_ERROR(
            "Temporary hostclass filename",
            hostclass_file_tmpname, PATH_MAX, "/var/tmp/roll_hostclass.%ld",
            (long)getpid()
        );
        unlink(hostclass_file_tmpname); /* ignore error */
        log_info("Downloading hostclass config from %s", hostclass_config_url);
        if(!download(hostclass_config_url, hostclass_file_tmpname, options.proxy)) {
            goto error;
        }
        log_info("Saved hostclass config file to %s", hostclass_file_tmpname);
    }

    log_info("Parsing hostclass config file");
    hostclass_file = fopen(hostclass_file_tmpname, "rb");
    if(!hostclass_file) {
        log_error("Unable to open %s: %s", hostclass_file_tmpname, strerror(errno));
        goto error;
    }
    if(!parse_hostclass_config(&hostclass_config, hostclass_file)) {
        goto error;
    }

    /* === Configuration ======================================== */
    log_header("Configuration", failsafe_mode);
    /* TODO verify image is defined */
    /* TODO verify current image matches desired image */

    log_message("Hostclass tag:     %s\n", host_config.hostclass_tag);
    log_message("Hardware type:     %s\n", "__TODO__");
    log_message("Current OS image:  %s\n", "__TODO__");
    log_message("Required OS image: %s\n", "__TODO__");

    /* === Download packages ========================================== */
    log_header("Downloading packages", failsafe_mode);

    SNPRINTF_OR_ERROR(
        "Package stow directory name",
        package_stow_dir, PATH_MAX, PACKAGE_STOW_DIR_FORMAT,
        options.package_dir
    );
    SNPRINTF_OR_ERROR(
        "Package download directory name",
        package_download_dir, PATH_MAX, PACKAGE_DOWNLOAD_DIR_FORMAT,
        options.package_dir
    );
    SNPRINTF_OR_ERROR(
        "Package temp directory name",
        package_temp_dir, PATH_MAX, PACKAGE_TEMP_DIR_FORMAT,
        options.package_dir
    );
    SNPRINTF_OR_ERROR(
        "Download URL format",
        download_url_format, PATH_MAX, DOWNLOAD_URL_METAFORMAT,
        options.base_url
    );
    MKPATH_OR_ERROR("package repository", package_stow_dir);
    MKPATH_OR_ERROR("package download", package_download_dir);
    MKPATH_OR_ERROR("package temp", package_temp_dir);
    if(!download_packages(hostclass_config.package_list,
                          download_groups,
                          download_url_format,
                          package_stow_dir,
                          package_download_dir,
                          package_temp_dir,
                          options.proxy))
    {
        goto error;
    }

 failsafe:

    /* === Build symlink tree ========================================= */
    log_header("Building symlink tree", failsafe_mode);
    /* TODO determine additional package groups to link from host */

    /* Figure out where to put things */
    SNPRINTF_OR_ERROR(
        "Package target directory name",
        package_target_dir, PATH_MAX, PACKAGE_TARGET_DIR_FORMAT,
        options.package_dir
    );
    SNPRINTF_OR_ERROR(
        "Hostclass symlink tree directory name",
        package_link_dir, PATH_MAX, "%s/%s%s",
        package_target_dir,
        (failsafe_mode ?
            (options.hostclass_file ? "__FAILSAFE__DEV__" : "__FAILSAFE__") :
            (options.hostclass_file ? "__DEV__" : "") ),
        host_config.hostclass_tag
    );
    SNPRINTF_OR_ERROR(
        "Hostclass temp symlink tree directory name",
        temp_package_link_dir, PATH_MAX, "%s.%ld",
        package_link_dir, (long)getpid()
    );

    RMRF_OR_ERROR("temporary package link", temp_package_link_dir);
    MKPATH_OR_ERROR("temporary package link", temp_package_link_dir);

    if(!create_package_tree(hostclass_config.package_list,
                            (failsafe_mode ? failsafe_groups : base_groups),
                            package_stow_dir,
                            temp_package_link_dir))
    {
        goto error;
    }

    /* !! Any failures from here on out will trigger failsafe mode */
    try_failsafe = 1;

    /* === Install /etc/init.d/local_initd ============================ */
    log_header("Installing local_initd script", failsafe_mode);
    if(!(fp = fopen(options.local_initd_file, "w")) ) {
        log_error("  Cannot open %s for writing.", options.local_initd_file);
        goto error;
    }
    fputs(LOCAL_INITD_SCRIPT, fp);
    fclose(fp);
    if(0 != chmod(options.local_initd_file, EXE_MODE)) {
        log_error("  Could not chmod %04o %s", EXE_MODE, options.local_initd_file);
        goto error;
    }
    log_info("Installed %s", options.local_initd_file);

    if(options.dryrun) {
        log_info("Skipping symlink creation in dry run mode");
    } else {

        /* If Gentoo/OpenRC, use textual default runlevel */
        if(dir_exists("/etc/runlevels/default")) {

            unlink(OPENRC_LOCAL_INITD_SYMLINK);    /* ignore errors */
            if(0 != symlink(LOCAL_INITD_FILENAME, OPENRC_LOCAL_INITD_SYMLINK)) {
                log_error("  Could not symlink %s to %s: %s", LOCAL_INITD_SCRIPT, OPENRC_LOCAL_INITD_SYMLINK, strerror(errno));
                goto error;
            }
            log_info("Installed Gentoo/OpenRC runlevel symlink %s", OPENRC_LOCAL_INITD_SYMLINK);

        /* Otherwise, assume SysV numeric runlevels */
        } else {

            char **s, *symlink_dests[] = LOCAL_INITD_SYMLINKS;
            for(s = symlink_dests; *s != NULL; s++) {
                unlink(*s);
                if(0 != symlink(LOCAL_INITD_FILENAME, *s)) {
                    log_error("  Could not symlink %s to %s: %s", LOCAL_INITD_SCRIPT, *s, strerror(errno));
                    goto error;
                }
                log_info("Installed SysV runlevel symlink %s", *s);
            }

        }
    }

    /* === Install /etc/profile.d/local_profiled.sh =================== */
    log_header("Installing local_profiled.sh script", failsafe_mode);
    strlcpy(local_profiled_file_copy, options.local_profiled_file, sizeof(local_profiled_file_copy));
    local_profiled_dir = dirname(local_profiled_file_copy);
    MKPATH_OR_ERROR("bash local_profiled.sh directory", local_profiled_dir);
    if(!(fp = fopen(options.local_profiled_file, "w")) ) {
        log_error("  Cannot open %s for writing.", options.local_profiled_file);
        goto error;
    }
    fputs(LOCAL_PROFILED_SCRIPT, fp);
    fclose(fp);
    log_info("Installed %s", options.local_profiled_file);

    /* === Run /etc/init.d/local_initd stop =========================== */
    log_header("Shutting down services", failsafe_mode);
    if(options.dryrun) {
        log_info("Skipping in dry run mode");
    } else {
        exit_code = run_command(options.local_initd_file, "stop", NULL);
        if(0 != exit_code) {
            log_error("  Could not run %s stop (status = %d)", options.local_initd_file, exit_code);
            goto error;
        }
    }

    /* === Move symlink tree into place =============================== */
    log_header("Moving package link tree into /usr/local", failsafe_mode);
    if(options.dryrun) {
        log_info("Skipping in dry run mode");
    } else {
        if(0 == lstat(PACKAGE_TARGET_LINK, &st)) {
            if(S_ISLNK(st.st_mode)) {
                log_message("Removing old %s symlink\n", PACKAGE_TARGET_LINK);

                /* Remember the current value for later; ignore error */
                readlink(PACKAGE_TARGET_LINK, previous_package_link_dir, PATH_MAX);

                if(0 != unlink(PACKAGE_TARGET_LINK)) {
                    log_error("Cannot unlink %s: %s", PACKAGE_TARGET_LINK, strerror(errno));
                    goto error;
                }
            } else {
                log_error("%s is expected to be missing or a symlink", PACKAGE_TARGET_LINK);
                goto error;
            }
        } else {
            if(ENOENT != errno) {
                log_error("%s: cannot stat", PACKAGE_TARGET_LINK);
                goto error;
            }
        }

        if(dir_exists(package_link_dir)) {
            log_message("Removing old link tree\n");
            RMRF_OR_ERROR("old package link", package_link_dir);
        }

        log_message("Moving link tree to %s\n", package_link_dir);
        if(0 != rename(temp_package_link_dir, package_link_dir)) {
            log_error("Cannot move temp symlink tree from %s to %s: %s", temp_package_link_dir, package_link_dir, strerror(errno));
            goto error;
        }

        log_message("Creating symlink from %s to %s\n", PACKAGE_TARGET_LINK, package_link_dir);
        if(0 != symlink(package_link_dir, PACKAGE_TARGET_LINK)) {
            log_error("Cannot create symlink from %s to %s: %s", PACKAGE_TARGET_LINK, package_link_dir, strerror(errno));
            goto error;
        }
    }

    /* === Copy hostclass file and host file to config_dir ====== */
    SNPRINTF_OR_ERROR(
        "hostclass configuration file",
        hostclass_file_name, PATH_MAX, "%s/hostclass.yml",
        options.config_dir
    );
    CP_OR_ERROR("hostclass configuration file",
        hostclass_file_tmpname,
        hostclass_file_name,
        0644
    );

    SNPRINTF_OR_ERROR(
        "host configuration file",
        host_file_name, PATH_MAX, "%s/host.yml",
        options.config_dir
    );
    CP_OR_ERROR("host configuration file",
        host_file_tmpname,
        host_file_name,
        0644
    );

    /* === Run configuration scripts ================================== */
    log_header("Processing package configuration scripts", failsafe_mode);

    MKPATH_OR_ERROR("config output directory", options.config_dir);

    exit_code = run_command(CONFIGURATE,
                            "--template-outdir", options.config_dir,
                            hostclass_file_tmpname,
                            host_file_tmpname,
                            NULL
    );
    if(0 != exit_code) {
        log_error("  Exit code from %s is %d", CONFIGURATE, exit_code);
        goto error;
    }

    /* === Run /etc/init.d/local_initd start ========================== */
    log_header("Starting services", failsafe_mode);
    if(options.dryrun) {
        log_info("Skipping in dry run mode");
    } else {
        exit_code = run_command(options.local_initd_file, "start", NULL);
        if(0 != exit_code) {
            log_error("  Could not run %s start", options.local_initd_file);
            goto error;
        }
    }

    /* === Cleanup ==================================================== */
    log_header("Cleanup", failsafe_mode);

    if(!failsafe_mode) {
        log_info("Removing package target directories from prior installations.");
        /* ignore errors */
        clean_previous_package_trees(package_target_dir,
                                     options.dryrun ? temp_package_link_dir : package_link_dir,
                                     previous_package_link_dir);
        if(options.dryrun) {
          log_info("Skipping package prune in dry run mode");
        }
        else if (prune_packages) {
          log_info("Removing unused packages from prior installations.");
          clean_previous_packages(hostclass_config.package_list,
                                  package_stow_dir);
        }
    } else {
        log_info("Not removing package target directories from prior installations in failsafe mode.");
    }

    /* TODO reboot if necessary */

    goto done;
 error:
    if(!failsafe_mode && try_failsafe) {
        if(hostclass_config.has_failsafe) {
            failsafe_mode = 1;
            log_message("\n!!! Falling back to failsafe configuration...\n");
            goto failsafe;
        } else {
            log_message("\n!!! Tried to enter failsafe mode, but the hostclass config is\n");
            log_message("!!! missing a failsafe mode definition.\n");
        }
    }
    exit_code = 1;
 done:
    if(host_file)
        fclose(host_file);
    host_file = NULL;
    if(host_file_tmpname[0] && !options.host_file)
        unlink(host_file_tmpname);

    if(hostclass_file)
        fclose(hostclass_file);
    hostclass_file = NULL;
    if(hostclass_file_tmpname[0] && !options.hostclass_file)
        unlink(hostclass_file_tmpname);
    free_hostclass_config(&hostclass_config);

    if(report_errors) {
        if(failsafe_mode) {
            if(exit_code != 0) {
                log_header("Failsafe roll failed", failsafe_mode);
                log_message("!!! An error occurred during both normal and failsafe mode.\n\n");
            } else {
                log_header("Failsafe roll succeeded", failsafe_mode);
                log_message("!!! An error occurred during normal roll.\n");
                log_message("!!! Failsafe mode completed successfully.\n\n");
                exit_code = 6;
            }
        } else {
            if(exit_code != 0) {
                log_header("Roll failed; failsafe mode not attempted", failsafe_mode);
                log_message("!!! An error occurred during roll.\n\n");
            } else {
                log_header("Roll succeeded", failsafe_mode);
                log_message("!!! Done.\n\n");
            }
        }
    }

    log_close();
    if(pid_file_fd >= 0)
        close_pid_file(pid_file_fd, options.pid_file);
    return exit_code;

    #undef SNPRINTF_OR_ERROR
    #undef MKPATH_OR_ERROR
    #undef RMRF_OR_ERROR
}
示例#2
0
// --------------------
// main
// --------------------
int main(int argc, char *argv[])
{
	int ret = 0;
	if (!open_log(get_log_filename()))
	{
		perror("Open log error");
		return 1;
	}

	write_log(1);

	if (argc != 3 && argc != 4)
	{
		usage();
		write_log(9);
		ret = 1;
		goto ARG_ERROR;
	}

	simple_list *host_list = read_file_to_list(argv[HOSTFILE_INDEX]);
	if (host_list == NULL)
	{
		if (errno == ENOENT)
			write_log(4, argv[HOSTFILE_INDEX]);
		else
			write_log(5, argv[HOSTFILE_INDEX]);
		ret = 2;
		goto OPEN_HOST_ERROR;
	}

	simple_list *sql_list = read_file_to_list(argv[SQLFILE_INDEX]);
	if (sql_list == NULL)
	{
		if (errno == ENOENT)
			write_log(4, argv[SQLFILE_INDEX]);
		else
			write_log(5, argv[SQLFILE_INDEX]);
		ret = 3;
		goto OPEN_SQL_ERROR;
	}

	if (argc == 4)
	{
		g_commit_per_execution = atoi(argv[COMMIT_PER_EXECUTION_INDEX]);
	}

	sql_connections *connections = sql_connections_new(host_list);
	if (connections)
	{
		do
		{
			if (sql_connections_execute(connections, sql_list) != 0)
			{
				sql_connections_execute_rollback(connections);
				ret = 5;
				break;
			}
			if (sql_connections_commit(connections, argv[2]) != 0)
			{
				sql_connections_commit_rollback(connections);
				ret = 6;
				break;
			}
		} while(0);
		sql_connections_delete(connections);
	}
	else
	{
		ret = 4;
	}

OPEN_SQL_ERROR:
	simple_list_delete_string(sql_list);
OPEN_HOST_ERROR:
	simple_list_delete_string(host_list);
ARG_ERROR:
	if (ret == 0)
		write_log(3);
	else
		write_log(8);
	close_log();
	return ret;
}