From: Eduard Bagdasaryan Date: Mon, 30 Apr 2018 16:57:53 +0000 (+0000) Subject: Do not abuse argv[0] to supply roles and IDs to SMP kids (#176) X-Git-Tag: SQUID_4_0_25~17 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c71ec3658f4eebfb557e5bc53aa1c54fd05aa3ae;p=thirdparty%2Fsquid.git Do not abuse argv[0] to supply roles and IDs to SMP kids (#176) Use a newly added "--kid role-ID" command line option instead. Just like argv[0], the new option is not meant for direct/human use. This change allows exec(3)-wrapping tools like Valgrind to work with SMP Squid: When launching kid processes, Valgrind does not pass Squid-formed argv[0] to kid processes, breaking old kid role and ID detection code. This change does not alter argv[0] of Squid processes. There is nothing wrong with Squid-formed argv[0] values for Squid kids. Also added a CommandLine class to support command line parsing without code duplication. Squid needs to handle the new --kid option way before the old mainParseOptions() handles the other options. The new class also encapsulates argv manipulations, reducing main.cc pollution. --- diff --git a/src/CommandLine.cc b/src/CommandLine.cc new file mode 100644 index 0000000000..69bd2a71e4 --- /dev/null +++ b/src/CommandLine.cc @@ -0,0 +1,169 @@ +/* + * Copyright (C) 1996-2018 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" + +#include "CommandLine.h" +#include "sbuf/SBuf.h" + +static void +ResetGetopt(const bool allowStderrWarnings) +{ + opterr = allowStderrWarnings; + // Resetting optind to zero instead of conventional '1' has an + // advantage, since it also resets getopt(3) global state. + // getopt(3) always skips argv[0], even if optind is zero + optind = 0; +} + +CommandLine::CommandLine(int argC, char *argV[], const char *shortRules, const RawLongOption *longRules): + argv_(), + shortOptions_(shortRules ? xstrdup(shortRules) : ""), + longOptions_() +{ + assert(argC > 0); // C++ main() requirement that makes our arg0() safe + assert(shortRules); + + /* copy argV items */ + argv_.reserve(argC+1); + for (int i = 0; i < argC; ++i) + argv_.push_back(xstrdup(argV[i])); + argv_.push_back(nullptr); // POSIX argv "must be terminated by a null pointer" + + /* copy grammar rules for the long options */ + if (longRules) { + for (auto longOption = longRules; longOption->name; ++longOption) + longOptions_.emplace_back(*longOption); + longOptions_.emplace_back(); + } +} + +CommandLine::CommandLine(const CommandLine &them): + CommandLine(them.argc(), them.argv(), them.shortOptions_, them.longOptions()) +{ +} + +CommandLine & +CommandLine::operator =(const CommandLine &them) +{ + // cannot just swap(*this, them): std::swap(T,T) may call this assignment op + CommandLine tmp(them); + std::swap(argv_, tmp.argv_); + std::swap(shortOptions_, tmp.shortOptions_); + std::swap(longOptions_, tmp.longOptions_); + return *this; +} + +CommandLine::~CommandLine() +{ + for (auto arg: argv_) + xfree(arg); + + xfree(shortOptions_); +} + +bool +CommandLine::hasOption(const int optIdToFind, const char **optValue) const +{ + ResetGetopt(false); // avoid duped warnings; forEachOption() will complain + int optId = 0; + while (nextOption(optId)) { + if (optId == optIdToFind) { + if (optValue) { + // do not need to copy the optarg string because it is a pointer into the original + // argv array (https://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html) + *optValue = optarg; + } + return true; + } + } + return false; +} + +void +CommandLine::forEachOption(Visitor visitor) const +{ + ResetGetopt(true); + int optId = 0; + while (nextOption(optId)) + visitor(optId, optarg); +} + +/// extracts the next option (if any) +/// \returns whether the option was extracted +/// throws on unknown option or missing required argument +bool +CommandLine::nextOption(int &optId) const +{ + optId = getopt_long(argc(), argv(), shortOptions_, longOptions(), nullptr); + if ((optId == ':' && shortOptions_[0] == ':') || optId == '?') { + assert(optind > 0 && static_cast(optind) < argv_.size()); + SBuf errMsg; + errMsg.Printf("'%s': %s", argv_[optind - 1], optId == '?' ? + "unrecognized option or missing required argument" : "missing required argument"); + throw TexcHere(errMsg); + } + return optId != -1; +} + +void +CommandLine::resetArg0(const char *programName) +{ + assert(programName); + xfree(argv_[0]); + argv_[0] = xstrdup(programName); +} + +void +CommandLine::pushFrontOption(const char *name, const char *value) +{ + assert(name); + argv_.insert(argv_.begin() + 1, xstrdup(name)); + if (value) + argv_.insert(argv_.begin() + 2, xstrdup(value)); +} + +LongOption::LongOption() : + option({nullptr, 0, nullptr, 0}) +{ +} + +LongOption::LongOption(const RawLongOption &opt) : + option({nullptr, 0, nullptr, 0}) +{ + copy(opt); +} + +LongOption::LongOption(const LongOption &opt): + LongOption(static_cast(opt)) +{ +} + +LongOption::~LongOption() +{ + xfree(name); +} + +LongOption & +LongOption::operator =(const LongOption &opt) +{ + if (this != &opt) + copy(static_cast(opt)); + return *this; +} + +void +LongOption::copy(const RawLongOption &opt) +{ + xfree(name); + name = opt.name ? xstrdup(opt.name) : nullptr; + has_arg = opt.has_arg; + flag = opt.flag; + val = opt.val; +} + diff --git a/src/CommandLine.h b/src/CommandLine.h new file mode 100644 index 0000000000..0371dc792f --- /dev/null +++ b/src/CommandLine.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 1996-2018 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_COMMANDLINE_H +#define SQUID_COMMANDLINE_H + +#if HAVE_GETOPT_H +#include +#endif +#include + +typedef struct option RawLongOption; + +/// A struct option C++ wrapper, helps with option::name copying/freeing. +class LongOption : public RawLongOption +{ +public: + LongOption(); + explicit LongOption(const RawLongOption &); + LongOption(const LongOption&); + LongOption &operator =(const LongOption &); + ~LongOption(); + +private: + void copy(const RawLongOption &); +}; + +/// Manages arguments passed to a program (i.e., main(argc, argv) parameters). +class CommandLine +{ +public: + /// expects main() input plus getopt_long(3) grammar rules for parsing argv + CommandLine(int argc, char *argv[], const char *shortRules, const RawLongOption *longRules); + CommandLine(const CommandLine &them); + ~CommandLine(); + + CommandLine &operator =(const CommandLine &); + + /// \returns whether the option with optId identifier is present + /// When returning true, sets non-nil optValue to the found option's value. + /// For letter options (-x) and their --long synonyms, the letter is the ID. + /// For long-only --options, the ID is the configured options::val value. + bool hasOption(const int optId, const char **optValue = nullptr) const; + + /// A callback function for forEachOption(); receives parsed options. + /// Must not call pushFrontOption(), hasOption() or forEachOption() -- getopt(3) uses globals! + typedef void Visitor(const int optId, const char *optValue); + + /// calls Visitor for each of the configured command line option + void forEachOption(Visitor) const; + + /// \returns argv[0], which is usually a program "name" + const char *arg0() const { return argv_[0]; } + + /// \returns main()'s argc, which is traditionally missing the last/nil item + int argc() const { return static_cast(argv_.size()) - 1; } + + /// \returns main()'s argv[] which is traditionally const-wrong + char **argv() const { return const_cast(argv_.data()); } + + /// replaces argv[0] with the new value + void resetArg0(const char *programName); + + /// inserts a (possibly duplicated) option at the beginning of options (just after argv[0]) + void pushFrontOption(const char *name, const char *value = nullptr); + +private: + const RawLongOption *longOptions() const { return longOptions_.size() ? longOptions_.data() : nullptr; } + bool nextOption(int &optId) const; + + /// raw main() parameters, including argv[0] and a nil argv[argc] + std::vector argv_; + + /* getopt_long() grammar rules */ + const char *shortOptions_; ///< single-dash, single-letter (-x) option rules + std::vector longOptions_; ///< long --option rules +}; + +#endif /* SQUID_COMMANDLINE_H */ + diff --git a/src/Makefile.am b/src/Makefile.am index 30aa0ef702..acc0520089 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -250,6 +250,8 @@ squid_SOURCES = \ clientStreamForward.h \ CollapsedForwarding.cc \ CollapsedForwarding.h \ + CommandLine.cc \ + CommandLine.h \ CompletionDispatcher.cc \ CompletionDispatcher.h \ CommRead.h \ diff --git a/src/cache_cf.cc b/src/cache_cf.cc index ad91538f24..66f23c016e 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -338,7 +338,7 @@ static void ProcessMacros(char*& line, int& len) { SubstituteMacro(line, len, "${service_name}", service_name.c_str()); - SubstituteMacro(line, len, "${process_name}", TheKidName); + SubstituteMacro(line, len, "${process_name}", TheKidName.c_str()); SubstituteMacro(line, len, "${process_number}", xitoa(KidIdentifier)); } diff --git a/src/ipc/Kid.cc b/src/ipc/Kid.cc index 0a41674325..439199836c 100644 --- a/src/ipc/Kid.cc +++ b/src/ipc/Kid.cc @@ -20,22 +20,13 @@ int TheProcessKind = pkOther; -Kid::Kid(): - badFailures(0), - pid(-1), - startTime(0), - isRunning(false), - status(0) +Kid::Kid() { } -Kid::Kid(const String& kid_name): - theName(kid_name), - badFailures(0), - pid(-1), - startTime(0), - isRunning(false), - status(0) +Kid::Kid(const char *aRole, const int anId): + processRole(aRole), + processId(anId) { } @@ -77,20 +68,20 @@ Kid::reportStopped() const if (calledExit()) { syslog(LOG_NOTICE, "Squid Parent: %s process %d exited with status %d", - theName.termedBuf(), pid, exitStatus()); + gist().c_str(), pid, exitStatus()); } else if (signaled()) { syslog(LOG_NOTICE, "Squid Parent: %s process %d exited due to signal %d with status %d", - theName.termedBuf(), pid, termSignal(), exitStatus()); + gist().c_str(), pid, termSignal(), exitStatus()); } else { syslog(LOG_NOTICE, "Squid Parent: %s process %d exited", - theName.termedBuf(), pid); + gist().c_str(), pid); } if (hopeless() && Config.hopelessKidRevivalDelay) { syslog(LOG_NOTICE, "Squid Parent: %s process %d will not be restarted for %ld " "seconds due to repeated, frequent failures", - theName.termedBuf(), + gist().c_str(), pid, static_cast(Config.hopelessKidRevivalDelay)); } @@ -170,9 +161,19 @@ bool Kid::signaled(int sgnl) const } /// returns kid name -const String& Kid::name() const +SBuf Kid::processName() const { - return theName; + SBuf name("("); + name.append(gist()); + name.append(")"); + return name; +} + +SBuf Kid::gist() const +{ + SBuf name(processRole); + name.appendf("-%d", processId); + return name; } time_t diff --git a/src/ipc/Kid.h b/src/ipc/Kid.h index 0eff062cda..d174c84a64 100644 --- a/src/ipc/Kid.h +++ b/src/ipc/Kid.h @@ -27,7 +27,7 @@ public: public: Kid(); - Kid(const String& kid_name); + Kid(const char *role, const int id); /// called when this kid got started, records PID void start(pid_t cpid); @@ -74,22 +74,26 @@ public: /// whether the process was terminated by a given signal bool signaled(int sgnl) const; - /// returns kid name - const String& name() const; + /// \returns kid's role and ID formatted for use as a process name + SBuf processName() const; + + /// \returns kid's role and ID summary; usable as a --kid parameter value + SBuf gist() const; private: void reportStopped() const; // Information preserved across restarts - String theName; ///< process name - int badFailures; ///< number of "repeated frequent" failures + SBuf processRole; + int processId = 0; + int badFailures = 0; ///< number of "repeated frequent" failures // Information specific to a running or stopped kid - pid_t pid; ///< current (for a running kid) or last (for stopped kid) PID - time_t startTime; ///< last start time + pid_t pid = -1; ///< current (for a running kid) or last (for stopped kid) PID + time_t startTime = 0; ///< last start time time_t stopTime = 0; ///< last termination time - bool isRunning; ///< whether the kid is assumed to be alive - PidStatus status; ///< exit status of a stopped kid + bool isRunning = false; ///< whether the kid is assumed to be alive + PidStatus status = 0; ///< exit status of a stopped kid }; // TODO: processes may not be kids; is there a better place to put this? diff --git a/src/ipc/Kids.cc b/src/ipc/Kids.cc index 6911414a19..249df11d6a 100644 --- a/src/ipc/Kids.cc +++ b/src/ipc/Kids.cc @@ -16,7 +16,7 @@ #include "tools.h" Kids TheKids; -KidName TheKidName; +SBuf TheKidName; Kids::Kids() { @@ -29,25 +29,16 @@ void Kids::init() storage.reserve(NumberOfKids()); - char kid_name[32]; - - // add Kid records for all workers - for (int i = 0; i < Config.workers; ++i) { - snprintf(kid_name, sizeof(kid_name), "(squid-%d)", (int)(storage.size()+1)); - storage.push_back(Kid(kid_name)); - } + for (int i = 0; i < Config.workers; ++i) + storage.emplace_back("squid", storage.size() + 1); // add Kid records for all disk processes - for (int i = 0; i < Config.cacheSwap.n_strands; ++i) { - snprintf(kid_name, sizeof(kid_name), "(squid-disk-%d)", (int)(storage.size()+1)); - storage.push_back(Kid(kid_name)); - } + for (int i = 0; i < Config.cacheSwap.n_strands; ++i) + storage.emplace_back("squid-disk", storage.size() + 1); // if coordination is needed, add a Kid record for Coordinator - if (storage.size() > 1) { - snprintf(kid_name, sizeof(kid_name), "(squid-coord-%d)", (int)(storage.size()+1)); - storage.push_back(Kid(kid_name)); - } + if (storage.size() > 1) + storage.emplace_back("squid-coord", storage.size() + 1); Must(storage.size() == static_cast(NumberOfKids())); } diff --git a/src/ipc/Kids.h b/src/ipc/Kids.h index 89f49ff0b7..03b12b1fe9 100644 --- a/src/ipc/Kids.h +++ b/src/ipc/Kids.h @@ -64,8 +64,7 @@ private: extern Kids TheKids; ///< All kids being maintained -typedef char KidName[64]; ///< Squid process name (e.g., "squid-coord") -extern KidName TheKidName; ///< current Squid process name +extern SBuf TheKidName; ///< current Squid process name (e.g., "squid-coord") #endif /* SQUID_IPC_KIDS_H */ diff --git a/src/main.cc b/src/main.cc index bb7ca6d00e..0e17be587c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -24,6 +24,7 @@ #include "client_db.h" #include "client_side.h" #include "comm.h" +#include "CommandLine.h" #include "ConfigParser.h" #include "CpuAffinity.h" #include "DiskIO/DiskIOModule.h" @@ -56,6 +57,7 @@ #include "mime.h" #include "neighbors.h" #include "parser/Tokenizer.h" +#include "Parsing.h" #include "pconn.h" #include "peer_sourcehash.h" #include "peer_userhash.h" @@ -168,11 +170,11 @@ static void mainReconfigureStart(void); static void mainReconfigureFinish(void*); static void mainInitialize(void); static void usage(void); -static void mainParseOptions(int argc, char *argv[]); +static void mainHandleCommandLineOption(const int optId, const char *optValue); static void sendSignal(void); static void serverConnectionsOpen(void); static void serverConnectionsClose(void); -static void watch_child(char **); +static void watch_child(const CommandLine &); static void setEffectiveUser(void); static void SquidShutdown(void); static void mainSetCwd(void); @@ -393,6 +395,9 @@ usage(void) " -N Master process runs in foreground and is a worker. No kids.\n" " --foreground\n" " Master process runs in foreground and creates worker kids.\n" + " --kid role-ID\n" + " Play a given SMP kid process role, with a given ID. Do not use\n" + " this option. It is meant for the master process use only.\n" #if USE_WIN32_SERVICE " -O options\n" " Set Windows Service Command line options in Registry.\n" @@ -402,311 +407,320 @@ usage(void) " -X Force full debugging.\n" " -Y Only return UDP_HIT or UDP_MISS_NOFETCH during fast reload.\n", APP_SHORTNAME, CACHE_HTTP_PORT, DefaultConfigFile, CACHE_ICP_PORT); - exit(1); + exit(EXIT_FAILURE); } -/** - * Parse the parameters received via command line interface. - * - * \param argc Number of options received on command line - * \param argv List of parameters received on command line - */ -static void -mainParseOptions(int argc, char *argv[]) -{ - int optIndex = 0; +/// CommandLine option IDs for --long options that lack a short (-x) equivalent +enum { + // The absolute values do not matter except that the following values should + // not be used: Values below 2 are for special getopt_long(3) use cases, and + // values in the [33,126] range are reserved for short options (-x). + optForeground = 2, + optKid +}; - // short options - const char *shortOpStr = +// short options +// TODO: consider prefixing with ':' for better logging +// (distinguish missing required argument cases) +static const char *shortOpStr = #if USE_WIN32_SERVICE - "O:Vir" + "O:Vir" #endif - "CDFNRSYXa:d:f:hk:m::n:sl:u:vz?"; - - // long options - static struct option squidOptions[] = { - {"foreground", no_argument, 0, 1 }, - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, - {0, 0, 0, 0} - }; - - int c; - while ((c = getopt_long(argc, argv, shortOpStr, squidOptions, &optIndex)) != -1) { - - switch (c) { + "CDFNRSYXa:d:f:hk:m::n:sl:u:vz?"; - case 'C': - /** \par C - * Unset/disabel global option for catchign signals. opt_catch_signals */ - opt_catch_signals = 0; - break; +// long options +static struct option squidOptions[] = { + {"foreground", no_argument, 0, optForeground}, + {"kid", required_argument, 0, optKid}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} +}; - case 'D': - /** \par D - * OBSOLETE: WAS: override to prevent optional startup DNS tests. */ - debugs(1,DBG_CRITICAL, "WARNING: -D command-line option is obsolete."); - break; +// handle a command line parameter +static void +mainHandleCommandLineOption(const int optId, const char *optValue) +{ + switch (optId) { + + case 'C': + /** \par C + * Unset/disabel global option for catchign signals. opt_catch_signals */ + opt_catch_signals = 0; + break; + + case 'D': + /** \par D + * OBSOLETE: WAS: override to prevent optional startup DNS tests. */ + debugs(1,DBG_CRITICAL, "WARNING: -D command-line option is obsolete."); + break; + + case 'F': + /** \par F + * Set global option for foreground rebuild. opt_foreground_rebuild */ + opt_foreground_rebuild = 1; + break; + + case 'N': + /** \par N + * Set global option for 'no_daemon' mode. opt_no_daemon */ + opt_no_daemon = 1; + break; - case 'F': - /** \par F - * Set global option for foreground rebuild. opt_foreground_rebuild */ - opt_foreground_rebuild = 1; - break; +#if USE_WIN32_SERVICE - case 'N': - /** \par N - * Set global option for 'no_daemon' mode. opt_no_daemon */ - opt_no_daemon = 1; - break; + case 'O': + /** \par O + * Set global option. opt_command_lin and WIN32_Command_Line */ + opt_command_line = 1; + WIN32_Command_Line = xstrdup(optValue); + break; +#endif + + case 'R': + /** \par R + * Unset/disable global option opt_reuseaddr */ + opt_reuseaddr = 0; + break; + + case 'S': + /** \par S + * Set global option opt_store_doublecheck */ + opt_store_doublecheck = 1; + break; + + case 'X': + /** \par X + * Force full debugging */ + Debug::parseOptions("rotate=0 ALL,9"); + Debug::override_X = 1; + sigusr2_handle(SIGUSR2); + break; + + case 'Y': + /** \par Y + * Set global option opt_reload_hit_only */ + opt_reload_hit_only = 1; + break; #if USE_WIN32_SERVICE - case 'O': - /** \par O - * Set global option. opt_command_lin and WIN32_Command_Line */ - opt_command_line = 1; - WIN32_Command_Line = xstrdup(optarg); - break; -#endif - - case 'R': - /** \par R - * Unset/disable global option opt_reuseaddr */ - opt_reuseaddr = 0; - break; - - case 'S': - /** \par S - * Set global option opt_store_doublecheck */ - opt_store_doublecheck = 1; - break; - - case 'X': - /** \par X - * Force full debugging */ - Debug::parseOptions("rotate=0 ALL,9"); - Debug::override_X = 1; - sigusr2_handle(SIGUSR2); - break; - - case 'Y': - /** \par Y - * Set global option opt_reload_hit_only */ - opt_reload_hit_only = 1; - break; + case 'i': + /** \par i + * Set global option opt_install_service (to TRUE) */ + opt_install_service = TRUE; + break; +#endif -#if USE_WIN32_SERVICE + case 'a': + { + /** \par a + * Add optional HTTP port as given following the option */ + char *port = xstrdup(optValue); + // use a copy to avoid optValue modification + add_http_port(port); + xfree(port); + break; + } - case 'i': - /** \par i - * Set global option opt_install_service (to TRUE) */ - opt_install_service = TRUE; - break; -#endif - - case 'a': - /** \par a - * Add optional HTTP port as given following the option */ - add_http_port(optarg); - break; - - case 'd': - /** \par d - * Set global option Debug::log_stderr to the number given following the option */ - Debug::log_stderr = atoi(optarg); - break; - - case 'f': - /** \par f - * Load the file given instead of the default squid.conf. */ - xfree(ConfigFile); - ConfigFile = xstrdup(optarg); - break; - - case 'k': - /** \par k - * Run the administrative action given following the option */ - - /** \li When it is missing or an unknown option display the usage help. */ - if (!optarg || strlen(optarg) < 1) - usage(); - - else if (!strncmp(optarg, "reconfigure", strlen(optarg))) - /** \li On reconfigure send SIGHUP. */ - opt_send_signal = SIGHUP; - else if (!strncmp(optarg, "rotate", strlen(optarg))) - /** \li On rotate send SIGQUIT or SIGUSR1. */ + case 'd': + /** \par d + * Set global option Debug::log_stderr to the number given following the option */ + Debug::log_stderr = xatoi(optValue); + break; + + case 'f': + /** \par f + * Load the file given instead of the default squid.conf. */ + xfree(ConfigFile); + ConfigFile = xstrdup(optValue); + break; + + case 'k': + /** \par k + * Run the administrative action given following the option */ + + /** \li When it is missing or an unknown option display the usage help. */ + if (!optValue || strlen(optValue) < 1) + usage(); + + else if (!strncmp(optValue, "reconfigure", strlen(optValue))) + /** \li On reconfigure send SIGHUP. */ + opt_send_signal = SIGHUP; + else if (!strncmp(optValue, "rotate", strlen(optValue))) + /** \li On rotate send SIGQUIT or SIGUSR1. */ #if defined(_SQUID_LINUX_THREADS_) - opt_send_signal = SIGQUIT; + opt_send_signal = SIGQUIT; #else - opt_send_signal = SIGUSR1; + opt_send_signal = SIGUSR1; #endif - else if (!strncmp(optarg, "debug", strlen(optarg))) - /** \li On debug send SIGTRAP or SIGUSR2. */ + else if (!strncmp(optValue, "debug", strlen(optValue))) + /** \li On debug send SIGTRAP or SIGUSR2. */ #if defined(_SQUID_LINUX_THREADS_) - opt_send_signal = SIGTRAP; + opt_send_signal = SIGTRAP; #else - opt_send_signal = SIGUSR2; + opt_send_signal = SIGUSR2; #endif - else if (!strncmp(optarg, "shutdown", strlen(optarg))) - /** \li On shutdown send SIGTERM. */ - opt_send_signal = SIGTERM; - else if (!strncmp(optarg, "interrupt", strlen(optarg))) - /** \li On interrupt send SIGINT. */ - opt_send_signal = SIGINT; - else if (!strncmp(optarg, "kill", strlen(optarg))) - /** \li On kill send SIGKILL. */ - opt_send_signal = SIGKILL; + else if (!strncmp(optValue, "shutdown", strlen(optValue))) + /** \li On shutdown send SIGTERM. */ + opt_send_signal = SIGTERM; + else if (!strncmp(optValue, "interrupt", strlen(optValue))) + /** \li On interrupt send SIGINT. */ + opt_send_signal = SIGINT; + else if (!strncmp(optValue, "kill", strlen(optValue))) + /** \li On kill send SIGKILL. */ + opt_send_signal = SIGKILL; #ifdef SIGTTIN - else if (!strncmp(optarg, "restart", strlen(optarg))) - /** \li On restart send SIGTTIN. (exit and restart by parent) */ - opt_send_signal = SIGTTIN; + else if (!strncmp(optValue, "restart", strlen(optValue))) + /** \li On restart send SIGTTIN. (exit and restart by parent) */ + opt_send_signal = SIGTTIN; #endif - else if (!strncmp(optarg, "check", strlen(optarg))) - /** \li On check send 0 / SIGNULL. */ - opt_send_signal = 0; /* SIGNULL */ - else if (!strncmp(optarg, "parse", strlen(optarg))) - /** \li On parse set global flag to re-parse the config file only. */ - opt_parse_cfg_only = 1; - else - usage(); + else if (!strncmp(optValue, "check", strlen(optValue))) + /** \li On check send 0 / SIGNULL. */ + opt_send_signal = 0; /* SIGNULL */ + else if (!strncmp(optValue, "parse", strlen(optValue))) + /** \li On parse set global flag to re-parse the config file only. */ + opt_parse_cfg_only = 1; + else + usage(); - break; + break; - case 'm': - /** \par m - * Set global malloc_debug_level to the value given following the option. - * if none is given it toggles the xmalloc_trace option on/off */ - if (optarg) { + case 'm': + /** \par m + * Set global malloc_debug_level to the value given following the option. + * if none is given it toggles the xmalloc_trace option on/off */ + if (optValue) { #if MALLOC_DBG - malloc_debug_level = atoi(optarg); + malloc_debug_level = xatoi(optValue); #else - fatal("Need to add -DMALLOC_DBG when compiling to use -mX option"); + fatal("Need to add -DMALLOC_DBG when compiling to use -mX option"); #endif - } - break; - - case 'n': - /** \par n - * Set global option opt_signal_service (to true). - * Stores the additional parameter given in global service_name */ - if (optarg && *optarg != '\0') { - const SBuf t(optarg); - ::Parser::Tokenizer tok(t); - const CharacterSet chr = CharacterSet::ALPHA+CharacterSet::DIGIT; - if (!tok.prefix(service_name, chr)) - fatalf("Expected alphanumeric service name for the -n option but got: %s", optarg); - if (!tok.atEnd()) - fatalf("Garbage after alphanumeric service name in the -n option value: %s", optarg); - if (service_name.length() > 32) - fatalf("Service name (-n option) must be limited to 32 characters but got %u", service_name.length()); - opt_signal_service = true; - } else { - fatal("A service name is required for the -n option"); - } - break; + } + break; + + case 'n': + /** \par n + * Set global option opt_signal_service (to true). + * Stores the additional parameter given in global service_name */ + if (optValue && *optValue != '\0') { + const SBuf t(optValue); + ::Parser::Tokenizer tok(t); + const CharacterSet chr = CharacterSet::ALPHA+CharacterSet::DIGIT; + if (!tok.prefix(service_name, chr)) + fatalf("Expected alphanumeric service name for the -n option but got: %s", optValue); + if (!tok.atEnd()) + fatalf("Garbage after alphanumeric service name in the -n option value: %s", optValue); + if (service_name.length() > 32) + fatalf("Service name (-n option) must be limited to 32 characters but got %u", service_name.length()); + opt_signal_service = true; + } else { + fatal("A service name is required for the -n option"); + } + break; #if USE_WIN32_SERVICE - case 'r': - /** \par r - * Set global option opt_remove_service (to TRUE) */ - opt_remove_service = TRUE; + case 'r': + /** \par r + * Set global option opt_remove_service (to TRUE) */ + opt_remove_service = TRUE; - break; + break; #endif - case 'l': - /** \par l - * Stores the syslog facility name in global opt_syslog_facility - * then performs actions for -s option. */ - xfree(opt_syslog_facility); // ignore any previous options sent - opt_syslog_facility = xstrdup(optarg); + case 'l': + /** \par l + * Stores the syslog facility name in global opt_syslog_facility + * then performs actions for -s option. */ + xfree(opt_syslog_facility); // ignore any previous options sent + opt_syslog_facility = xstrdup(optValue); - case 's': - /** \par s - * Initialize the syslog for output */ + case 's': + /** \par s + * Initialize the syslog for output */ #if HAVE_SYSLOG - _db_set_syslog(opt_syslog_facility); + _db_set_syslog(opt_syslog_facility); - break; + break; #else - fatal("Logging to syslog not available on this platform"); + fatal("Logging to syslog not available on this platform"); - /* NOTREACHED */ + /* NOTREACHED */ #endif - case 'u': - /** \par u - * Store the ICP port number given in global option icpPortNumOverride - * ensuring its a positive number. */ - icpPortNumOverride = atoi(optarg); + case 'u': + /** \par u + * Store the ICP port number given in global option icpPortNumOverride + * ensuring its a positive number. */ + icpPortNumOverride = atoi(optValue); - if (icpPortNumOverride < 0) - icpPortNumOverride = 0; + if (icpPortNumOverride < 0) + icpPortNumOverride = 0; - break; + break; - case 'v': - /** \par v - * Display squid version and build information. Then exit. */ - printf("Squid Cache: Version %s\n" ,version_string); - printf("Service Name: " SQUIDSBUFPH "\n", SQUIDSBUFPRINT(service_name)); - if (strlen(SQUID_BUILD_INFO)) - printf("%s\n",SQUID_BUILD_INFO); + case 'v': + /** \par v + * Display squid version and build information. Then exit. */ + printf("Squid Cache: Version %s\n",version_string); + printf("Service Name: " SQUIDSBUFPH "\n", SQUIDSBUFPRINT(service_name)); + if (strlen(SQUID_BUILD_INFO)) + printf("%s\n",SQUID_BUILD_INFO); #if USE_OPENSSL - printf("\nThis binary uses %s. ", SSLeay_version(SSLEAY_VERSION)); - printf("For legal restrictions on distribution see https://www.openssl.org/source/license.html\n\n"); + printf("\nThis binary uses %s. ", SSLeay_version(SSLEAY_VERSION)); + printf("For legal restrictions on distribution see https://www.openssl.org/source/license.html\n\n"); #endif - printf( "configure options: %s\n", SQUID_CONFIGURE_OPTIONS); + printf( "configure options: %s\n", SQUID_CONFIGURE_OPTIONS); #if USE_WIN32_SERVICE - printf("Compiled as Windows System Service.\n"); + printf("Compiled as Windows System Service.\n"); #endif - exit(0); + exit(EXIT_SUCCESS); - /* NOTREACHED */ + /* NOTREACHED */ - case 'z': - /** \par z - * Set global option Debug::log_stderr and opt_create_swap_dirs */ - Debug::log_stderr = 1; - opt_create_swap_dirs = 1; - break; + case 'z': + /** \par z + * Set global option Debug::log_stderr and opt_create_swap_dirs */ + Debug::log_stderr = 1; + opt_create_swap_dirs = 1; + break; - case 1: - /** \par --foreground - * Set global option opt_foreground */ - opt_foreground = 1; - break; + case optForeground: + /** \par --foreground + * Set global option opt_foreground */ + opt_foreground = 1; + break; - case 'h': + case optKid: + // already processed in ConfigureCurrentKid() + break; - case '?': + case 'h': - default: - /** \par h,?, or unknown - * \copydoc usage() */ - usage(); + case '?': - break; - } + default: + /** \par h,?, or unknown + * \copydoc usage() */ + usage(); + break; } } @@ -1408,26 +1422,27 @@ SquidMainSafe(int argc, char **argv) /// computes name and ID for the current kid process static void -ConfigureCurrentKid(const char *processName) +ConfigureCurrentKid(const CommandLine &cmdLine) { - // kids are marked with parenthesis around their process names - if (processName && processName[0] == '(') { - if (const char *idStart = strrchr(processName, '-')) { - KidIdentifier = atoi(idStart + 1); - const size_t nameLen = idStart - (processName + 1); - assert(nameLen < sizeof(TheKidName)); - xstrncpy(TheKidName, processName + 1, nameLen + 1); - if (!strcmp(TheKidName, "squid-coord")) - TheProcessKind = pkCoordinator; - else if (!strcmp(TheKidName, "squid")) - TheProcessKind = pkWorker; - else if (!strcmp(TheKidName, "squid-disk")) - TheProcessKind = pkDisker; - else - TheProcessKind = pkOther; // including coordinator - } + const char *kidParams = nullptr; + if (cmdLine.hasOption(optKid, &kidParams)) { + SBuf processName(kidParams); + SBuf kidId; + Parser::Tokenizer tok(processName); + tok.suffix(kidId, CharacterSet::DIGIT); + KidIdentifier = xatoi(kidId.c_str()); + tok.skipSuffix(SBuf("-")); + TheKidName = tok.remaining(); + if (TheKidName.cmp("squid-coord") == 0) + TheProcessKind = pkCoordinator; + else if (TheKidName.cmp("squid") == 0) + TheProcessKind = pkWorker; + else if (TheKidName.cmp("squid-disk") == 0) + TheProcessKind = pkDisker; + else + TheProcessKind = pkOther; // including coordinator } else { - xstrncpy(TheKidName, APP_SHORTNAME, sizeof(TheKidName)); + TheKidName.assign(APP_SHORTNAME); KidIdentifier = 0; } } @@ -1441,7 +1456,9 @@ static void StartUsingConfig() int SquidMain(int argc, char **argv) { - ConfigureCurrentKid(argv[0]); + const CommandLine cmdLine(argc, argv, shortOpStr, squidOptions); + + ConfigureCurrentKid(cmdLine); Debug::parseOptions(NULL); @@ -1488,7 +1505,7 @@ SquidMain(int argc, char **argv) #endif - mainParseOptions(argc, argv); + cmdLine.forEachOption(mainHandleCommandLineOption); if (opt_foreground && opt_no_daemon) { debugs(1, DBG_CRITICAL, "WARNING: --foreground command-line option has no effect with -N."); @@ -1604,7 +1621,7 @@ SquidMain(int argc, char **argv) if (IamMasterProcess()) { if (InDaemonMode()) { - watch_child(argv); + watch_child(cmdLine); // NOTREACHED } else { Instance::WriteOurPid(); @@ -1877,10 +1894,9 @@ masterExit() #endif /* !_SQUID_WINDOWS_ */ static void -watch_child(char *argv[]) +watch_child(const CommandLine &masterCommand) { #if !_SQUID_WINDOWS_ - char *prog; pid_t pid; #ifdef TIOCNOTTY @@ -1984,23 +2000,28 @@ watch_child(char *argv[]) continue; if (!mainStartScriptCalled) { - mainStartScript(argv[0]); + mainStartScript(masterCommand.arg0()); mainStartScriptCalled = true; } + // These are only needed by the forked child below, but let's keep + // them out of that "no man's land" between fork() and execvp(). + auto kidCommand = masterCommand; + kidCommand.resetArg0(kid.processName().c_str()); + assert(!kidCommand.hasOption(optKid)); + kidCommand.pushFrontOption("--kid", kid.gist().c_str()); + if ((pid = fork()) == 0) { /* child */ openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); - prog = argv[0]; - argv[0] = const_cast(kid.name().termedBuf()); - execvp(prog, argv); + (void)execvp(masterCommand.arg0(), kidCommand.argv()); int xerrno = errno; syslog(LOG_ALERT, "execvp failed: %s", xstrerr(xerrno)); } kid.start(pid); syslog(LOG_NOTICE, "Squid Parent: %s process %d started", - kid.name().termedBuf(), pid); + kid.processName().c_str(), pid); } /* parent */ diff --git a/src/squid.8.in b/src/squid.8.in index 5665833e2e..b760c95fe6 100644 --- a/src/squid.8.in +++ b/src/squid.8.in @@ -124,6 +124,11 @@ Parent process does not exit until its children have finished. It has no effect which does not fork/exit at startup. . .if !'po4a'hide' .TP +.if !'po4a'hide' .B "\--kid roleID" +Play a given SMP kid process role, with a given ID. Do not use +this option. It is meant for the master process use only. +. +.if !'po4a'hide' .TP .if !'po4a'hide' .B "\-O options" Set Windows Service Command line options in Registry. .