-
Notifications
You must be signed in to change notification settings - Fork 38
/
sh.h
605 lines (547 loc) · 19.3 KB
/
sh.h
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/* $OpenBSD: sh.h,v 1.77 2023/06/21 22:22:08 millert Exp $ */
/*
* Public Domain Bourne/Korn shell
*/
/* $From: sh.h,v 1.2 1994/05/19 18:32:40 michael Exp michael $ */
#include "config.h" /* system and option configuration info */
/* Start of common headers */
#include <limits.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <signal.h>
#include <stdbool.h>
/* end of common headers */
#define NELEM(a) (sizeof(a) / sizeof((a)[0]))
#define BIT(i) (1<<(i)) /* define bit in flag */
#define NUFILE 32 /* Number of user-accessible files */
#define FDBASE 10 /* First file usable by Shell */
#define BITS(t) (CHAR_BIT * sizeof(t))
/* Make MAGIC a char that might be printed to make bugs more obvious, but
* not a char that is used often. Also, can't use the high bit as it causes
* portability problems (calling strchr(x, 0x80|'x') is error prone).
*/
#define MAGIC (7) /* prefix for *?[!{,} during expand */
#define ISMAGIC(c) ((unsigned char)(c) == MAGIC)
#define LINE 4096 /* input line size */
extern const char *kshname; /* $0 */
extern pid_t kshpid; /* $$, shell pid */
extern pid_t procpid; /* pid of executing process */
extern uid_t ksheuid; /* effective uid of shell */
extern int exstat; /* exit status */
extern int subst_exstat; /* exit status of last $(..)/`..` */
extern const char *safe_prompt; /* safe prompt if PS1 substitution fails */
extern char username[]; /* username for \u prompt expansion */
extern int disable_subst; /* disable substitution during evaluation */
/*
* Area-based allocation built on malloc/free
*/
typedef struct Area {
struct link *freelist; /* free list */
} Area;
extern Area aperm; /* permanent object space */
#define APERM &aperm
#define ATEMP &genv->area
#ifdef KSH_DEBUG
# define kshdebug_init() kshdebug_init_()
# define kshdebug_printf(a) kshdebug_printf_ a
# define kshdebug_dump(a) kshdebug_dump_ a
#else /* KSH_DEBUG */
# define kshdebug_init()
# define kshdebug_printf(a)
# define kshdebug_dump(a)
#endif /* KSH_DEBUG */
/*
* parsing & execution environment
*/
struct env {
short type; /* environment type - see below */
short flags; /* EF_* */
Area area; /* temporary allocation area */
struct block *loc; /* local variables and functions */
short *savefd; /* original redirected fd's */
struct env *oenv; /* link to previous environment */
sigjmp_buf jbuf; /* long jump back to env creator */
struct temp *temps; /* temp files */
};
extern struct env *genv;
/* struct env.type values */
#define E_NONE 0 /* dummy environment */
#define E_PARSE 1 /* parsing command # */
#define E_FUNC 2 /* executing function # */
#define E_INCL 3 /* including a file via . # */
#define E_EXEC 4 /* executing command tree */
#define E_LOOP 5 /* executing for/while # */
#define E_ERRH 6 /* general error handler # */
/* # indicates env has valid jbuf (see unwind()) */
/* struct env.flag values */
#define EF_FUNC_PARSE BIT(0) /* function being parsed */
#define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */
#define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */
/* Do breaks/continues stop at env type e? */
#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE \
|| (t) == E_FUNC || (t) == E_INCL)
/* Do returns stop at env type e? */
#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL)
/* values for siglongjmp(e->jbuf, 0) */
#define LRETURN 1 /* return statement */
#define LEXIT 2 /* exit statement */
#define LERROR 3 /* errorf() called */
#define LLEAVE 4 /* untrappable exit/error */
#define LINTR 5 /* ^C noticed */
#define LBREAK 6 /* break statement */
#define LCONTIN 7 /* continue statement */
#define LSHELL 8 /* return to interactive shell() */
#define LAEXPR 9 /* error in arithmetic expression */
/* option processing */
#define OF_CMDLINE 0x01 /* command line */
#define OF_SET 0x02 /* set builtin */
#define OF_SPECIAL 0x04 /* a special variable changing */
#define OF_INTERNAL 0x08 /* set internally by shell */
#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL)
struct option {
const char *name; /* long name of option */
char c; /* character flag (if any) */
short flags; /* OF_* */
};
extern const struct option sh_options[];
/*
* flags (the order of these enums MUST match the order in misc.c(options[]))
*/
enum sh_flag {
FEXPORT = 0, /* -a: export all */
FBRACEEXPAND, /* enable {} globbing */
FBGNICE, /* bgnice */
FCOMMAND, /* -c: (invocation) execute specified command */
FCSHHISTORY, /* csh-style history enabled */
#ifdef EMACS
FEMACS, /* emacs command editing */
#endif
FERREXIT, /* -e: quit on error */
#ifdef EMACS
FGMACS, /* gmacs command editing */
#endif
FIGNOREEOF, /* eof does not exit */
FTALKING, /* -i: interactive */
FKEYWORD, /* -k: name=value anywhere */
FLOGIN, /* -l: a login shell */
FMARKDIRS, /* mark dirs with / in file name completion */
FMONITOR, /* -m: job control monitoring */
FNOCLOBBER, /* -C: don't overwrite existing files */
FNOEXEC, /* -n: don't execute any commands */
FNOGLOB, /* -f: don't do file globbing */
FNOHUP, /* -H: don't kill running jobs when login shell exits */
FNOLOG, /* don't save functions in history (ignored) */
FNOTIFY, /* -b: asynchronous job completion notification */
FNOUNSET, /* -u: using an unset var is an error */
FPHYSICAL, /* -o physical: don't do logical cd's/pwd's */
FPIPEFAIL, /* -o pipefail: all commands in pipeline can affect $? */
FPOSIX, /* -o posix: be posixly correct */
FPRIVILEGED, /* -p: use suid_profile */
FRESTRICTED, /* -r: restricted shell */
FSH, /* -o sh: favor sh behaviour */
FSTDIN, /* -s: (invocation) parse stdin */
FTRACKALL, /* -h: create tracked aliases for all commands */
FVERBOSE, /* -v: echo input */
#ifdef VI
FVI, /* vi command editing */
FVIRAW, /* always read in raw mode (ignored) */
FVISHOW8, /* display chars with 8th bit set as is (versus M-) */
FVITABCOMPLETE, /* enable tab as file name completion char */
FVIESCCOMPLETE, /* enable ESC as file name completion in command mode */
#endif
FXTRACE, /* -x: execution trace */
FTALKING_I, /* (internal): initial shell was interactive */
FNFLAGS /* (place holder: how many flags are there) */
};
#define Flag(f) (shell_flags[(int) (f)])
extern char shell_flags[FNFLAGS];
extern char null[]; /* null value for variable */
enum temp_type {
TT_HEREDOC_EXP, /* expanded heredoc */
TT_HIST_EDIT /* temp file used for history editing (fc -e) */
};
typedef enum temp_type Temp_type;
/* temp/heredoc files. The file is removed when the struct is freed. */
struct temp {
struct temp *next;
struct shf *shf;
int pid; /* pid of process parsed here-doc */
Temp_type type;
char *name;
};
/*
* stdio and our IO routines
*/
#define shl_spare (&shf_iob[0]) /* for c_read()/c_print() */
#define shl_stdout (&shf_iob[1])
#define shl_out (&shf_iob[2])
extern int shl_stdout_ok;
/*
* trap handlers
*/
typedef struct trap {
int signal; /* signal number */
const char *name; /* short name */
const char *mess; /* descriptive name */
char *trap; /* trap command */
volatile sig_atomic_t set; /* trap pending */
int flags; /* TF_* */
sig_t cursig; /* current handler (valid if TF_ORIG_* set) */
sig_t shtrap; /* shell signal handler */
} Trap;
/* values for Trap.flags */
#define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */
#define TF_USER_SET BIT(1) /* user has (tried to) set trap */
#define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */
#define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */
#define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */
#define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */
#define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */
#define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */
#define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */
#define TF_FATAL BIT(9) /* causes termination if not trapped */
/* values for setsig()/setexecsig() flags argument */
#define SS_RESTORE_MASK 0x3 /* how to restore a signal before an exec() */
#define SS_RESTORE_CURR 0 /* leave current handler in place */
#define SS_RESTORE_ORIG 1 /* restore original handler */
#define SS_RESTORE_DFL 2 /* restore to SIG_DFL */
#define SS_RESTORE_IGN 3 /* restore to SIG_IGN */
#define SS_FORCE BIT(3) /* set signal even if original signal ignored */
#define SS_USER BIT(4) /* user is doing the set (ie, trap command) */
#define SS_SHTRAP BIT(5) /* trap for internal use (CHLD,ALRM,WINCH) */
#define SIGEXIT_ 0 /* for trap EXIT */
#define SIGERR_ NSIG /* for trap ERR */
extern volatile sig_atomic_t trap; /* traps pending? */
extern volatile sig_atomic_t intrsig; /* pending trap interrupts command */
extern volatile sig_atomic_t fatal_trap; /* received a fatal signal */
extern volatile sig_atomic_t got_sigwinch;
extern Trap sigtraps[NSIG+1];
/*
* TMOUT support
*/
/* values for ksh_tmout_state */
enum tmout_enum {
TMOUT_EXECUTING = 0, /* executing commands */
TMOUT_READING, /* waiting for input */
TMOUT_LEAVING /* have timed out */
};
extern unsigned int ksh_tmout;
extern enum tmout_enum ksh_tmout_state;
/* For "You have stopped jobs" message */
extern int really_exit;
/*
* fast character classes
*/
#define C_ALPHA BIT(0) /* a-z_A-Z */
/* was C_DIGIT */
#define C_LEX1 BIT(2) /* \0 \t\n|&;<>() */
#define C_VAR1 BIT(3) /* *@#!$-? */
#define C_IFSWS BIT(4) /* \t \n (IFS white space) */
#define C_SUBOP1 BIT(5) /* "=-+?" */
#define C_SUBOP2 BIT(6) /* "#%" */
#define C_IFS BIT(7) /* $IFS */
#define C_QUOTE BIT(8) /* \n\t"#$&'()*;<>?[\`| (needing quoting) */
extern short ctypes [];
#define ctype(c, t) !!(ctypes[(unsigned char)(c)]&(t))
#define letter(c) ctype(c, C_ALPHA)
#define digit(c) isdigit((unsigned char)(c))
#define letnum(c) (ctype(c, C_ALPHA) || isdigit((unsigned char)(c)))
extern int ifs0; /* for "$*" */
/* Argument parsing for built-in commands and getopts command */
/* Values for Getopt.flags */
#define GF_ERROR BIT(0) /* call errorf() if there is an error */
#define GF_PLUSOPT BIT(1) /* allow +c as an option */
#define GF_NONAME BIT(2) /* don't print argv[0] in errors */
/* Values for Getopt.info */
#define GI_MINUS BIT(0) /* an option started with -... */
#define GI_PLUS BIT(1) /* an option started with +... */
#define GI_MINUSMINUS BIT(2) /* arguments were ended with -- */
typedef struct {
int optind;
int uoptind;/* what user sees in $OPTIND */
char *optarg;
int flags; /* see GF_* */
int info; /* see GI_* */
unsigned int p; /* 0 or index into argv[optind - 1] */
char buf[2]; /* for bad option OPTARG value */
} Getopt;
extern Getopt builtin_opt; /* for shell builtin commands */
extern Getopt user_opt; /* parsing state for getopts builtin command */
/* This for co-processes */
typedef int Coproc_id; /* something that won't (realistically) wrap */
struct coproc {
int read; /* pipe from co-process's stdout */
int readw; /* other side of read (saved temporarily) */
int write; /* pipe to co-process's stdin */
Coproc_id id; /* id of current output pipe */
int njobs; /* number of live jobs using output pipe */
void *job; /* 0 or job of co-process using input pipe */
};
extern struct coproc coproc;
/* Used in jobs.c and by coprocess stuff in exec.c */
extern sigset_t sm_default, sm_sigchld;
extern const char ksh_version[];
/* name of called builtin function (used by error functions) */
extern char *builtin_argv0;
extern int builtin_flag; /* flags of called builtin (SPEC_BI, etc.) */
/* current working directory, and size of memory allocated for same */
extern char *current_wd;
extern int current_wd_size;
/* Minimum required space to work with on a line - if the prompt leaves less
* space than this on a line, the prompt is truncated.
*/
#define MIN_EDIT_SPACE 7
/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
*/
#define MIN_COLS (2 + MIN_EDIT_SPACE + 3)
extern int x_cols; /* tty columns */
/* These to avoid bracket matching problems */
#define OPAREN '('
#define CPAREN ')'
#define OBRACK '['
#define CBRACK ']'
#define OBRACE '{'
#define CBRACE '}'
/* Determine the location of the system (common) profile */
#define KSH_SYSTEM_PROFILE "/etc/profile"
/* Used by v_evaluate() and setstr() to control action when error occurs */
#define KSH_UNWIND_ERROR 0x0 /* unwind the stack (longjmp) */
#define KSH_RETURN_ERROR 0x1 /* return 1/0 for success/failure */
#define KSH_IGNORE_RDONLY 0x4 /* ignore the read-only flag */
#include "shf.h"
#include "table.h"
#include "tree.h"
#include "expand.h"
#include "lex.h"
/* alloc.c */
Area * ainit(Area *);
void afreeall(Area *);
void * alloc(size_t, Area *);
void * areallocarray(void *, size_t, size_t, Area *);
void * aresize(void *, size_t, Area *);
void afree(void *, Area *);
/* c_ksh.c */
int c_cd(char **);
int c_pwd(char **);
int c_print(char **);
int c_whence(char **);
int c_command(char **);
int c_type(char **);
int c_typeset(char **);
int c_alias(char **);
int c_unalias(char **);
int c_let(char **);
int c_jobs(char **);
int c_fgbg(char **);
int c_kill(char **);
void getopts_reset(int);
int c_getopts(char **);
int c_bind(char **);
/* c_sh.c */
int c_label(char **);
int c_shift(char **);
int c_umask(char **);
int c_dot(char **);
int c_wait(char **);
int c_read(char **);
int c_eval(char **);
int c_trap(char **);
int c_brkcont(char **);
int c_exitreturn(char **);
int c_set(char **);
int c_unset(char **);
int c_ulimit(char **);
int c_times(char **);
int timex(struct op *, int, volatile int *);
void timex_hook(struct op *, char ** volatile *);
int c_exec(char **);
int c_builtin(char **);
/* c_test.c */
int c_test(char **);
/* edit.c: most prototypes in edit.h */
void x_init(void);
int x_read(char *, size_t);
void set_editmode(const char *);
/* emacs.c: most prototypes in edit.h */
int x_bind(const char *, const char *, int, int);
/* eval.c */
char * substitute(const char *, int);
char ** eval(char **, int);
char * evalstr(char *cp, int);
char * evalonestr(char *cp, int);
char *debunk(char *, const char *, size_t);
void expand(char *, XPtrV *, int);
int glob_str(char *, XPtrV *, int);
/* exec.c */
int execute(struct op * volatile, volatile int, volatile int *);
int shcomexec(char **);
struct tbl * findfunc(const char *, unsigned int, int);
int define(const char *, struct op *);
void builtin(const char *, int (*)(char **));
struct tbl * findcom(const char *, int);
void flushcom(int);
char * search(const char *, const char *, int, int *);
int search_access(const char *, int, int *);
int pr_menu(char *const *);
/* expr.c */
int evaluate(const char *, int64_t *, int, bool);
int v_evaluate(struct tbl *, const char *, volatile int, bool);
/* history.c */
void init_histvec(void);
void hist_init(Source *);
void hist_finish(void);
void histsave(int, const char *, int);
int c_fc(char **);
void c_fc_reset(void);
void sethistcontrol(const char *);
void sethistsize(int);
void sethistfile(const char *);
char ** histpos(void);
int histnum(int);
int findhist(int, int, const char *, int);
int findhistrel(const char *);
char **hist_get_newest(int);
/* io.c */
void errorf(const char *, ...)
__attribute__((__noreturn__, __format__ (printf, 1, 2)));
void warningf(bool, const char *, ...)
__attribute__((__format__ (printf, 2, 3)));
void bi_errorf(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void internal_errorf(const char *, ...)
__attribute__((__noreturn__, __format__ (printf, 1, 2)));
void internal_warningf(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void error_prefix(int);
void shellf(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void shprintf(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
#ifdef KSH_DEBUG
void kshdebug_init_(void);
void kshdebug_printf_(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void kshdebug_dump_(const char *, const void *, int);
#endif /* KSH_DEBUG */
int can_seek(int);
void initio(void);
int ksh_dup2(int, int, int);
int savefd(int);
void restfd(int, int);
void openpipe(int *);
void closepipe(int *);
int check_fd(char *, int, const char **);
void coproc_init(void);
void coproc_read_close(int);
void coproc_readw_close(int);
void coproc_write_close(int);
int coproc_getfd(int, const char **);
void coproc_cleanup(int);
struct temp *maketemp(Area *, Temp_type, struct temp **);
/* jobs.c */
void j_init(int);
void j_suspend(void);
void j_exit(void);
void j_change(void);
int exchild(struct op *, int, volatile int *, int);
void startlast(void);
int waitlast(void);
int waitfor(const char *, int *);
int j_kill(const char *, int);
int j_resume(const char *, int);
int j_jobs(const char *, int, int);
int j_njobs(void);
void j_notify(void);
pid_t j_async(void);
int j_stopped_running(void);
/* mail.c */
void mcheck(void);
void mcset(int64_t);
void mbset(char *);
void mpset(char *);
/* main.c */
int include(const char *, int, char **, int);
int command(const char *, int);
int shell(Source *volatile, int volatile);
void unwind(int) __attribute__((__noreturn__));
void newenv(int);
void quitenv(struct shf *);
void cleanup_parents_env(void);
void cleanup_proc_env(void);
/* misc.c */
void setctypes(const char *, int);
void initctypes(void);
char * u64ton(uint64_t, int);
char * str_save(const char *, Area *);
char * str_nsave(const char *, int, Area *);
int option(const char *);
char * getoptions(void);
void change_flag(enum sh_flag, int, int);
int parse_args(char **, int, int *);
int getn(const char *, int *);
int bi_getn(const char *, int *);
int gmatch_(const char *, const char *, int);
int has_globbing(const char *, const char *);
const unsigned char *pat_scan(const unsigned char *, const unsigned char *,
int);
void qsortp(void **, size_t, int (*)(const void *, const void *));
int xstrcmp(const void *, const void *);
void ksh_getopt_reset(Getopt *, int);
int ksh_getopt(char **, Getopt *, const char *);
void print_value_quoted(const char *);
void print_columns(struct shf *, int, char *(*)(void *, int, char *, int),
void *, int, int prefcol);
int strip_nuls(char *, int);
int blocking_read(int, char *, int);
int reset_nonblock(int);
char *ksh_get_wd(char *, int);
/* path.c */
int make_path(const char *, const char *, char **, XString *, int *);
void simplify_path(char *);
char *get_phys_path(const char *);
void set_current_wd(char *);
/* syn.c */
void initkeywords(void);
struct op * compile(Source *);
/* trap.c */
void inittraps(void);
void alarm_init(void);
Trap * gettrap(const char *, int);
void trapsig(int);
void intrcheck(void);
int fatal_trap_check(void);
int trap_pending(void);
void runtraps(int intr);
void runtrap(Trap *);
void cleartraps(void);
void restoresigs(void);
void settrap(Trap *, char *);
int block_pipe(void);
void restore_pipe(int);
int setsig(Trap *, sig_t, int);
void setexecsig(Trap *, int);
/* var.c */
void newblock(void);
void popblock(void);
void initvar(void);
struct tbl * global(const char *);
struct tbl * local(const char *, bool);
char * str_val(struct tbl *);
int64_t intval(struct tbl *);
int setstr(struct tbl *, const char *, int);
struct tbl *setint_v(struct tbl *, struct tbl *, bool);
void setint(struct tbl *, int64_t);
int getint(struct tbl *, int64_t *, bool);
struct tbl *typeset(const char *, int, int, int, int);
void unset(struct tbl *, int);
char * skip_varname(const char *, int);
char *skip_wdvarname(const char *, int);
int is_wdvarname(const char *, int);
int is_wdvarassign(const char *);
char ** makenv(void);
void change_random(void);
int array_ref_len(const char *);
char * arrayname(const char *);
void set_array(const char *, int, char **);
/* vi.c: see edit.h */