/* * click-install.cc -- configuration installer for Click kernel module * Eddie Kohler * * Copyright (c) 1999-2000 Massachusetts Institute of Technology * Copyright (c) 2000 Mazu Networks, Inc. * Copyright (c) 2002 International Computer Science Institute * Copyright (c) 2006 Regents of the University of California * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, subject to the conditions * listed in the Click LICENSE file. These conditions include: you must * preserve this copyright notice, and you cannot mention the copyright * holders in advertising related to the Software without their permission. * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This * notice is a summary of the Click LICENSE file; the license in that file is * legally binding. */ #include #include "common.hh" #include "routert.hh" #include "lexert.hh" #include #include #include #include #include #include "toolutils.hh" #include #include #include #include #include #include #include #include #if FOR_BSDMODULE # include # include #elif FOR_LINUXMODULE # include #endif #include #include #define HELP_OPT 300 #define VERSION_OPT 301 #define CLICKPATH_OPT 302 #define ROUTER_OPT 303 #define EXPRESSION_OPT 304 #define UNINSTALL_OPT 305 #define HOTSWAP_OPT 306 #define MAP_OPT 307 #define VERBOSE_OPT 308 #define THREADS_OPT 309 #define PRIVATE_OPT 310 #define PRIORITY_OPT 311 #define GREEDY_OPT 312 static Clp_Option options[] = { { "cabalistic", 0, PRIVATE_OPT, 0, Clp_Negate }, { "clickpath", 'C', CLICKPATH_OPT, Clp_ArgString, 0 }, { "expression", 'e', EXPRESSION_OPT, Clp_ArgString, 0 }, { "file", 'f', ROUTER_OPT, Clp_ArgString, 0 }, { "help", 0, HELP_OPT, 0, 0 }, { "hot-swap", 'h', HOTSWAP_OPT, 0, Clp_Negate }, { "hotswap", 'h', HOTSWAP_OPT, 0, Clp_Negate }, { "priority", 'n', PRIORITY_OPT, Clp_ArgInt, 0 }, #if FOR_LINUXMODULE { "map", 'm', MAP_OPT, 0, 0 }, { "private", 'p', PRIVATE_OPT, 0, Clp_Negate }, { "threads", 't', THREADS_OPT, Clp_ArgUnsigned, 0 }, { "greedy", 'G', GREEDY_OPT, 0, Clp_Negate }, #endif { "uninstall", 'u', UNINSTALL_OPT, 0, Clp_Negate }, { "verbose", 'V', VERBOSE_OPT, 0, Clp_Negate }, { "version", 'v', VERSION_OPT, 0, 0 }, }; static const char *program_name; #if FOR_LINUXMODULE static bool output_map; #endif static String::Initializer string_initializer; static String tmpdir; static String click_buildtool_prog; void short_usage() { fprintf(stderr, "Usage: %s [OPTION]... [ROUTERFILE]\n\ Try '%s --help' for more information.\n", program_name, program_name); } void usage() { printf("\ 'Click-install' installs a kernel Click configuration. It loads the Click\n\ kernel module, and any other necessary modules, as required.\n\ \n\ Usage: %s [OPTION]... [ROUTERFILE]\n\ \n\ Options:\n\ -f, --file FILE Read router configuration from FILE.\n\ -e, --expression EXPR Use EXPR as router configuration.\n\ -h, --hot-swap Hot-swap install new configuration.\n\ -u, --uninstall Uninstall Click from kernel, then reinstall.\n\ -n, --priority N Set kernel thread priority to N (lower is better).\n", program_name); #if FOR_LINUXMODULE printf("\ -p, --private Make /proc/click readable only by root.\n\ -t, --threads N Use N threads (multithreaded Click only).\n\ -G, --greedy Make Click thread take up an entire CPU.\n"); # if HAVE_LINUXMODULE_2_6 printf("\ -m, --map Print load map to the standard output.\n"); # endif #endif printf("\ -V, --verbose Print information about files installed.\n\ -C, --clickpath PATH Use PATH for CLICKPATH.\n\ --help Print this message and exit.\n\ -v, --version Print version number and exit.\n\ \n\ Report bugs to .\n"); } static void prepare_tmpdir(RouterT *r, ErrorHandler *errh) { ContextErrorHandler cerrh(errh, "While preparing to compile packages:"); BailErrorHandler berrh(&cerrh); // change to temporary directory tmpdir = click_mktmpdir(&berrh); assert(tmpdir); if (chdir(tmpdir.c_str()) < 0) berrh.fatal("cannot chdir to %s: %s", tmpdir.c_str(), strerror(errno)); // find compile program click_buildtool_prog = clickpath_find_file("click-buildtool", "bin", CLICK_BINDIR, &cerrh); assert(click_buildtool_prog); // look for .hh files if (r) { const Vector &archive = r->archive(); for (int i = 0; i < archive.size(); i++) if (archive[i].name.substring(-3) == ".hh") { String filename = archive[i].name; FILE *f = fopen(filename.c_str(), "w"); if (!f) cerrh.warning("%s: %s", filename.c_str(), strerror(errno)); else { fwrite(archive[i].data.data(), 1, archive[i].data.length(), f); fclose(f); } } } } static void compile_archive_packages(RouterT *r, HashMap &packages, ErrorHandler *errh) { Vector requirements = r->requirements(); // go over requirements for (int i = 0; i < requirements.size(); i++) { const String &req = requirements[i]; // skip if already have object file if (r->archive_index(req + OBJSUFFIX) >= 0 || packages[req] >= 0) continue; // look for source file, prepare temporary directory int source_ae = r->archive_index(req + CXXSUFFIX); if (source_ae < 0) source_ae = r->archive_index(req + ".cc"); if (source_ae < 0) continue; if (!tmpdir) prepare_tmpdir(r, errh); // found source file, so compile it ArchiveElement ae = r->archive(source_ae); errh->message("Compiling package %s from config archive", ae.name.c_str()); ContextErrorHandler cerrh (errh, "While compiling package '" + req + OBJSUFFIX "':"); // write .cc file String filename = req + "_.cc"; String source_text = ae.data; FILE *f = fopen(filename.c_str(), "w"); if (!f) cerrh.fatal("%s: %s", filename.c_str(), strerror(errno)); fwrite(source_text.data(), 1, source_text.length(), f); fclose(f); // run click-compile StringAccum compile_command; compile_command << click_buildtool_prog << " makepackage -C " << tmpdir << " -t " COMPILETARGET " " << req << " " << req << "_.cc 1>&2"; int compile_retval = system(compile_command.c_str()); if (compile_retval == 127) cerrh.fatal("could not run '%s'", compile_command.c_str()); else if (compile_retval < 0) cerrh.fatal("could not run '%s': %s", compile_command.c_str(), strerror(errno)); else if (compile_retval != 0) cerrh.fatal("'%s' failed", compile_command.c_str()); // grab object file and add to archive ArchiveElement obj_ae = init_archive_element(req + OBJSUFFIX, 0600); obj_ae.data = file_string(req + OBJSUFFIX, &cerrh); r->add_archive(obj_ae); } } static void install_module(const String &filename, const String &options, ErrorHandler *errh) { #if FOR_LINUXMODULE String cmdline = "/sbin/insmod "; if (output_map) cmdline += "-m "; cmdline += filename; if (options) cmdline += " " + options; int retval = system(cmdline.c_str()); if (retval != 0) errh->fatal("'%s' failed", cmdline.c_str()); #else String cmdline = "/sbin/kldload " + filename; assert(!options); int retval = system(cmdline.c_str()); if (retval != 0) errh->fatal("'%s' failed", cmdline.c_str()); #endif } static void install_required_packages(RouterT *r, HashMap &packages, HashMap &active_modules, ErrorHandler *errh) { // check for uncompiled archive packages and try to compile them compile_archive_packages(r, packages, errh); Vector requirements = r->requirements(); // go over requirements for (int i = 0; i < requirements.size(); i++) { String req = requirements[i]; // look for object in archive int obj_aei = r->archive_index(req + OBJSUFFIX); if (obj_aei >= 0 && packages[req] < 0) { // install archived objects. mark them with leading underscores. // may require renaming to avoid clashes in 'insmod' // choose module name String insmod_name = req + OBJSUFFIX; if (verbose) errh->message("Installing package %s (%s" OBJSUFFIX " from config archive)", insmod_name.c_str(), req.c_str()); // install module if (!tmpdir) prepare_tmpdir(0, errh); const ArchiveElement &ae = r->archive(obj_aei); String tmpnam = tmpdir + insmod_name; FILE *f = fopen(tmpnam.c_str(), "w"); if (!f) errh->fatal("%s: %s", tmpnam.c_str(), strerror(errno)); fwrite(ae.data.data(), 1, ae.data.length(), f); fclose(f); install_module(tmpnam, String(), errh); // cleanup packages.insert(req, 1); active_modules.insert(insmod_name, 1); } else if (packages[req] < 0) { // install required package from CLICKPATH String filename = req + OBJSUFFIX; String pathname = clickpath_find_file(filename, "lib", CLICK_LIBDIR); if (!pathname) { filename = req + ".o"; pathname = clickpath_find_file(filename, "lib", CLICK_LIBDIR); if (!pathname) errh->fatal("cannot find required package '%s" OBJSUFFIX "'\nin CLICKPATH or '%s'", req.c_str(), CLICK_LIBDIR); } // install module if (verbose) errh->message("Installing package %s (%s)", req.c_str(), pathname.c_str()); install_module(pathname, String(), errh); packages.insert(req, 1); active_modules.insert(filename, 1); } else { // package already loaded; note in 'active_modules' that we still need // it if (verbose) errh->message("Not installing package %s, version already exists", req.c_str()); String filename = req; if (active_modules[filename] < 0) filename = req + OBJSUFFIX; if (active_modules[filename] < 0) filename = req + ".o"; if (active_modules[filename] == 0) active_modules.insert(filename, 1); } } } int main(int argc, char **argv) { click_static_initialize(); CLICK_DEFAULT_PROVIDES; ErrorHandler *nop_errh = ErrorHandler::default_handler(); ErrorHandler *errh = new PrefixErrorHandler(nop_errh, "click-install: "); // read command line arguments Clp_Parser *clp = Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); program_name = Clp_ProgramName(clp); const char *router_file = 0; bool file_is_expr = false; bool uninstall = false; bool hotswap = false; int priority = -100; #if FOR_LINUXMODULE bool accessible = true; int threads = 1; bool greedy = false; output_map = false; #endif while (1) { int opt = Clp_Next(clp); switch (opt) { case HELP_OPT: usage(); exit(0); break; case VERSION_OPT: printf("click-install (Click) %s\n", CLICK_VERSION); printf("Click packages in %s, binaries in %s\n", CLICK_LIBDIR, CLICK_BINDIR); printf("Copyright (c) 1999-2000 Massachusetts Institute of Technology\n\ Copyright (c) 2000-2005 Mazu Networks, Inc.\n\ Copyright (c) 2002 International Computer Science Institute\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(0); break; case CLICKPATH_OPT: set_clickpath(clp->arg); break; case ROUTER_OPT: case EXPRESSION_OPT: case Clp_NotOption: if (router_file) { errh->error("router configuration specified twice"); goto bad_option; } router_file = clp->arg; file_is_expr = (opt == EXPRESSION_OPT); break; #if FOR_LINUXMODULE case THREADS_OPT: threads = clp->val.u; if (threads < 1) { errh->error("must have at least one thread"); goto bad_option; } break; case PRIVATE_OPT: accessible = clp->negated; break; case PRIORITY_OPT: priority = clp->val.i; break; case MAP_OPT: # if HAVE_LINUXMODULE_2_6 errh->warning("'%s' ignored on 2.6 kernels", Clp_CurOptionName(clp)); # else output_map = !clp->negated; # endif break; case GREEDY_OPT: greedy = !clp->negated; break; #endif case UNINSTALL_OPT: uninstall = !clp->negated; break; case HOTSWAP_OPT: hotswap = !clp->negated; break; case VERBOSE_OPT: verbose = !clp->negated; break; bad_option: case Clp_BadOption: short_usage(); exit(1); break; case Clp_Done: goto done; } } done: // check options if (hotswap && uninstall) errh->warning("'--hotswap' and '--uninstall' are mutually exclusive"); RouterT *r = read_router(router_file, file_is_expr, nop_errh); if (r) r->flatten(nop_errh); if (!r || errh->nerrors() > 0) exit(1); // pathnames of important Click files String clickfs_config = clickfs_prefix + String("/config"); String clickfs_hotconfig = clickfs_prefix + String("/hotconfig"); String clickfs_errors = clickfs_prefix + String("/errors"); String clickfs_packages = clickfs_prefix + String("/packages"); String clickfs_priority = clickfs_prefix + String("/priority"); // uninstall Click if requested if (uninstall) unload_click(errh); // install Click module if required if (access(clickfs_packages.c_str(), F_OK) < 0) { #if FOR_LINUXMODULE // find and install proclikefs.o StringMap modules(-1); if (read_active_modules(modules, errh) && modules["proclikefs"] < 0) { # if HAVE_LINUXMODULE_2_6 String proclikefs_o = clickpath_find_file("proclikefs.ko", "lib", CLICK_LIBDIR, errh); # else String proclikefs_o = clickpath_find_file("proclikefs.o", "lib", CLICK_LIBDIR, errh); # endif if (verbose) errh->message("Installing proclikefs (%s)", proclikefs_o.c_str()); install_module(proclikefs_o, String(), errh); } #endif // find loadable module #if FOR_BSDMODULE || (FOR_LINUXMODULE && HAVE_LINUXMODULE_2_6) String click_o = clickpath_find_file("click.ko", "lib", CLICK_LIBDIR, errh); #elif FOR_LINUXMODULE String click_o = clickpath_find_file("click.o", "lib", CLICK_LIBDIR, errh); #endif if (verbose) errh->message("Installing Click module (%s)", click_o.c_str()); // install it in the kernel #if FOR_LINUXMODULE String options; if (threads > 1) options += "threads=" + String(threads); if (greedy) options += " greedy=1"; if (!accessible) options += " accessible=0"; install_module(click_o, options, errh); #elif FOR_BSDMODULE install_module(click_o, String(), errh); #endif #if FOR_BSDMODULE || FOR_LINUXMODULE // make clickfs_prefix directory if required if (access(clickfs_prefix, F_OK) < 0 && errno == ENOENT) { if (mkdir(clickfs_prefix, 0777) < 0) errh->fatal("cannot make directory %s: %s", clickfs_prefix, strerror(errno)); } // mount Click file system if (verbose) errh->message("Mounting Click module at %s", clickfs_prefix); # if FOR_BSDMODULE int mount_retval = mount("click", clickfs_prefix, 0, 0); # else int mount_retval = mount("none", clickfs_prefix, "click", 0, 0); # endif if (mount_retval < 0 && (verbose || errno != EBUSY)) errh->error("cannot mount %s: %s", clickfs_prefix, strerror(errno)); #endif // check that all is well if (access(clickfs_packages.c_str(), F_OK) < 0) errh->fatal("cannot install Click module"); } else { #if FOR_LINUXMODULE if (threads > 1) errh->warning("Click module already installed, '--threads' ignored"); #endif } // find current packages HashMap active_modules(-1); HashMap packages(-1); read_active_modules(active_modules, errh); read_package_file(clickfs_packages, packages, errh); // install required packages install_required_packages(r, packages, active_modules, errh); // set priority if (priority > -100) { FILE *f = fopen(clickfs_priority.c_str(), "w"); if (!f) errh->fatal("%s: %s", clickfs_priority.c_str(), strerror(errno)); fprintf(f, "%d\n", priority); fclose(f); } // write flattened configuration to CLICKFS/config int exit_status = 0; { String config_place = (hotswap ? clickfs_hotconfig : clickfs_config); if (verbose) errh->message("Writing configuration to %s", config_place.c_str()); int fd = open(config_place.c_str(), O_WRONLY | O_TRUNC); if (fd < 0) errh->fatal("cannot install configuration: %s", strerror(errno)); // XXX include packages? String config = r->configuration_string(); int pos = 0; while (pos < config.length()) { ssize_t written = write(fd, config.data() + pos, config.length() - pos); if (written >= 0) pos += written; else if (errno != EAGAIN && errno != EINTR) errh->fatal("%s: %s", config_place.c_str(), strerror(errno)); } int retval = close(fd); if (retval < 0 && errno == EINVAL) exit_status = 2; else if (retval < 0) errh->error("%s: %s", config_place.c_str(), strerror(errno)); } // report errors { char buf[1024]; int fd = open(clickfs_errors.c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) errh->warning("%s: %s", clickfs_errors.c_str(), strerror(errno)); else { if (verbose) errh->message("Waiting for errors"); while (1) { struct timeval wait; wait.tv_sec = 0; wait.tv_usec = 50000; (void) select(0, 0, 0, 0, &wait); ssize_t got = read(fd, buf, 1024); if (got > 0) fwrite(buf, 1, got, stderr); else if (got == 0) break; else if (errno != EINTR && errno != EAGAIN) { errh->error("%s: %s", clickfs_errors.c_str(), strerror(errno)); break; } } close(fd); } } // remove unused packages remove_unneeded_packages(active_modules, packages, errh); if (verbose) errh->message("Done"); exit(exit_status); }