summaryrefslogtreecommitdiff
path: root/docs/nofork_noexec.txt
blob: 0ad4e6e605bfa31caf31916d9552ab6b179ce9c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
	NOEXEC and NOFORK applets.

Unix shells traditionally execute some commands internally in the attempt
to dramatically speed up execution. It will be slow as hell if for every
"echo blah" shell will fork and exec /bin/echo. To this end, shells
have to _reimplement_ these commands internally.

Busybox is unique in this regard because it already is a collection
of reimplemented Unix commands, and we can do the same trick
for speeding up busybox shells, and more. NOEXEC and NOFORK applets
are exactly those applets which are eligible for these tricks.

Applet will be subject to NOFORK/NOEXEC tricks if it is marked as such
in applets.h. FEATURE_PREFER_APPLETS is a config option which
globally enables usage of NOFORK/NOEXEC tricks.
If it is enabled, FEATURE_SH_STANDALONE can be enabled too,
and then shells will use NOFORK/NOEXEC tricks for ordinary commands.
NB: shell builtins use these tricks regardless of FEATURE_SH_STANDALONE
or FEATURE_PREFER_APPLETS.

In C, if you want to call a program and wait for it, use
spawn_and_wait(argv), BB_EXECVP(prog,argv) or BB_EXECLP(prog,argv0,...).
They check whether program name is an applet name and optionally
do NOFORK/NOEXEC thing depending on configuration.


	NOEXEC

NOEXEC applet should work correctly if another applet forks and then
executes exit(<applet>_main(argc,argv)) in the child. The rules
roughly are:

* do not expect shared global variables/buffers to be in their
  "initialized" state. Examples: xfunc_error_retval can be != 1,
  bb_common_bufsiz1 can be scribbled over, ...
  (although usually xfunc_error_retval's state is not a problem).
* do not expect that stdio wasn't used before. Calling set[v]buf()
  can be disastrous.
* ...

NOEXEC applets save only one half of fork+exec overhead.
NOEXEC trick is disabled for NOMMU build.


	NOFORK

NOFORK applet should work correctly if another applet simply runs
<applet>_main(argc,argv) and then continues with its business.
xargs, find, shells do it (grep for "spawn_and_wait" and
"run_nofork_applet" to find more users).

This poses much more serious limitations on what applet can do:

* all NOEXEC limitations apply.
* do not run for a long time or wait for user input:
  hush shell only handles signals (like ^C) after you return
  from APPLET_main().
* do not ever exit() or exec().
  - xfuncs are okay. They are using special trick to return
    to the caller applet instead of dying when they detect "x" condition.
  - you may "exit" to caller applet by calling xfunc_die(). Return value
    is taken from xfunc_error_retval.
  - fflush_stdout_and_exit(n) is ok to use.
* do not use shared global data, or save/restore shared global data
  (e.g. bb_common_bufsiz1) prior to returning.
  - getopt32() is ok to use. You do not need to save/restore option_mask32,
    it is already done by core code.
* if you allocate memory, you can use xmalloc() only on the very first
  allocation. All other allocations should use malloc[_or_warn]().
  After first allocation, you cannot use any xfuncs.
  Otherwise, failing xfunc will return to caller applet
  without freeing malloced data!
* All allocated data, opened files, signal handlers, termios settings,
  O_NONBLOCK flags etc should be freed/closed/restored prior to return.
* ...

NOFORK applets give the most of speed advantage, but are trickiest
to implement. In order to minimize amount of bugs and maintenance,
prime candidates for NOFORK-ification are those applets which
are small and easy to audit, and those which are more likely to be
frequently executed from shell/find/xargs, particularly in shell
script loops. Applets which mess with signal handlers, termios etc
are probably not worth the effort.

Any NOFORK applet is also a NOEXEC applet.


	Calling NOFORK applets

API to call NOFORK applets is two functions:

	run_nofork_applet(appno, argv)
	spawn_and_wait(argv) // only if FEATURE_PREFER_APPLETS=y

First one is directly used by shells if FEATURE_SH_NOFORK=y.
Second one is used by many applets, but main users are xargs and find.
It itself calls run_nofork_applet(), if argv[0] turned out to be a name
of a NOFORK applet.

run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval,
applet_name. Thus, for example, caller does not need to worry about
option_mask32 getting trashed.


	Calling NOEXEC applets

It's the same trusty spawn_and_wait(argv). If FEATURE_PREFER_APPLETS=y,
it does NOEXEC trick. It resets xfunc_error_retval = 1 and
logmode = LOGMODE_STDIO in the child.


	Relevant CONFIG options

FEATURE_PREFER_APPLETS
  BB_EXECVP(cmd, argv) will try to exec /proc/self/exe
  if command's name matches some applet name;
  spawn_and_wait(argv) will do NOFORK/NOEXEC tricks

//TODO: the above two things probably should have separate options?

FEATURE_SH_STANDALONE
  shells will try to exec /proc/self/exe if command's name matches
  some applet name; shells will do NOEXEC trick on NOEXEC applets

//TODO: split (same as for PREFER_APPLETS)

FEATURE_SH_NOFORK
  shells will do NOFORK trick on NOFORK applets