And take 4 (modified slightly from take 3)
There were a couple other glitches that showed up when compiling (and
quickly testing) on windows, which have been resolved in the attached patch.
The off-by-one error resulted from a copy & paste. I will follow this
patch up with a patch to correct the source of the off-by-one, along
with the negative values returned from strtol.
--Andrew Black
Martin Sebor wrote:
Martin Sebor wrote:
Andrew Black wrote:
And take 3, log below (slightly modified from last time).
The --ulimit command line option doesn't seem to work at all (it
gives an error no matter how I try to use it). Stepping through
the code, it seems that the problem might be an off-by-1 error
in eval_options.
[...]
@@ -397,7 +574,16 @@
}
}
}
-
+ else if ( sizeof opt_ulimit <= arglen
+ && !memcmp (opt_ulimit, argv [i], sizeof
opt_ulimit - 1)) {
+ optname = opt_ulimit;
+ optarg = get_long_val (argv, &i, sizeof opt_ulimit);
Shouldn't this be instead:
get_long_val (argv, &i, sizeof opt_ulimit - 1);
This fixed it for me but while playing with it I noticed that
the function accepts negative limits and silently converts them
to rlim_t which is an unsigned type. Since negative limits don't
make sense we should reject them. In fact, according to section
12.4, bullet 6 of POSIX Utility Argument Syntax (which we said
we'd follow), unless a utility explicitly states that it accepts
negative arguments only non-negative numerals are recognized.
So I would suggest to check all the places where we currently
recognize and accept negative arguments (or even zero preceded
by a minus sign) and diagnose as an error those that do not
start with a decimal digit.
Martin
Index: exec.cpp
===================================================================
--- exec.cpp (revision 442689)
+++ exec.cpp (working copy)
@@ -40,6 +40,9 @@
#if !defined (_WIN32) && !defined (_WIN64)
# include <unistd.h> /* for close, dup, exec, fork */
# include <sys/wait.h>
+# ifdef _XOPEN_UNIX
+# include <sys/resource.h> /* for setlimit(), RLIMIT_CORE, ... */
+# endif
#else
# include <windows.h> /* for PROCESS_INFORMATION, ... */
# include <process.h> /* for CreateProcess, ... */
@@ -625,7 +628,71 @@
name, strerror (errno));
}
+#ifdef _XOPEN_UNIX
/**
+ Utility macro to generate an rlimit tuple.
+
+ @parm val 'short' resource name (no leading RLIMIT_).
+ @param idx limit structure name in child_limits global
+ @see limit_process
+ @see child_limits
+*/
+#undef LIMIT
+#define LIMIT(val, idx) { RLIMIT_ ## val, &child_limits.idx, #val }
+
+/**
+ Set process resource limits, based on the child_limits global.
+
+ This method uses the LIMIT macro to build an internal array of limits
+ to try setting. If setrlimit fails
+*/
+static void
+limit_process ()
+{
+ static const struct {
+ int resource;
+ rw_rlimit* limit;
+ const char* name;
+ } limits[] = {
+#ifdef RLIMIT_CORE
+ LIMIT (CORE, core),
+#endif // RLIMIT_CORE
+#ifdef RLIMIT_CPU
+ LIMIT (CPU, cpu),
+#endif // RLIMIT_CPU
+#ifdef RLIMIT_DATA
+ LIMIT (DATA, data),
+#endif // RLIMIT_DATA
+#ifdef RLIMIT_FSIZE
+ LIMIT (FSIZE, fsize),
+#endif // RLIMIT_FSIZE
+#ifdef RLIMIT_NOFILE
+ LIMIT (NOFILE, nofile),
+#endif // RLIMIT_NOFILE
+#ifdef RLIMIT_STACK
+ LIMIT (STACK, stack),
+#endif // RLIMIT_STACK
+#ifdef RLIMIT_AS
+ LIMIT (AS, as),
+#endif // RLIMIT_AS
+ { 0, 0, 0 }
+ };
+
+ for (size_t i = 0; limits [i].limit; ++i) {
+ rw_rlimit local;
+
+ memcpy (&local, limits [i].limit, sizeof (struct rlimit));
+
+ if (setrlimit (limits [i].resource, &local)) {
+ warn ("error setting process limits for %s (soft: %lu, hard: "
+ "%lu): %s\n", limits [i].name, local.rlim_cur,
+ local.rlim_max, strerror (errno));
+ }
+ }
+}
+#endif /* _XOPEN_UNIX *//
+
+/**
Entry point to the child process (watchdog) subsystem.
This method fork ()s, creating a child process. This child process becomes
@@ -701,6 +768,10 @@
terminate (1, "Redirection of stderr to stdout failed: %s\n",
strerror (errno));
+#ifdef _XOPEN_UNIX
+ limit_process ();
+#endif /* _XOPEN_UNIX *//
+
execv (argv [0], argv);
fprintf (error_file, "%s (%s): execv (\"%s\", ...) error: %s\n",
Index: cmdopt.cpp
===================================================================
--- cmdopt.cpp (revision 442689)
+++ cmdopt.cpp (working copy)
@@ -37,6 +37,11 @@
#include <string.h> /* for str* */
#if !defined (_WIN32) && !defined (_WIN64)
# include <unistd.h> /* for sleep */
+
+# if defined (_XOPEN_UNIX)
+# include <sys/resource.h> /* for struct rlimit, RLIMIT_CORE, ... */
+# endif
+
#else
# include <windows.h> /* for Sleep */
#endif /* _WIN{32,64} */
@@ -66,6 +71,28 @@
const size_t exe_suffix_len = 4; /* strlen(".exe") == 4 */
#endif
+#ifndef RLIM_INFINITY
+# define RLIM_INFINITY -1
+#endif /* RLIM_INFINITY */
+
+#ifndef RLIM_SAVED_CUR
+# define RLIM_SAVED_CUR RLIM_INFINITY
+#endif /* RLIM_SAVED_CUR */
+
+#ifndef RLIM_SAVED_MAX
+# define RLIM_SAVED_MAX RLIM_INFINITY
+#endif /* RLIM_SAVED_MAX */
+struct limit_set child_limits = {
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX },
+ { RLIM_SAVED_CUR, RLIM_SAVED_MAX }
+};
+
static const char
usage_text[] = {
"Usage: %s [OPTIONS] [targets]\n"
@@ -91,12 +118,39 @@
" --sleep=sec Sleep for the specified number of seconds.\n"
" --signal=sig Send itself the specified signal.\n"
" --ignore=sig Ignore the specified signal.\n"
+ " --ulimit=lim Set child process usage limits (see below).\n"
"\n"
" All short (single dash) options must be specified seperately.\n"
" If a short option takes a value, it may either be provided like\n"
" '-sval' or '-s val'.\n"
" If a long option take a value, it may either be provided like\n"
" '--option=value' or '--option value'.\n"
+ "\n"
+ " --ulimit sets limits on how much of a given resource or resorces\n"
+ " child processes are allowed to utilize. These limits take on two\n"
+ " forms, 'soft' and 'hard' limits. Options are specified in the form\n"
+ " 'resource:limit', where resource is a resource named below, and and\n"
+ " limit is a number, with value specifing the limit for the named\n"
+ " resource. If multiple limits are to be set, they can be specified\n"
+ " either in multiple instances of the --ulimit switch, or by specifying\n"
+ " additional limits in the same call by seperating the pairs with\n"
+ " commas. 'Soft' limits are specified by providing the resource name\n"
+ " in lowercase letters, while 'hard' limits are specified by providing\n"
+ " the resource name in uppercase letters. To set both limits, specify\n"
+ " the resource name in title case.\n"
+ "\n"
+ " --ulimit modes:\n"
+ " core Maximum size of core file, in bytes.\n"
+ " cpu Maximum CPU time, in seconds.\n"
+ " data Maximum data segment size, in bytes.\n"
+ " fsize Maximum size of generated files, in bytes.\n"
+ " nofile Maximum number of open file descriptors.\n"
+ " stack Maximum size of initial thread's stack, in bytes.\n"
+ " as Maximum size of available memory, in bytes.\n"
+ "\n"
+ " Note: Some operating systems lack support for some or all of the\n"
+ " ulimit modes. If a system is unable to limit a given property, a\n"
+ " warning message will be produced.\n"
};
#if !defined (_WIN32) && !defined (_WIN64)
@@ -201,6 +255,128 @@
}
/**
+ Helper function to parse a ulimit value string
+
+ @param opts ulimit value string to pares
+ @see child_limits
+*/
+static bool
+parse_limit_opts (const char* opts)
+{
+ static const struct {
+ rw_rlimit* limit;
+ const char* name;
+ const char* caps;
+ const char* mixd;
+ size_t len;
+ } limits[] = {
+ {
+#ifdef RLIMIT_CORE
+ &child_limits.core,
+#else
+ 0,
+#endif // RLIMIT_CORE
+ "core", "CORE", "Core", 4 },
+ {
+#ifdef RLIMIT_CPU
+ &child_limits.cpu,
+#else
+ 0,
+#endif // RLIMIT_CPU
+ "cpu", "CPU", "Cpu", 3 },
+ {
+#ifdef RLIMIT_DATA
+ &child_limits.data,
+#else
+ 0,
+#endif // RLIMIT_DATA
+ "data", "DATA", "Data", 4 },
+ {
+#ifdef RLIMIT_FSIZE
+ &child_limits.fsize,
+#else
+ 0,
+#endif // RLIMIT_FSIZE
+ "fsize", "FSIZE", "Fsize", 5 },
+ {
+#ifdef RLIMIT_NOFILE
+ &child_limits.nofile,
+#else
+ 0,
+#endif // RLIMIT_NOFILE
+ "nofile", "NOFILE", "Nofile", 6 },
+ {
+#ifdef RLIMIT_STACK
+ &child_limits.stack,
+#else
+ 0,
+#endif // RLIMIT_STACK
+ "stack", "STACK", "Stack", 5 },
+ {
+#ifdef RLIMIT_AS
+ &child_limits.as,
+#else
+ 0,
+#endif // RLIMIT_AS
+ "as", "AS", "As", 2 },
+ { 0, 0, 0, 0, 0 }
+ };
+
+ const char* arg = opts;
+
+ assert (0 != opts);
+
+ while (arg && *arg) {
+
+ const size_t arglen = strlen (arg);
+
+ for (size_t i = 0; limits [i].name; ++i) {
+ if ( limits [i].len < arglen
+ && ( 0 == memcmp (limits [i].name, arg, limits [i].len)
+ || 0 == memcmp (limits [i].caps, arg, limits [i].len)
+ || 0 == memcmp (limits [i].mixd, arg, limits [i].len))
+ && ':' == arg [limits [i].len]) {
+
+ // determine whether the hard limit and/or
+ // the soft limit should be set
+ const bool hard = isupper (arg [0]);
+ const bool soft = islower (arg [1]);
+
+ arg += limits [i].len + 1;
+
+ char *end;
+ const long lim = strtol (arg, &end, 10);
+
+ arg = end;
+
+ if ('\0' != *arg && ',' != *arg)
+ break;
+
+ if (limits [i].limit) {
+ if (soft)
+ limits [i].limit->rlim_cur = lim;
+
+ if (hard)
+ limits [i].limit->rlim_max = lim;
+ } else
+ warn ("Unable to process %s limit: Not supported\n",
+ limits [i].name);
+ break;
+ }
+ }
+
+ if (',' == *arg) {
+ ++arg;
+ }
+ else if ('\0' != *arg) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
Helper function to produce 'Bad argument' error message.
@param opt name of option encountered
@@ -266,6 +442,7 @@
const char opt_nocompat[] = "--nocompat";
const char opt_signal[] = "--signal";
const char opt_sleep[] = "--sleep";
+ const char opt_ulimit[] = "--ulimit";
int i;
@@ -397,7 +574,16 @@
}
}
}
-
+ else if ( sizeof opt_ulimit <= arglen
+ && !memcmp (opt_ulimit, argv [i], sizeof opt_ulimit - 1)) {
+ optname = opt_ulimit;
+ optarg = get_long_val (argv, &i, sizeof opt_ulimit - 1);
+ if (optarg && *optarg) {
+ if (!parse_limit_opts (optarg)) {
+ break;
+ }
+ }
+ }
/* fall through */
}
default:
Index: cmdopt.h
===================================================================
--- cmdopt.h (revision 442689)
+++ cmdopt.h (working copy)
@@ -27,6 +27,10 @@
#ifndef RW_PARSE_OPTS_H
#define RW_PARSE_OPTS_H
+#if !defined (_WIN32) && !defined (_WIN64)
+# include <unistd.h> /* For _XOPEN_UNIX */
+#endif
+
extern int timeout;
extern int compat;
extern unsigned verbose; /**< Verbose output mode switch. Defaults to 0 */
@@ -39,6 +43,41 @@
extern const char suffix_sep; /**< File suffix seperator. */
extern const size_t exe_suffix_len; /**< Length of executable suffix. */
+#ifdef _XOPEN_UNIX
+# include <sys/resource.h> /* for struct rlimit */
+/**
+ Abstraction typedef for struct rlimit using real struct
+*/
+typedef struct rlimit rw_rlimit;
+#else
+/**
+ Placeholder rlim_t for use in rw_rlimit
+*/
+typedef unsigned long rw_rlim_t;
+/**
+ Placeholder struct rlimit to use if _XOPEN_UNIX isn't defined
+*/
+struct rw_rlimit {
+ rw_rlim_t rlim_cur;
+ rw_rlim_t rlim_max;
+};
+/**
+ Abstraction typedef for struct rlimit using placeholder struct
+*/
+typedef struct rw_rlimit rw_rlimit;
+#endif
+
+extern struct limit_set {
+ rw_rlimit core;
+ rw_rlimit cpu;
+ rw_rlimit data;
+ rw_rlimit fsize;
+ rw_rlimit nofile;
+ rw_rlimit stack;
+ rw_rlimit mem;
+ rw_rlimit as;
+} child_limits; /**< Container holding child process limits. */
+
void
show_usage (int status);