rcfile.c 35.3 KB
Newer Older
1
2
/* $Id$ */
/**************************************************************************
3
 *   rcfile.c                                                             *
4
 *                                                                        *
5
6
 *   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,  *
 *   2010, 2011, 2013, 2014 Free Software Foundation, Inc.                *
7
8
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
9
 *   the Free Software Foundation; either version 3, or (at your option)  *
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

26
#include <glob.h>
27
#include <stdarg.h>
28
29
30
#include <string.h>
#include <stdio.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
#include <unistd.h>
32
#include <ctype.h>
33

34
#ifndef DISABLE_NANORC
35

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
36
static const rcoption rcopts[] = {
37
    {"boldtext", BOLD_TEXT},
38
39
#ifndef DISABLE_JUSTIFY
    {"brackets", 0},
40
#endif
41
42
    {"const", CONST_UPDATE},  /* deprecated form, remove in 2018 */
    {"constantshow", CONST_UPDATE},
Chris Allegretta's avatar
Chris Allegretta committed
43
#ifndef DISABLE_WRAPJUSTIFY
44
    {"fill", 0},
Chris Allegretta's avatar
Chris Allegretta committed
45
#endif
46
47
#ifndef DISABLE_HISTORIES
    {"historylog", HISTORYLOG},
48
#endif
49
    {"morespace", MORE_SPACE},
50
#ifndef DISABLE_MOUSE
51
    {"mouse", USE_MOUSE},
52
#endif
53
#ifndef DISABLE_MULTIBUFFER
54
55
56
    {"multibuffer", MULTIBUFFER},
#endif
    {"nohelp", NO_HELP},
57
    {"nonewlines", NO_NEWLINES},
Chris Allegretta's avatar
Chris Allegretta committed
58
#ifndef DISABLE_WRAPPING
59
    {"nowrap", NO_WRAP},
Chris Allegretta's avatar
Chris Allegretta committed
60
#endif
61
#ifndef DISABLE_OPERATINGDIR
62
    {"operatingdir", 0},
63
64
#endif
#ifndef DISABLE_HISTORIES
65
66
    {"poslog", POS_HISTORY},  /* deprecated form, remove in 2018 */
    {"positionlog", POS_HISTORY},
67
#endif
Chris Allegretta's avatar
Chris Allegretta committed
68
    {"preserve", PRESERVE},
69
#ifndef DISABLE_JUSTIFY
70
    {"punct", 0},
71
72
    {"quotestr", 0},
#endif
73
    {"rebinddelete", REBIND_DELETE},
74
    {"rebindkeypad", REBIND_KEYPAD},
75
76
77
#ifdef HAVE_REGEX_H
    {"regexp", USE_REGEXP},
#endif
78
#ifndef DISABLE_SPELLER
79
    {"speller", 0},
80
81
82
#endif
    {"suspend", SUSPEND},
    {"tabsize", 0},
83
    {"tempfile", TEMP_FILE},
84
    {"view", VIEW_MODE},
85
#ifndef NANO_TINY
86
    {"allow_insecure_backup", INSECURE_BACKUP},
87
88
89
90
91
92
    {"autoindent", AUTOINDENT},
    {"backup", BACKUP_FILE},
    {"backupdir", 0},
    {"backwards", BACKWARDS_SEARCH},
    {"casesensitive", CASE_SENSITIVE},
    {"cut", CUT_TO_END},
93
    {"justifytrim", JUSTIFY_TRIM},
94
    {"locking", LOCKING},
95
    {"matchbrackets", 0},
96
    {"noconvert", NO_CONVERT},
97
    {"quickblank", QUICK_BLANK},
98
    {"quiet", QUIET},
99
100
    {"smarthome", SMART_HOME},
    {"smooth", SMOOTH_SCROLL},
101
    {"softwrap", SOFTWRAP},
102
    {"tabstospaces", TABS_TO_SPACES},
103
    {"unix", MAKE_IT_UNIX},
104
    {"whitespace", 0},
105
    {"wordbounds", WORD_BOUNDS},
106
107
108
109
110
111
#endif
#ifndef DISABLE_COLOR
    {"titlecolor", 0},
    {"statuscolor", 0},
    {"keycolor", 0},
    {"functioncolor", 0},
112
#endif
113
    {NULL, 0}
114
};
115

116
static bool errors = FALSE;
117
	/* Whether we got any errors while parsing an rcfile. */
118
static size_t lineno = 0;
119
	/* If we did, the line number where the last error occurred. */
120
static char *nanorc = NULL;
121
	/* The path to the rcfile we're parsing. */
122
#ifndef DISABLE_COLOR
123
124
125
126
127
static syntaxtype *endsyntax = NULL;
	/* The end of the list of syntaxes. */
static colortype *endcolor = NULL;
	/* The end of the color list for the current syntax. */
#endif
128

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
129
130
131
/* We have an error in some part of the rcfile.  Print the error message
 * on stderr, and then make the user hit Enter to continue starting
 * nano. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
void rcfile_error(const char *msg, ...)
133
134
135
{
    va_list ap;

136
137
138
    if (ISSET(QUIET))
	return;

139
    fprintf(stderr, "\n");
140
141
    if (lineno > 0) {
	errors = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
142
	fprintf(stderr, _("Error in %s on line %lu: "), nanorc, (unsigned long)lineno);
143
    }
Chris Allegretta's avatar
Chris Allegretta committed
144

145
    va_start(ap, msg);
146
    vfprintf(stderr, _(msg), ap);
147
    va_end(ap);
148
149

    fprintf(stderr, "\n");
150
}
151
#endif /* !DISABLE_NANORC */
152

153
#if !defined(DISABLE_NANORC) || !defined(DISABLE_HISTORIES)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
154
155
156
/* Parse the next word from the string, null-terminate it, and return
 * a pointer to the first character after the null terminator.  The
 * returned pointer will point to '\0' if we hit the end of the line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
157
char *parse_next_word(char *ptr)
158
{
159
    while (!isblank(*ptr) && *ptr != '\0')
160
161
162
	ptr++;

    if (*ptr == '\0')
163
	return ptr;
164

165
166
    /* Null-terminate and advance ptr. */
    *ptr++ = '\0';
167

168
    while (isblank(*ptr))
169
170
171
172
	ptr++;

    return ptr;
}
173
#endif /* !DISABLE_NANORC || !DISABLE_HISTORIES */
174

175
#ifndef DISABLE_NANORC
176
177
178
179
/* Parse an argument, with optional quotes, after a keyword that takes
 * one.  If the next word starts with a ", we say that it ends with the
 * last " of the line.  Otherwise, we interpret it as usual, so that the
 * arguments can contain "'s too. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
180
181
char *parse_argument(char *ptr)
{
182
    const char *ptr_save = ptr;
Chris Allegretta's avatar
Chris Allegretta committed
183
184
185
186
187
188
189
190
191
192
193
    char *last_quote = NULL;

    assert(ptr != NULL);

    if (*ptr != '"')
	return parse_next_word(ptr);

    do {
	ptr++;
	if (*ptr == '"')
	    last_quote = ptr;
194
    } while (*ptr != '\0');
Chris Allegretta's avatar
Chris Allegretta committed
195
196
197
198
199
200

    if (last_quote == NULL) {
	if (*ptr == '\0')
	    ptr = NULL;
	else
	    *ptr++ = '\0';
201
	rcfile_error(N_("Argument '%s' has an unterminated \""), ptr_save);
Chris Allegretta's avatar
Chris Allegretta committed
202
203
204
205
206
    } else {
	*last_quote = '\0';
	ptr = last_quote + 1;
    }
    if (ptr != NULL)
207
	while (isblank(*ptr))
Chris Allegretta's avatar
Chris Allegretta committed
208
209
210
211
	    ptr++;
    return ptr;
}

212
#ifndef DISABLE_COLOR
213
/* Parse the next regex string from the line at ptr, and return it. */
214
215
char *parse_next_regex(char *ptr)
{
216
217
218
219
    assert(ptr != NULL);

    /* Continue until the end of the line, or a " followed by a space, a
     * blank character, or \0. */
220
    while ((*ptr != '"' || (!isblank(*(ptr + 1)) &&
221
	*(ptr + 1) != '\0')) && *ptr != '\0')
222
223
	ptr++;

224
225
226
    assert(*ptr == '"' || *ptr == '\0');

    if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
227
228
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
229
	return NULL;
230
    }
231

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
232
    /* Null-terminate and advance ptr. */
233
234
    *ptr++ = '\0';

235
    while (isblank(*ptr))
236
237
238
239
240
	ptr++;

    return ptr;
}

241
242
/* Compile the regular expression regex to see if it's valid.  Return
 * TRUE if it is, or FALSE otherwise. */
243
bool nregcomp(const char *regex, int eflags)
244
{
245
    regex_t preg;
246
    const char *r = fixbounds(regex);
247
    int rc = regcomp(&preg, r, REG_EXTENDED | eflags);
248
249

    if (rc != 0) {
250
	size_t len = regerror(rc, &preg, NULL, 0);
251
252
	char *str = charalloc(len);

253
	regerror(rc, &preg, str, len);
254
	rcfile_error(N_("Bad regex \"%s\": %s"), r, str);
255
256
	free(str);
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
257

258
    regfree(&preg);
259
    return (rc == 0);
260
261
}

262
263
/* Parse the next syntax string from the line at ptr, and add it to the
 * global list of color syntaxes. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
264
void parse_syntax(char *ptr)
265
{
Chris Allegretta's avatar
Chris Allegretta committed
266
    const char *fileregptr = NULL, *nameptr = NULL;
267
    syntaxtype *tmpsyntax, *prev_syntax;
268
    regexlisttype *endext = NULL;
269
	/* The end of the extensions list for this syntax. */
270

271
    assert(ptr != NULL);
272

273
274
    if (*ptr == '\0') {
	rcfile_error(N_("Missing syntax name"));
275
	return;
276
    }
277
278

    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
279
280
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
281
	return;
282
    }
283

284
285
286
287
288
    ptr++;

    nameptr = ptr;
    ptr = parse_next_regex(ptr);

289
    if (ptr == NULL)
290
	return;
291

292
293
    /* Search for a duplicate syntax name.  If we find one, free it, so
     * that we always use the last syntax with a given name. */
294
    prev_syntax = NULL;
295
296
    for (tmpsyntax = syntaxes; tmpsyntax != NULL;
	tmpsyntax = tmpsyntax->next) {
297
	if (strcmp(nameptr, tmpsyntax->desc) == 0) {
298
299
300
301
	    syntaxtype *old_syntax = tmpsyntax;

	    if (endsyntax == tmpsyntax)
		endsyntax = prev_syntax;
302
303

	    tmpsyntax = tmpsyntax->next;
304
305
306
307
308
309
310
	    if (prev_syntax != NULL)
		prev_syntax->next = tmpsyntax;
	    else
		syntaxes = tmpsyntax;

	    free(old_syntax->desc);
	    free(old_syntax);
311
	    break;
312
	}
313
	prev_syntax = tmpsyntax;
314
315
    }

Chris Allegretta's avatar
Chris Allegretta committed
316
317
    if (syntaxes == NULL) {
	syntaxes = (syntaxtype *)nmalloc(sizeof(syntaxtype));
318
	endsyntax = syntaxes;
Chris Allegretta's avatar
Chris Allegretta committed
319
    } else {
320
321
	endsyntax->next = (syntaxtype *)nmalloc(sizeof(syntaxtype));
	endsyntax = endsyntax->next;
322
#ifdef DEBUG
323
	fprintf(stderr, "Adding new syntax after first one\n");
324
#endif
Chris Allegretta's avatar
Chris Allegretta committed
325
    }
326

327
328
329
330
    endsyntax->desc = mallocstrcpy(NULL, nameptr);
    endsyntax->color = NULL;
    endcolor = NULL;
    endsyntax->extensions = NULL;
331
    endsyntax->headers = NULL;
332
    endsyntax->magics = NULL;
333
    endsyntax->next = NULL;
334
    endsyntax->nmultis = 0;
335
    endsyntax->linter = NULL;
336
    endsyntax->formatter = NULL;
337

338
#ifdef DEBUG
339
    fprintf(stderr, "Starting a new syntax type: \"%s\"\n", nameptr);
340
341
#endif

342
343
344
345
346
347
348
    /* The "none" syntax is the same as not having a syntax at all, so
     * we can't assign any extensions or colors to it. */
    if (strcmp(endsyntax->desc, "none") == 0) {
	rcfile_error(N_("The \"none\" syntax is reserved"));
	return;
    }

349
    /* The default syntax should have no associated extensions. */
350
    if (strcmp(endsyntax->desc, "default") == 0 && *ptr != '\0') {
351
352
	rcfile_error(
		N_("The \"default\" syntax must take no extensions"));
353
354
355
	return;
    }

356
    /* Now load the extension regexes into their part of the struct. */
357
    while (*ptr != '\0') {
358
	regexlisttype *newext;
359

360
	while (*ptr != '"' && *ptr != '\0')
361
362
	    ptr++;

363
	if (*ptr == '\0')
364
	    return;
365

366
367
368
369
	ptr++;

	fileregptr = ptr;
	ptr = parse_next_regex(ptr);
370
371
	if (ptr == NULL)
	    break;
372

373
	newext = (regexlisttype *)nmalloc(sizeof(regexlisttype));
374
375
376

	/* Save the extension regex if it's valid. */
	if (nregcomp(fileregptr, REG_NOSUB)) {
377
	    newext->full_regex = mallocstrcpy(NULL, fileregptr);
378
	    newext->rgx = NULL;
379

380
	    if (endext == NULL)
381
		endsyntax->extensions = newext;
382
383
384
385
	    else
		endext->next = newext;
	    endext = newext;
	    endext->next = NULL;
386
387
	} else
	    free(newext);
388
    }
389
}
390
#endif /* !DISABLE_COLOR */
391

392
393
394
395
396
397
398
/* Check whether the given executable function is "universal" (meaning
 * any horizontal movement or deletion) and thus is present in almost
 * all menus. */
bool is_universal(void (*func))
{
    if (func == do_left || func == do_right ||
	func == do_home || func == do_end ||
399
#ifndef NANO_TINY
400
	func == do_prev_word_void || func == do_next_word_void ||
401
#endif
402
403
404
405
406
407
408
409
	func == do_verbatim_input || func == do_cut_text_void ||
	func == do_delete || func == do_backspace ||
	func == do_tab || func == do_enter)
	return TRUE;
    else
	return FALSE;
}

410
/* Bind or unbind a key combo, to or from a function. */
411
void parse_binding(char *ptr, bool dobind)
412
413
{
    char *keyptr = NULL, *keycopy = NULL, *funcptr = NULL, *menuptr = NULL;
414
    sc *s, *newsc = NULL;
415
    int menu;
416
417
418

    assert(ptr != NULL);

419
420
421
422
#ifdef DEBUG
    fprintf(stderr, "Starting the rebinding code...\n");
#endif

423
424
425
426
427
428
429
430
    if (*ptr == '\0') {
	rcfile_error(N_("Missing key name"));
	return;
    }

    keyptr = ptr;
    ptr = parse_next_word(ptr);
    keycopy = mallocstrcpy(NULL, keyptr);
431
432
433

    if (strlen(keycopy) < 2) {
	rcfile_error(N_("Key name is too short"));
Benno Schulenberg's avatar
Benno Schulenberg committed
434
	goto free_copy;
435
436
437
438
439
440
441
442
443
444
    }

    /* Uppercase only the first two or three characters of the key name. */
    keycopy[0] = toupper(keycopy[0]);
    keycopy[1] = toupper(keycopy[1]);
    if (keycopy[0] == 'M' && keycopy[1] == '-') {
	if (strlen(keycopy) > 2)
	    keycopy[2] = toupper(keycopy[2]);
	else {
	    rcfile_error(N_("Key name is too short"));
Benno Schulenberg's avatar
Benno Schulenberg committed
445
	    goto free_copy;
446
447
	}
    }
448

449
450
451
452
453
    /* Allow the codes for Insert and Delete to be rebound, but apart
     * from those two only Control, Meta and Function sequences. */
    if (!strcasecmp(keycopy, "Ins") || !strcasecmp(keycopy, "Del"))
	keycopy[1] = tolower(keycopy[1]);
    else if (keycopy[0] != '^' && keycopy[0] != 'M' && keycopy[0] != 'F') {
454
	rcfile_error(N_("Key name must begin with \"^\", \"M\", or \"F\""));
Benno Schulenberg's avatar
Benno Schulenberg committed
455
	goto free_copy;
456
457
458
    } else if (keycopy[0] == '^' && (keycopy[1] < 64 || keycopy[1] > 127)) {
	rcfile_error(N_("Key name %s is invalid"), keycopy);
	goto free_copy;
459
460
    }

461
462
463
    if (dobind) {
	funcptr = ptr;
	ptr = parse_next_word(ptr);
464

465
	if (funcptr[0] == '\0') {
466
	    rcfile_error(N_("Must specify a function to bind the key to"));
Benno Schulenberg's avatar
Benno Schulenberg committed
467
	    goto free_copy;
468
	}
469
470
471
472
473
    }

    menuptr = ptr;
    ptr = parse_next_word(ptr);

474
    if (menuptr[0] == '\0') {
475
	/* TRANSLATORS: Do not translate the word "all". */
476
	rcfile_error(N_("Must specify a menu (or \"all\") in which to bind/unbind the key"));
Benno Schulenberg's avatar
Benno Schulenberg committed
477
	goto free_copy;
478
    }
479
480

    if (dobind) {
481
	newsc = strtosc(funcptr);
482
	if (newsc == NULL) {
483
	    rcfile_error(N_("Cannot map name \"%s\" to a function"), funcptr);
Benno Schulenberg's avatar
Benno Schulenberg committed
484
	    goto free_copy;
485
	}
486
487
    }

488
489
490
    menu = strtomenu(menuptr);
    if (menu < 1) {
	rcfile_error(N_("Cannot map name \"%s\" to a menu"), menuptr);
Benno Schulenberg's avatar
Benno Schulenberg committed
491
	goto free_copy;
492
493
    }

494
#ifdef DEBUG
495
    if (dobind)
496
497
	fprintf(stderr, "newsc address is now %ld, assigned func = %ld, menu = %x\n",
	    (long)&newsc, (long)newsc->scfunc, menu);
498
    else
499
	fprintf(stderr, "unbinding \"%s\" from menu %x\n", keycopy, menu);
500
501
#endif

502
    if (dobind) {
503
504
505
506
507
508
509
510
	subnfunc *f;
	int mask = 0;

	/* Tally up the menus where the function exists. */
	for (f = allfuncs; f != NULL; f = f->next)
	    if (f->scfunc == newsc->scfunc)
		mask = mask | f->menus;

511
512
513
514
	/* Handle the special case of the toggles. */
	if (newsc->scfunc == do_toggle_void)
	    mask = MMAIN;

515
516
517
518
519
520
521
522
523
	/* Now limit the given menu to those where the function exists. */
	if (is_universal(newsc->scfunc))
	    menu = menu & MMOST;
	else
	    menu = menu & mask;

	if (!menu) {
	    rcfile_error(N_("Function '%s' does not exist in menu '%s'"), funcptr, menuptr);
	    free(newsc);
Benno Schulenberg's avatar
Benno Schulenberg committed
524
	    goto free_copy;
525
526
	}

527
	newsc->keystr = keycopy;
528
	newsc->menus = menu;
529
530
	newsc->type = strtokeytype(newsc->keystr);
	assign_keyinfo(newsc);
531

532
533
	/* Do not allow rebinding the equivalent of the Escape key. */
	if (newsc->type == META && newsc->seq == 91) {
534
535
	    rcfile_error(N_("Sorry, keystroke \"%s\" may not be rebound"), newsc->keystr);
	    free(newsc);
Benno Schulenberg's avatar
Benno Schulenberg committed
536
	    goto free_copy;
537
	}
538
539
540
541
#ifdef DEBUG
	fprintf(stderr, "s->keystr = \"%s\"\n", newsc->keystr);
	fprintf(stderr, "s->seq = \"%d\"\n", newsc->seq);
#endif
542
    }
543

544
    /* Now find and delete any existing same shortcut in the menu(s). */
545
    for (s = sclist; s != NULL; s = s->next) {
546
	if ((s->menus & menu) && !strcmp(s->keystr, keycopy)) {
547
#ifdef DEBUG
548
	    fprintf(stderr, "deleting entry from among menus %x\n", s->menus);
549
#endif
550
	    s->menus &= ~menu;
551
552
	}
    }
553
554

    if (dobind) {
555
556
557
	/* If this is a toggle, copy its sequence number. */
	if (newsc->scfunc == do_toggle_void) {
	    for (s = sclist; s != NULL; s = s->next)
558
		if (s->scfunc == do_toggle_void && s->toggle == newsc->toggle)
559
560
561
		    newsc->ordinal = s->ordinal;
	} else
	    newsc->ordinal = 0;
562
563
564
	/* Add the new shortcut at the start of the list. */
	newsc->next = sclist;
	sclist = newsc;
Benno Schulenberg's avatar
Benno Schulenberg committed
565
566
567
568
569
	return;
    }

  free_copy:
    free(keycopy);
570
571
}

572

573
#ifndef DISABLE_COLOR
574
/* Read and parse additional syntax files. */
575
static void _parse_include(char *file)
576
577
578
579
{
    struct stat rcinfo;
    FILE *rcstream;

580
581
582
    /* Can't get the specified file's full path because it may screw up
     * our cwd depending on the parent directories' permissions (see
     * Savannah bug #25297). */
583
584

    /* Don't open directories, character files, or block files. */
585
    if (stat(file, &rcinfo) != -1) {
586
587
588
589
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode)) {
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
590
		_("\"%s\" is a device file"), file);
591
592
593
594
	}
    }

    /* Open the new syntax file. */
595
596
    if ((rcstream = fopen(file, "rb")) == NULL) {
	rcfile_error(_("Error reading %s: %s"), file,
597
		strerror(errno));
598
	return;
599
600
601
602
    }

    /* Use the name and line number position of the new syntax file
     * while parsing it, so we can know where any errors in it are. */
603
    nanorc = file;
604
605
    lineno = 0;

606
#ifdef DEBUG
607
    fprintf(stderr, "Parsing file \"%s\"\n", file);
608
609
#endif

610
    parse_rcfile(rcstream, TRUE);
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
}

void parse_include(char *ptr)
{
    char *option, *nanorc_save = nanorc, *expanded;
    size_t lineno_save = lineno, i;
    glob_t files;

    option = ptr;
    if (*option == '"')
	option++;
    ptr = parse_argument(ptr);

    /* Expand tildes first, then the globs. */
    expanded = real_dir_from_tilde(option);

    if (glob(expanded, GLOB_ERR|GLOB_NOSORT, NULL, &files) == 0) {
	for (i = 0; i < files.gl_pathc; ++i)
	    _parse_include(files.gl_pathv[i]);
    } else {
	rcfile_error(_("Error expanding %s: %s"), option,
		strerror(errno));
    }
634

635
636
637
    globfree(&files);
    free(expanded);

638
639
640
641
642
643
    /* We're done with the new syntax file.  Restore the original
     * filename and line number position. */
    nanorc = nanorc_save;
    lineno = lineno_save;
}

644
645
/* Return the short value corresponding to the color named in colorname,
 * and set bright to TRUE if that color is bright. */
646
short color_to_short(const char *colorname, bool *bright)
647
{
648
    short mcolor = -1;
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682

    assert(colorname != NULL && bright != NULL);

    if (strncasecmp(colorname, "bright", 6) == 0) {
	*bright = TRUE;
	colorname += 6;
    }

    if (strcasecmp(colorname, "green") == 0)
	mcolor = COLOR_GREEN;
    else if (strcasecmp(colorname, "red") == 0)
	mcolor = COLOR_RED;
    else if (strcasecmp(colorname, "blue") == 0)
	mcolor = COLOR_BLUE;
    else if (strcasecmp(colorname, "white") == 0)
	mcolor = COLOR_WHITE;
    else if (strcasecmp(colorname, "yellow") == 0)
	mcolor = COLOR_YELLOW;
    else if (strcasecmp(colorname, "cyan") == 0)
	mcolor = COLOR_CYAN;
    else if (strcasecmp(colorname, "magenta") == 0)
	mcolor = COLOR_MAGENTA;
    else if (strcasecmp(colorname, "black") == 0)
	mcolor = COLOR_BLACK;
    else
	rcfile_error(N_("Color \"%s\" not understood.\n"
		"Valid colors are \"green\", \"red\", \"blue\",\n"
		"\"white\", \"yellow\", \"cyan\", \"magenta\" and\n"
		"\"black\", with the optional prefix \"bright\"\n"
		"for foreground colors."), colorname);

    return mcolor;
}

683
684
685
/* Parse the color string in the line at ptr, and add it to the current
 * file's associated colors.  If icase is TRUE, treat the color string
 * as case insensitive. */
686
void parse_colors(char *ptr, bool icase)
687
{
688
    short fg, bg;
689
    bool bright = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
690
    char *fgstr;
691

692
    assert(ptr != NULL);
693

694
695
    if (syntaxes == NULL) {
	rcfile_error(
696
		N_("Cannot add a color command without a syntax command"));
697
698
699
	return;
    }

700
    if (*ptr == '\0') {
701
	rcfile_error(N_("Missing color name"));
702
	return;
703
704
    }

705
706
    fgstr = ptr;
    ptr = parse_next_word(ptr);
707
708
    if (!parse_color_names(fgstr, &fg, &bg, &bright))
	return;
709

710
    if (*ptr == '\0') {
711
	rcfile_error(N_("Missing regex string"));
712
713
714
	return;
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
715
    /* Now for the fun part.  Start adding regexes to individual strings
716
717
     * in the colorstrings array, woo! */
    while (ptr != NULL && *ptr != '\0') {
718
	colortype *newcolor;
719
	    /* The container for a color plus its regexes. */
720
	bool cancelled = FALSE;
721
	    /* The start expression was bad. */
722
723
	bool expectend = FALSE;
	    /* Do we expect an end= line? */
724

725
	if (strncasecmp(ptr, "start=", 6) == 0) {
726
	    ptr += 6;
727
	    expectend = TRUE;
728
729
730
	}

	if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
731
732
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
733
	    ptr = parse_next_regex(ptr);
734
735
	    continue;
	}
736

737
	ptr++;
738

739
740
	fgstr = ptr;
	ptr = parse_next_regex(ptr);
741
742
743
	if (ptr == NULL)
	    break;

744
	newcolor = (colortype *)nmalloc(sizeof(colortype));
745

746
747
748
	/* Save the starting regex string if it's valid, and set up the
	 * color information. */
	if (nregcomp(fgstr, icase ? REG_ICASE : 0)) {
749
750
751
	    newcolor->fg = fg;
	    newcolor->bg = bg;
	    newcolor->bright = bright;
752
	    newcolor->icase = icase;
753
754
755
756

	    newcolor->start_regex = mallocstrcpy(NULL, fgstr);
	    newcolor->start = NULL;

757
	    newcolor->end_regex = NULL;
758
	    newcolor->end = NULL;
759

760
	    newcolor->next = NULL;
761

762
763
	    if (endcolor == NULL) {
		endsyntax->color = newcolor;
764
#ifdef DEBUG
765
		fprintf(stderr, "Starting a new colorstring for fg %hd, bg %hd\n", fg, bg);
766
#endif
767
	    } else {
Chris Allegretta's avatar
Chris Allegretta committed
768
#ifdef DEBUG
769
		fprintf(stderr, "Adding new entry for fg %hd, bg %hd\n", fg, bg);
Chris Allegretta's avatar
Chris Allegretta committed
770
#endif
771
772
		/* Need to recompute endcolor now so we can extend
		 * colors to syntaxes. */
773
		for (endcolor = endsyntax->color; endcolor->next != NULL; endcolor = endcolor->next)
774
		    ;
775
		endcolor->next = newcolor;
776
	    }
777

778
	    endcolor = newcolor;
779
780
781
	} else {
	    free(newcolor);
	    cancelled = TRUE;
782
	}
Chris Allegretta's avatar
Chris Allegretta committed
783

784
	if (expectend) {
785
	    if (ptr == NULL || strncasecmp(ptr, "end=", 4) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
786
787
		rcfile_error(
			N_("\"start=\" requires a corresponding \"end=\""));
788
789
790
791
		return;
	    }
	    ptr += 4;
	    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
792
793
		rcfile_error(
			N_("Regex strings must begin and end with a \" character"));
794
		continue;
795
	    }
796

797
798
	    ptr++;

799
	    fgstr = ptr;
800
	    ptr = parse_next_regex(ptr);
801
802
	    if (ptr == NULL)
		break;
803

804
805
	    /* If the start regex was invalid, skip past the end regex
	     * to stay in sync. */
806
807
	    if (cancelled)
		continue;
808

809
810
811
	    /* Save the ending regex string if it's valid. */
	    newcolor->end_regex = (nregcomp(fgstr, icase ? REG_ICASE :
		0)) ? mallocstrcpy(NULL, fgstr) : NULL;
812

813
	    /* Lame way to skip another static counter. */
814
815
	    newcolor->id = endsyntax->nmultis;
	    endsyntax->nmultis++;
Chris Allegretta's avatar
Chris Allegretta committed
816
	}
817
    }
818
}
819

820
821
822
823
824
825
/* Parse the color name, or pair of color names, in combostr. */
bool parse_color_names(char *combostr, short *fg, short *bg, bool *bright)
{
    bool no_fgcolor = FALSE;

    if (combostr == NULL)
826
	return FALSE;
827
828
829
830
831
832
833
834
835
836
837
838
839

    if (strchr(combostr, ',') != NULL) {
	char *bgcolorname;
	strtok(combostr, ",");
	bgcolorname = strtok(NULL, ",");
	if (bgcolorname == NULL) {
	    /* If we have a background color without a foreground color,
	     * parse it properly. */
	    bgcolorname = combostr + 1;
	    no_fgcolor = TRUE;
	}
	if (strncasecmp(bgcolorname, "bright", 6) == 0) {
	    rcfile_error(N_("Background color \"%s\" cannot be bright"), bgcolorname);
840
	    return FALSE;
841
842
843
844
845
846
847
848
849
850
	}
	*bg = color_to_short(bgcolorname, bright);
    } else
	*bg = -1;

    if (!no_fgcolor) {
	*fg = color_to_short(combostr, bright);

	/* Don't try to parse screwed-up foreground colors. */
	if (*fg == -1)
851
	    return FALSE;
852
853
854
    } else
	*fg = -1;

855
    return TRUE;
856
857
}

858

859
860
861
862
863
/* Read regex strings enclosed in double quotes from the line pointed at
 * by ptr, and store them quoteless in the passed storage place. */
void grab_and_store(char *ptr, const char *kind, regexlisttype **storage)
{
    regexlisttype *lastthing = NULL;
864
865
866

    if (syntaxes == NULL) {
	rcfile_error(
867
		N_("A '%s' command requires a preceding 'syntax' command"), kind);
868
869
870
871
	return;
    }

    if (*ptr == '\0') {
872
	rcfile_error(N_("Missing regex string after '%s' command"), kind);
873
874
875
	return;
    }

876
    /* Now load the regexes into their part of the struct. */
877
878
    while (*ptr != '\0') {
	const char *regexstring;
879
	regexlisttype *newthing;
880
881
882
883

	if (*ptr != '"') {
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
884
	    return;
885
886
	}

887
	regexstring = ++ptr;
888
889
	ptr = parse_next_regex(ptr);
	if (ptr == NULL)
890
	    return;
891

892
	newthing = (regexlisttype *)nmalloc(sizeof(regexlisttype));
893

894
	/* Save the regex string if it's valid. */
895
	if (nregcomp(regexstring, REG_NOSUB)) {
896
897
	    newthing->full_regex = mallocstrcpy(NULL, regexstring);
	    newthing->rgx = NULL;
898

899
	    if (lastthing == NULL)
900
		*storage = newthing;
901
	    else
902
903
904
		lastthing->next = newthing;

	    lastthing = newthing;
905
	    lastthing->next = NULL;
906
	} else
907
	    free(newthing);
908
909
    }
}
910

911
912
913
914
915
916
/* Parse the header-line regexes that may influence the choice of syntax. */
void parse_header_exp(char *ptr)
{
    grab_and_store(ptr, "header", &endsyntax->headers);
}

917
#ifdef HAVE_LIBMAGIC
918
919
920
/* Parse the magic regexes that may influence the choice of syntax. */
void parse_magic_exp(char *ptr)
{
921
    grab_and_store(ptr, "magic", &endsyntax->magics);
922
}
923
#endif /* HAVE_LIBMAGIC */
924

925
/* Parse the linter requested for this syntax. */
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
void parse_linter(char *ptr)
{
    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add a linter without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing linter command"));
	return;
    }

941
    free(endsyntax->linter);
942

Benno Schulenberg's avatar
Benno Schulenberg committed
943
    /* Let them unset the linter by using "". */
944
945
946
    if (!strcmp(ptr, "\"\""))
	endsyntax->linter = NULL;
    else
947
	endsyntax->linter = mallocstrcpy(NULL, ptr);
948
}
949

950
#ifndef DISABLE_SPELLER
951
/* Parse the formatter requested for this syntax. */
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
void parse_formatter(char *ptr)
{
    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add formatter without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing formatter command"));
	return;
    }

967
    free(endsyntax->formatter);
968
969
970
971
972

    /* Let them unset the formatter by using "". */
    if (!strcmp(ptr, "\"\""))
	endsyntax->formatter = NULL;
    else
973
	endsyntax->formatter = mallocstrcpy(NULL, ptr);
974
}
975
#endif /* !DISABLE_SPELLER */
976
#endif /* !DISABLE_COLOR */
977

978
/* Check whether the user has unmapped every shortcut for a
979
 * sequence we consider 'vital', like the exit function. */
980
981
982
983
984
static void check_vitals_mapped(void)
{
    subnfunc *f;
    int v;
#define VITALS 5
985
    void (*vitals[VITALS])(void) = { do_exit, do_exit, do_cancel, do_cancel, do_cancel };
986
987
988
    int inmenus[VITALS] = { MMAIN, MHELP, MWHEREIS, MREPLACE, MGOTOLINE };

    for  (v = 0; v < VITALS; v++) {
989
990
991
992
993
	for (f = allfuncs; f != NULL; f = f->next) {
	    if (f->scfunc == vitals[v] && f->menus & inmenus[v]) {
		const sc *s = first_sc_for(inmenus[v], f->scfunc);
		if (!s) {
		    fprintf(stderr, _("Fatal error: no keys mapped for function "
994
				     "\"%s\".  Exiting.\n"), f->desc);
995
		    fprintf(stderr, _("If needed, use nano with the -I option "
996
				     "to adjust your nanorc settings.\n"));
997
998
999
1000
1001
		     exit(1);
		}
		break;
	    }
	}
1002
1003
1004
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1005
1006
1007
/* Parse the rcfile, once it has been opened successfully at rcstream,
 * and close it afterwards.  If syntax_only is TRUE, only allow the file
 * to contain color syntax commands: syntax, color, and icolor. */
1008
void parse_rcfile(FILE *rcstream
1009
#ifndef DISABLE_COLOR
1010
1011
1012
	, bool syntax_only
#endif
	)
1013
{
1014
1015
    char *buf = NULL;
    ssize_t len;
1016
    size_t n = 0;
1017
#ifndef DISABLE_COLOR
1018
1019
    syntaxtype *end_syn_save = NULL;
#endif
1020
1021
1022

    while ((len = getline(&buf, &n, rcstream)) > 0) {
	char *ptr, *keyword, *option;
1023
1024
	int set = 0;
	size_t i;
1025

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1026
	/* Ignore the newline. */
1027
1028
	if (buf[len - 1] == '\n')
	    buf[len - 1] = '\0';
1029
1030
1031

	lineno++;
	ptr = buf;
1032
	while (isblank(*ptr))
1033
1034
	    ptr++;

1035
1036
1037
	/* If we have a blank line or a comment, skip to the next
	 * line. */
	if (*ptr == '\0' || *ptr == '#')
1038
1039
	    continue;

1040
	/* Otherwise, skip to the next space. */
1041
1042
1043
	keyword = ptr;
	ptr = parse_next_word(ptr);

1044
#ifndef DISABLE_COLOR
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
	/* Handle extending first... */
	if (strcasecmp(keyword, "extendsyntax") == 0) {
	    char *syntaxname = ptr;
	    syntaxtype *ts = NULL;

	    ptr = parse_next_word(ptr);
	    for (ts = syntaxes; ts != NULL; ts = ts->next)
		if (!strcmp(ts->desc, syntaxname))
		    break;

	    if (ts == NULL) {
		rcfile_error(N_("Could not find syntax \"%s\" to extend"), syntaxname);
		continue;
	    } else {
		end_syn_save = endsyntax;
		endsyntax = ts;
		keyword = ptr;
		ptr = parse_next_word(ptr);
	    }
	}
1065
#endif
1066

1067
	/* Try to parse the keyword. */
1068
	if (strcasecmp(keyword, "set") == 0) {
1069
#ifndef DISABLE_COLOR
1070
1071
	    if (syntax_only)
		rcfile_error(
1072
			N_("Command \"%s\" not allowed in included file"),
1073
1074
1075
1076
1077
			keyword);
	    else
#endif
		set = 1;
	} else if (strcasecmp(keyword, "unset") == 0) {
1078
#ifndef DISABLE_COLOR
1079
1080
	    if (syntax_only)
		rcfile_error(
1081
			N_("Command \"%s\" not allowed in included file"),
1082
1083
1084
1085
1086
			keyword);
	    else
#endif
		set = -1;
	}
1087
#ifndef DISABLE_COLOR
1088
1089
1090
	else if (strcasecmp(keyword, "include") == 0) {
	    if (syntax_only)
		rcfile_error(
1091
			N_("Command \"%s\" not allowed in included file"),
1092
1093
1094
			keyword);
	    else
		parse_include(ptr);
1095
1096
	} else if (strcasecmp(keyword, "syntax") == 0) {
	    if (endsyntax != NULL && endcolor == NULL)
1097
		rcfile_error(N_("Syntax \"%s\" has no color commands"),
1098
			endsyntax->desc);
Chris Allegretta's avatar
Chris Allegretta committed
1099
	    parse_syntax(ptr);
1100
	}
1101
	else if (strcasecmp(keyword, "magic") == 0)
1102
#ifdef HAVE_LIBMAGIC
1103
	    parse_magic_exp(ptr);
1104
1105
1106
#else
	    ;
#endif
1107
	else if (strcasecmp(keyword, "header") == 0)
1108
	    parse_header_exp(ptr);
1109
	else if (strcasecmp(keyword, "color") == 0)
1110
1111
1112
	    parse_colors(ptr, FALSE);
	else if (strcasecmp(keyword, "icolor") == 0)
	    parse_colors(ptr, TRUE);
1113
1114
	else if (strcasecmp(keyword, "linter") == 0)
	    parse_linter(ptr);
1115
	else if (strcasecmp(keyword, "formatter") == 0)
1116
#ifndef DISABLE_SPELLER
1117
	    parse_formatter(ptr);
1118
1119
1120
#else
	    ;
#endif
1121
#endif /* !DISABLE_COLOR */
1122
	else if (strcasecmp(keyword, "bind") == 0)
1123
	    parse_binding(ptr, TRUE);
1124
	else if (strcasecmp(keyword, "unbind") == 0)
1125
	    parse_binding(ptr, FALSE);
1126
	else
1127
	    rcfile_error(N_("Command \"%s\" not understood"), keyword);
1128

1129
1130
1131
#ifndef DISABLE_COLOR
	/* If we temporarily reset endsyntax to allow extending,
	 * restore the value here. */
1132
1133
1134
1135
1136
1137
	if (end_syn_save != NULL) {
	    endsyntax = end_syn_save;
	    end_syn_save = NULL;
	}
#endif

1138
1139
1140
1141
	if (set == 0)
	    continue;

	if (*ptr == '\0') {
1142
	    rcfile_error(N_("Missing option"));
1143
1144
1145
1146
1147
1148
	    continue;
	}

	option = ptr;
	ptr = parse_next_word(ptr);

1149
1150
	for (i = 0; rcopts[i].name != NULL; i++) {
	    if (strcasecmp(option, rcopts[i].name) == 0) {
1151
#ifdef DEBUG
1152
1153
1154
1155
1156
1157
1158
1159
1160
		fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name);
#endif
		if (set == 1) {
		    if (rcopts[i].flag != 0)
			/* This option has a flag, so it doesn't take an
			 * argument. */
			SET(rcopts[i].flag);
		    else {
			/* This option doesn't have a flag, so it takes
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1161
			 * an argument. */
1162
			if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1163
			    rcfile_error(
1164
				N_("Option \"%s\" requires an argument"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1165
				rcopts[i].name);
1166
1167
1168
1169
1170
1171
			    break;
			}
			option = ptr;
			if (*option == '"')
			    option++;
			ptr = parse_argument(ptr);
1172

1173
			option = mallocstrcpy(NULL, option);
Chris Allegretta's avatar
Chris Allegretta committed
1174
#ifdef DEBUG
1175
			fprintf(stderr, "option = \"%s\"\n", option);
Chris Allegretta's avatar
Chris Allegretta committed
1176
#endif
1177
1178
1179
1180
1181
1182
1183
1184
1185

			/* Make sure option is a valid multibyte
			 * string. */
			if (!is_valid_mbstring(option)) {
			    rcfile_error(
				N_("Option is not a valid multibyte string"));
			    break;
			}

1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
#ifndef DISABLE_COLOR
			if (strcasecmp(rcopts[i].name, "titlecolor") == 0)
			    specified_color_combo[TITLE_BAR] = option;
			else if (strcasecmp(rcopts[i].name, "statuscolor") == 0)
			    specified_color_combo[STATUS_BAR] = option;
			else if (strcasecmp(rcopts[i].name, "keycolor") == 0)
			    specified_color_combo[KEY_COMBO] = option;
			else if (strcasecmp(rcopts[i].name, "functioncolor") == 0)
			    specified_color_combo[FUNCTION_TAG] = option;
			else
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1197
#ifndef DISABLE_OPERATINGDIR
1198
			if (strcasecmp(rcopts[i].name, "operatingdir") == 0)
1199
			    operating_dir = option;
1200
			else
Chris Allegretta's avatar
Chris Allegretta committed
1201
#endif
1202
#ifndef DISABLE_WRAPJUSTIFY
1203
1204
			if (strcasecmp(rcopts[i].name, "fill") == 0) {
			    if (!parse_num(option, &wrap_at)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1205
				rcfile_error(
1206
					N_("Requested fill size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1207
					option);
1208
				wrap_at = -CHARS_FROM_EOL;
1209
1210
			    } else
				free(option);
1211
			} else
Chris Allegretta's avatar
Chris Allegretta committed
1212
#endif
1213
#ifndef NANO_TINY
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
			if (strcasecmp(rcopts[i].name,
				"matchbrackets") == 0) {
			    matchbrackets = option;
			    if (has_blank_mbchars(matchbrackets)) {
				rcfile_error(
					N_("Non-blank characters required"));
				free(matchbrackets);
				matchbrackets = NULL;
			    }
			} else if (strcasecmp(rcopts[i].name,
				"whitespace") == 0) {
1225
			    whitespace = option;
1226
1227
			    if (mbstrlen(whitespace) != 2 ||
				strlenpt(whitespace) != 2) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1228
1229
				rcfile_error(
					N_("Two single-column characters required"));
1230
1231
				free(whitespace);
				whitespace = NULL;
1232
1233
1234
			    } else {
				whitespace_len[0] =
					parse_mbchar(whitespace, NULL,
1235
					NULL);
1236
1237
				whitespace_len[1] =
					parse_mbchar(whitespace +
1238
					whitespace_len[0], NULL, NULL);
1239
1240
			    }
			} else
1241
#endif
1242
#ifndef DISABLE_JUSTIFY
1243
			if (strcasecmp(rcopts[i].name, "punct") == 0) {
1244
			    punct = option;
1245
			    if (has_blank_mbchars(punct)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1246
				rcfile_error(
1247
					N_("Non-blank characters required"));
1248
1249
1250
				free(punct);
				punct = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1251
1252
			} else if (strcasecmp(rcopts[i].name,
				"brackets") == 0) {
1253
			    brackets = option;
1254
			    if (has_blank_mbchars(brackets)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1255
				rcfile_error(
1256
					N_("Non-blank characters required"));
1257
1258
1259
				free(brackets);
				brackets = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1260
1261
			} else if (strcasecmp(rcopts[i].name,
				"quotestr") == 0)
1262
			    quotestr = option;
1263
			else
1264
#endif
1265
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1266
1267
			if (strcasecmp(rcopts[i].name,
				"backupdir") == 0)
1268
			    backup_dir = option;
1269
			else
1270
#endif
1271
#ifndef DISABLE_SPELLER
1272
			if (strcasecmp(rcopts[i].name, "speller") == 0)
1273
			    alt_speller = option;
1274
1275
			else
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1276
1277
1278
1279
1280
			if (strcasecmp(rcopts[i].name,
				"tabsize") == 0) {
			    if (!parse_num(option, &tabsize) ||
				tabsize <= 0) {
				rcfile_error(
1281
					N_("Requested tab size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1282
					option);
1283
				tabsize = -1;
1284
1285
			    } else
				free(option);
1286
			} else
1287
1288
			    assert(FALSE);
		    }
1289
#ifdef DEBUG
1290
		    fprintf(stderr, "flag = %ld\n", rcopts[i].flag);
1291
#endif
1292
1293
1294
		} else if (rcopts[i].flag != 0)
		    UNSET(rcopts[i].flag);
		else
1295
		    rcfile_error(N_("Cannot unset option \"%s\""),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1296
			rcopts[i].name);
1297
		break;
1298
1299
	    }
	}
1300
	if (rcopts[i].name == NULL)
1301
	    rcfile_error(N_("Unknown option \"%s\""), option);
1302
    }
1303

1304
#ifndef DISABLE_COLOR
1305
    if (endsyntax != NULL && endcolor == NULL)
1306
	rcfile_error(N_("Syntax \"%s\" has no color commands"),
1307
		endsyntax->desc);
1308
#endif
1309

Chris Allegretta's avatar
Chris Allegretta committed
1310
    free(buf);
1311
1312
    fclose(rcstream);
    lineno = 0;
1313

Chris Allegretta's avatar
Chris Allegretta committed
1314
    check_vitals_mapped();
1315
1316
1317
    return;
}

1318
/* The main rcfile function.  It tries to open the system-wide rcfile,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1319
 * followed by the current user's rcfile. */
1320
1321
void do_rcfile(void)
{
1322
    struct stat rcinfo;
1323
1324
    FILE *rcstream;

1325
    nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc");
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335

    /* Don't open directories, character files, or block files. */
    if (stat(nanorc, &rcinfo) != -1) {
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
		_("\"%s\" is a device file"), nanorc);
    }

1336
1337
1338
1339
#ifdef DEBUG
    fprintf(stderr, "Parsing file \"%s\"\n", nanorc);
#endif

1340
    /* Try to open the system-wide nanorc. */
1341
    rcstream = fopen(nanorc, "rb");
1342
    if (rcstream != NULL)
1343
	parse_rcfile(rcstream
1344
#ifndef DISABLE_COLOR
1345
1346
1347
		, FALSE
#endif
		);
1348

1349
#ifdef DISABLE_ROOTWRAPPING
1350
    /* We've already read SYSCONFDIR/nanorc, if it's there.  If we're
1351
1352
     * root, and --disable-wrapping-as-root is used, turn wrapping off
     * now. */
1353
1354
1355
    if (geteuid() == NANO_ROOT_UID)
	SET(NO_WRAP);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1356

1357
    get_homedir();
1358

1359
    if (homedir == NULL)
1360
	rcfile_error(N_("I can't find my home directory!  Wah!"));
1361
    else {
1362
1363
1364
1365
1366
#ifndef RCFILE_NAME
#define RCFILE_NAME ".nanorc"
#endif
	nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2);
	sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME);
1367

1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
	/* Don't open directories, character files, or block files. */
	if (stat(nanorc, &rcinfo) != -1) {
	    if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
		rcfile_error(S_ISDIR(rcinfo.st_mode) ?
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), nanorc);
	}

	/* Try to open the current user's nanorc. */
	rcstream = fopen(nanorc, "rb");
1379
	if (rcstream == NULL) {
1380
1381
	    /* Don't complain about the file's not existing. */
	    if (errno != ENOENT)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1382
1383
		rcfile_error(N_("Error reading %s: %s"), nanorc,
			strerror(errno));
1384
	} else
1385
	    parse_rcfile(rcstream
1386
#ifndef DISABLE_COLOR
1387
1388
1389
		, FALSE
#endif
		);
1390
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1391

1392
1393
    free(nanorc);
    nanorc = NULL;
1394

1395
1396
1397
1398
1399
1400
1401
    if (errors && !ISSET(QUIET)) {
	errors = FALSE;
	fprintf(stderr,
		_("\nPress Enter to continue starting nano.\n"));
	while (getchar() != '\n')
	    ;
    }
1402
1403
}

1404
#endif /* !DISABLE_NANORC */