aboutsummaryrefslogtreecommitdiff
path: root/miscutils
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2014-05-06 20:41:10 +0100
committerRon Yorston <rmy@pobox.com>2014-05-06 20:41:10 +0100
commitd3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e (patch)
tree4b364ba4b6b9e96c2629fe382fef0248d76833dd /miscutils
parent7905d97aeece18da362a5a1e066abff2d2e5c16b (diff)
parentd257608a8429b64e1a04c7cb6d99975eeb2c3955 (diff)
downloadbusybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.tar.gz
busybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.tar.bz2
busybox-w32-d3bef66324a8ca5eed9ad7c15ead3a1cc9a9151e.zip
Merge branch 'busybox' into merge
Conflicts: debianutils/which.c editors/vi.c libbb/executable.c
Diffstat (limited to 'miscutils')
-rw-r--r--miscutils/Config.src34
-rw-r--r--miscutils/Kbuild.src1
-rw-r--r--miscutils/adjtimex.c25
-rw-r--r--miscutils/crond.c332
-rw-r--r--miscutils/less.c38
5 files changed, 232 insertions, 198 deletions
diff --git a/miscutils/Config.src b/miscutils/Config.src
index 1da9800bd..1b2a3ae9a 100644
--- a/miscutils/Config.src
+++ b/miscutils/Config.src
@@ -133,40 +133,6 @@ config CHRT
133 manipulate real-time attributes of a process. 133 manipulate real-time attributes of a process.
134 This requires sched_{g,s}etparam support in your libc. 134 This requires sched_{g,s}etparam support in your libc.
135 135
136config CROND
137 bool "crond"
138 default y
139 select FEATURE_SYSLOG
140 help
141 Crond is a background daemon that parses individual crontab
142 files and executes commands on behalf of the users in question.
143 This is a port of dcron from slackware. It uses files of the
144 format /var/spool/cron/crontabs/<username> files, for example:
145 $ cat /var/spool/cron/crontabs/root
146 # Run daily cron jobs at 4:40 every day:
147 40 4 * * * /etc/cron/daily > /dev/null 2>&1
148
149config FEATURE_CROND_D
150 bool "Support option -d to redirect output to stderr"
151 depends on CROND
152 default y
153 help
154 -d sets loglevel to 0 (most verbose) and directs all output to stderr.
155
156config FEATURE_CROND_CALL_SENDMAIL
157 bool "Report command output via email (using sendmail)"
158 default y
159 depends on CROND
160 help
161 Command output will be sent to corresponding user via email.
162
163config FEATURE_CROND_DIR
164 string "crond spool directory"
165 default "/var/spool/cron"
166 depends on CROND || CRONTAB
167 help
168 Location of crond spool.
169
170config CRONTAB 136config CRONTAB
171 bool "crontab" 137 bool "crontab"
172 default y 138 default y
diff --git a/miscutils/Kbuild.src b/miscutils/Kbuild.src
index 9e164f16e..8eaa82de9 100644
--- a/miscutils/Kbuild.src
+++ b/miscutils/Kbuild.src
@@ -12,7 +12,6 @@ lib-$(CONFIG_BBCONFIG) += bbconfig.o
12lib-$(CONFIG_BEEP) += beep.o 12lib-$(CONFIG_BEEP) += beep.o
13lib-$(CONFIG_CHAT) += chat.o 13lib-$(CONFIG_CHAT) += chat.o
14lib-$(CONFIG_CHRT) += chrt.o 14lib-$(CONFIG_CHRT) += chrt.o
15lib-$(CONFIG_CROND) += crond.o
16lib-$(CONFIG_CRONTAB) += crontab.o 15lib-$(CONFIG_CRONTAB) += crontab.o
17lib-$(CONFIG_DC) += dc.o 16lib-$(CONFIG_DC) += dc.o
18lib-$(CONFIG_DEVFSD) += devfsd.o 17lib-$(CONFIG_DEVFSD) += devfsd.o
diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c
index c8816e9e7..534364a69 100644
--- a/miscutils/adjtimex.c
+++ b/miscutils/adjtimex.c
@@ -14,12 +14,12 @@
14//usage:#define adjtimex_trivial_usage 14//usage:#define adjtimex_trivial_usage
15//usage: "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]" 15//usage: "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]"
16//usage:#define adjtimex_full_usage "\n\n" 16//usage:#define adjtimex_full_usage "\n\n"
17//usage: "Read and optionally set system timebase parameters. See adjtimex(2)\n" 17//usage: "Read or set kernel time variables. See adjtimex(2)\n"
18//usage: "\n -q Quiet" 18//usage: "\n -q Quiet"
19//usage: "\n -o OFF Time offset, microseconds" 19//usage: "\n -o OFF Time offset, microseconds"
20//usage: "\n -f FREQ Frequency adjust, integer kernel units (65536 is 1ppm)" 20//usage: "\n -f FREQ Frequency adjust, integer kernel units (65536 is 1ppm)"
21//usage: "\n (positive values make clock run faster)"
22//usage: "\n -t TICK Microseconds per tick, usually 10000" 21//usage: "\n -t TICK Microseconds per tick, usually 10000"
22//usage: "\n (positive -t or -f values make clock run faster)"
23//usage: "\n -p TCONST" 23//usage: "\n -p TCONST"
24 24
25#include "libbb.h" 25#include "libbb.h"
@@ -111,13 +111,13 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv)
111 } 111 }
112 112
113 if (!(opt & OPT_quiet)) { 113 if (!(opt & OPT_quiet)) {
114 int sep; 114 const char *sep;
115 const char *name; 115 const char *name;
116 116
117 printf( 117 printf(
118 " mode: %d\n" 118 " mode: %d\n"
119 "-o offset: %ld\n" 119 "-o offset: %ld us\n"
120 "-f frequency: %ld\n" 120 "-f freq.adjust: %ld (65536 = 1ppm)\n"
121 " maxerror: %ld\n" 121 " maxerror: %ld\n"
122 " esterror: %ld\n" 122 " esterror: %ld\n"
123 " status: %d (", 123 " status: %d (",
@@ -125,15 +125,14 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv)
125 txc.esterror, txc.status); 125 txc.esterror, txc.status);
126 126
127 /* representative output of next code fragment: 127 /* representative output of next code fragment:
128 "PLL | PPSTIME" */ 128 * "PLL | PPSTIME"
129 */
129 name = statlist_name; 130 name = statlist_name;
130 sep = 0; 131 sep = "";
131 for (i = 0; statlist_bit[i]; i++) { 132 for (i = 0; statlist_bit[i]; i++) {
132 if (txc.status & statlist_bit[i]) { 133 if (txc.status & statlist_bit[i]) {
133 if (sep) 134 printf("%s%s", sep, name);
134 fputs(" | ", stdout); 135 sep = " | ";
135 fputs(name, stdout);
136 sep = 1;
137 } 136 }
138 name += strlen(name) + 1; 137 name += strlen(name) + 1;
139 } 138 }
@@ -143,9 +142,9 @@ int adjtimex_main(int argc UNUSED_PARAM, char **argv)
143 descript = nth_string(ret_code_descript, ret); 142 descript = nth_string(ret_code_descript, ret);
144 printf(")\n" 143 printf(")\n"
145 "-p timeconstant: %ld\n" 144 "-p timeconstant: %ld\n"
146 " precision: %ld\n" 145 " precision: %ld us\n"
147 " tolerance: %ld\n" 146 " tolerance: %ld\n"
148 "-t tick: %ld\n" 147 "-t tick: %ld us\n"
149 " time.tv_sec: %ld\n" 148 " time.tv_sec: %ld\n"
150 " time.tv_usec: %ld\n" 149 " time.tv_usec: %ld\n"
151 " return value: %d (%s)\n", 150 " return value: %d (%s)\n",
diff --git a/miscutils/crond.c b/miscutils/crond.c
index 582dc991a..3659b9a6f 100644
--- a/miscutils/crond.c
+++ b/miscutils/crond.c
@@ -1,7 +1,5 @@
1/* vi: set sw=4 ts=4: */ 1/* vi: set sw=4 ts=4: */
2/* 2/*
3 * crond -d[#] -c <crondir> -f -b
4 *
5 * run as root, but NOT setuid root 3 * run as root, but NOT setuid root
6 * 4 *
7 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) 5 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
@@ -10,6 +8,43 @@
10 * 8 *
11 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 */ 10 */
11//config:config CROND
12//config: bool "crond"
13//config: default y
14//config: select FEATURE_SYSLOG
15//config: help
16//config: Crond is a background daemon that parses individual crontab
17//config: files and executes commands on behalf of the users in question.
18//config: This is a port of dcron from slackware. It uses files of the
19//config: format /var/spool/cron/crontabs/<username> files, for example:
20//config: $ cat /var/spool/cron/crontabs/root
21//config: # Run daily cron jobs at 4:40 every day:
22//config: 40 4 * * * /etc/cron/daily > /dev/null 2>&1
23//config:
24//config:config FEATURE_CROND_D
25//config: bool "Support option -d to redirect output to stderr"
26//config: depends on CROND
27//config: default y
28//config: help
29//config: -d N sets loglevel (0:most verbose) and directs all output to stderr.
30//config:
31//config:config FEATURE_CROND_CALL_SENDMAIL
32//config: bool "Report command output via email (using sendmail)"
33//config: default y
34//config: depends on CROND
35//config: help
36//config: Command output will be sent to corresponding user via email.
37//config:
38//config:config FEATURE_CROND_DIR
39//config: string "crond spool directory"
40//config: default "/var/spool/cron"
41//config: depends on CROND || CRONTAB
42//config: help
43//config: Location of crond spool.
44
45//applet:IF_CROND(APPLET(crond, BB_DIR_USR_SBIN, BB_SUID_DROP))
46
47//kbuild:lib-$(CONFIG_CROND) += crond.o
13 48
14//usage:#define crond_trivial_usage 49//usage:#define crond_trivial_usage
15//usage: "-fbS -l N " IF_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR" 50//usage: "-fbS -l N " IF_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR"
@@ -17,12 +52,12 @@
17//usage: " -f Foreground" 52//usage: " -f Foreground"
18//usage: "\n -b Background (default)" 53//usage: "\n -b Background (default)"
19//usage: "\n -S Log to syslog (default)" 54//usage: "\n -S Log to syslog (default)"
20//usage: "\n -l Set log level. 0 is the most verbose, default 8" 55//usage: "\n -l N Set log level. Most verbose:0, default:8"
21//usage: IF_FEATURE_CROND_D( 56//usage: IF_FEATURE_CROND_D(
22//usage: "\n -d Set log level, log to stderr" 57//usage: "\n -d N Set log level, log to stderr"
23//usage: ) 58//usage: )
24//usage: "\n -L Log to file" 59//usage: "\n -L FILE Log to FILE"
25//usage: "\n -c Working dir" 60//usage: "\n -c DIR Cron dir. Default:"CONFIG_FEATURE_CROND_DIR"/crontabs"
26 61
27#include "libbb.h" 62#include "libbb.h"
28#include <syslog.h> 63#include <syslog.h>
@@ -36,7 +71,7 @@
36#endif 71#endif
37 72
38 73
39#define TMPDIR CONFIG_FEATURE_CROND_DIR 74#define CRON_DIR CONFIG_FEATURE_CROND_DIR
40#define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" 75#define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs"
41#ifndef SENDMAIL 76#ifndef SENDMAIL
42# define SENDMAIL "sendmail" 77# define SENDMAIL "sendmail"
@@ -69,6 +104,7 @@ typedef struct CronLine {
69 int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ 104 int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */
70 char *cl_mailto; /* whom to mail results, may be NULL */ 105 char *cl_mailto; /* whom to mail results, may be NULL */
71#endif 106#endif
107 char *cl_shell;
72 /* ordered by size, not in natural order. makes code smaller: */ 108 /* ordered by size, not in natural order. makes code smaller: */
73 char cl_Dow[7]; /* 0-6, beginning sunday */ 109 char cl_Dow[7]; /* 0-6, beginning sunday */
74 char cl_Mons[12]; /* 0-11 */ 110 char cl_Mons[12]; /* 0-11 */
@@ -90,12 +126,6 @@ enum {
90 OPT_c = (1 << 5), 126 OPT_c = (1 << 5),
91 OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, 127 OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
92}; 128};
93#if ENABLE_FEATURE_CROND_D
94# define DebugOpt (option_mask32 & OPT_d)
95#else
96# define DebugOpt 0
97#endif
98
99 129
100struct globals { 130struct globals {
101 unsigned log_level; /* = 8; */ 131 unsigned log_level; /* = 8; */
@@ -106,6 +136,8 @@ struct globals {
106#if SETENV_LEAKS 136#if SETENV_LEAKS
107 char *env_var_user; 137 char *env_var_user;
108 char *env_var_home; 138 char *env_var_home;
139 char *env_var_shell;
140 char *env_var_logname;
109#endif 141#endif
110} FIX_ALIASING; 142} FIX_ALIASING;
111#define G (*(struct globals*)&bb_common_bufsiz1) 143#define G (*(struct globals*)&bb_common_bufsiz1)
@@ -114,56 +146,56 @@ struct globals {
114 G.crontab_dir_name = CRONTABS; \ 146 G.crontab_dir_name = CRONTABS; \
115} while (0) 147} while (0)
116 148
149/* Log levels:
150 * 0 is the most verbose, default 8.
151 * For some reason, in fact only 5, 7 and 8 are used.
152 */
153static void crondlog(unsigned level, const char *msg, va_list va)
154{
155 if (level >= G.log_level) {
156 /*
157 * We are called only for info meesages.
158 * Warnings/errors use plain bb_[p]error_msg's, which
159 * need not touch syslog_level
160 * (they are ok with LOG_ERR default).
161 */
162 syslog_level = LOG_INFO;
163 bb_verror_msg(msg, va, /* strerr: */ NULL);
164 syslog_level = LOG_ERR;
165 }
166}
117 167
118/* 0 is the most verbose, default 8 */ 168static void log5(const char *msg, ...)
119#define LVL5 "\x05" 169{
120#define LVL7 "\x07" 170 va_list va;
121#define LVL8 "\x08" 171 va_start(va, msg);
122#define WARN9 "\x49" 172 crondlog(4, msg, va);
123#define DIE9 "\xc9" 173 va_end(va);
124/* level >= 20 is "error" */ 174}
125#define ERR20 "\x14"
126 175
127static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2))); 176static void log7(const char *msg, ...)
128static void crondlog(const char *ctl, ...)
129{ 177{
130 va_list va; 178 va_list va;
131 int level = (ctl[0] & 0x1f); 179 va_start(va, msg);
132 180 crondlog(7, msg, va);
133 va_start(va, ctl); 181 va_end(va);
134 if (level >= (int)G.log_level) { 182}
135 /* Debug mode: all to (non-redirected) stderr, */ 183
136 /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ 184static void log8(const char *msg, ...)
137 if (!DebugOpt && G.log_filename) { 185{
138 /* Otherwise (log to file): we reopen log file at every write: */ 186 va_list va;
139 int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND); 187 va_start(va, msg);
140 if (logfd >= 0) 188 crondlog(8, msg, va);
141 xmove_fd(logfd, STDERR_FILENO);
142 }
143 /* When we log to syslog, level > 8 is logged at LOG_ERR
144 * syslog level, level <= 8 is logged at LOG_INFO. */
145 if (level > 8) {
146 bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
147 } else {
148 char *msg = NULL;
149 vasprintf(&msg, ctl + 1, va);
150 bb_info_msg("%s: %s", applet_name, msg);
151 free(msg);
152 }
153 }
154 va_end(va); 189 va_end(va);
155 if (ctl[0] & 0x80)
156 exit(20);
157} 190}
158 191
192
159static const char DowAry[] ALIGN1 = 193static const char DowAry[] ALIGN1 =
160 "sun""mon""tue""wed""thu""fri""sat" 194 "sun""mon""tue""wed""thu""fri""sat"
161 /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
162; 195;
163 196
164static const char MonAry[] ALIGN1 = 197static const char MonAry[] ALIGN1 =
165 "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec" 198 "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
166 /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
167; 199;
168 200
169static void ParseField(char *user, char *ary, int modvalue, int off, 201static void ParseField(char *user, char *ary, int modvalue, int off,
@@ -267,12 +299,12 @@ static void ParseField(char *user, char *ary, int modvalue, int off,
267 299
268 if (*ptr) { 300 if (*ptr) {
269 err: 301 err:
270 crondlog(WARN9 "user %s: parse error at %s", user, base); 302 bb_error_msg("user %s: parse error at %s", user, base);
271 return; 303 return;
272 } 304 }
273 305
274 if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */ 306 /* can't use log5 (it inserts newlines), open-coding it */
275 /* can't use crondlog, it inserts '\n' */ 307 if (G.log_level <= 5 && logmode != LOGMODE_SYSLOG) {
276 int i; 308 int i;
277 for (i = 0; i < modvalue; ++i) 309 for (i = 0; i < modvalue; ++i)
278 fprintf(stderr, "%d", (unsigned char)ary[i]); 310 fprintf(stderr, "%d", (unsigned char)ary[i]);
@@ -368,11 +400,12 @@ static void load_crontab(const char *fileName)
368#if ENABLE_FEATURE_CROND_CALL_SENDMAIL 400#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
369 char *mailTo = NULL; 401 char *mailTo = NULL;
370#endif 402#endif
403 char *shell = NULL;
371 404
372 delete_cronfile(fileName); 405 delete_cronfile(fileName);
373 406
374 if (!getpwnam(fileName)) { 407 if (!getpwnam(fileName)) {
375 crondlog(LVL7 "ignoring file '%s' (no such user)", fileName); 408 log7("ignoring file '%s' (no such user)", fileName);
376 return; 409 return;
377 } 410 }
378 411
@@ -393,14 +426,16 @@ static void load_crontab(const char *fileName)
393 while (1) { 426 while (1) {
394 CronLine *line; 427 CronLine *line;
395 428
396 if (!--maxLines) 429 if (!--maxLines) {
430 bb_error_msg("user %s: too many lines", fileName);
397 break; 431 break;
432 }
433
398 n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); 434 n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
399 if (!n) 435 if (!n)
400 break; 436 break;
401 437
402 if (DebugOpt) 438 log5("user:%s entry:%s", fileName, parser->data);
403 crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
404 439
405 /* check if line is setting MAILTO= */ 440 /* check if line is setting MAILTO= */
406 if (0 == strncmp(tokens[0], "MAILTO=", 7)) { 441 if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
@@ -410,6 +445,24 @@ static void load_crontab(const char *fileName)
410#endif /* otherwise just ignore such lines */ 445#endif /* otherwise just ignore such lines */
411 continue; 446 continue;
412 } 447 }
448 if (0 == strncmp(tokens[0], "SHELL=", 6)) {
449 free(shell);
450 shell = xstrdup(&tokens[0][6]);
451 continue;
452 }
453//TODO: handle HOME= too? "man crontab" says:
454//name = value
455//
456//where the spaces around the equal-sign (=) are optional, and any subsequent
457//non-leading spaces in value will be part of the value assigned to name.
458//The value string may be placed in quotes (single or double, but matching)
459//to preserve leading or trailing blanks.
460//
461//Several environment variables are set up automatically by the cron(8) daemon.
462//SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd
463//line of the crontab's owner. HOME and SHELL may be overridden by settings
464//in the crontab; LOGNAME may not.
465
413 /* check if a minimum of tokens is specified */ 466 /* check if a minimum of tokens is specified */
414 if (n < 6) 467 if (n < 6)
415 continue; 468 continue;
@@ -429,11 +482,9 @@ static void load_crontab(const char *fileName)
429 /* copy mailto (can be NULL) */ 482 /* copy mailto (can be NULL) */
430 line->cl_mailto = xstrdup(mailTo); 483 line->cl_mailto = xstrdup(mailTo);
431#endif 484#endif
485 line->cl_shell = xstrdup(shell);
432 /* copy command */ 486 /* copy command */
433 line->cl_cmd = xstrdup(tokens[5]); 487 line->cl_cmd = xstrdup(tokens[5]);
434 if (DebugOpt) {
435 crondlog(LVL5 " command:%s", tokens[5]);
436 }
437 pline = &line->cl_next; 488 pline = &line->cl_next;
438//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); 489//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
439 } 490 }
@@ -441,12 +492,12 @@ static void load_crontab(const char *fileName)
441 492
442 file->cf_next = G.cron_files; 493 file->cf_next = G.cron_files;
443 G.cron_files = file; 494 G.cron_files = file;
444
445 if (maxLines == 0) {
446 crondlog(WARN9 "user %s: too many lines", fileName);
447 }
448 } 495 }
449 config_close(parser); 496 config_close(parser);
497#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
498 free(mailTo);
499#endif
500 free(shell);
450} 501}
451 502
452static void process_cron_update_file(void) 503static void process_cron_update_file(void)
@@ -482,17 +533,16 @@ static void rescan_crontab_dir(void)
482 /* Remove cron update file */ 533 /* Remove cron update file */
483 unlink(CRONUPDATE); 534 unlink(CRONUPDATE);
484 /* Re-chdir, in case directory was renamed & deleted */ 535 /* Re-chdir, in case directory was renamed & deleted */
485 if (chdir(G.crontab_dir_name) < 0) { 536 xchdir(G.crontab_dir_name);
486 crondlog(DIE9 "chdir(%s)", G.crontab_dir_name);
487 }
488 537
489 /* Scan directory and add associated users */ 538 /* Scan directory and add associated users */
490 { 539 {
491 DIR *dir = opendir("."); 540 DIR *dir = opendir(".");
492 struct dirent *den; 541 struct dirent *den;
493 542
543 /* xopendir exists, but "can't open '.'" is not informative */
494 if (!dir) 544 if (!dir)
495 crondlog(DIE9 "chdir(%s)", "."); /* exits */ 545 bb_error_msg_and_die("can't open '%s'", G.crontab_dir_name);
496 while ((den = readdir(dir)) != NULL) { 546 while ((den = readdir(dir)) != NULL) {
497 if (strchr(den->d_name, '.') != NULL) { 547 if (strchr(den->d_name, '.') != NULL) {
498 continue; 548 continue;
@@ -519,19 +569,22 @@ static void safe_setenv(char **pvar_val, const char *var, const char *val)
519} 569}
520#endif 570#endif
521 571
522static void set_env_vars(struct passwd *pas) 572static void set_env_vars(struct passwd *pas, const char *shell)
523{ 573{
574 /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL.
575 * We assume crond inherited suitable PATH.
576 */
524#if SETENV_LEAKS 577#if SETENV_LEAKS
578 safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name);
525 safe_setenv(&G.env_var_user, "USER", pas->pw_name); 579 safe_setenv(&G.env_var_user, "USER", pas->pw_name);
526 safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); 580 safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
527 /* if we want to set user's shell instead: */ 581 safe_setenv(&G.env_var_shell, "SHELL", shell);
528 /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/
529#else 582#else
583 xsetenv("LOGNAME", pas->pw_name);
530 xsetenv("USER", pas->pw_name); 584 xsetenv("USER", pas->pw_name);
531 xsetenv("HOME", pas->pw_dir); 585 xsetenv("HOME", pas->pw_dir);
586 xsetenv("SHELL", shell);
532#endif 587#endif
533 /* currently, we use constant one: */
534 /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
535} 588}
536 589
537static void change_user(struct passwd *pas) 590static void change_user(struct passwd *pas)
@@ -539,10 +592,8 @@ static void change_user(struct passwd *pas)
539 /* careful: we're after vfork! */ 592 /* careful: we're after vfork! */
540 change_identity(pas); /* - initgroups, setgid, setuid */ 593 change_identity(pas); /* - initgroups, setgid, setuid */
541 if (chdir(pas->pw_dir) < 0) { 594 if (chdir(pas->pw_dir) < 0) {
542 crondlog(WARN9 "chdir(%s)", pas->pw_dir); 595 bb_error_msg("can't change directory to '%s'", pas->pw_dir);
543 if (chdir(TMPDIR) < 0) { 596 xchdir(CRON_DIR);
544 crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */
545 }
546 } 597 }
547} 598}
548 599
@@ -550,46 +601,53 @@ static void change_user(struct passwd *pas)
550#if ENABLE_FEATURE_CROND_CALL_SENDMAIL 601#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
551 602
552static pid_t 603static pid_t
553fork_job(const char *user, int mailFd, 604fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail)
554 const char *prog, 605{
555 const char *shell_cmd /* if NULL, we run sendmail */
556) {
557 struct passwd *pas; 606 struct passwd *pas;
607 const char *shell, *prog;
608 smallint sv_logmode;
558 pid_t pid; 609 pid_t pid;
559 610
560 /* prepare things before vfork */ 611 /* prepare things before vfork */
561 pas = getpwnam(user); 612 pas = getpwnam(user);
562 if (!pas) { 613 if (!pas) {
563 crondlog(WARN9 "can't get uid for %s", user); 614 bb_error_msg("can't get uid for %s", user);
564 goto err; 615 goto err;
565 } 616 }
566 set_env_vars(pas);
567 617
618 shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL;
619 prog = run_sendmail ? SENDMAIL : shell;
620
621 set_env_vars(pas, shell);
622
623 sv_logmode = logmode;
568 pid = vfork(); 624 pid = vfork();
569 if (pid == 0) { 625 if (pid == 0) {
570 /* CHILD */ 626 /* CHILD */
571 /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ 627 /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
572 change_user(pas); 628 change_user(pas);
573 if (DebugOpt) { 629 log5("child running %s", prog);
574 crondlog(LVL5 "child running %s", prog);
575 }
576 if (mailFd >= 0) { 630 if (mailFd >= 0) {
577 xmove_fd(mailFd, shell_cmd ? 1 : 0); 631 xmove_fd(mailFd, run_sendmail ? 0 : 1);
578 dup2(1, 2); 632 dup2(1, 2);
579 } 633 }
580 /* crond 3.0pl1-100 puts tasks in separate process groups */ 634 /* crond 3.0pl1-100 puts tasks in separate process groups */
581 bb_setpgrp(); 635 bb_setpgrp();
582 execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL); 636 if (!run_sendmail)
583 crondlog(ERR20 "can't execute '%s' for user %s", prog, user); 637 execlp(prog, prog, "-c", line->cl_cmd, (char *) NULL);
584 if (shell_cmd) { 638 else
585 fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd); 639 execlp(prog, prog, SENDMAIL_ARGS, (char *) NULL);
586 } 640 /*
587 _exit(EXIT_SUCCESS); 641 * I want this error message on stderr too,
642 * even if other messages go only to syslog:
643 */
644 logmode |= LOGMODE_STDIO;
645 bb_error_msg_and_die("can't execute '%s' for user %s", prog, user);
588 } 646 }
647 logmode = sv_logmode;
589 648
590 if (pid < 0) { 649 if (pid < 0) {
591 /* FORK FAILED */ 650 bb_perror_msg("vfork");
592 crondlog(ERR20 "can't vfork");
593 err: 651 err:
594 pid = 0; 652 pid = 0;
595 } /* else: PARENT, FORK SUCCESS */ 653 } /* else: PARENT, FORK SUCCESS */
@@ -614,7 +672,7 @@ static void start_one_job(const char *user, CronLine *line)
614 672
615 if (line->cl_mailto) { 673 if (line->cl_mailto) {
616 /* Open mail file (owner is root so nobody can screw with it) */ 674 /* Open mail file (owner is root so nobody can screw with it) */
617 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); 675 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, getpid());
618 mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); 676 mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
619 677
620 if (mailFd >= 0) { 678 if (mailFd >= 0) {
@@ -622,18 +680,18 @@ static void start_one_job(const char *user, CronLine *line)
622 line->cl_cmd); 680 line->cl_cmd);
623 line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR); 681 line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR);
624 } else { 682 } else {
625 crondlog(ERR20 "can't create mail file %s for user %s, " 683 bb_error_msg("can't create mail file %s for user %s, "
626 "discarding output", mailFile, user); 684 "discarding output", mailFile, user);
627 } 685 }
628 } 686 }
629 687
630 line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd); 688 line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 0);
631 if (mailFd >= 0) { 689 if (mailFd >= 0) {
632 if (line->cl_pid <= 0) { 690 if (line->cl_pid <= 0) {
633 unlink(mailFile); 691 unlink(mailFile);
634 } else { 692 } else {
635 /* rename mail-file based on pid of process */ 693 /* rename mail-file based on pid of process */
636 char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid); 694 char *mailFile2 = xasprintf("%s/cron.%s.%d", CRON_DIR, user, (int)line->cl_pid);
637 rename(mailFile, mailFile2); // TODO: xrename? 695 rename(mailFile, mailFile2); // TODO: xrename?
638 free(mailFile2); 696 free(mailFile2);
639 } 697 }
@@ -665,7 +723,7 @@ static void process_finished_job(const char *user, CronLine *line)
665 * End of primary job - check for mail file. 723 * End of primary job - check for mail file.
666 * If size has changed and the file is still valid, we send it. 724 * If size has changed and the file is still valid, we send it.
667 */ 725 */
668 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid); 726 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, (int)pid);
669 mailFd = open(mailFile, O_RDONLY); 727 mailFd = open(mailFile, O_RDONLY);
670 unlink(mailFile); 728 unlink(mailFile);
671 if (mailFd < 0) { 729 if (mailFd < 0) {
@@ -683,43 +741,41 @@ static void process_finished_job(const char *user, CronLine *line)
683 } 741 }
684 line->cl_empty_mail_size = 0; 742 line->cl_empty_mail_size = 0;
685 /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */ 743 /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */
686 line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL); 744 line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 1);
687} 745}
688 746
689#else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ 747#else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
690 748
691static void start_one_job(const char *user, CronLine *line) 749static void start_one_job(const char *user, CronLine *line)
692{ 750{
751 const char *shell;
693 struct passwd *pas; 752 struct passwd *pas;
694 pid_t pid; 753 pid_t pid;
695 754
696 pas = getpwnam(user); 755 pas = getpwnam(user);
697 if (!pas) { 756 if (!pas) {
698 crondlog(WARN9 "can't get uid for %s", user); 757 bb_error_msg("can't get uid for %s", user);
699 goto err; 758 goto err;
700 } 759 }
701 760
702 /* Prepare things before vfork */ 761 /* Prepare things before vfork */
703 set_env_vars(pas); 762 shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL;
763 set_env_vars(pas, shell);
704 764
705 /* Fork as the user in question and run program */ 765 /* Fork as the user in question and run program */
706 pid = vfork(); 766 pid = vfork();
707 if (pid == 0) { 767 if (pid == 0) {
708 /* CHILD */ 768 /* CHILD */
709 /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ 769 /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
710 change_user(pas); 770 change_user(pas);
711 if (DebugOpt) { 771 log5("child running %s", shell);
712 crondlog(LVL5 "child running %s", DEFAULT_SHELL);
713 }
714 /* crond 3.0pl1-100 puts tasks in separate process groups */ 772 /* crond 3.0pl1-100 puts tasks in separate process groups */
715 bb_setpgrp(); 773 bb_setpgrp();
716 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL); 774 execl(shell, shell, "-c", line->cl_cmd, (char *) NULL);
717 crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user); 775 bb_error_msg_and_die("can't execute '%s' for user %s", shell, user);
718 _exit(EXIT_SUCCESS);
719 } 776 }
720 if (pid < 0) { 777 if (pid < 0) {
721 /* FORK FAILED */ 778 bb_perror_msg("vfork");
722 crondlog(ERR20 "can't vfork");
723 err: 779 err:
724 pid = 0; 780 pid = 0;
725 } 781 }
@@ -751,24 +807,20 @@ static void flag_starting_jobs(time_t t1, time_t t2)
751 807
752 ptm = localtime(&t); 808 ptm = localtime(&t);
753 for (file = G.cron_files; file; file = file->cf_next) { 809 for (file = G.cron_files; file; file = file->cf_next) {
754 if (DebugOpt) 810 log5("file %s:", file->cf_username);
755 crondlog(LVL5 "file %s:", file->cf_username);
756 if (file->cf_deleted) 811 if (file->cf_deleted)
757 continue; 812 continue;
758 for (line = file->cf_lines; line; line = line->cl_next) { 813 for (line = file->cf_lines; line; line = line->cl_next) {
759 if (DebugOpt) 814 log5(" line %s", line->cl_cmd);
760 crondlog(LVL5 " line %s", line->cl_cmd);
761 if (line->cl_Mins[ptm->tm_min] 815 if (line->cl_Mins[ptm->tm_min]
762 && line->cl_Hrs[ptm->tm_hour] 816 && line->cl_Hrs[ptm->tm_hour]
763 && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) 817 && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
764 && line->cl_Mons[ptm->tm_mon] 818 && line->cl_Mons[ptm->tm_mon]
765 ) { 819 ) {
766 if (DebugOpt) { 820 log5(" job: %d %s",
767 crondlog(LVL5 " job: %d %s",
768 (int)line->cl_pid, line->cl_cmd); 821 (int)line->cl_pid, line->cl_cmd);
769 }
770 if (line->cl_pid > 0) { 822 if (line->cl_pid > 0) {
771 crondlog(LVL8 "user %s: process already running: %s", 823 log8("user %s: process already running: %s",
772 file->cf_username, line->cl_cmd); 824 file->cf_username, line->cl_cmd);
773 } else if (line->cl_pid == 0) { 825 } else if (line->cl_pid == 0) {
774 line->cl_pid = -1; 826 line->cl_pid = -1;
@@ -797,7 +849,7 @@ static void start_jobs(void)
797 849
798 start_one_job(file->cf_username, line); 850 start_one_job(file->cf_username, line);
799 pid = line->cl_pid; 851 pid = line->cl_pid;
800 crondlog(LVL8 "USER %s pid %3d cmd %s", 852 log8("USER %s pid %3d cmd %s",
801 file->cf_username, (int)pid, line->cl_cmd); 853 file->cf_username, (int)pid, line->cl_cmd);
802 if (pid < 0) { 854 if (pid < 0) {
803 file->cf_wants_starting = 1; 855 file->cf_wants_starting = 1;
@@ -849,12 +901,21 @@ static int check_completions(void)
849 return num_still_running; 901 return num_still_running;
850} 902}
851 903
904static void reopen_logfile_to_stderr(void)
905{
906 if (G.log_filename) {
907 int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
908 if (logfd >= 0)
909 xmove_fd(logfd, STDERR_FILENO);
910 }
911}
912
852int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 913int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
853int crond_main(int argc UNUSED_PARAM, char **argv) 914int crond_main(int argc UNUSED_PARAM, char **argv)
854{ 915{
855 time_t t2; 916 time_t t2;
856 int rescan; 917 unsigned rescan;
857 int sleep_time; 918 unsigned sleep_time;
858 unsigned opts; 919 unsigned opts;
859 920
860 INIT_G(); 921 INIT_G();
@@ -880,10 +941,11 @@ int crond_main(int argc UNUSED_PARAM, char **argv)
880 logmode = LOGMODE_SYSLOG; 941 logmode = LOGMODE_SYSLOG;
881 } 942 }
882 943
883 xchdir(G.crontab_dir_name);
884 //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ 944 //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
885 xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ 945
886 crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level); 946 reopen_logfile_to_stderr();
947 xchdir(G.crontab_dir_name);
948 log8("crond (busybox "BB_VER") started, log level %d", G.log_level);
887 rescan_crontab_dir(); 949 rescan_crontab_dir();
888 write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); 950 write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid");
889 951
@@ -896,14 +958,14 @@ int crond_main(int argc UNUSED_PARAM, char **argv)
896 time_t t1; 958 time_t t1;
897 long dt; 959 long dt;
898 960
899 t1 = t2;
900
901 /* Synchronize to 1 minute, minimum 1 second */ 961 /* Synchronize to 1 minute, minimum 1 second */
902 sleep(sleep_time - (time(NULL) % sleep_time) + 1); 962 t1 = t2;
903 963 sleep(sleep_time - (time(NULL) % sleep_time));
904 t2 = time(NULL); 964 t2 = time(NULL);
905 dt = (long)t2 - (long)t1; 965 dt = (long)t2 - (long)t1;
906 966
967 reopen_logfile_to_stderr();
968
907 /* 969 /*
908 * The file 'cron.update' is checked to determine new cron 970 * The file 'cron.update' is checked to determine new cron
909 * jobs. The directory is rescanned once an hour to deal 971 * jobs. The directory is rescanned once an hour to deal
@@ -931,20 +993,18 @@ int crond_main(int argc UNUSED_PARAM, char **argv)
931 rescan_crontab_dir(); 993 rescan_crontab_dir();
932 } 994 }
933 process_cron_update_file(); 995 process_cron_update_file();
934 if (DebugOpt) 996 log5("wakeup dt=%ld", dt);
935 crondlog(LVL5 "wakeup dt=%ld", dt);
936 if (dt < -60 * 60 || dt > 60 * 60) { 997 if (dt < -60 * 60 || dt > 60 * 60) {
937 crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60); 998 bb_error_msg("time disparity of %ld minutes detected", dt / 60);
938 /* and we do not run any jobs in this case */ 999 /* and we do not run any jobs in this case */
939 } else if (dt > 0) { 1000 } else if (dt > 0) {
940 /* Usual case: time advances forward, as expected */ 1001 /* Usual case: time advances forward, as expected */
941 flag_starting_jobs(t1, t2); 1002 flag_starting_jobs(t1, t2);
942 start_jobs(); 1003 start_jobs();
1004 sleep_time = 60;
943 if (check_completions() > 0) { 1005 if (check_completions() > 0) {
944 /* some jobs are still running */ 1006 /* some jobs are still running */
945 sleep_time = 10; 1007 sleep_time = 10;
946 } else {
947 sleep_time = 60;
948 } 1008 }
949 } 1009 }
950 /* else: time jumped back, do not run any jobs */ 1010 /* else: time jumped back, do not run any jobs */
diff --git a/miscutils/less.c b/miscutils/less.c
index 574f222e0..d84df469c 100644
--- a/miscutils/less.c
+++ b/miscutils/less.c
@@ -404,6 +404,9 @@ static void fill_match_lines(unsigned pos);
404 * last_line_pos - screen line position of next char to be read 404 * last_line_pos - screen line position of next char to be read
405 * (takes into account tabs and backspaces) 405 * (takes into account tabs and backspaces)
406 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error 406 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
407 *
408 * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
409 * "/search on very long input" and "reaching max line count" corner cases.
407 */ 410 */
408static void read_lines(void) 411static void read_lines(void)
409{ 412{
@@ -414,9 +417,13 @@ static void read_lines(void)
414#if ENABLE_FEATURE_LESS_REGEXP 417#if ENABLE_FEATURE_LESS_REGEXP
415 unsigned old_max_fline = max_fline; 418 unsigned old_max_fline = max_fline;
416 time_t last_time = 0; 419 time_t last_time = 0;
417 unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */ 420 int had_progress = 2;
418#endif 421#endif
419 422
423 /* (careful: max_fline can be -1) */
424 if (max_fline + 1 > MAXLINES)
425 return;
426
420 if (option_mask32 & FLAG_N) 427 if (option_mask32 & FLAG_N)
421 w -= 8; 428 w -= 8;
422 429
@@ -441,6 +448,7 @@ static void read_lines(void)
441 char c; 448 char c;
442 /* if no unprocessed chars left, eat more */ 449 /* if no unprocessed chars left, eat more */
443 if (readpos >= readeof) { 450 if (readpos >= readeof) {
451 errno = 0;
444 ndelay_on(0); 452 ndelay_on(0);
445 eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); 453 eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
446 ndelay_off(0); 454 ndelay_off(0);
@@ -448,6 +456,7 @@ static void read_lines(void)
448 readeof = eof_error; 456 readeof = eof_error;
449 if (eof_error <= 0) 457 if (eof_error <= 0)
450 goto reached_eof; 458 goto reached_eof;
459 IF_FEATURE_LESS_REGEXP(had_progress = 1;)
451 } 460 }
452 c = readbuf[readpos]; 461 c = readbuf[readpos];
453 /* backspace? [needed for manpages] */ 462 /* backspace? [needed for manpages] */
@@ -519,31 +528,23 @@ static void read_lines(void)
519#endif 528#endif
520 } 529 }
521 if (eof_error <= 0) { 530 if (eof_error <= 0) {
522 if (eof_error < 0) {
523 if (errno == EAGAIN) {
524 /* not yet eof or error, reset flag (or else
525 * we will hog CPU - select() will return
526 * immediately */
527 eof_error = 1;
528 } else {
529 print_statusline(bb_msg_read_error);
530 }
531 }
532#if !ENABLE_FEATURE_LESS_REGEXP 531#if !ENABLE_FEATURE_LESS_REGEXP
533 break; 532 break;
534#else 533#else
535 if (wanted_match < num_matches) { 534 if (wanted_match < num_matches) {
536 break; 535 break;
537 } else { /* goto_match called us */ 536 } /* else: goto_match() called us */
537 if (errno == EAGAIN) {
538 time_t t = time(NULL); 538 time_t t = time(NULL);
539 if (t != last_time) { 539 if (t != last_time) {
540 last_time = t; 540 last_time = t;
541 if (--seconds_p1 == 0) 541 if (--had_progress < 0)
542 break; 542 break;
543 } 543 }
544 sched_yield(); 544 sched_yield();
545 goto again0; /* go loop again (max 2 seconds) */ 545 goto again0;
546 } 546 }
547 break;
547#endif 548#endif
548 } 549 }
549 max_fline++; 550 max_fline++;
@@ -551,6 +552,15 @@ static void read_lines(void)
551 p = current_line; 552 p = current_line;
552 last_line_pos = 0; 553 last_line_pos = 0;
553 } /* end of "read lines until we reach cur_fline" loop */ 554 } /* end of "read lines until we reach cur_fline" loop */
555
556 if (eof_error < 0) {
557 if (errno == EAGAIN) {
558 eof_error = 1;
559 } else {
560 print_statusline(bb_msg_read_error);
561 }
562 }
563
554 fill_match_lines(old_max_fline); 564 fill_match_lines(old_max_fline);
555#if ENABLE_FEATURE_LESS_REGEXP 565#if ENABLE_FEATURE_LESS_REGEXP
556 /* prevent us from being stuck in search for a match */ 566 /* prevent us from being stuck in search for a match */