generalized setting and getting of vserver limits
[util-vserver.git] / src / vlimit.c
1 // $Id: vlimit.c,v 1.20 2005/03/24 12:44:17 ensc Exp $
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 //  
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2, or (at your option)
8 // any later version.
9 //  
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //  
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 /*
20         Set the global per context limit of a resource (memory, file handle).
21         This utility can do it either for the current context or a selected
22         one.
23
24         It uses the same options as ulimit, when possible
25 */
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29 #include "compat.h"
30
31 #include "vserver.h"
32 #include "internal.h"
33 #include "util.h"
34
35 #include <getopt.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <assert.h>
39 #include <stdbool.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <libgen.h>
43 #include <sys/resource.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <sys/stat.h>
47 #include <ctype.h>
48
49 #define ENSC_WRAPPERS_PREFIX    "vlimit: "
50 #define ENSC_WRAPPERS_UNISTD    1
51 #define ENSC_WRAPPERS_VSERVER   1
52 #include <wrappers.h>
53
54 #define CMD_HELP                0x1000
55 #define CMD_VERSION             0x1001
56 #define CMD_XID                 0x4000
57 #define CMD_DIR                 0x8000
58 #define CMD_MISSINGOK           0x8001
59
60 int             wrapper_exit_code = 255;
61
62 #define NUMLIM(X) \
63 { #X, required_argument, 0, 2048|X }
64 #define OPT_RESLIM(RES,V) \
65   { #RES, required_argument, 0, 2048|RLIMIT_##V }
66
67 static struct option const
68 CMDLINE_OPTIONS[] = {
69   { "help",      no_argument,       0, CMD_HELP },
70   { "version",   no_argument,       0, CMD_VERSION },
71   { "all",       no_argument,       0, 'a' },
72   { "xid",       required_argument, 0, CMD_XID },
73   { "dir",       required_argument, 0, CMD_DIR },
74   { "missingok", no_argument,       0, CMD_MISSINGOK },
75   NUMLIM( 0), NUMLIM( 1), NUMLIM( 2), NUMLIM( 3),
76   NUMLIM( 4), NUMLIM( 5), NUMLIM( 6), NUMLIM( 7),
77   NUMLIM( 8), NUMLIM( 9), NUMLIM(10), NUMLIM(11),
78   NUMLIM(12), NUMLIM(13), NUMLIM(14), NUMLIM(15),
79   NUMLIM(16), NUMLIM(17), NUMLIM(18), NUMLIM(19),
80   NUMLIM(20), NUMLIM(21), NUMLIM(22), NUMLIM(23),
81   NUMLIM(24), NUMLIM(25), NUMLIM(26), NUMLIM(27),
82   NUMLIM(28), NUMLIM(29), NUMLIM(30), NUMLIM(31),
83   OPT_RESLIM(cpu,     CPU),
84   OPT_RESLIM(fsize,   FSIZE),
85   OPT_RESLIM(data,    DATA),
86   OPT_RESLIM(stack,   STACK),
87   OPT_RESLIM(core,    CORE),
88   OPT_RESLIM(rss,     RSS),
89   OPT_RESLIM(nproc,   NPROC),
90   OPT_RESLIM(nofile,  NOFILE),
91   OPT_RESLIM(memlock, MEMLOCK),
92   OPT_RESLIM(as,      AS),
93   OPT_RESLIM(locks,   LOCKS),
94   { 0,0,0,0 }
95 };
96
97 #define REV_RESLIM(X)   [RLIMIT_##X] = #X
98 static char const * const LIMIT_STR[] = {
99   REV_RESLIM(CPU),     REV_RESLIM(FSIZE), REV_RESLIM(DATA),  REV_RESLIM(STACK),
100   REV_RESLIM(CORE),    REV_RESLIM(RSS),   REV_RESLIM(NPROC), REV_RESLIM(NOFILE),
101   REV_RESLIM(MEMLOCK), REV_RESLIM(AS),    REV_RESLIM(LOCKS)
102 };
103
104 static void
105 showHelp(int fd, char const *cmd, int res)
106 {
107   VSERVER_DECLARE_CMD(cmd);
108   
109   WRITE_MSG(fd, "Usage:  ");
110   WRITE_STR(fd, cmd);
111   WRITE_MSG(fd,
112             " [--xid|-c <xid>] [-nd] [-a|--all] [[-MSH] --(<resource>|<nr>) <value>]*\n"
113             "               [--dir <pathname> [--missingok]] [--] [<program> <args>*]\n\n"
114             "Options:\n"
115             "    -c|--xid <xid>\n"
116             "                ...  operate on context <xid>\n"
117             "    -a|--all    ...  show all available limits\n"
118             "    -n          ...  do not resolve limit-names\n"
119             "    -d          ...  show limits in decimal\n"
120             "    -M          ...  set Minimum limit\n"
121             "    -S          ...  set Soft limit\n"
122             "    -H          ...  set Hard limit (assumed by default, when neither\n"
123             "                     M nor S was requested)\n"
124             "    --dir <pathname>\n"
125             "                ...  read limits from <pathname>/; allowed filenames are\n"
126             "                     <resource> and <resource>.{min,soft,hard}. When a limit\n"
127             "                     was set by the CLI already, the corresponding file\n"
128             "                     will be ignored\n"
129             "    --missingok ...  do not fail when <pathname> does not exist\n"
130             "    --<resource>|<nr> <value>\n"
131             "                ...  set specified (MSH) limit for <resource> to <value>\n\n"
132             "Valid values for resource are cpu, fsize, data, stack, core, rss, nproc,\n"
133             "nofile, memlock, as and locks.\n\n"
134             "Please report bugs to " PACKAGE_BUGREPORT "\n");
135   exit(res);
136 }
137
138 static void
139 showVersion()
140 {
141   WRITE_MSG(1,
142             "vlimit " VERSION " -- limits context-resources\n"
143             "This program is part of " PACKAGE_STRING "\n\n"
144             "Copyright (C) 2003 Enrico Scholz\n"
145             VERSION_COPYRIGHT_DISCLAIMER);
146   exit(0);
147 }
148
149 static size_t
150 fmtHex(char *ptr, vc_limit_t lim)
151 {
152   memcpy(ptr, "0x", 2);
153   return utilvserver_fmt_xuint64(ptr+2, lim) + 2;
154 }
155
156 static bool             do_resolve = true;
157 static size_t           (*fmt_func)(char *, vc_limit_t) = fmtHex;
158
159 static void *
160 appendLimit(char *ptr, bool do_it, vc_limit_t lim)
161 {
162   memcpy(ptr, "  ", 2);
163   ptr += 2;
164   if (do_it) {
165     if (lim==VC_LIM_INFINITY) {
166       strcpy(ptr, "inf");
167       ptr += 3;
168     }
169     else {
170       ptr += (*fmt_func)(ptr, lim);
171       *ptr = ' ';
172     }
173   }
174   else {
175     memcpy(ptr, "N/A", 3);
176     ptr += 3;
177   }
178
179   return ptr;
180 }
181
182 static void
183 showAll(int ctx)
184 {
185   struct vc_rlimit_mask mask;
186   size_t                i;
187
188   if (vc_get_rlimit_mask(ctx, &mask)==-1) {
189     perror("vc_get_rlimit_mask()");
190     exit(wrapper_exit_code);
191   }
192
193   for (i=0; i<32; ++i) {
194     uint32_t            bitmask = (1<<i);
195     struct vc_rlimit    limit;
196     char                buf[128], *ptr=buf;
197
198     if (((mask.min|mask.soft|mask.hard) & bitmask)==0) continue;
199     if (vc_get_rlimit(ctx, i, &limit)==-1) {
200       perror("vc_get_rlimit()");
201       //continue;
202     }
203
204     memset(buf, ' ', sizeof buf);
205     if (do_resolve && i<DIM_OF(LIMIT_STR)) {
206       size_t            l = strlen(LIMIT_STR[i]);
207       memcpy(ptr, LIMIT_STR[i], l);
208       ptr += l;
209     }
210     else {
211       ptr += utilvserver_fmt_uint(ptr, i);
212       *ptr = ' ';
213     }
214
215     ptr  = appendLimit(buf+10, mask.min &bitmask, limit.min);
216     ptr  = appendLimit(buf+30, mask.soft&bitmask, limit.soft);
217     ptr  = appendLimit(buf+50, mask.hard&bitmask, limit.hard);
218
219     *ptr++ = '\n';
220     Vwrite(1, buf, ptr-buf);
221   }
222 }
223
224 static void
225 setLimits(int ctx, struct vc_rlimit const limits[], uint32_t mask)
226 {
227   size_t                i;
228   for (i=0; i<32; ++i) {
229     if ((mask & (1<<i))==0) continue;
230     if (vc_set_rlimit(ctx, i, limits+i)) {
231       perror("vc_set_rlimit()");
232     }
233   }
234 }
235
236 static vc_limit_t
237 readValue(int fd, char const *filename)
238 {
239   char          buf[128];
240   size_t        len = Eread(fd, buf, sizeof(buf)-1);
241   vc_limit_t    res;
242
243   buf[len] = '\0';
244
245   if (!vc_parseLimit(buf, &res)) {
246     WRITE_MSG(2, "Invalid limit in '");
247     WRITE_STR(2, filename);
248     WRITE_STR(2, "'\n");
249     exit(wrapper_exit_code);
250   }
251
252   return res;
253 }
254
255 static bool
256 readFile(char const *file, char *base, char const *suffix,
257          vc_limit_t *limit)
258 {
259   int           fd;
260   
261   strcpy(base, suffix);
262   fd = open(file, O_RDONLY);
263   if (fd!=-1) {
264     *limit = readValue(fd, file);
265     Eclose(fd);
266   }
267
268   return fd!=-1;
269 }
270          
271 static void
272 readFromDir(struct vc_rlimit limits[32], uint_least32_t *mask,
273             char const *pathname, bool missing_ok)
274 {
275   struct stat           st;
276   size_t                i;
277   size_t                l_pathname = strlen(pathname);
278   char                  buf[l_pathname + sizeof("/memlock.hard") + 32];
279   
280   if (stat(pathname, &st)==-1) {
281     if (errno==ENOENT && missing_ok) return;
282     PERROR_Q("vlimit: fstat", pathname);
283     exit(wrapper_exit_code);
284   }
285
286   memcpy(buf, pathname, l_pathname);
287   if (l_pathname>0 && pathname[l_pathname-1]!='/')
288     buf[l_pathname++] = '/';
289     
290   for (i=0; i<DIM_OF(LIMIT_STR); ++i) {
291     size_t      l_res;
292     char *      ptr   = buf+l_pathname;
293
294     // ignore unimplemented limits
295     if (LIMIT_STR[i]==0) continue;
296     
297     // ignore limits set on cli already
298     if (*mask & (1<<i)) continue;
299
300     l_res = strlen(LIMIT_STR[i]);
301     memcpy(ptr, LIMIT_STR[i], l_res+1);
302     while (*ptr) {
303       *ptr = tolower(*ptr);
304       ++ptr;
305     }
306
307     if (readFile(buf, ptr, "", &limits[i].min)) {
308       limits[i].soft = limits[i].hard = limits[i].min;
309       *mask |= (1<<i);
310     }
311
312     if (readFile(buf, ptr, ".min",  &limits[i].min))
313       *mask |= (1<<i);
314
315     if (readFile(buf, ptr, ".soft", &limits[i].soft))
316       *mask |= (1<<i);
317
318     if (readFile(buf, ptr, ".hard", &limits[i].hard))
319       *mask |= (1<<i);
320   }
321 }
322
323 int main (int argc, char *argv[])
324 {
325   // overall used limits
326   uint32_t              lim_mask = 0;
327   int                   set_mask = 0;
328   struct vc_rlimit      limits[32];
329   bool                  show_all   = false;
330   xid_t                 ctx        = VC_NOCTX;
331   char const *          dir        = 0;
332   bool                  missing_ok = false;
333
334   {
335     size_t              i;
336     for (i=0; i<32; ++i) {
337       limits[i].min  = VC_LIM_KEEP;
338       limits[i].soft = VC_LIM_KEEP;
339       limits[i].hard = VC_LIM_KEEP;
340     }
341   }
342   
343   while (1) {
344     int         c = getopt_long(argc, argv, "+MSHndac:", CMDLINE_OPTIONS, 0);
345     if (c==-1) break;
346
347     if (2048<=c && c<2048+32) {
348       int               id  = c-2048;
349       vc_limit_t        val;
350       
351       if (!vc_parseLimit(optarg, &val)) {
352         WRITE_MSG(2, "Can not parse limit '");
353         WRITE_STR(2, optarg);
354         WRITE_STR(2, "'\n");
355         exit(wrapper_exit_code);
356       }
357
358       if (set_mask==0)  set_mask=4;
359       
360       if (set_mask & 1) limits[id].min  = val;
361       if (set_mask & 2) limits[id].soft = val;
362       if (set_mask & 4) limits[id].hard = val;
363
364       lim_mask |= (1<<id);
365       set_mask  = 0;
366     }
367     else switch (c) {
368       case CMD_HELP     :  showHelp(1, argv[0], 0);
369       case CMD_VERSION  :  showVersion();
370       case 'a'          :  show_all   = true;            break;
371       case 'n'          :  do_resolve = false;           break;
372       case CMD_DIR      :  dir        = optarg;          break;
373       case CMD_MISSINGOK:  missing_ok = true;            break;
374       case CMD_XID      :  /*@fallthrough@*/
375       case 'c'          :  ctx = Evc_xidopt2xid(optarg,true);   break;
376       case 'd'          :  fmt_func   = utilvserver_fmt_uint64; break;
377       case 'M'          :
378       case 'S'          :
379       case 'H'          :
380         switch (c) {
381           case 'M'      :  set_mask |= 1; break;
382           case 'S'      :  set_mask |= 2; break;
383           case 'H'      :  set_mask |= 4; break;
384           default       :  assert(false);
385         }
386         break;
387         
388       default           :
389         WRITE_MSG(2, "Try '");
390         WRITE_STR(2, argv[0]);
391         WRITE_MSG(2, " --help\" for more information.\n");
392         exit(wrapper_exit_code) ;
393         break;
394     }
395   }
396
397   if (ctx==VC_NOCTX)
398     ctx = Evc_get_task_xid(0);
399
400   if (dir)
401     readFromDir(limits, &lim_mask, dir, missing_ok);
402
403   setLimits(ctx, limits, lim_mask);
404   if (show_all) showAll(ctx);
405
406   if (optind<argc)
407     EexecvpD(argv[optind], argv+optind);
408
409   return EXIT_SUCCESS;
410 }