Coverity: fix potential buffer overflow
[wmaker-crm.git] / WINGs / menuparser_macros.c
blobaca0bf5f7e40d07558715701af60fdacf0b66683
1 /*
2 * Window Maker window manager
4 * Copyright (c) 2012 Christophe Curis
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "wconfig.h"
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <unistd.h>
27 #include <pwd.h>
29 #include <WINGs/WUtil.h>
31 #include "menuparser.h"
34 This file contains the functions related to macros:
35 - parse a macro being defined
36 - handle single macro expansion
37 - pre-defined parser's macros
39 Some design notes for macro internal storage:
41 arg_count is -1 when the macro does not take arguments
42 but 0 when no args but still using parenthesis. The
43 difference is explained in GNU cpp's documentation:
44 http://gcc.gnu.org/onlinedocs/cpp/Function_002dlike-Macros.html
46 the value is stored for fast expansion; here is an
47 example of the storage format used:
48 #define EXAMPLE(a, b) "text:" a and b!
49 will be stored in macro->value[] as:
50 0x0000: 0x00 0x07 (strlen(part 1))
51 0x0002: '"', 't', 'e', 'x', 't', ':', '"' (first part)
52 0x0009: 0x00 (part 2, id=0 for replacement by 1st parameter 'a')
53 0x000A: 0x00 0x03 (strlen(part 3))
54 0x000C: 'a', 'n', 'd' (part 3)
55 0x000F: 0x01 (part 4, id=1 for replacement by 2nd parameter 'b')
56 0x0010: 0x00 0x01 (strlen(part 5))
57 0x0012: '!' (part 5)
58 0x0013: 0xFF (end of macro)
59 This structure allows to store any number and combination
60 of text/parameter and still provide very fast generation
61 at macro replacement time.
63 Predefined macros are using a call-back function mechanism
64 to generate the value on-demand. This value is generated
65 in the 'value' buffer of the structure.
66 Most of these call-backs will actually cache the value:
67 they generate it on the first use (inside a parser,
68 not globally) and reuse that value on next call(s).
70 Because none of these macros take parameters, the call-back
71 mechanism does not include passing of user arguments; the
72 complex storage mechanism for argument replacement being
73 not necessary the macro->value parameter is used as a
74 plain C string to be copied, this fact being recognised
75 by macro->function being non-null. It was chosen that the
76 call-back function would not have the possibility to fail.
79 static Bool menu_parser_read_macro_def(WMenuParser parser, WParserMacro *macro, char **argname);
81 static Bool menu_parser_read_macro_args(WMenuParser parser, WParserMacro *macro,
82 char *array[], char *buffer, ssize_t buffer_size);
84 /* Free all used memory associated with parser's macros */
85 void menu_parser_free_macros(WMenuParser parser)
87 WParserMacro *macro, *mnext;
88 #ifdef DEBUG
89 unsigned char *rd;
90 unsigned int size;
91 unsigned int count;
93 /* if we were compiled with debugging, we take the opportunity that we
94 parse the list of macros, for memory release, to print all the
95 definitions */
96 printf(__FILE__ ": Macros defined while parsing \"%s\"\n", parser->file_name);
97 count = 0;
98 #endif
99 for (macro = parser->macros; macro != NULL; macro = mnext) {
100 #ifdef DEBUG
101 printf(" %s", macro->name);
102 if (macro->arg_count >= 0)
103 printf("(args=%d)", macro->arg_count);
104 printf(" = ");
106 if (macro->function != NULL) {
107 macro->function(macro, parser);
108 printf("function:\"%s\"", macro->value);
109 } else {
110 rd = macro->value;
111 for (;;) {
112 putchar('"');
113 size = (*rd++) << 8;
114 size |= *rd++;
115 while (size-- > 0) putchar(*rd++);
116 putchar('"');
117 if (*rd == 0xFF) break;
118 printf(" #%d ", (*rd++) + 1);
121 printf(", used %d times\n", macro->usage_count);
122 count++;
123 #endif
124 mnext = macro->next;
125 wfree(macro);
127 #ifdef DEBUG
128 printf(__FILE__ ": %d macros\n", count);
129 #endif
130 parser->macros = NULL; // Security
133 /* Check wether the specified character is valid for a name (macro, parameter) or not */
134 int isnamechr(char ch)
136 static const int table[256] = {
137 [0] = 0, // In case we'd fall on buggy compiler, to avoid crash
138 // C99: 6.7.8.21 -> non specified values are initialised to 0
139 ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1,
140 ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1,
141 ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1,
142 ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
143 ['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1,
144 ['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
145 ['Y'] = 1, ['Z'] = 1,
146 ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1,
147 ['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1,
148 ['m'] = 1, ['n'] = 1, ['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1,
149 ['s'] = 1, ['t'] = 1, ['u'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1,
150 ['y'] = 1, ['z'] = 1,
151 ['_'] = 1
152 // We refuse any UTF-8 coded character, or accents in ISO-xxx codepages
154 return table[0x00FF & (unsigned)ch ];
157 /* Parse the definition of the macro and add it to the top-most parser's list */
158 void menu_parser_define_macro(WMenuParser parser)
160 WParserMacro *macro;
161 int idx;
162 char arg_names_buf[MAXLINE];
163 char *arg_name[MAX_MACRO_ARG_COUNT];
165 if (!menu_parser_skip_spaces_and_comments(parser)) {
166 WMenuParserError(parser, _("no macro name found for #define") );
167 return;
169 macro = wmalloc(sizeof(*macro));
171 /* Isolate name of macro */
172 idx = 0;
173 while (isnamechr(*parser->rd)) {
174 if (idx < sizeof(macro->name) - 1)
175 macro->name[idx++] = *parser->rd;
176 parser->rd++;
178 // macro->name[idx] = '\0'; -> Already present because wmalloc filled struct with 0s
180 /* Build list of expected arguments */
181 if (*parser->rd == '(') {
182 parser->rd++;
183 idx = 0;
184 for (;;) {
185 if (!menu_parser_skip_spaces_and_comments(parser)) {
186 arglist_error_premature_eol:
187 WMenuParserError(parser, _("premature end of file while reading arg-list for macro \"%s\""), macro->name);
188 wfree(macro);
189 return;
191 if (*parser->rd == ')') break;
193 if (macro->arg_count >= wlengthof(arg_name)) {
194 WMenuParserError(parser, _("too many parameters for macro \"%s\" definition"), macro->name);
195 wfree(macro);
196 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
197 return;
199 if (isnamechr(*parser->rd)) {
200 arg_name[macro->arg_count++] = arg_names_buf + idx;
201 do {
202 if (idx < sizeof(arg_names_buf) - 1)
203 arg_names_buf[idx++] = *parser->rd;
204 parser->rd++;
205 } while (isnamechr(*parser->rd));
206 arg_names_buf[idx] = '\0';
207 if (idx < sizeof(arg_names_buf) - 1) idx++;
208 } else {
209 WMenuParserError(parser, _("invalid character '%c' in arg-list for macro \"%s\" while expecting parameter name"),
210 *parser->rd, macro->name);
211 wfree(macro);
212 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
213 return;
215 if (!menu_parser_skip_spaces_and_comments(parser))
216 goto arglist_error_premature_eol;
217 if (*parser->rd == ')') break;
219 if (*parser->rd != ',') {
220 WMenuParserError(parser, _("invalid character '%c' in arg-list for macro \"%s\" while expecting ',' or ')'"),
221 *parser->rd, macro->name);
222 wfree(macro);
223 *parser->rd = '\0'; // fake end-of-line to avoid warnings from remaining line content
224 return;
226 parser->rd++;
228 parser->rd++; // skip the closing ')'
229 } else
230 macro->arg_count = -1; // Means no parenthesis at all to expect
232 /* If we're inside a #if sequence, we abort now, but not sooner in
233 order to keep the syntax check */
234 if (parser->cond.stack[0].skip) {
235 wfree(macro);
236 *parser->rd = '\0'; // Ignore macro content
237 return;
240 /* Get the macro's definition */
241 menu_parser_skip_spaces_and_comments(parser);
242 if (!menu_parser_read_macro_def(parser, macro, arg_name)) {
243 wfree(macro);
244 return;
247 /* Create the macro in the Root parser */
248 while (parser->parent_file != NULL)
249 parser = parser->parent_file;
251 /* Check that the macro was not already defined */
252 if (menu_parser_find_macro(parser, macro->name) != NULL) {
253 WMenuParserError(parser, _("macro \"%s\" already defined, ignoring redefinition"),
254 macro->name);
255 wfree(macro);
256 return;
259 /* Append at beginning of list */
260 macro->next = parser->macros;
261 parser->macros = macro;
264 /* Check if the current word in the parser matches a macro */
265 WParserMacro *menu_parser_find_macro(WMenuParser parser, const char *name)
267 const char *ref, *cmp;
268 WParserMacro *macro;
270 while (parser->parent_file != NULL)
271 parser = parser->parent_file;
272 for (macro = parser->macros; macro != NULL; macro = macro->next) {
273 ref = macro->name;
274 cmp = name;
275 while (*ref != '\0')
276 if (*ref++ != *cmp++)
277 goto check_next_macro;
278 if (isnamechr(*cmp))
279 continue;
281 return macro;
282 check_next_macro:
285 return NULL;
288 /* look to see if the next word matches the name of one of the parameter
289 names for a macro definition
290 This function is internal to the macro definition function as this is
291 where the analysis is done */
292 static inline char *mp_is_parameter(char *parse, const char *param) {
293 while (*param)
294 if (*parse++ != *param++)
295 return NULL;
296 if (isnamechr(*parse))
297 return NULL;
298 return parse;
301 /* Read the content definition part of a #define construct (the part after the optional
302 argument list) and store it in the prepared format for quick expansion
304 There is no need to keep track of the names of the parameters, so they are stored in
305 a temporary storage for the time of the macro parsing. */
306 static Bool menu_parser_read_macro_def(WMenuParser parser, WParserMacro *macro, char **arg)
308 unsigned char *wr_size;
309 unsigned char *wr;
310 unsigned int size_data;
311 unsigned int size_max;
312 int i;
314 wr_size = macro->value;
315 size_data = 0;
316 wr = wr_size + 2;
317 size_max = sizeof(macro->value) - (wr - macro->value) - 3;
318 while (menu_parser_skip_spaces_and_comments(parser)) {
319 if (isnamechr(*parser->rd)) {
320 char *next_rd;
322 /* Is the current word a parameter to replace? */
323 for (i = 0; i < macro->arg_count; i++) {
324 next_rd = mp_is_parameter(parser->rd, arg[i]);
325 if (next_rd != NULL) {
326 if (wr + 4 >= macro->value + sizeof(macro->value))
327 goto error_too_much_data;
328 wr_size[0] = (size_data >> 8) & 0xFF;
329 wr_size[1] = size_data & 0xFF;
330 *wr++ = i;
331 wr_size = wr;
332 wr += 2;
333 parser->rd = next_rd;
334 *wr++ = ' ';
335 size_data = 1;
336 size_max = sizeof(macro->value) - (wr - macro->value) - 3;
337 goto next_loop; // Because we can't 'break' this loop and 'continue'
338 // the outer one in a clean and easy way
342 /* Not parameter name -> copy as-is */
343 do {
344 *wr++ = *parser->rd++;
345 if (++size_data >= size_max) {
346 error_too_much_data:
347 WMenuParserError(parser, _("more content than supported for the macro \"%s\""),
348 macro->name);
349 return False;
351 } while (isnamechr(*parser->rd));
352 if (isspace(*parser->rd)) {
353 *wr++ = ' ';
354 if (++size_data >= size_max)
355 goto error_too_much_data;
357 } else {
358 /* Some uninterresting characters, copy as-is */
359 while (*parser->rd != '\0') {
360 if (isnamechr(*parser->rd)) break; // handle in next loop
361 if (parser->rd[0] == '/')
362 if ((parser->rd[1] == '*') || (parser->rd[1] == '/'))
363 break; // Comments are handled by std function
364 if ((parser->rd[0] == '\\') &&
365 (parser->rd[1] == '\n') &&
366 (parser->rd[2] == '\0'))
367 break; // Long-lines are handled by std function
368 *wr++ = *parser->rd++;
369 if (++size_data >= size_max)
370 goto error_too_much_data;
373 next_loop:
376 wr_size[0] = (size_data >> 8) & 0xFF;
377 wr_size[1] = size_data & 0xFF;
378 *wr = 0xFF;
379 return True;
382 /* When a macro is being used in the file, this function will generate the
383 expanded value for the macro in the parser's work line.
384 It blindly supposes that the data generated in macro->value is valid */
385 void menu_parser_expand_macro(WMenuParser parser, WParserMacro *macro)
387 char save_buf[sizeof(parser->line_buffer)];
388 char arg_values_buf[MAXLINE];
389 char *arg_value[MAX_MACRO_ARG_COUNT];
390 char *src, *dst;
391 unsigned char *rd;
392 unsigned int size;
393 int i, space_left;
395 /* Skip the name of the macro, this was not done by caller */
396 for (i = 0; macro->name[i] != '\0'; i++)
397 parser->rd++;
399 if (macro->arg_count >= 0) {
400 menu_parser_skip_spaces_and_comments(parser);
401 if (!menu_parser_read_macro_args(parser, macro, arg_value, arg_values_buf, sizeof(arg_values_buf)))
402 return;
405 #ifdef DEBUG
406 macro->usage_count++;
407 #endif
409 /* Save the remaining data from current line as we will overwrite the
410 current line's workspace with the expanded macro, so we can re-append
411 it afterwards */
412 dst = save_buf;
413 while ((*dst++ = *parser->rd++) != '\0') ;
415 /* Generate expanded macro */
416 dst = parser->line_buffer;
417 parser->rd = dst;
418 space_left = sizeof(parser->line_buffer) - 1;
419 if (macro->function != NULL) {
420 /* Parser's pre-defined macros actually proposes a function call to
421 generate dynamic value for the expansion of the macro. In this case
422 it is generated as a C string in the macro->value and used directly */
423 macro->function(macro, parser);
424 rd = macro->value;
425 while (--space_left > 0)
426 if ((*dst = *rd++) == '\0')
427 break;
428 else
429 dst++;
430 } else {
431 rd = macro->value;
432 for (;;) {
433 size = (*rd++) << 8;
434 size |= *rd++;
435 while (size-- > 0) {
436 *dst = *rd++;
437 if (--space_left > 0) dst++;
439 if (*rd == 0xFF) break;
440 src = arg_value[*rd++];
441 while (*src) {
442 *dst = *src++;
443 if (--space_left > 0) dst++;
448 /* Copy finished -> Re-append the text that was following the macro */
449 src = save_buf;
450 while (--space_left > 0)
451 if ((*dst++ = *src++) == '\0')
452 break;
453 *dst = '\0';
455 if (space_left <= 0)
456 WMenuParserError(parser, _("expansion for macro \"%s\" too big, line truncated"),
457 macro->name);
460 /* When reading a macro to be expanded (not being defined), that takes arguments,
461 this function parses the arguments being provided */
462 static Bool menu_parser_read_macro_args(WMenuParser parser, WParserMacro *macro,
463 char *array[], char *buffer, ssize_t buffer_size)
465 int arg;
467 if (*parser->rd != '(') {
468 WMenuParserError(parser, _("macro \"%s\" needs parenthesis for arguments"),
469 macro->name);
470 return False;
472 parser->rd++;
474 buffer_size--; // Room for final '\0'
475 menu_parser_skip_spaces_and_comments(parser);
476 arg = 0;
477 for (;;) {
478 int paren_count;
480 array[arg] = buffer;
481 paren_count = 0;
482 while (*parser->rd != '\0') {
484 if (*parser->rd == '(')
485 paren_count++;
487 if (paren_count <= 0)
488 if ((*parser->rd == ',') ||
489 (*parser->rd == ')') ) break;
491 if ((*parser->rd == '"') || (*parser->rd == '\'')) {
492 char eot = *parser->rd++;
493 if (buffer_size-- > 0) *buffer++ = eot;
494 while (*parser->rd) {
495 if ((*buffer = *parser->rd++) == eot)
496 goto found_end_of_string;
497 if (buffer_size-- > 0) buffer++;
499 WMenuParserError(parser, _("missing closing quote or double-quote before end-of-line") );
500 return False;
501 found_end_of_string:
502 continue;
505 if (isspace(*parser->rd)) {
506 if (buffer_size-- > 0) *buffer++ = ' ';
507 menu_parser_skip_spaces_and_comments(parser);
508 continue;
511 *buffer = *parser->rd++;
512 if (buffer_size-- > 0) buffer++;
514 *buffer = '\0';
515 if (buffer_size-- > 0) buffer++;
517 arg++;
519 if (*parser->rd == ',') {
520 parser->rd++;
521 if (arg >= macro->arg_count) {
522 WMenuParserError(parser, _("too many arguments for macro \"%s\", expected only %d"),
523 macro->name, macro->arg_count);
524 return False;
526 continue;
528 break;
530 if (*parser->rd != ')') {
531 WMenuParserError(parser, _("premature end of line while searching for arguments to macro \"%s\""),
532 macro->name);
533 return False;
535 parser->rd++;
536 if (arg < macro->arg_count) {
537 WMenuParserError(parser, _("not enough arguments for macro \"%s\", expected %d but got only %d"),
538 macro->name, macro->arg_count, arg);
539 return False;
541 if (buffer_size < 0)
542 WMenuParserError(parser, _("too much data in parameter list of macro \"%s\", truncated"),
543 macro->name);
544 return True;
547 /******************************************************************************/
548 /* Definition of pre-defined macros */
549 /******************************************************************************/
551 void WMenuParserRegisterSimpleMacro(WMenuParser parser, const char *name, const char *value)
553 WParserMacro *macro;
554 size_t len;
555 unsigned char *wr;
557 macro = wmalloc(sizeof(*macro));
558 strncpy(macro->name, name, sizeof(macro->name)-1);
559 macro->arg_count = -1;
560 len = strlen(value);
561 if (len > sizeof(macro->value) - 3) {
562 wwarning(_("size of value for macro '%s' is too big, truncated"), name);
563 len = sizeof(macro->value) - 3;
565 macro->value[0] = (len >> 8) & 0xFF;
566 macro->value[1] = len & 0xFF;
567 wr = &macro->value[2];
568 while (len-- > 0)
569 *wr++ = *value++;
570 *wr = 0xFF;
571 macro->next = parser->macros;
572 parser->macros = macro;
575 /* Name of the originally loaded file (before #includes) */
576 static void mpm_base_file(WParserMacro *this, WMenuParser parser)
578 unsigned char *src, *dst;
580 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
582 while (parser->parent_file != NULL)
583 parser = parser->parent_file;
585 dst = this->value;
586 src = (unsigned char *) parser->file_name;
587 *dst++ = '\"';
588 while (*src != '\0')
589 if (dst < this->value + sizeof(this->value) - 2)
590 *dst++ = *src++;
591 else
592 break;
593 *dst++ = '\"';
594 *dst = '\0';
597 /* Number of #include currently nested */
598 static void mpm_include_level(WParserMacro *this, WMenuParser parser)
600 int level = 0;
601 while (parser->parent_file != NULL) {
602 parser = parser->parent_file;
603 level++;
605 snprintf((char *) this->value, sizeof(this->value), "%d", level);
608 /* Name of current file */
609 static void mpm_current_file(WParserMacro *this, WMenuParser parser)
611 unsigned char *src, *dst;
613 dst = this->value;
614 src = (unsigned char *) parser->file_name;
615 *dst++ = '\"';
616 while (*src != '\0')
617 if (dst < this->value + sizeof(this->value) - 2)
618 *dst++ = *src++;
619 else
620 break;
621 *dst++ = '\"';
622 *dst = '\0';
625 /* Number of current line */
626 static void mpm_current_line(WParserMacro *this, WMenuParser parser)
628 snprintf((char *) this->value, sizeof(this->value), "%d", parser->line_number);
631 /* Name of host on which we are running, not necessarily displaying */
632 static void mpm_get_hostname(WParserMacro *this, WMenuParser parser)
634 char *h;
636 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
638 h = getenv("HOSTNAME");
639 if (h == NULL) {
640 h = getenv("HOST");
641 if (h == NULL) {
642 if (gethostname((char *) this->value, sizeof(this->value) ) != 0) {
643 WMenuParserError(parser, _("could not determine %s"), "HOSTNAME");
644 this->value[0] = '?';
645 this->value[1] = '?';
646 this->value[2] = '?';
647 this->value[3] = '\0';
649 return;
652 wstrlcpy((char *) this->value, h, sizeof(this->value) );
655 /* Name of the current user */
656 static void mpm_get_user_name(WParserMacro *this, WMenuParser parser)
658 char *user;
660 if (this->value[0] != '\0') return; // Value already evaluated, re-use previous
662 user = getlogin();
663 if (user == NULL) {
664 struct passwd *pw_user;
666 pw_user = getpwuid(getuid());
667 if (pw_user == NULL) {
668 error_no_username:
669 WMenuParserError(parser, _("could not determine %s"), "USER" );
670 /* Fall back on numeric id - better than nothing */
671 snprintf((char *) this->value, sizeof(this->value), "%d", getuid() );
672 return;
674 user = pw_user->pw_name;
675 if (user == NULL) goto error_no_username;
677 wstrlcpy((char *) this->value, user, sizeof(this->value) );
680 /* Number id of the user under which we are running */
681 static void mpm_get_user_id(WParserMacro *this, WMenuParser parser)
683 /* Parameter not used, but tell the compiler that it is ok */
684 (void) parser;
686 if (this->value[0] != '\0') return; // Already evaluated, re-use previous
687 snprintf((char *) this->value, sizeof(this->value), "%d", getuid() );
690 /* Small helper to automate creation of one pre-defined macro in the parser */
691 static void w_create_macro(WMenuParser parser, const char *name, WParserMacroFunction *handler)
693 WParserMacro *macro;
695 macro = wmalloc(sizeof(*macro));
696 strncpy(macro->name, name, sizeof(macro->name) - 1);
697 macro->function = handler;
698 macro->arg_count = -1;
699 macro->next = parser->macros;
700 parser->macros = macro;
703 /***** Register all the pre-defined macros in the parser *****/
704 void menu_parser_register_preset_macros(WMenuParser parser)
706 /* Defined by CPP: common predefined macros (GNU C extension) */
707 w_create_macro(parser, "__BASE_FILE__", mpm_base_file);
708 w_create_macro(parser, "__INCLUDE_LEVEL__", mpm_include_level);
710 /* Defined by CPP: standard predefined macros */
711 w_create_macro(parser, "__FILE__", mpm_current_file);
712 w_create_macro(parser, "__LINE__", mpm_current_line);
713 // w_create_macro(parser, "__DATE__", NULL); [will be implemented only per user request]
714 // w_create_macro(parser, "__TIME__", NULL); [will be implemented only per user request]
716 /* Historically defined by WindowMaker */
717 w_create_macro(parser, "HOST", mpm_get_hostname);
718 w_create_macro(parser, "UID", mpm_get_user_id);
719 w_create_macro(parser, "USER", mpm_get_user_name);