install.c revision 143067
1/*
2 * The new sysinstall program.
3 *
4 * This is probably the last program in the `sysinstall' line - the next
5 * generation being essentially a complete rewrite.
6 *
7 * $FreeBSD: head/usr.sbin/sade/install.c 143067 2005-03-02 22:48:13Z jhb $
8 *
9 * Copyright (c) 1995
10 *	Jordan Hubbard.  All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer,
17 *    verbatim and that no modifications are made prior to this
18 *    point in the file.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 */
36
37#include "sysinstall.h"
38#include <ctype.h>
39#include <sys/consio.h>
40#include <sys/disklabel.h>
41#include <sys/errno.h>
42#include <sys/ioctl.h>
43#include <sys/fcntl.h>
44#include <sys/wait.h>
45#include <sys/uio.h>
46#include <sys/param.h>
47#define MSDOSFS
48#include <sys/mount.h>
49#include <ufs/ufs/ufsmount.h>
50#include <fs/msdosfs/msdosfsmount.h>
51#undef MSDOSFS
52#include <sys/stat.h>
53#include <sys/sysctl.h>
54#include <libdisk.h>
55#include <limits.h>
56#include <unistd.h>
57#include <termios.h>
58
59/* Hack for rsaref package add, which displays interactive license.
60 * Used by package.c
61 */
62int _interactiveHack;
63int FixItMode = 0;
64
65static void	create_termcap(void);
66static void	fixit_common(void);
67
68#define TERMCAP_FILE	"/usr/share/misc/termcap"
69
70static void	installConfigure(void);
71
72Boolean
73checkLabels(Boolean whinge)
74{
75    Device **devs;
76    Boolean status;
77    Disk *disk;
78    PartInfo *pi;
79    Chunk *c1, *c2;
80    int i;
81
82    /* Don't allow whinging if noWarn is set */
83    if (variable_get(VAR_NO_WARN))
84	whinge = FALSE;
85
86    status = TRUE;
87    HomeChunk = RootChunk = SwapChunk = NULL;
88    TmpChunk = UsrChunk = VarChunk = NULL;
89#ifdef __ia64__
90    EfiChunk = NULL;
91#endif
92
93    /* We don't need to worry about root/usr/swap if we're already multiuser */
94    if (!RunningAsInit)
95	return status;
96
97    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
98    /* First verify that we have a root device */
99    for (i = 0; devs[i]; i++) {
100	if (!devs[i]->enabled)
101	    continue;
102	disk = (Disk *)devs[i]->private;
103	msgDebug("Scanning disk %s for root filesystem\n", disk->name);
104	if (!disk->chunks)
105	    msgFatal("No chunk list found for %s!", disk->name);
106	for (c1 = disk->chunks->part; c1; c1 = c1->next) {
107#ifdef __ia64__
108	    c2 = c1;
109#elif defined(__powerpc__)
110	    if (c1->type == apple) {
111		for (c2 = c1->part; c2; c2 = c2->next) {
112#else
113	    if (c1->type == freebsd) {
114		for (c2 = c1->part; c2; c2 = c2->next) {
115#endif
116
117		    pi = (PartInfo *)c2->private_data;
118		    if (c2->type == part && c2->subtype != FS_SWAP && pi != NULL) {
119			if (!strcmp(pi->mountpoint, "/")) {
120			    if (RootChunk) {
121				if (whinge)
122				    msgConfirm("WARNING:  You have more than one root device set?!\n"
123					       "Using the first one found.");
124				continue;
125			    }
126			    else {
127				RootChunk = c2;
128				if (isDebug())
129				    msgDebug("Found rootdev at %s!\n", RootChunk->name);
130			    }
131			}
132			else if (!strcmp(pi->mountpoint, "/usr")) {
133			    if (UsrChunk) {
134				if (whinge)
135				    msgConfirm("WARNING:  You have more than one /usr filesystem.\n"
136					       "Using the first one found.");
137				continue;
138			    }
139			    else {
140				UsrChunk = c2;
141				if (isDebug())
142				    msgDebug("Found usrdev at %s!\n", UsrChunk->name);
143			    }
144			}
145			else if (!strcmp(pi->mountpoint, "/var")) {
146			    if (VarChunk) {
147				if (whinge)
148				    msgConfirm("WARNING:  You have more than one /var filesystem.\n"
149					       "Using the first one found.");
150				continue;
151			    }
152			    else {
153				VarChunk = c2;
154				if (isDebug())
155				    msgDebug("Found vardev at %s!\n", VarChunk->name);
156			    }
157			} else if (!strcmp(pi->mountpoint, "/tmp")) {
158			    if (TmpChunk) {
159				if (whinge)
160				    msgConfirm("WARNING:  You have more than one /tmp filesystem.\n"
161					       "Using the first one found.");
162				continue;
163			    }
164			    else {
165				TmpChunk = c2;
166				if (isDebug())
167				    msgDebug("Found tmpdev at %s!\n", TmpChunk->name);
168			    }
169			} else if (!strcmp(pi->mountpoint, "/home")) {
170			    if (HomeChunk) {
171				if (whinge)
172				    msgConfirm("WARNING:  You have more than one /home filesystem.\n"
173					       "Using the first one found.");
174				continue;
175			    }
176			    else {
177				HomeChunk = c2;
178				if (isDebug())
179				    msgDebug("Found homedev at %s!\n", HomeChunk->name);
180			    }
181			}
182		    }
183#ifndef __ia64__
184		}
185	    }
186#endif
187	}
188    }
189
190    /* Now check for swap devices */
191    for (i = 0; devs[i]; i++) {
192	if (!devs[i]->enabled)
193	    continue;
194	disk = (Disk *)devs[i]->private;
195	msgDebug("Scanning disk %s for swap partitions\n", disk->name);
196	if (!disk->chunks)
197	    msgFatal("No chunk list found for %s!", disk->name);
198	for (c1 = disk->chunks->part; c1; c1 = c1->next) {
199
200#ifdef __ia64__
201	    c2 = c1;
202#elif defined(__powerpc__)
203	    if (c1->type == apple) {
204		for (c2 = c1->part; c2; c2 = c2->next) {
205#else
206	    if (c1->type == freebsd) {
207		for (c2 = c1->part; c2; c2 = c2->next) {
208#endif
209		    if (c2->type == part && c2->subtype == FS_SWAP && !SwapChunk) {
210			SwapChunk = c2;
211			if (isDebug())
212			    msgDebug("Found swapdev at %s!\n", SwapChunk->name);
213			break;
214		    }
215#ifndef __ia64__
216		}
217	    }
218#endif
219	}
220    }
221
222#ifdef __ia64__
223    for (i = 0; devs[i] != NULL; i++) {
224	if (!devs[i]->enabled)
225	    continue;
226	disk = (Disk *)devs[i]->private;
227	for (c1 = disk->chunks->part; c1 != NULL; c1 = c1->next) {
228		pi = (PartInfo *)c1->private_data;
229	    if (c1->type == efi && pi != NULL && pi->mountpoint[0] == '/')
230		EfiChunk = c1;
231	}
232    }
233#endif
234
235    if (!RootChunk && whinge) {
236	msgConfirm("No root device found - you must label a partition as /\n"
237		   "in the label editor.");
238	status = FALSE;
239    }
240    if (!SwapChunk && whinge) {
241	if (msgYesNo("No swap devices found - you should create at least one\n"
242		     "swap partition.  Without swap, the install will fail\n"
243		     "if you do not have enough RAM.  Continue anyway?"))
244	    status = FALSE;
245    }
246#ifdef __ia64__
247    if (EfiChunk == NULL && whinge) {
248	if (msgYesNo("No (mounted) EFI system partition found. Is this what you want?"))
249	    status = FALSE;
250    }
251#endif
252    return status;
253}
254
255static int
256installInitial(void)
257{
258    static Boolean alreadyDone = FALSE;
259    int status = DITEM_SUCCESS;
260
261    if (alreadyDone)
262	return DITEM_SUCCESS;
263
264    if (!variable_get(DISK_LABELLED)) {
265	msgConfirm("You need to assign disk labels before you can proceed with\n"
266		   "the installation.");
267	return DITEM_FAILURE;
268    }
269    /* If it's labelled, assume it's also partitioned */
270    if (!variable_get(DISK_PARTITIONED))
271	variable_set2(DISK_PARTITIONED, "yes", 0);
272
273    /* If we refuse to proceed, bail. */
274    dialog_clear_norefresh();
275    if (!variable_get(VAR_NO_WARN)) {
276	if (msgYesNo(
277	    "Last Chance!  Are you SURE you want continue the installation?\n\n"
278	    "If you're running this on a disk with data you wish to save\n"
279	    "then WE STRONGLY ENCOURAGE YOU TO MAKE PROPER BACKUPS before\n"
280	    "proceeding!\n\n"
281	    "We can take no responsibility for lost disk contents!") != 0)
282	return DITEM_FAILURE;
283    }
284
285    if (DITEM_STATUS(diskLabelCommit(NULL)) != DITEM_SUCCESS) {
286	msgConfirm("Couldn't make filesystems properly.  Aborting.");
287	return DITEM_FAILURE;
288    }
289
290    if (!copySelf()) {
291	msgConfirm("installInitial: Couldn't clone the boot floppy onto the\n"
292		   "root file system.  Aborting!");
293	return DITEM_FAILURE;
294    }
295
296    if (!Restarting && chroot("/mnt") == -1) {
297	msgConfirm("installInitial: Unable to chroot to %s - this is bad!",
298		   "/mnt");
299	return DITEM_FAILURE;
300    }
301
302    chdir("/");
303    variable_set2(RUNNING_ON_ROOT, "yes", 0);
304
305    /* Configure various files in /etc */
306    if (DITEM_STATUS(configResolv(NULL)) == DITEM_FAILURE)
307	status = DITEM_FAILURE;
308    if (DITEM_STATUS(configFstab(NULL)) == DITEM_FAILURE)
309	status = DITEM_FAILURE;
310
311    /* stick a helpful shell over on the 4th VTY */
312    if (!variable_get(VAR_NO_HOLOSHELL))
313	systemCreateHoloshell();
314
315    alreadyDone = TRUE;
316    return status;
317}
318
319int
320installFixitHoloShell(dialogMenuItem *self)
321{
322    FixItMode = 1;
323    systemCreateHoloshell();
324    return DITEM_SUCCESS;
325    FixItMode = 0;
326}
327
328int
329installFixitCDROM(dialogMenuItem *self)
330{
331    struct stat sb;
332    int need_eject;
333
334    if (!RunningAsInit)
335	return DITEM_SUCCESS;
336
337    variable_set2(SYSTEM_STATE, "fixit", 0);
338    (void)unlink("/mnt2");
339    (void)rmdir("/mnt2");
340
341    need_eject = 0;
342    CDROMInitQuiet = 1;
343    while (1) {
344	if (need_eject)
345	    msgConfirm(
346	"Please insert a FreeBSD live filesystem CD/DVD and press return");
347	if (DITEM_STATUS(mediaSetCDROM(NULL)) != DITEM_SUCCESS
348	    || !DEVICE_INIT(mediaDevice)) {
349	    /* If we can't initialize it, it's probably not a FreeBSD CDROM so punt on it */
350	    mediaClose();
351	    if (need_eject && msgYesNo("Unable to mount the disc. Do you want to try again?") != 0)
352		return DITEM_FAILURE;
353	} else if (!file_readable("/dist/rescue/ldconfig")) {
354		mediaClose();
355		if (need_eject &&
356		    msgYesNo("Unable to find a FreeBSD live filesystem. Do you want to try again?") != 0)
357		    return DITEM_FAILURE;
358	} else
359	    break;
360	CDROMInitQuiet = 0;
361	need_eject = 1;
362    }
363    CDROMInitQuiet = 0;
364
365    /* Since the fixit code expects everything to be in /mnt2, and the CDROM mounting stuff /dist, do
366     * a little kludge dance here..
367     */
368    if (symlink("/dist", "/mnt2")) {
369	msgConfirm("Unable to symlink /mnt2 to the disc mount point.  Please report this\n"
370		   "unexpected failure to freebsd-bugs@FreeBSD.org.");
371	return DITEM_FAILURE;
372    }
373
374    /*
375     * If /tmp points to /mnt2/tmp from a previous fixit floppy session, it's
376     * not very good for us if we point it to the CDROM now.  Rather make it
377     * a directory in the root MFS then.  Experienced admins will still be
378     * able to mount their disk's /tmp over this if they need.
379     */
380    if (lstat("/tmp", &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFLNK)
381	(void)unlink("/tmp");
382    Mkdir("/tmp");
383
384    /*
385     * Since setuid binaries ignore LD_LIBRARY_PATH, we indeed need the
386     * ld.so.hints file.  Fortunately, it's fairly small (~ 3 KB).
387     */
388    if (!file_readable("/var/run/ld.so.hints")) {
389	Mkdir("/var/run");
390	if (vsystem("/mnt2/rescue/ldconfig -s /mnt2/lib /mnt2/usr/lib")) {
391	    msgConfirm("Warning: ldconfig could not create the ld.so hints file.\n"
392		       "Dynamic executables from the disc likely won't work.");
393	}
394    }
395
396    /* Yet more iggly hardcoded pathnames. */
397    Mkdir("/libexec");
398    if (!file_readable("/libexec/ld.so") && file_readable("/mnt2/libexec/ld.so")) {
399	if (symlink("/mnt2/libexec/ld.so", "/libexec/ld.so"))
400	    msgDebug("Couldn't link to ld.so - not necessarily a problem for ELF\n");
401    }
402    if (!file_readable("/libexec/ld-elf.so.1")) {
403	if (symlink("/mnt2/libexec/ld-elf.so.1", "/libexec/ld-elf.so.1")) {
404	    msgConfirm("Warning: could not create the symlink for ld-elf.so.1\n"
405		       "Dynamic executables from the disc likely won't work.");
406	}
407    }
408    /* optional nicety */
409    if (!file_readable("/usr/bin/vi"))
410	symlink("/mnt2/usr/bin/vi", "/usr/bin/vi");
411    fixit_common();
412    mediaClose();
413    if (need_eject)
414	msgConfirm("Please remove the FreeBSD fixit CDROM/DVD now.");
415    return DITEM_SUCCESS;
416}
417
418int
419installFixitFloppy(dialogMenuItem *self)
420{
421    struct ufs_args args;
422    extern char *distWanted;
423
424    if (!RunningAsInit)
425	return DITEM_SUCCESS;
426
427    /* Try to open the floppy drive */
428    if (DITEM_STATUS(mediaSetFloppy(NULL)) == DITEM_FAILURE || !mediaDevice) {
429	msgConfirm("Unable to set media device to floppy.");
430	mediaClose();
431	return DITEM_FAILURE;
432    }
433
434    memset(&args, 0, sizeof(args));
435    args.fspec = mediaDevice->devname;
436    mediaDevice->private = "/mnt2";
437    distWanted = NULL;
438    Mkdir("/mnt2");
439
440    variable_set2(SYSTEM_STATE, "fixit", 0);
441
442    while (1) {
443	if (!DEVICE_INIT(mediaDevice)) {
444	    if (msgYesNo("The attempt to mount the fixit floppy failed, bad floppy\n"
445			 "or unclean filesystem.  Do you want to try again?"))
446		return DITEM_FAILURE;
447	}
448	else
449	    break;
450    }
451    if (!directory_exists("/tmp"))
452	(void)symlink("/mnt2/tmp", "/tmp");
453    fixit_common();
454    mediaClose();
455    msgConfirm("Please remove the fixit floppy now.");
456    return DITEM_SUCCESS;
457}
458
459/*
460 * The common code for both fixit variants.
461 */
462static void
463fixit_common(void)
464{
465    pid_t child;
466    int waitstatus;
467
468    if (!directory_exists("/var/tmp/vi.recover")) {
469	if (DITEM_STATUS(Mkdir("/var/tmp/vi.recover")) != DITEM_SUCCESS) {
470	    msgConfirm("Warning:  Was unable to create a /var/tmp/vi.recover directory.\n"
471		       "vi will kvetch and moan about it as a result but should still\n"
472		       "be essentially usable.");
473	}
474    }
475    if (!directory_exists("/bin"))
476	(void)Mkdir("/bin");
477    (void)symlink("/stand/sh", "/bin/sh");
478    /* Link the /etc/ files */
479    if (DITEM_STATUS(Mkdir("/etc")) != DITEM_SUCCESS)
480	msgConfirm("Unable to create an /etc directory!  Things are weird on this floppy..");
481    else if ((symlink("/mnt2/etc/spwd.db", "/etc/spwd.db") == -1 && errno != EEXIST) ||
482	     (symlink("/mnt2/etc/protocols", "/etc/protocols") == -1 && errno != EEXIST) ||
483	     (symlink("/mnt2/etc/group", "/etc/group") == -1 && errno != EEXIST) ||
484	     (symlink("/mnt2/etc/services", "/etc/services") == -1 && errno != EEXIST))
485	msgConfirm("Couldn't symlink the /etc/ files!  I'm not sure I like this..");
486    if (!file_readable(TERMCAP_FILE))
487	create_termcap();
488    if (strcmp(variable_get(VAR_FIXIT_TTY), "serial") == 0)
489	systemSuspendDialog();	/* must be before the fork() */
490    if (!(child = fork())) {
491	int i, fd;
492	struct termios foo;
493	extern int login_tty(int);
494
495	ioctl(0, TIOCNOTTY, NULL);
496	for (i = getdtablesize(); i >= 0; --i)
497	    close(i);
498
499	if (strcmp(variable_get(VAR_FIXIT_TTY), "serial") == 0)
500	    fd = open("/dev/console", O_RDWR);
501	else
502	    fd = open("/dev/ttyv3", O_RDWR);
503	ioctl(0, TIOCSCTTY, &fd);
504	dup2(0, 1);
505	dup2(0, 2);
506	DebugFD = 2;
507	if (login_tty(fd) == -1)
508	    msgDebug("fixit: I can't set the controlling terminal.\n");
509
510	signal(SIGTTOU, SIG_IGN);
511	if (tcgetattr(0, &foo) != -1) {
512	    foo.c_cc[VERASE] = '\010';
513	    if (tcsetattr(0, TCSANOW, &foo) == -1)
514		msgDebug("fixit shell: Unable to set erase character.\n");
515	}
516	else
517	    msgDebug("fixit shell: Unable to get terminal attributes!\n");
518	setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/stand:"
519	       "/mnt2/stand:/mnt2/bin:/mnt2/sbin:/mnt2/usr/bin:/mnt2/usr/sbin", 1);
520	if (strcmp(variable_get(VAR_FIXIT_TTY), "serial") == 0) {
521	    printf("Waiting for fixit shell to exit.\n"
522		"When you are done, type ``exit'' to exit\n"
523		"the fixit shell and be returned here.\n\n");
524	    fflush(stdout);
525	} else {
526	    ioctl(fd, VT_ACTIVATE, 0);
527	}
528
529	/* use the .profile from the fixit medium */
530	setenv("HOME", "/mnt2", 1);
531	chdir("/mnt2");
532	execlp("sh", "-sh", (char *)0);
533	msgDebug("fixit shell: Failed to execute shell!\n");
534	_exit(1);;
535    }
536    else {
537	if (strcmp(variable_get(VAR_FIXIT_TTY), "standard") == 0) {
538	    dialog_clear_norefresh();
539	    msgNotify("Waiting for fixit shell to exit.  Go to VTY4 now by\n"
540		"typing ALT-F4.  When you are done, type ``exit'' to exit\n"
541		"the fixit shell and be returned here\n.");
542	}
543	(void)waitpid(child, &waitstatus, 0);
544	if (strcmp(variable_get(VAR_FIXIT_TTY), "serial") == 0)
545	    systemResumeDialog();
546	else if (OnVTY) {
547	    ioctl(0, VT_ACTIVATE, 0);
548	    msgInfo(NULL);
549	}
550    }
551    dialog_clear();
552}
553
554
555int
556installExpress(dialogMenuItem *self)
557{
558    int i;
559
560    dialog_clear_norefresh();
561    variable_set2(SYSTEM_STATE, "express", 0);
562#ifdef WITH_SLICES
563    if (DITEM_STATUS((i = diskPartitionEditor(self))) == DITEM_FAILURE)
564	return i;
565#endif
566
567    if (DITEM_STATUS((i = diskLabelEditor(self))) == DITEM_FAILURE)
568	return i;
569
570    if (DITEM_STATUS((i = installCommit(self))) == DITEM_SUCCESS) {
571	i |= DITEM_LEAVE_MENU;
572
573	/* Give user the option of one last configuration spree */
574	installConfigure();
575    }
576    return i;
577}
578
579/* Standard mode installation */
580int
581installStandard(dialogMenuItem *self)
582{
583    int i, tries = 0;
584    Device **devs;
585
586    variable_set2(SYSTEM_STATE, "standard", 0);
587    dialog_clear_norefresh();
588#ifdef WITH_SLICES
589    msgConfirm("In the next menu, you will need to set up a DOS-style (\"fdisk\") partitioning\n"
590	       "scheme for your hard disk.  If you simply wish to devote all disk space\n"
591	       "to FreeBSD (overwriting anything else that might be on the disk(s) selected)\n"
592	       "then use the (A)ll command to select the default partitioning scheme followed\n"
593	       "by a (Q)uit.  If you wish to allocate only free space to FreeBSD, move to a\n"
594	       "partition marked \"unused\" and use the (C)reate command.");
595
596nodisks:
597    if (DITEM_STATUS(diskPartitionEditor(self)) == DITEM_FAILURE)
598	return DITEM_FAILURE;
599
600    if (diskGetSelectCount(&devs) <= 0 && tries < 3) {
601	msgConfirm("You need to select some disks to operate on!  Be sure to use SPACE\n"
602		   "instead of RETURN in the disk selection menu when selecting a disk.");
603	++tries;
604	goto nodisks;
605    }
606
607    msgConfirm("Now you need to create BSD partitions inside of the fdisk partition(s)\n"
608	       "just created.  If you have a reasonable amount of disk space (200MB or more)\n"
609	       "and don't have any special requirements, simply use the (A)uto command to\n"
610	       "allocate space automatically.  If you have more specific needs or just don't\n"
611	       "care for the layout chosen by (A)uto, press F1 for more information on\n"
612	       "manual layout.");
613#else
614    msgConfirm("First you need to create BSD partitions on the disk which you are\n"
615	       "installing to.  If you have a reasonable amount of disk space (200MB or more)\n"
616	       "and don't have any special requirements, simply use the (A)uto command to\n"
617	       "allocate space automatically.  If you have more specific needs or just don't\n"
618	       "care for the layout chosen by (A)uto, press F1 for more information on\n"
619	       "manual layout.");
620#endif
621
622    if (DITEM_STATUS(diskLabelEditor(self)) == DITEM_FAILURE)
623	return DITEM_FAILURE;
624
625    if (DITEM_STATUS((i = installCommit(self))) == DITEM_FAILURE) {
626	dialog_clear();
627	msgConfirm("Installation completed with some errors.  You may wish to\n"
628		   "scroll through the debugging messages on VTY1 with the\n"
629		   "scroll-lock feature.  You can also choose \"No\" at the next\n"
630		   "prompt and go back into the installation menus to retry\n"
631		   "whichever operations have failed.");
632	return i;
633
634    }
635    else {
636	dialog_clear();
637	msgConfirm("Congratulations!  You now have FreeBSD installed on your system.\n\n"
638		   "We will now move on to the final configuration questions.\n"
639		   "For any option you do not wish to configure, simply select\n"
640		   "No.\n\n"
641		   "If you wish to re-enter this utility after the system is up, you\n"
642		   "may do so by typing: /usr/sbin/sysinstall.");
643    }
644    if (mediaDevice->type != DEVICE_TYPE_FTP && mediaDevice->type != DEVICE_TYPE_NFS) {
645	if (!msgYesNo("Would you like to configure any Ethernet or SLIP/PPP network devices?")) {
646	    Device *tmp = tcpDeviceSelect();
647
648	    if (tmp && !((DevInfo *)tmp->private)->use_dhcp && !msgYesNo("Would you like to bring the %s interface up right now?", tmp->name))
649		if (!DEVICE_INIT(tmp))
650		    msgConfirm("Initialization of %s device failed.", tmp->name);
651	}
652	dialog_clear_norefresh();
653    }
654
655    if (!msgNoYes("Do you want this machine to function as a network gateway?"))
656	variable_set2("gateway_enable", "YES", 1);
657
658    dialog_clear_norefresh();
659    if (!msgNoYes("Do you want to configure inetd and the network services that it provides?"))
660        configInetd(self);
661
662    dialog_clear_norefresh();
663    if (!msgNoYes("Would you like to enable SSH login?"))
664	variable_set2("sshd_enable", "YES", 1);
665
666    dialog_clear_norefresh();
667    if (!msgNoYes("Do you want to have anonymous FTP access to this machine?"))
668	configAnonFTP(self);
669
670    dialog_clear_norefresh();
671    if (!msgNoYes("Do you want to configure this machine as an NFS server?"))
672	configNFSServer(self);
673
674    dialog_clear_norefresh();
675    if (!msgNoYes("Do you want to configure this machine as an NFS client?"))
676	variable_set2("nfs_client_enable", "YES", 1);
677
678#ifdef WITH_SYSCONS
679    dialog_clear_norefresh();
680    if (!msgNoYes("Would you like to customize your system console settings?"))
681	dmenuOpenSimple(&MenuSyscons, FALSE);
682#endif
683
684    dialog_clear_norefresh();
685    if (!msgYesNo("Would you like to set this machine's time zone now?"))
686	systemExecute("tzsetup");
687
688#ifdef WITH_LINUX
689    dialog_clear_norefresh();
690    if (!msgYesNo("Would you like to enable Linux binary compatibility?"))
691	(void)configLinux(self);
692#endif
693
694#ifdef __alpha__
695    dialog_clear_norefresh();
696    if (!msgYesNo("Would you like to enable OSF/1 binary compatibility?"))
697	(void)configOSF1(self);
698#endif
699
700#ifdef WITH_MICE
701    dialog_clear_norefresh();
702    if (!msgNoYes("Does this system have a PS/2, serial, or bus mouse?"))
703	dmenuOpenSimple(&MenuMouse, FALSE);
704#endif
705
706#ifdef __i386__
707    if (checkLoaderACPI() != 0) {
708    	dialog_clear_norefresh();
709    	if (!msgNoYes("ACPI was disabled during boot.\n"
710		      "Would you like to disable it permanently?"))
711		(void)configLoaderACPI(1 /*disable*/);
712    }
713#endif
714
715    /* Now would be a good time to checkpoint the configuration data */
716    configRC_conf();
717    sync();
718
719    dialog_clear_norefresh();
720    if (!msgYesNo("The FreeBSD package collection is a collection of thousands of ready-to-run\n"
721		  "applications, from text editors to games to WEB servers and more.  Would you\n"
722		  "like to browse the collection now?")) {
723	(void)configPackages(self);
724    }
725
726    if (!msgYesNo("Would you like to add any initial user accounts to the system?\n"
727		  "Adding at least one account for yourself at this stage is suggested\n"
728		  "since working as the \"root\" user is dangerous (it is easy to do\n"
729		  "things which adversely affect the entire system)."))
730	(void)configUsers(self);
731
732    msgConfirm("Now you must set the system manager's password.\n"
733	       "This is the password you'll use to log in as \"root\".");
734    if (!systemExecute("passwd root"))
735	variable_set2("root_password", "YES", 0);
736
737    /* XXX Put whatever other nice configuration questions you'd like to ask the user here XXX */
738
739    /* Give user the option of one last configuration spree */
740    dialog_clear_norefresh();
741    installConfigure();
742    return DITEM_LEAVE_MENU;
743}
744
745/* The version of commit we call from the Install Custom menu */
746int
747installCustomCommit(dialogMenuItem *self)
748{
749    int i;
750
751    i = installCommit(self);
752    if (DITEM_STATUS(i) == DITEM_SUCCESS) {
753	/* Give user the option of one last configuration spree */
754	installConfigure();
755	return i;
756    }
757    else
758	msgConfirm("The commit operation completed with errors.  Not\n"
759		   "updating /etc files.");
760    return i;
761}
762
763/*
764 * What happens when we finally decide to going ahead with the installation.
765 *
766 * This is broken into multiple stages so that the user can do a full
767 * installation but come back here again to load more distributions,
768 * perhaps from a different media type.  This would allow, for
769 * example, the user to load the majority of the system from CDROM and
770 * then use ftp to load a different dist.
771 */
772int
773installCommit(dialogMenuItem *self)
774{
775    int i;
776    char *str;
777
778    dialog_clear_norefresh();
779    if (!Dists)
780	distConfig(NULL);
781
782    if (!Dists) {
783	(void)dmenuOpenSimple(&MenuDistributions, FALSE);
784	/* select reasonable defaults if necessary */
785	if (!Dists)
786	    Dists = _DIST_USER;
787    }
788
789    if (!mediaVerify())
790	return DITEM_FAILURE;
791
792    str = variable_get(SYSTEM_STATE);
793    if (isDebug())
794	msgDebug("installCommit: System state is `%s'\n", str);
795
796    /* Installation stuff we wouldn't do to a running system */
797    if (RunningAsInit && DITEM_STATUS((i = installInitial())) == DITEM_FAILURE)
798	return i;
799
800try_media:
801    if (!DEVICE_INIT(mediaDevice)) {
802	if (!msgYesNo("Unable to initialize selected media. Would you like to\n"
803		      "adjust your media configuration and try again?")) {
804	    mediaDevice = NULL;
805	    if (!mediaVerify())
806		return DITEM_FAILURE;
807	    else
808		goto try_media;
809	}
810	else
811	    return DITEM_FAILURE;
812    }
813
814    /* Now go get it all */
815    i = distExtractAll(self);
816
817    /* When running as init, *now* it's safe to grab the rc.foo vars */
818    installEnvironment();
819
820    variable_set2(SYSTEM_STATE, DITEM_STATUS(i) == DITEM_FAILURE ? "error-install" : "full-install", 0);
821
822    return i;
823}
824
825static void
826installConfigure(void)
827{
828    /* Final menu of last resort */
829    if (!msgNoYes("Visit the general configuration menu for a chance to set\n"
830		  "any last options?"))
831	dmenuOpenSimple(&MenuConfigure, FALSE);
832    configRC_conf();
833    sync();
834}
835
836int
837installFixupBase(dialogMenuItem *self)
838{
839    FILE *fp;
840#ifdef __ia64__
841    const char *efi_mntpt;
842#endif
843
844    /* All of this is done only as init, just to be safe */
845    if (RunningAsInit) {
846#if defined(__i386__) || defined(__amd64__)
847	if ((fp = fopen("/boot/loader.conf", "a")) != NULL) {
848	    if (!OnVTY) {
849		fprintf(fp, "# -- sysinstall generated deltas -- #\n");
850		fprintf(fp, "console=\"comconsole\"\n");
851	    }
852	    fclose(fp);
853	}
854#endif
855
856	/* BOGON #2: We leave /etc in a bad state */
857	chmod("/etc", 0755);
858
859	/* BOGON #3: No /var/db/mountdtab complains */
860	Mkdir("/var/db");
861	creat("/var/db/mountdtab", 0644);
862
863	/* BOGON #4: /compat created by default in root fs */
864	Mkdir("/usr/compat");
865	vsystem("ln -s usr/compat /compat");
866
867	/* BOGON #5: aliases database not build for bin */
868	vsystem("newaliases");
869
870	/* BOGON #6: Remove /stand (finally) */
871	vsystem("rm -rf /stand");
872
873	/* Now run all the mtree stuff to fix things up */
874        vsystem("mtree -deU -f /etc/mtree/BSD.root.dist -p /");
875        vsystem("mtree -deU -f /etc/mtree/BSD.var.dist -p /var");
876        vsystem("mtree -deU -f /etc/mtree/BSD.usr.dist -p /usr");
877
878#ifdef __ia64__
879	/* Move /boot to the the EFI partition and make /boot a link to it. */
880	efi_mntpt = (EfiChunk != NULL) ? ((PartInfo *)EfiChunk->private_data)->mountpoint : NULL;
881	if (efi_mntpt != NULL) {
882		vsystem("if [ ! -L /boot ]; then mv /boot %s; fi", efi_mntpt);
883		vsystem("if [ ! -e /boot ]; then ln -sf %s/boot /boot; fi",
884		    efi_mntpt + 1);	/* Skip leading '/' */
885		/* Make sure the kernel knows which partition is the root file system. */
886		vsystem("echo 'vfs.root.mountfrom=\"ufs:/dev/%s\"' >> /boot/loader.conf", RootChunk->name);
887	}
888#endif
889
890	/* Do all the last ugly work-arounds here */
891    }
892    return DITEM_SUCCESS | DITEM_RESTORE;
893}
894
895#define	QUEUE_YES	1
896#define	QUEUE_NO	0
897static int
898performNewfs(PartInfo *pi, char *dname, int queue)
899{
900	char buffer[LINE_MAX];
901
902	if (pi->do_newfs) {
903		switch(pi->newfs_type) {
904		case NEWFS_UFS:
905			snprintf(buffer, LINE_MAX, "%s %s %s %s %s",
906			    NEWFS_UFS_CMD,
907			    pi->newfs_data.newfs_ufs.softupdates ?  "-U" : "",
908			    pi->newfs_data.newfs_ufs.ufs1 ? "-O1" : "-O2",
909			    pi->newfs_data.newfs_ufs.user_options,
910			    dname);
911			break;
912
913		case NEWFS_MSDOS:
914			snprintf(buffer, LINE_MAX, "%s %s", NEWFS_MSDOS_CMD,
915			    dname);
916			break;
917
918		case NEWFS_CUSTOM:
919			snprintf(buffer, LINE_MAX, "%s %s",
920			    pi->newfs_data.newfs_custom.command, dname);
921			break;
922		}
923
924		if (queue == QUEUE_YES) {
925			command_shell_add(pi->mountpoint, buffer);
926			return (0);
927		} else
928			return (vsystem(buffer));
929	}
930	return (0);
931}
932
933/* Go newfs and/or mount all the filesystems we've been asked to */
934int
935installFilesystems(dialogMenuItem *self)
936{
937    int i;
938    Disk *disk;
939    Chunk *c1, *c2;
940    Device **devs;
941    PartInfo *root;
942    char dname[80];
943    Boolean upgrade = FALSE;
944
945    /* If we've already done this, bail out */
946    if (!variable_cmp(DISK_LABELLED, "written"))
947	return DITEM_SUCCESS;
948
949    upgrade = !variable_cmp(SYSTEM_STATE, "upgrade");
950    if (!checkLabels(TRUE))
951	return DITEM_FAILURE;
952
953    root = (RootChunk != NULL) ? (PartInfo *)RootChunk->private_data : NULL;
954
955    command_clear();
956    if (SwapChunk && RunningAsInit) {
957	/* As the very first thing, try to get ourselves some swap space */
958	sprintf(dname, "/dev/%s", SwapChunk->name);
959	if (!Fake && !file_readable(dname)) {
960	    msgConfirm("Unable to find device node for %s in /dev!\n"
961		       "The creation of filesystems will be aborted.", dname);
962	    return DITEM_FAILURE;
963	}
964
965	if (!Fake) {
966	    if (!swapon(dname)) {
967		dialog_clear_norefresh();
968		msgNotify("Added %s as initial swap device", dname);
969	    }
970	    else {
971		msgConfirm("WARNING!  Unable to swap to %s: %s\n"
972			   "This may cause the installation to fail at some point\n"
973			   "if you don't have a lot of memory.", dname, strerror(errno));
974	    }
975	}
976    }
977
978    if (RootChunk && RunningAsInit) {
979	/* Next, create and/or mount the root device */
980	sprintf(dname, "/dev/%s", RootChunk->name);
981	if (!Fake && !file_readable(dname)) {
982	    msgConfirm("Unable to make device node for %s in /dev!\n"
983		       "The creation of filesystems will be aborted.", dname);
984	    return DITEM_FAILURE | DITEM_RESTORE;
985	}
986	if (strcmp(root->mountpoint, "/"))
987	    msgConfirm("Warning: %s is marked as a root partition but is mounted on %s", RootChunk->name, root->mountpoint);
988
989	if (root->do_newfs && (!upgrade ||
990	    !msgNoYes("You are upgrading - are you SURE you want to newfs "
991	    "the root partition?"))) {
992	    int i;
993
994	    dialog_clear_norefresh();
995	    msgNotify("Making a new root filesystem on %s", dname);
996	    i = performNewfs(root, dname, QUEUE_NO);
997	    if (i) {
998		msgConfirm("Unable to make new root filesystem on %s!\n"
999			   "Command returned status %d", dname, i);
1000		return DITEM_FAILURE | DITEM_RESTORE;
1001	    }
1002	}
1003	else {
1004	    if (!upgrade) {
1005		msgConfirm("Warning:  Using existing root partition.  It will be assumed\n"
1006			   "that you have the appropriate device entries already in /dev.");
1007	    }
1008	    dialog_clear_norefresh();
1009	    msgNotify("Checking integrity of existing %s filesystem.", dname);
1010	    i = vsystem("fsck_ffs -y %s", dname);
1011	    if (i)
1012		msgConfirm("Warning: fsck returned status of %d for %s.\n"
1013			   "This partition may be unsafe to use.", i, dname);
1014	}
1015
1016	/*
1017	 * If soft updates was enabled in the editor but we didn't newfs,
1018	 * use tunefs to update the soft updates flag on the file system.
1019	 */
1020	if (!root->do_newfs && root->newfs_type == NEWFS_UFS &&
1021	    root->newfs_data.newfs_ufs.softupdates) {
1022		i = vsystem("tunefs -n enable %s", dname);
1023		if (i)
1024			msgConfirm("Warning: Unable to enable soft updates"
1025			    " for root file system on %s", dname);
1026	}
1027
1028	/* Switch to block device */
1029	sprintf(dname, "/dev/%s", RootChunk->name);
1030	if (Mount("/mnt", dname)) {
1031	    msgConfirm("Unable to mount the root file system on %s!  Giving up.", dname);
1032	    return DITEM_FAILURE | DITEM_RESTORE;
1033	}
1034
1035	/* Mount devfs for other partitions to mount */
1036	Mkdir("/mnt/dev");
1037	if (!Fake) {
1038	    struct iovec iov[4];
1039
1040	    iov[0].iov_base = "fstype";
1041	    iov[0].iov_len = strlen(iov[0].iov_base) + 1;
1042	    iov[1].iov_base = "devfs";
1043	    iov[1].iov_len = strlen(iov[1].iov_base) + 1;
1044	    iov[2].iov_base = "fspath";
1045	    iov[2].iov_len = strlen(iov[2].iov_base) + 1;
1046	    iov[3].iov_base = "/mnt/dev";
1047	    iov[3].iov_len = strlen(iov[3].iov_base) + 1;
1048	    i = nmount(iov, 4, 0);
1049
1050	    if (i) {
1051		dialog_clear_norefresh();
1052		msgConfirm("Unable to mount DEVFS (error %d)", errno);
1053		return DITEM_FAILURE | DITEM_RESTORE;
1054	    }
1055	}
1056    }
1057
1058    /* Now buzz through the rest of the partitions and mount them too */
1059    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
1060    for (i = 0; devs[i]; i++) {
1061	if (!devs[i]->enabled)
1062	    continue;
1063
1064	disk = (Disk *)devs[i]->private;
1065	if (!disk->chunks) {
1066	    msgConfirm("No chunk list found for %s!", disk->name);
1067	    return DITEM_FAILURE | DITEM_RESTORE;
1068	}
1069	for (c1 = disk->chunks->part; c1; c1 = c1->next) {
1070#ifdef __ia64__
1071	if (c1->type == part) {
1072		c2 = c1;
1073		{
1074#elif defined(__powerpc__)
1075	    if (c1->type == apple) {
1076		for (c2 = c1->part; c2; c2 = c2->next) {
1077#else
1078	    if (c1->type == freebsd) {
1079		for (c2 = c1->part; c2; c2 = c2->next) {
1080#endif
1081		    if (c2->type == part && c2->subtype != FS_SWAP && c2->private_data) {
1082			PartInfo *tmp = (PartInfo *)c2->private_data;
1083
1084			/* Already did root */
1085			if (c2 == RootChunk)
1086			    continue;
1087
1088			sprintf(dname, "%s/dev/%s",
1089			    RunningAsInit ? "/mnt" : "", c2->name);
1090
1091			if (tmp->do_newfs && (!upgrade ||
1092			    !msgNoYes("You are upgrading - are you SURE you"
1093			    " want to newfs /dev/%s?", c2->name)))
1094				performNewfs(tmp, dname, QUEUE_YES);
1095			else
1096			    command_shell_add(tmp->mountpoint,
1097				"fsck_ffs -y %s/dev/%s", RunningAsInit ?
1098				"/mnt" : "", c2->name);
1099#if 0
1100			if (tmp->soft)
1101			    command_shell_add(tmp->mountpoint,
1102			    "tunefs -n enable %s/dev/%s", RunningAsInit ?
1103			    "/mnt" : "", c2->name);
1104#endif
1105			command_func_add(tmp->mountpoint, Mount, c2->name);
1106		    }
1107		    else if (c2->type == part && c2->subtype == FS_SWAP) {
1108			char fname[80];
1109			int i;
1110
1111			if (c2 == SwapChunk)
1112			    continue;
1113			sprintf(fname, "%s/dev/%s", RunningAsInit ? "/mnt" : "", c2->name);
1114			i = (Fake || swapon(fname));
1115			if (!i) {
1116			    dialog_clear_norefresh();
1117			    msgNotify("Added %s as an additional swap device", fname);
1118			}
1119			else {
1120			    msgConfirm("Unable to add %s as a swap device: %s", fname, strerror(errno));
1121			}
1122		    }
1123		}
1124	    }
1125	    else if (c1->type == fat && c1->private_data &&
1126		(root->do_newfs || upgrade)) {
1127		char name[FILENAME_MAX];
1128
1129		sprintf(name, "%s/%s", RunningAsInit ? "/mnt" : "", ((PartInfo *)c1->private_data)->mountpoint);
1130		Mkdir(name);
1131	    }
1132#if defined(__ia64__)
1133	    else if (c1->type == efi && c1->private_data) {
1134		char bootdir[FILENAME_MAX];
1135		PartInfo *pi = (PartInfo *)c1->private_data;
1136		char *p;
1137
1138		sprintf(dname, "%s/dev/%s", RunningAsInit ? "/mnt" : "",
1139		    c1->name);
1140
1141		if (pi->do_newfs && (!upgrade ||
1142		    !msgNoYes("You are upgrading - are you SURE you want to "
1143		    "newfs /dev/%s?", c1->name)))
1144			performNewfs(pi, dname, QUEUE_YES);
1145
1146		command_func_add(pi->mountpoint, Mount_msdosfs, c1->name);
1147	    }
1148#endif
1149	}
1150    }
1151
1152    command_sort();
1153    command_execute();
1154    dialog_clear_norefresh();
1155    return DITEM_SUCCESS | DITEM_RESTORE;
1156}
1157
1158static char *
1159getRelname(void)
1160{
1161    static char buf[64];
1162    size_t sz = (sizeof buf) - 1;
1163
1164    if (sysctlbyname("kern.osrelease", buf, &sz, NULL, 0) != -1) {
1165	buf[sz] = '\0';
1166	return buf;
1167    }
1168    else
1169	return "<unknown>";
1170}
1171
1172/* Initialize various user-settable values to their defaults */
1173int
1174installVarDefaults(dialogMenuItem *self)
1175{
1176    char *cp;
1177
1178    /* Set default startup options */
1179    variable_set2(VAR_RELNAME,			getRelname(), 0);
1180    variable_set2(VAR_CPIO_VERBOSITY,		"high", 0);
1181    variable_set2(VAR_TAPE_BLOCKSIZE,		DEFAULT_TAPE_BLOCKSIZE, 0);
1182    variable_set2(VAR_INSTALL_ROOT,		"/", 0);
1183    variable_set2(VAR_INSTALL_CFG,		"install.cfg", 0);
1184    variable_set2(VAR_SKIP_PCCARD,		"NO", 0);
1185    cp = getenv("EDITOR");
1186    if (!cp)
1187	cp = "/usr/bin/ee";
1188    variable_set2(VAR_EDITOR,			cp, 0);
1189    variable_set2(VAR_FTP_USER,			"ftp", 0);
1190    variable_set2(VAR_BROWSER_PACKAGE,		"links", 0);
1191    variable_set2(VAR_BROWSER_BINARY,		"/usr/local/bin/links", 0);
1192    variable_set2(VAR_FTP_STATE,		"passive", 0);
1193    variable_set2(VAR_NFS_SECURE,		"NO", -1);
1194    variable_set2(VAR_NFS_TCP,   		"NO", -1);
1195    variable_set2(VAR_NFS_V3,   		"YES", -1);
1196    if (OnVTY)
1197	    variable_set2(VAR_FIXIT_TTY,		"standard", 0);
1198    else
1199	    variable_set2(VAR_FIXIT_TTY,		"serial", 0);
1200    variable_set2(VAR_PKG_TMPDIR,		"/var/tmp", 0);
1201    variable_set2(VAR_MEDIA_TIMEOUT,		itoa(MEDIA_TIMEOUT), 0);
1202    if (getpid() != 1)
1203	variable_set2(SYSTEM_STATE,		"update", 0);
1204    else
1205	variable_set2(SYSTEM_STATE,		"init", 0);
1206    variable_set2(VAR_NEWFS_ARGS,		"-b 16384 -f 2048", 0);
1207    variable_set2(VAR_CONSTERM,                 "NO", 0);
1208    return DITEM_SUCCESS;
1209}
1210
1211/* Load the environment up from various system configuration files */
1212void
1213installEnvironment(void)
1214{
1215    configEnvironmentRC_conf();
1216    if (file_readable("/etc/resolv.conf"))
1217	configEnvironmentResolv("/etc/resolv.conf");
1218}
1219
1220/* Copy the boot floppy contents into /stand */
1221Boolean
1222copySelf(void)
1223{
1224    int i;
1225
1226    if (file_readable("/boot.help"))
1227	vsystem("cp /boot.help /mnt");
1228    msgWeHaveOutput("Copying the boot floppy to /stand on root filesystem");
1229    i = vsystem("find -x /stand | cpio %s -pdum /mnt", cpioVerbosity());
1230    if (i) {
1231	msgConfirm("Copy returned error status of %d!", i);
1232	return FALSE;
1233    }
1234
1235    /* Copy the /etc files into their rightful place */
1236    if (vsystem("cd /mnt/stand; find etc | cpio %s -pdum /mnt", cpioVerbosity())) {
1237	msgConfirm("Couldn't copy up the /etc files!");
1238	return TRUE;
1239    }
1240    return TRUE;
1241}
1242
1243static void
1244create_termcap(void)
1245{
1246    FILE *fp;
1247
1248    const char *caps[] = {
1249	termcap_vt100, termcap_cons25, termcap_cons25_m, termcap_cons25r,
1250	termcap_cons25r_m, termcap_cons25l1, termcap_cons25l1_m,
1251	termcap_xterm, NULL,
1252    };
1253    const char **cp;
1254
1255    if (!file_readable(TERMCAP_FILE)) {
1256	Mkdir("/usr/share/misc");
1257	fp = fopen(TERMCAP_FILE, "w");
1258	if (!fp) {
1259	    msgConfirm("Unable to initialize termcap file. Some screen-oriented\nutilities may not work.");
1260	    return;
1261	}
1262	cp = caps;
1263	while (*cp)
1264	    fprintf(fp, "%s\n", *(cp++));
1265	fclose(fp);
1266    }
1267}
1268