1/*	$NetBSD: util.c,v 1.173.2.1 2012/05/17 18:57:11 sborrill Exp $	*/
2
3/*
4 * Copyright 1997 Piermont Information Systems Inc.
5 * All rights reserved.
6 *
7 * Written by Philip A. Nelson for Piermont Information Systems Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. The name of Piermont Information Systems Inc. may not be used to endorse
18 *    or promote products derived from this software without specific prior
19 *    written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31 * THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 */
34
35/* util.c -- routines that don't really fit anywhere else... */
36
37#include <stdio.h>
38#include <stdarg.h>
39#include <string.h>
40#include <unistd.h>
41#include <sys/mount.h>
42#include <sys/disklabel.h>
43#include <sys/dkio.h>
44#include <sys/ioctl.h>
45#include <sys/types.h>
46#include <sys/param.h>
47#include <sys/sysctl.h>
48#include <sys/stat.h>
49#include <sys/statvfs.h>
50#include <isofs/cd9660/iso.h>
51#include <curses.h>
52#include <err.h>
53#include <errno.h>
54#include <dirent.h>
55#include <util.h>
56#include "defs.h"
57#include "md.h"
58#include "msg_defs.h"
59#include "menu_defs.h"
60
61#ifndef MD_SETS_SELECTED
62#define MD_SETS_SELECTED SET_KERNEL_1, SET_SYSTEM, SET_X11, SET_MD
63#endif
64#ifndef MD_SETS_SELECTED_MINIMAL
65#define MD_SETS_SELECTED_MINIMAL SET_KERNEL_1, SET_CORE
66#endif
67#ifndef MD_SETS_SELECTED_NOX
68#define MD_SETS_SELECTED_NOX SET_KERNEL_1, SET_SYSTEM, SET_MD
69#endif
70#ifndef MD_SETS_VALID
71#define MD_SETS_VALID SET_KERNEL, SET_SYSTEM, SET_X11, SET_MD, SET_SOURCE
72#endif
73
74#define MAX_CD_DEVS	256	/* how many cd drives do we expect to attach */
75#define ISO_BLKSIZE	ISO_DEFAULT_BLOCK_SIZE
76
77static const char *msg_yes, *msg_no, *msg_all, *msg_some, *msg_none;
78static const char *msg_cur_distsets_row;
79static int select_menu_width;
80
81static uint8_t set_status[SET_GROUP_END];
82#define SET_VALID	0x01
83#define SET_SELECTED	0x02
84#define SET_SKIPPED	0x04
85#define SET_INSTALLED	0x08
86
87struct  tarstats {
88	int nselected;
89	int nfound;
90	int nnotfound;
91	int nerror;
92	int nsuccess;
93	int nskipped;
94} tarstats;
95
96distinfo dist_list[] = {
97#ifdef SET_KERNEL_1_NAME
98	{SET_KERNEL_1_NAME,	SET_KERNEL_1,		MSG_set_kernel_1, NULL},
99#endif
100#ifdef SET_KERNEL_2_NAME
101	{SET_KERNEL_2_NAME,	SET_KERNEL_2,		MSG_set_kernel_2, NULL},
102#endif
103#ifdef SET_KERNEL_3_NAME
104	{SET_KERNEL_3_NAME,	SET_KERNEL_3,		MSG_set_kernel_3, NULL},
105#endif
106#ifdef SET_KERNEL_4_NAME
107	{SET_KERNEL_4_NAME,	SET_KERNEL_4,		MSG_set_kernel_4, NULL},
108#endif
109#ifdef SET_KERNEL_5_NAME
110	{SET_KERNEL_5_NAME,	SET_KERNEL_5,		MSG_set_kernel_5, NULL},
111#endif
112#ifdef SET_KERNEL_6_NAME
113	{SET_KERNEL_6_NAME,	SET_KERNEL_6,		MSG_set_kernel_6, NULL},
114#endif
115#ifdef SET_KERNEL_7_NAME
116	{SET_KERNEL_7_NAME,	SET_KERNEL_7,		MSG_set_kernel_7, NULL},
117#endif
118#ifdef SET_KERNEL_8_NAME
119	{SET_KERNEL_8_NAME,	SET_KERNEL_8,		MSG_set_kernel_8, NULL},
120#endif
121
122	{"modules",		SET_MODULES,		MSG_set_modules, NULL},
123	{"base",		SET_BASE,		MSG_set_base, NULL},
124	{"etc",			SET_ETC,		MSG_set_system, NULL},
125	{"comp",		SET_COMPILER,		MSG_set_compiler, NULL},
126	{"games",		SET_GAMES,		MSG_set_games, NULL},
127	{"man",			SET_MAN_PAGES,		MSG_set_man_pages, NULL},
128	{"misc",		SET_MISC,		MSG_set_misc, NULL},
129	{"tests",		SET_TESTS,		MSG_set_tests, NULL},
130	{"text",		SET_TEXT_TOOLS,		MSG_set_text_tools, NULL},
131
132	{NULL,			SET_GROUP,		MSG_set_X11, NULL},
133	{"xbase",		SET_X11_BASE,		MSG_set_X11_base, NULL},
134	{"xcomp",		SET_X11_PROG,		MSG_set_X11_prog, NULL},
135	{"xetc",		SET_X11_ETC,		MSG_set_X11_etc, NULL},
136	{"xfont",		SET_X11_FONTS,		MSG_set_X11_fonts, NULL},
137	{"xserver",		SET_X11_SERVERS,	MSG_set_X11_servers, NULL},
138	{NULL,			SET_GROUP_END,		NULL, NULL},
139
140#ifdef SET_MD_1_NAME
141	{SET_MD_1_NAME,		SET_MD_1,		MSG_set_md_1, NULL},
142#endif
143#ifdef SET_MD_2_NAME
144	{SET_MD_2_NAME,		SET_MD_2,		MSG_set_md_2, NULL},
145#endif
146#ifdef SET_MD_3_NAME
147	{SET_MD_3_NAME,		SET_MD_3,		MSG_set_md_3, NULL},
148#endif
149#ifdef SET_MD_4_NAME
150	{SET_MD_4_NAME,		SET_MD_4,		MSG_set_md_4, NULL},
151#endif
152
153	{NULL,			SET_GROUP,		MSG_set_source, NULL},
154	{"syssrc",		SET_SYSSRC,		MSG_set_syssrc, NULL},
155	{"src",			SET_SRC,		MSG_set_src, NULL},
156	{"sharesrc",		SET_SHARESRC,		MSG_set_sharesrc, NULL},
157	{"gnusrc",		SET_GNUSRC,		MSG_set_gnusrc, NULL},
158	{"xsrc",		SET_XSRC,		MSG_set_xsrc, NULL},
159	{NULL,			SET_GROUP_END,		NULL, NULL},
160
161	{NULL,			SET_LAST,		NULL, NULL},
162};
163
164#define MAX_CD_INFOS	16	/* how many media can be found? */
165struct cd_info {
166	char device_name[16];
167	char menu[100];
168};
169static struct cd_info cds[MAX_CD_INFOS];
170
171/*
172 * local prototypes
173 */
174
175static int check_for(unsigned int mode, const char *pathname);
176static int get_iso9660_volname(int dev, int sess, char *volname);
177static int get_available_cds(void);
178
179void
180init_set_status(int flags)
181{
182	static const uint8_t sets_valid[] = {MD_SETS_VALID};
183	static const uint8_t sets_selected_full[] = {MD_SETS_SELECTED};
184	static const uint8_t sets_selected_minimal[] = {MD_SETS_SELECTED_MINIMAL};
185	static const uint8_t sets_selected_nox[] = {MD_SETS_SELECTED_NOX};
186	static const uint8_t *sets_selected;
187	unsigned int nelem_selected;
188	unsigned int i, len;
189	const char *longest;
190
191	if (flags & SFLAG_MINIMAL) {
192		sets_selected = sets_selected_minimal;
193		nelem_selected = nelem(sets_selected_minimal);
194	} else if (flags & SFLAG_NOX) {
195		sets_selected = sets_selected_nox;
196		nelem_selected = nelem(sets_selected_nox);
197	} else {
198		sets_selected = sets_selected_full;
199		nelem_selected = nelem(sets_selected_full);
200	}
201
202	for (i = 0; i < nelem(sets_valid); i++)
203		set_status[sets_valid[i]] = SET_VALID;
204	for (i = 0; i < nelem_selected; i++)
205		set_status[sets_selected[i]] |= SET_SELECTED;
206
207	set_status[SET_GROUP] = SET_VALID;
208
209	/* Lookup some strings we need lots of times */
210	msg_yes = msg_string(MSG_Yes);
211	msg_no = msg_string(MSG_No);
212	msg_all = msg_string(MSG_All);
213	msg_some = msg_string(MSG_Some);
214	msg_none = msg_string(MSG_None);
215	msg_cur_distsets_row = msg_string(MSG_cur_distsets_row);
216
217	/* Find longest and use it to determine width of selection menu */
218	len = strlen(msg_no); longest = msg_no;
219	i = strlen(msg_yes); if (i > len) {len = i; longest = msg_yes; }
220	i = strlen(msg_all); if (i > len) {len = i; longest = msg_all; }
221	i = strlen(msg_some); if (i > len) {len = i; longest = msg_some; }
222	i = strlen(msg_none); if (i > len) {len = i; longest = msg_none; }
223	select_menu_width = snprintf(NULL, 0, msg_cur_distsets_row, "",longest);
224
225	/* Give the md code a chance to choose the right kernel, etc. */
226	md_init_set_status(flags);
227}
228
229int
230dir_exists_p(const char *path)
231{
232
233	return file_mode_match(path, S_IFDIR);
234}
235
236int
237file_exists_p(const char *path)
238{
239
240	return file_mode_match(path, S_IFREG);
241}
242
243int
244file_mode_match(const char *path, unsigned int mode)
245{
246	struct stat st;
247
248	return (stat(path, &st) == 0 && (st.st_mode & S_IFMT) == mode);
249}
250
251uint
252get_ramsize(void)
253{
254	uint64_t ramsize;
255	size_t len = sizeof ramsize;
256	int mib[2] = {CTL_HW, HW_PHYSMEM64};
257
258	sysctl(mib, 2, &ramsize, &len, NULL, 0);
259
260	/* Find out how many Megs ... round up. */
261	return (ramsize + MEG - 1) / MEG;
262}
263
264void
265run_makedev(void)
266{
267	char *owd;
268
269	msg_display_add("\n\n");
270	msg_display_add(MSG_makedev);
271
272	owd = getcwd(NULL, 0);
273
274	/* make /dev, in case the user  didn't extract it. */
275	make_target_dir("/dev");
276	target_chdir_or_die("/dev");
277	run_program(0, "/bin/sh MAKEDEV all");
278
279	chdir(owd);
280	free(owd);
281}
282
283/*
284 * Performs in-place replacement of a set of patterns in a file that lives
285 * inside the installed system.  The patterns must be separated by a semicolon.
286 * For example:
287 *
288 * replace("/etc/some-file.conf", "s/prop1=NO/prop1=YES/;s/foo/bar/");
289 */
290void
291replace(const char *path, const char *patterns, ...)
292{
293	char *spatterns;
294	va_list ap;
295
296	va_start(ap, patterns);
297	vasprintf(&spatterns, patterns, ap);
298	va_end(ap);
299	if (spatterns == NULL)
300		err(1, "vasprintf(&spatterns, \"%s\", ...)", patterns);
301
302	run_program(RUN_CHROOT, "sed -an -e '%s;H;$!d;g;w %s' %s", spatterns,
303	    path, path);
304
305	free(spatterns);
306}
307
308static int
309floppy_fetch(const char *set_name)
310{
311	char post[4];
312	msg errmsg;
313	int menu;
314	int status;
315	const char *write_mode = ">";
316
317	strcpy(post, "aa");
318
319	errmsg = "";
320	menu = MENU_fdok;
321	for (;;) {
322		umount_mnt2();
323		msg_display(errmsg);
324		msg_display_add(MSG_fdmount, set_name, post);
325		process_menu(menu, &status);
326		if (status != SET_CONTINUE)
327			return status;
328		menu = MENU_fdremount;
329		errmsg = MSG_fdremount;
330		if (run_program(0, "/sbin/mount -r -t %s %s /mnt2",
331							fd_type, fd_dev))
332			continue;
333		mnt2_mounted = 1;
334		errmsg = MSG_fdnotfound;
335
336		/* Display this because it might take a while.... */
337		if (run_program(RUN_DISPLAY,
338			    "sh -c '/bin/cat /mnt2/%s.%s %s %s/%s/%s%s'",
339			    set_name, post, write_mode,
340			    target_prefix(), xfer_dir, set_name, dist_postfix))
341			/* XXX: a read error will give a corrupt file! */
342			continue;
343
344		/* We got that file, advance to next fragment */
345		if (post[1] < 'z')
346			post[1]++;
347		else
348			post[1] = 'a', post[0]++;
349		write_mode = ">>";
350		errmsg = "";
351		menu = MENU_fdok;
352	}
353}
354
355/*
356 * Load files from floppy.  Requires a /mnt2 directory for mounting them.
357 */
358int
359get_via_floppy(void)
360{
361
362	process_menu(MENU_floppysource, NULL);
363
364	fetch_fn = floppy_fetch;
365
366	/* Set ext_dir for absolute path. */
367	snprintf(ext_dir_bin, sizeof ext_dir_bin, "%s/%s", target_prefix(), xfer_dir);
368	snprintf(ext_dir_src, sizeof ext_dir_src, "%s/%s", target_prefix(), xfer_dir);
369
370	return SET_OK;
371}
372
373/*
374 * Get the volume name of a ISO9660 file system
375 */
376static int
377get_iso9660_volname(int dev, int sess, char *volname)
378{
379	int blkno, error, last;
380	char buf[ISO_BLKSIZE];
381	struct iso_volume_descriptor *vd = NULL;
382	struct iso_primary_descriptor *pd = NULL;
383
384	for (blkno = sess+16; blkno < sess+16+100; blkno++) {
385		error = pread(dev, buf, ISO_BLKSIZE, blkno*ISO_BLKSIZE);
386		if (error == -1)
387			return -1;
388		vd = (struct iso_volume_descriptor *)&buf;
389		if (memcmp(vd->id, ISO_STANDARD_ID, sizeof(vd->id)) != 0)
390			return -1;
391		if (isonum_711((const unsigned char *)&vd->type)
392		     == ISO_VD_PRIMARY) {
393			pd = (struct iso_primary_descriptor*)buf;
394			strncpy(volname, pd->volume_id, sizeof pd->volume_id);
395			last = sizeof pd->volume_id-1;
396			while (last >= 0
397			    && (volname[last] == ' ' || volname[last] == 0))
398				last--;
399			volname[last+1] = 0;
400			return 0;
401		}
402	}
403	return -1;
404}
405
406/*
407 * Get a list of all available CD media (not drives!), return
408 * the number of entries collected.
409 */
410static int
411get_available_cds(void)
412{
413	char dname[16], volname[80];
414	struct cd_info *info = cds;
415	struct disklabel label;
416	int i, part, dev, error, sess, ready, count = 0;
417
418	for (i = 0; i < MAX_CD_DEVS; i++) {
419		sprintf(dname, "/dev/rcd%d%c", i, 'a'+RAW_PART);
420		dev = open(dname, O_RDONLY, 0);
421		if (dev == -1)
422			break;
423		ready = 0;
424		error = ioctl(dev, DIOCTUR, &ready);
425		if (error != 0 || ready == 0) {
426			close(dev);
427			continue;
428		}
429		error = ioctl(dev, DIOCGDINFO, &label);
430		close(dev);
431		if (error == 0) {
432			for (part = 0; part < label.d_npartitions; part++) {
433				if (label.d_partitions[part].p_fstype
434					== FS_UNUSED
435				    || label.d_partitions[part].p_size == 0)
436					continue;
437				if (label.d_partitions[part].p_fstype
438				    == FS_ISO9660) {
439					sess = label.d_partitions[part]
440					    .p_cdsession;
441					sprintf(dname, "/dev/rcd%d%c", i,
442					    'a'+part);
443					dev = open(dname, O_RDONLY, 0);
444					if (dev == -1)
445						continue;
446					error = get_iso9660_volname(dev, sess,
447					    volname);
448					close(dev);
449					if (error) continue;
450					sprintf(info->device_name, "cd%d%c",
451						i, 'a'+part);
452					sprintf(info->menu, "%s (%s)",
453						info->device_name,
454						volname);
455				} else {
456					/*
457					 * All install CDs use partition
458					 * a for the sets.
459					 */
460					if (part > 0)
461						continue;
462					sprintf(info->device_name, "cd%d%c",
463						i, 'a'+part);
464					strcpy(info->menu, info->device_name);
465				}
466				info++;
467				if (++count >= MAX_CD_INFOS)
468					break;
469			}
470		}
471	}
472	return count;
473}
474
475static int
476cd_has_sets(void)
477{
478	/* Mount it */
479	if (run_program(RUN_SILENT, "/sbin/mount -rt cd9660 /dev/%s /mnt2",
480	    cdrom_dev) != 0)
481		return 0;
482
483	mnt2_mounted = 1;
484
485	snprintf(ext_dir_bin, sizeof ext_dir_bin, "%s/%s", "/mnt2", set_dir_bin);
486	snprintf(ext_dir_src, sizeof ext_dir_src, "%s/%s", "/mnt2", set_dir_src);
487	return dir_exists_p(ext_dir_bin);
488}
489
490
491static int
492set_cd_select(menudesc *m, void *arg)
493{
494	*(int *)arg = m->cursel;
495	return 1;
496}
497
498/*
499 * Check whether we can remove the boot media.
500 * If it is not a local filesystem, return -1.
501 * If we can not decide for sure (can not tell MD content from plain ffs
502 * on hard disk, for example), return 0.
503 * If it is a CD/DVD, return 1.
504 */
505int
506boot_media_still_needed(void)
507{
508	struct statvfs sb;
509
510	if (statvfs("/", &sb) == 0) {
511		if (!(sb.f_flag & ST_LOCAL))
512			return -1;
513		if (strcmp(sb.f_fstypename, MOUNT_CD9660) == 0
514			   || strcmp(sb.f_fstypename, MOUNT_UDF) == 0)
515			return 1;
516	}
517
518	return 0;
519}
520
521/*
522 * Get from a CDROM distribution.
523 * Also used on "installation using bootable install media"
524 * as the default option in the "distmedium" menu.
525 */
526int
527get_via_cdrom(void)
528{
529	menu_ent cd_menu[MAX_CD_INFOS];
530	struct stat sb;
531	int num_cds, menu_cd, i, selected_cd = 0;
532	bool silent = false;
533	int mib[2];
534	char rootdev[SSTRSIZE] = "";
535	size_t varlen;
536
537	/* If root is not md(4) and we have set dir, skip this step. */
538	mib[0] = CTL_KERN;
539	mib[1] = KERN_ROOT_DEVICE;
540	varlen = sizeof(rootdev);
541	(void)sysctl(mib, 2, rootdev, &varlen, NULL, 0);
542	if (stat(set_dir_bin, &sb) == 0 && S_ISDIR(sb.st_mode) &&
543	    strncmp("md", rootdev, 2) != 0) {
544	    	strlcpy(ext_dir_bin, set_dir_bin, sizeof ext_dir_bin);
545	    	strlcpy(ext_dir_src, set_dir_src, sizeof ext_dir_src);
546		return SET_OK;
547	}
548
549	num_cds = get_available_cds();
550	if (num_cds <= 0) {
551		silent = true;
552	} else if (num_cds == 1) {
553		/* single CD found, check for sets on it */
554		strcpy(cdrom_dev, cds[0].device_name);
555		if (cd_has_sets())
556			return SET_OK;
557	} else {
558		for (i = 0; i< num_cds; i++) {
559			cd_menu[i].opt_name = cds[i].menu;
560			cd_menu[i].opt_menu = OPT_NOMENU;
561			cd_menu[i].opt_flags = OPT_EXIT;
562			cd_menu[i].opt_action = set_cd_select;
563		}
564		/* create a menu offering available choices */
565		menu_cd = new_menu(MSG_Available_cds,
566			cd_menu, num_cds, -1, 4, 0, 0,
567			MC_SCROLL | MC_NOEXITOPT,
568			NULL, NULL, NULL, NULL, NULL);
569		if (menu_cd == -1)
570			return SET_RETRY;
571		msg_display(MSG_ask_cd);
572		process_menu(menu_cd, &selected_cd);
573		free_menu(menu_cd);
574		strcpy(cdrom_dev, cds[selected_cd].device_name);
575		if (cd_has_sets())
576			return SET_OK;
577	}
578
579	if (silent)
580		msg_display("");
581	else {
582		umount_mnt2();
583		msg_display(MSG_cd_path_not_found);
584		process_menu(MENU_ok, NULL);
585	}
586
587	/* ask for paths on the CD */
588	process_menu(MENU_cdromsource, NULL);
589
590	if (cd_has_sets())
591		return SET_OK;
592
593	return SET_RETRY;
594}
595
596
597/*
598 * Get from a pathname inside an unmounted local filesystem
599 * (e.g., where sets were preloaded onto a local DOS partition)
600 */
601int
602get_via_localfs(void)
603{
604
605	/* Get device, filesystem, and filepath */
606	process_menu (MENU_localfssource, NULL);
607
608	/* Mount it */
609	if (run_program(0, "/sbin/mount -rt %s /dev/%s /mnt2",
610	    localfs_fs, localfs_dev))
611		return SET_RETRY;
612
613	mnt2_mounted = 1;
614
615	snprintf(ext_dir_bin, sizeof ext_dir_bin, "%s/%s/%s",
616		"/mnt2", localfs_dir, set_dir_bin);
617	snprintf(ext_dir_src, sizeof ext_dir_src, "%s/%s/%s",
618		"/mnt2", localfs_dir, set_dir_src);
619
620	return SET_OK;
621}
622
623/*
624 * Get from an already-mounted pathname.
625 */
626
627int
628get_via_localdir(void)
629{
630
631	/* Get filepath */
632	process_menu(MENU_localdirsource, NULL);
633
634	/*
635	 * We have to have an absolute path ('cos pax runs in a
636	 * different directory), make it so.
637	 */
638	snprintf(ext_dir_bin, sizeof ext_dir_bin, "/%s/%s", localfs_dir, set_dir_bin);
639	snprintf(ext_dir_src, sizeof ext_dir_src, "/%s/%s", localfs_dir, set_dir_src);
640
641	return SET_OK;
642}
643
644
645/*
646 * Support for custom distribution fetches / unpacks.
647 */
648
649unsigned int
650set_X11_selected(void)
651{
652	int i;
653
654	for (i = SET_X11_FIRST; ++i < SET_X11_LAST;)
655		if (set_status[i] & SET_SELECTED)
656			return 1;
657	return 0;
658}
659
660unsigned int
661get_kernel_set(void)
662{
663	int i;
664
665	for (i = SET_KERNEL_FIRST; ++i < SET_KERNEL_LAST;)
666		if (set_status[i] & SET_SELECTED)
667			return i;
668	return SET_NONE;
669}
670
671void
672set_kernel_set(unsigned int kernel_set)
673{
674	int i;
675
676	/* only one kernel set is allowed */
677	for (i = SET_KERNEL_FIRST; ++i < SET_KERNEL_LAST;)
678		set_status[i] &= ~SET_SELECTED;
679	set_status[kernel_set] |= SET_SELECTED;
680}
681
682static int
683set_toggle(menudesc *menu, void *arg)
684{
685	distinfo **distp = arg;
686	int set = distp[menu->cursel]->set;
687
688	if (set > SET_KERNEL_FIRST && set < SET_KERNEL_LAST &&
689	    !(set_status[set] & SET_SELECTED))
690		set_kernel_set(set);
691	else
692		set_status[set] ^= SET_SELECTED;
693	return 0;
694}
695
696static int
697set_all_none(menudesc *menu, void *arg, int set, int clr)
698{
699	distinfo **distp = arg;
700	distinfo *dist = *distp;
701	int nested;
702
703	for (nested = 0; dist->set != SET_GROUP_END || nested--; dist++) {
704		if (dist->set == SET_GROUP) {
705			nested++;
706			continue;
707		}
708		set_status[dist->set] = (set_status[dist->set] & ~clr) | set;
709	}
710	return 0;
711}
712
713static int
714set_all(menudesc *menu, void *arg)
715{
716	return set_all_none(menu, arg, SET_SELECTED, 0);
717}
718
719static int
720set_none(menudesc *menu, void *arg)
721{
722	return set_all_none(menu, arg, 0, SET_SELECTED);
723}
724
725static void
726set_label(menudesc *menu, int opt, void *arg)
727{
728	distinfo **distp = arg;
729	distinfo *dist = distp[opt];
730	const char *selected;
731	const char *desc;
732	int nested;
733
734	desc = dist->desc;
735
736	if (dist->set != SET_GROUP)
737		selected = set_status[dist->set] & SET_SELECTED ? msg_yes : msg_no;
738	else {
739		/* sub menu - display None/Some/All */
740		nested = 0;
741		selected = "unknown";
742		while ((++dist)->set != SET_GROUP_END || nested--) {
743			if (dist->set == SET_GROUP) {
744				nested++;
745				continue;
746			}
747			if (!(set_status[dist->set] & SET_VALID))
748				continue;
749			if (set_status[dist->set] & SET_SELECTED) {
750				if (selected == msg_none) {
751					selected = msg_some;
752					break;
753				}
754				selected = msg_all;
755			} else {
756				if (selected == msg_all) {
757					selected = msg_some;
758					break;
759				}
760				selected = msg_none;
761			}
762		}
763	}
764
765	wprintw(menu->mw, msg_cur_distsets_row, msg_string(desc), selected);
766}
767
768static int set_sublist(menudesc *menu, void *arg);
769
770static int
771initialise_set_menu(distinfo *dist, menu_ent *me, distinfo **de, int all_none)
772{
773	int set;
774	int sets;
775	int nested;
776
777	for (sets = 0; ; dist++) {
778		set = dist->set;
779		if (set == SET_LAST || set == SET_GROUP_END)
780			break;
781		if (!(set_status[set] & SET_VALID))
782			continue;
783		*de = dist;
784		me->opt_menu = OPT_NOMENU;
785		me->opt_flags = 0;
786		me->opt_name = NULL;
787		if (set != SET_GROUP)
788			me->opt_action = set_toggle;
789		else {
790			/* Collapse sublist */
791			nested = 0;
792			while ((++dist)->set != SET_GROUP_END || nested--) {
793				if (dist->set == SET_GROUP)
794					nested++;
795			}
796			me->opt_action = set_sublist;
797		}
798		sets++;
799		de++;
800		me++;
801	}
802
803	if (all_none) {
804		me->opt_menu = OPT_NOMENU;
805		me->opt_flags = 0;
806		me->opt_name = MSG_select_all;
807		me->opt_action = set_all;
808		me++;
809		me->opt_menu = OPT_NOMENU;
810		me->opt_flags = 0;
811		me->opt_name = MSG_select_none;
812		me->opt_action = set_none;
813		sets += 2;
814	}
815
816	return sets;
817}
818
819static int
820set_sublist(menudesc *menu, void *arg)
821{
822	distinfo *de[SET_LAST];
823	menu_ent me[SET_LAST];
824	distinfo **dist = arg;
825	int menu_no;
826	int sets;
827
828	sets = initialise_set_menu(dist[menu->cursel] + 1, me, de, 1);
829
830	menu_no = new_menu(NULL, me, sets, 20, 10, 0, select_menu_width,
831		MC_SUBMENU | MC_SCROLL | MC_DFLTEXIT,
832		NULL, set_label, NULL, NULL,
833		MSG_install_selected_sets);
834
835	process_menu(menu_no, de);
836	free_menu(menu_no);
837
838	return 0;
839}
840
841void
842customise_sets(void)
843{
844	distinfo *de[SET_LAST];
845	menu_ent me[SET_LAST];
846	int sets;
847	int menu_no;
848
849	msg_display(MSG_cur_distsets);
850	msg_table_add(MSG_cur_distsets_header);
851
852	sets = initialise_set_menu(dist_list, me, de, 0);
853
854	menu_no = new_menu(NULL, me, sets, 0, 5, 0, select_menu_width,
855		MC_SCROLL | MC_NOBOX | MC_DFLTEXIT | MC_NOCLEAR,
856		NULL, set_label, NULL, NULL,
857		MSG_install_selected_sets);
858
859	process_menu(menu_no, de);
860	free_menu(menu_no);
861}
862
863/*
864 * Extract_file **REQUIRES** an absolute path in ext_dir.  Any code
865 * that sets up xfer_dir for use by extract_file needs to put in the
866 * full path name to the directory.
867 */
868
869int
870extract_file(distinfo *dist, int update)
871{
872	char path[STRSIZE];
873	char *owd;
874	int   rval;
875
876	/* If we might need to tidy up, ensure directory exists */
877	if (fetch_fn != NULL)
878		make_target_dir(xfer_dir);
879
880	(void)snprintf(path, sizeof path, "%s/%s%s",
881	    ext_dir_for_set(dist->name), dist->name, dist_postfix);
882
883	owd = getcwd(NULL, 0);
884
885	/* Do we need to fetch the file now? */
886	if (fetch_fn != NULL) {
887		rval = fetch_fn(dist->name);
888		if (rval != SET_OK)
889			return rval;
890	}
891
892	/* check tarfile exists */
893	if (!file_exists_p(path)) {
894
895#ifdef SUPPORT_8_3_SOURCE_FILESYSTEM
896	/*
897	 * Update path to use dist->name truncated to the first eight
898	 * characters and check again
899	 */
900	(void)snprintf(path, sizeof path, "%s/%.8s%.4s", /* 4 as includes '.' */
901	    ext_dir_for_set(dist->name), dist->name, dist_postfix);
902		if (!file_exists_p(path)) {
903#endif /* SUPPORT_8_3_SOURCE_FILESYSTEM */
904
905		tarstats.nnotfound++;
906
907		msg_display(MSG_notarfile, path);
908		process_menu(MENU_ok, NULL);
909		return SET_RETRY;
910	}
911#ifdef SUPPORT_8_3_SOURCE_FILESYSTEM
912	}
913#endif /* SUPPORT_8_3_SOURCE_FILESYSTEM */
914
915	tarstats.nfound++;
916	/* cd to the target root. */
917	if (update && (dist->set == SET_ETC || dist->set == SET_X11_ETC)) {
918		make_target_dir("/.sysinst");
919		target_chdir_or_die("/.sysinst");
920	} else if (dist->set == SET_PKGSRC)
921		target_chdir_or_die("/usr");
922	else
923		target_chdir_or_die("/");
924
925	/*
926	 * /usr/X11R7/lib/X11/xkb/symbols/pc was a directory in 5.0
927	 * but is a file in 5.1 and beyond, so on upgrades we need to
928	 * delete it before extracting the xbase set.
929	 */
930	if (update && dist->set == SET_X11_BASE)
931		run_program(0, "rm -rf usr/X11R7/lib/X11/xkb/symbols/pc");
932
933	/* now extract set files into "./". */
934	rval = run_program(RUN_DISPLAY | RUN_PROGRESS,
935			"progress -zf %s tar --chroot -xhepf -", path);
936
937	chdir(owd);
938	free(owd);
939
940	/* Check rval for errors and give warning. */
941	if (rval != 0) {
942		tarstats.nerror++;
943		msg_display(MSG_tarerror, path);
944		process_menu(MENU_ok, NULL);
945		return SET_RETRY;
946	}
947
948	if (fetch_fn != NULL && clean_xfer_dir) {
949		run_program(0, "rm %s", path);
950		/* Plausibly we should unlink an empty xfer_dir as well */
951	}
952
953	set_status[dist->set] |= SET_INSTALLED;
954	tarstats.nsuccess++;
955	return SET_OK;
956}
957
958static void
959skip_set(distinfo *dist, int skip_type)
960{
961	int nested;
962	int set;
963
964	nested = 0;
965	while ((++dist)->set != SET_GROUP_END || nested--) {
966		set = dist->set;
967		if (set == SET_GROUP) {
968			nested++;
969			continue;
970		}
971		if (set == SET_LAST)
972			break;
973		if (set_status[set] == (SET_SELECTED | SET_VALID))
974			set_status[set] |= SET_SKIPPED;
975		tarstats.nskipped++;
976	}
977}
978
979/*
980 * Get and unpack the distribution.
981 * Show success_msg if installation completes.
982 * Otherwise show failure_msg and wait for the user to ack it before continuing.
983 * success_msg and failure_msg must both be 0-adic messages.
984 */
985int
986get_and_unpack_sets(int update, msg setupdone_msg, msg success_msg, msg failure_msg)
987{
988	distinfo *dist;
989	int status;
990	int set;
991
992	/* Ensure mountpoint for distribution files exists in current root. */
993	(void)mkdir("/mnt2", S_IRWXU| S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
994	if (script)
995		(void)fprintf(script, "mkdir -m 755 /mnt2\n");
996
997	/* reset failure/success counters */
998	memset(&tarstats, 0, sizeof(tarstats));
999
1000	/* Find out which files to "get" if we get files. */
1001
1002	/* Accurately count selected sets */
1003	for (dist = dist_list; (set = dist->set) != SET_LAST; dist++) {
1004		if ((set_status[set] & (SET_VALID | SET_SELECTED))
1005		    == (SET_VALID | SET_SELECTED))
1006			tarstats.nselected++;
1007	}
1008
1009	status = SET_RETRY;
1010	for (dist = dist_list; ; dist++) {
1011		set = dist->set;
1012		if (set == SET_LAST)
1013			break;
1014		if (dist->name == NULL)
1015			continue;
1016		if (set_status[set] != (SET_VALID | SET_SELECTED))
1017			continue;
1018
1019		if (status != SET_OK) {
1020			/* This might force a redraw.... */
1021			clearok(curscr, 1);
1022			touchwin(stdscr);
1023			wrefresh(stdscr);
1024			/* Sort out the location of the set files */
1025			do {
1026				umount_mnt2();
1027				msg_display(MSG_distmedium, tarstats.nselected,
1028				    tarstats.nsuccess + tarstats.nskipped,
1029				    dist->name);
1030				fetch_fn = NULL;
1031				process_menu(MENU_distmedium, &status);
1032			} while (status == SET_RETRY);
1033
1034			if (status == SET_SKIP) {
1035				set_status[set] |= SET_SKIPPED;
1036				tarstats.nskipped++;
1037				continue;
1038			}
1039			if (status == SET_SKIP_GROUP) {
1040				skip_set(dist, status);
1041				continue;
1042			}
1043			if (status != SET_OK) {
1044				msg_display(failure_msg);
1045				process_menu(MENU_ok, NULL);
1046				return 1;
1047			}
1048		}
1049
1050		/* Try to extract this set */
1051		status = extract_file(dist, update);
1052		if (status == SET_RETRY)
1053			dist--;
1054	}
1055
1056	if (tarstats.nerror == 0 && tarstats.nsuccess == tarstats.nselected) {
1057		msg_display(MSG_endtarok);
1058		/* Give user a chance to see the success message */
1059		sleep(1);
1060	} else {
1061		/* We encountered errors. Let the user know. */
1062		msg_display(MSG_endtar,
1063		    tarstats.nselected, tarstats.nnotfound, tarstats.nskipped,
1064		    tarstats.nfound, tarstats.nsuccess, tarstats.nerror);
1065		process_menu(MENU_ok, NULL);
1066		msg_clear();
1067	}
1068
1069	/*
1070	 * postinstall needs to be run after extracting all sets, because
1071	 * otherwise /var/db/obsolete will only have current information
1072	 * from the base, comp, and etc sets.
1073	 */
1074	if (update && (set_status[SET_ETC] & SET_INSTALLED)) {
1075		int oldsendmail;
1076		oldsendmail = run_program(RUN_DISPLAY | RUN_CHROOT |
1077					  RUN_ERROR_OK | RUN_PROGRESS,
1078					  "/usr/sbin/postinstall -s /.sysinst -d / check mailerconf");
1079		if (oldsendmail == 1) {
1080			msg_display(MSG_oldsendmail);
1081			process_menu(MENU_yesno, NULL);
1082			if (yesno) {
1083				run_program(RUN_DISPLAY | RUN_CHROOT,
1084					    "/usr/sbin/postinstall -s /.sysinst -d / fix mailerconf");
1085			}
1086		}
1087		run_program(RUN_DISPLAY | RUN_CHROOT,
1088			"/usr/sbin/postinstall -s /.sysinst -d / fix");
1089	}
1090
1091	/* Configure the system */
1092	if (set_status[SET_BASE] & SET_INSTALLED)
1093		run_makedev();
1094
1095	if (!update) {
1096		/* Save keybard type */
1097		save_kb_encoding();
1098
1099		/* Other configuration. */
1100		mnt_net_config();
1101	}
1102
1103	/* Mounted dist dir? */
1104	umount_mnt2();
1105
1106	/* Install/Upgrade complete ... reboot or exit to script */
1107	msg_display(success_msg);
1108	process_menu(MENU_ok, NULL);
1109	return 0;
1110}
1111
1112void
1113umount_mnt2(void)
1114{
1115	if (!mnt2_mounted)
1116		return;
1117	run_program(RUN_SILENT, "/sbin/umount /mnt2");
1118	mnt2_mounted = 0;
1119}
1120
1121
1122/*
1123 * Do a quick sanity check that  the target can reboot.
1124 * return 1 if everything OK, 0 if there is a problem.
1125 * Uses a table of files we expect to find after a base install/upgrade.
1126 */
1127
1128/* test flag and pathname to check for after unpacking. */
1129struct check_table { unsigned int mode; const char *path;} checks[] = {
1130  { S_IFREG, "/netbsd" },
1131  { S_IFDIR, "/etc" },
1132  { S_IFREG, "/etc/fstab" },
1133  { S_IFREG, "/sbin/init" },
1134  { S_IFREG, "/bin/sh" },
1135  { S_IFREG, "/etc/rc" },
1136  { S_IFREG, "/etc/rc.subr" },
1137  { S_IFREG, "/etc/rc.conf" },
1138  { S_IFDIR, "/dev" },
1139  { S_IFCHR, "/dev/console" },
1140/* XXX check for rootdev in target /dev? */
1141  { S_IFREG, "/etc/fstab" },
1142  { S_IFREG, "/sbin/fsck" },
1143  { S_IFREG, "/sbin/fsck_ffs" },
1144  { S_IFREG, "/sbin/mount" },
1145  { S_IFREG, "/sbin/mount_ffs" },
1146  { S_IFREG, "/sbin/mount_nfs" },
1147#if defined(DEBUG) || defined(DEBUG_CHECK)
1148  { S_IFREG, "/foo/bar" },		/* bad entry to exercise warning */
1149#endif
1150  { 0, 0 }
1151
1152};
1153
1154/*
1155 * Check target for a single file.
1156 */
1157static int
1158check_for(unsigned int mode, const char *pathname)
1159{
1160	int found;
1161
1162	found = (target_test(mode, pathname) == 0);
1163	if (found == 0)
1164		msg_display(MSG_rootmissing, pathname);
1165	return found;
1166}
1167
1168/*
1169 * Check that all the files in check_table are present in the
1170 * target root. Warn if not found.
1171 */
1172int
1173sanity_check(void)
1174{
1175	int target_ok = 1;
1176	struct check_table *p;
1177
1178	for (p = checks; p->path; p++) {
1179		target_ok = target_ok && check_for(p->mode, p->path);
1180	}
1181	if (target_ok)
1182		return 0;
1183
1184	/* Uh, oh. Something's missing. */
1185	msg_display(MSG_badroot);
1186	process_menu(MENU_ok, NULL);
1187	return 1;
1188}
1189
1190/*
1191 * Some globals to pass things back from callbacks
1192 */
1193static char zoneinfo_dir[STRSIZE];
1194static int zonerootlen;
1195static char *tz_selected;	/* timezonename (relative to share/zoneinfo */
1196const char *tz_default;		/* UTC, or whatever /etc/localtime points to */
1197static char tz_env[STRSIZE];
1198static int save_cursel, save_topline;
1199
1200/*
1201 * Callback from timezone menu
1202 */
1203static int
1204set_tz_select(menudesc *m, void *arg)
1205{
1206	time_t t;
1207	char *new;
1208
1209	if (m && strcmp(tz_selected, m->opts[m->cursel].opt_name) != 0) {
1210		/* Change the displayed timezone */
1211		new = strdup(m->opts[m->cursel].opt_name);
1212		if (new == NULL)
1213			return 0;
1214		free(tz_selected);
1215		tz_selected = new;
1216		snprintf(tz_env, sizeof tz_env, "%.*s%s",
1217			 zonerootlen, zoneinfo_dir, tz_selected);
1218		setenv("TZ", tz_env, 1);
1219	}
1220	if (m)
1221		/* Warp curser to 'Exit' line on menu */
1222		m->cursel = -1;
1223
1224	/* Update displayed time */
1225	t = time(NULL);
1226	msg_display(MSG_choose_timezone,
1227		    tz_default, tz_selected, ctime(&t), localtime(&t)->tm_zone);
1228	return 0;
1229}
1230
1231static int
1232set_tz_back(menudesc *m, void *arg)
1233{
1234
1235	zoneinfo_dir[zonerootlen] = 0;
1236	m->cursel = save_cursel;
1237	m->topline = save_topline;
1238	return 0;
1239}
1240
1241static int
1242set_tz_dir(menudesc *m, void *arg)
1243{
1244
1245	strlcpy(zoneinfo_dir + zonerootlen, m->opts[m->cursel].opt_name,
1246		sizeof zoneinfo_dir - zonerootlen);
1247	save_cursel = m->cursel;
1248	save_topline = m->topline;
1249	m->cursel = 0;
1250	m->topline = 0;
1251	return 0;
1252}
1253
1254/*
1255 * Alarm-handler to update example-display
1256 */
1257static void
1258/*ARGSUSED*/
1259timezone_sig(int sig)
1260{
1261
1262	set_tz_select(NULL, NULL);
1263	alarm(60);
1264}
1265
1266static int
1267tz_sort(const void *a, const void *b)
1268{
1269	return strcmp(((const menu_ent *)a)->opt_name, ((const menu_ent *)b)->opt_name);
1270}
1271
1272static void
1273tzm_set_names(menudesc *m, void *arg)
1274{
1275	DIR *dir;
1276	struct dirent *dp;
1277	static int nfiles;
1278	static int maxfiles = 32;
1279	static menu_ent *tz_menu;
1280	static char **tz_names;
1281	void *p;
1282	int maxfname;
1283	char *fp;
1284	struct stat sb;
1285
1286	if (tz_menu == NULL)
1287		tz_menu = malloc(maxfiles * sizeof *tz_menu);
1288	if (tz_names == NULL)
1289		tz_names = malloc(maxfiles * sizeof *tz_names);
1290	if (tz_menu == NULL || tz_names == NULL)
1291		return;	/* error - skip timezone setting */
1292	while (nfiles > 0)
1293		free(tz_names[--nfiles]);
1294
1295	dir = opendir(zoneinfo_dir);
1296	fp = strchr(zoneinfo_dir, 0);
1297	if (fp != zoneinfo_dir + zonerootlen) {
1298		tz_names[0] = 0;
1299		tz_menu[0].opt_name = msg_string(MSG_tz_back);
1300		tz_menu[0].opt_menu = OPT_NOMENU;
1301		tz_menu[0].opt_flags = 0;
1302		tz_menu[0].opt_action = set_tz_back;
1303		nfiles = 1;
1304	}
1305	maxfname = zoneinfo_dir + sizeof zoneinfo_dir - fp - 1;
1306	if (dir != NULL) {
1307		while ((dp = readdir(dir)) != NULL) {
1308			if (dp->d_namlen > maxfname || dp->d_name[0] == '.')
1309				continue;
1310			strlcpy(fp, dp->d_name, maxfname);
1311			if (stat(zoneinfo_dir, &sb) == -1)
1312				continue;
1313			if (nfiles >= maxfiles) {
1314				p = realloc(tz_menu, 2 * maxfiles * sizeof *tz_menu);
1315				if (p == NULL)
1316					break;
1317				tz_menu = p;
1318				p = realloc(tz_names, 2 * maxfiles * sizeof *tz_names);
1319				if (p == NULL)
1320					break;
1321				tz_names = p;
1322				maxfiles *= 2;
1323			}
1324			if (S_ISREG(sb.st_mode))
1325				tz_menu[nfiles].opt_action = set_tz_select;
1326			else if (S_ISDIR(sb.st_mode)) {
1327				tz_menu[nfiles].opt_action = set_tz_dir;
1328				strlcat(fp, "/",
1329				    sizeof(zoneinfo_dir) - (fp - zoneinfo_dir));
1330			} else
1331				continue;
1332			tz_names[nfiles] = strdup(zoneinfo_dir + zonerootlen);
1333			tz_menu[nfiles].opt_name = tz_names[nfiles];
1334			tz_menu[nfiles].opt_menu = OPT_NOMENU;
1335			tz_menu[nfiles].opt_flags = 0;
1336			nfiles++;
1337		}
1338		closedir(dir);
1339	}
1340	*fp = 0;
1341
1342	m->opts = tz_menu;
1343	m->numopts = nfiles;
1344	qsort(tz_menu, nfiles, sizeof *tz_menu, tz_sort);
1345}
1346
1347void
1348get_tz_default(void)
1349{
1350	char localtime_link[STRSIZE];
1351	static char localtime_target[STRSIZE];
1352	int rc;
1353
1354	strlcpy(localtime_link, target_expand("/etc/localtime"),
1355	    sizeof localtime_link);
1356
1357	/* Add sanity check that /mnt/usr/share/zoneinfo contains
1358	 * something useful
1359	 */
1360
1361	rc = readlink(localtime_link, localtime_target,
1362		      sizeof(localtime_target) - 1);
1363	if (rc < 0) {
1364		/* error, default to UTC */
1365		tz_default = "UTC";
1366	} else {
1367		localtime_target[rc] = '\0';
1368		tz_default = strchr(strstr(localtime_target, "zoneinfo"), '/') + 1;
1369	}
1370}
1371
1372/*
1373 * Choose from the files in usr/share/zoneinfo and set etc/localtime
1374 */
1375int
1376set_timezone(void)
1377{
1378	char localtime_link[STRSIZE];
1379	char localtime_target[STRSIZE];
1380	time_t t;
1381	int menu_no;
1382
1383	strlcpy(zoneinfo_dir, target_expand("/usr/share/zoneinfo/"),
1384	    sizeof zoneinfo_dir - 1);
1385	zonerootlen = strlen(zoneinfo_dir);
1386
1387	get_tz_default();
1388
1389	tz_selected = strdup(tz_default);
1390	snprintf(tz_env, sizeof(tz_env), "%s%s", zoneinfo_dir, tz_selected);
1391	setenv("TZ", tz_env, 1);
1392	t = time(NULL);
1393	msg_display(MSG_choose_timezone,
1394		    tz_default, tz_selected, ctime(&t), localtime(&t)->tm_zone);
1395
1396	signal(SIGALRM, timezone_sig);
1397	alarm(60);
1398
1399	menu_no = new_menu(NULL, NULL, 14, 23, 9,
1400			   12, 32, MC_ALWAYS_SCROLL | MC_NOSHORTCUT,
1401			   tzm_set_names, NULL, NULL,
1402			   "\nPlease consult the install documents.", NULL);
1403	if (menu_no < 0)
1404		goto done;	/* error - skip timezone setting */
1405
1406	process_menu(menu_no, NULL);
1407
1408	free_menu(menu_no);
1409
1410	signal(SIGALRM, SIG_IGN);
1411
1412	snprintf(localtime_target, sizeof(localtime_target),
1413		 "/usr/share/zoneinfo/%s", tz_selected);
1414	strlcpy(localtime_link, target_expand("/etc/localtime"),
1415	    sizeof localtime_link);
1416	unlink(localtime_link);
1417	symlink(localtime_target, localtime_link);
1418
1419done:
1420	return 1;
1421}
1422
1423void
1424scripting_vfprintf(FILE *f, const char *fmt, va_list ap)
1425{
1426
1427	if (f)
1428		(void)vfprintf(f, fmt, ap);
1429	if (script)
1430		(void)vfprintf(script, fmt, ap);
1431}
1432
1433void
1434scripting_fprintf(FILE *f, const char *fmt, ...)
1435{
1436	va_list ap;
1437
1438	va_start(ap, fmt);
1439	scripting_vfprintf(f, fmt, ap);
1440	va_end(ap);
1441}
1442
1443void
1444add_rc_conf(const char *fmt, ...)
1445{
1446	FILE *f;
1447	va_list ap;
1448
1449	va_start(ap, fmt);
1450	f = target_fopen("/etc/rc.conf", "a");
1451	if (f != 0) {
1452		scripting_fprintf(NULL, "cat <<EOF >>%s/etc/rc.conf\n",
1453		    target_prefix());
1454		scripting_vfprintf(f, fmt, ap);
1455		fclose(f);
1456		scripting_fprintf(NULL, "EOF\n");
1457	}
1458	va_end(ap);
1459}
1460
1461int
1462del_rc_conf(const char *value)
1463{
1464	FILE *fp, *nfp;
1465	char buf[4096]; /* Ridiculously high, but should be enough in any way */
1466	char *rcconf, *tempname, *bakname;
1467	char *cp;
1468	int done = 0;
1469	int fd;
1470	int retval = 0;
1471
1472	/* The paths might seem strange, but using /tmp would require copy instead
1473	 * of rename operations. */
1474	if (asprintf(&rcconf, "%s", target_expand("/etc/rc.conf")) < 0
1475			|| asprintf(&tempname, "%s", target_expand("/etc/rc.conf.tmp.XXXXXX")) < 0
1476			|| asprintf(&bakname, "%s", target_expand("/etc/rc.conf.bak.XXXXXX")) < 0) {
1477		if (rcconf)
1478			free(rcconf);
1479		if (tempname)
1480			free(tempname);
1481		msg_display(MSG_rcconf_delete_failed, value);
1482		process_menu(MENU_ok, NULL);
1483		return -1;
1484	}
1485
1486	if ((fd = mkstemp(bakname)) < 0) {
1487		msg_display(MSG_rcconf_delete_failed, value);
1488		process_menu(MENU_ok, NULL);
1489		return -1;
1490	}
1491	close(fd);
1492
1493	if (!(fp = fopen(rcconf, "r+")) || (fd = mkstemp(tempname)) < 0) {
1494		if (fp)
1495			fclose(fp);
1496		msg_display(MSG_rcconf_delete_failed, value);
1497		process_menu(MENU_ok, NULL);
1498		return -1;
1499	}
1500
1501	nfp = fdopen(fd, "w");
1502	if (!nfp) {
1503		fclose(fp);
1504		close(fd);
1505		msg_display(MSG_rcconf_delete_failed, value);
1506		process_menu(MENU_ok, NULL);
1507		return -1;
1508	}
1509
1510	while (fgets(buf, sizeof buf, fp) != NULL) {
1511
1512		cp = buf + strspn(buf, " \t"); /* Skip initial spaces */
1513		if (strncmp(cp, value, strlen(value)) == 0) {
1514			cp += strlen(value);
1515			if (*cp != '=')
1516				scripting_fprintf(nfp, "%s", buf);
1517			else
1518				done = 1;
1519		} else {
1520			scripting_fprintf(nfp, "%s", buf);
1521		}
1522	}
1523	fclose(fp);
1524	fclose(nfp);
1525
1526	if (done) {
1527		if (rename(rcconf, bakname)) {
1528			msg_display(MSG_rcconf_backup_failed);
1529			process_menu(MENU_noyes, NULL);
1530			if (!yesno) {
1531				retval = -1;
1532				goto done;
1533			}
1534		}
1535
1536		if (rename(tempname, rcconf)) {
1537			if (rename(bakname, rcconf)) {
1538				msg_display(MSG_rcconf_restore_failed);
1539				process_menu(MENU_ok, NULL);
1540			} else {
1541				msg_display(MSG_rcconf_delete_failed, value);
1542				process_menu(MENU_ok, NULL);
1543			}
1544		} else {
1545			(void)unlink(bakname);
1546		}
1547	}
1548
1549done:
1550	(void)unlink(tempname);
1551	free(rcconf);
1552	free(tempname);
1553	free(bakname);
1554	return retval;
1555}
1556
1557void
1558add_sysctl_conf(const char *fmt, ...)
1559{
1560	FILE *f;
1561	va_list ap;
1562
1563	va_start(ap, fmt);
1564	f = target_fopen("/etc/sysctl.conf", "a");
1565	if (f != 0) {
1566		scripting_fprintf(NULL, "cat <<EOF >>%s/etc/sysctl.conf\n",
1567		    target_prefix());
1568		scripting_vfprintf(f, fmt, ap);
1569		fclose(f);
1570		scripting_fprintf(NULL, "EOF\n");
1571	}
1572	va_end(ap);
1573}
1574
1575void
1576enable_rc_conf(void)
1577{
1578
1579	replace("/etc/rc.conf", "s/^rc_configured=NO/rc_configured=YES/");
1580}
1581
1582int
1583check_lfs_progs(void)
1584{
1585
1586#ifndef NO_LFS
1587	return (access("/sbin/fsck_lfs", X_OK) == 0 &&
1588		access("/sbin/mount_lfs", X_OK) == 0 &&
1589		access("/sbin/newfs_lfs", X_OK) == 0);
1590#else
1591	return 0;
1592#endif
1593}
1594
1595int
1596set_is_source(const char *set_name) {
1597	int len = strlen(set_name);
1598	return len >= 3 && memcmp(set_name + len - 3, "src", 3) == 0;
1599}
1600
1601const char *
1602set_dir_for_set(const char *set_name) {
1603	if (strcmp(set_name, "pkgsrc") == 0)
1604		return pkgsrc_dir;
1605	return set_is_source(set_name) ? set_dir_src : set_dir_bin;
1606}
1607
1608const char *
1609ext_dir_for_set(const char *set_name) {
1610	if (strcmp(set_name, "pkgsrc") == 0)
1611		return ext_dir_pkgsrc;
1612	return set_is_source(set_name) ? ext_dir_src : ext_dir_bin;
1613}
1614
1615