2 * The new sysinstall program.
4 * This is probably the last attempt in the `sysinstall' line, the next
5 * generation being slated to essentially a complete rewrite.
10 * Jordan Hubbard. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
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
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.
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
37 #include "sysinstall.h"
40 #include <sys/socket.h>
41 #include <sys/param.h>
42 #include <sys/mount.h>
43 #include <sys/errno.h>
44 #include <sys/fcntl.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
53 static Boolean got_intr = FALSE;
54 static Boolean ftp_skip_resolve = FALSE;
60 msgDebug("User generated interrupt.\n");
65 check_for_interrupt(void)
75 genericHook(dialogMenuItem *self, DeviceType type)
79 devs = deviceFind(self->prompt, type);
81 mediaDevice = devs[0];
82 return (devs ? DITEM_LEAVE_MENU : DITEM_FAILURE);
86 cdromHook(dialogMenuItem *self)
88 return genericHook(self, DEVICE_TYPE_CDROM);
94 static Boolean initted = FALSE;
98 cp = variable_get(VAR_MEDIA_TIMEOUT);
100 time = MEDIA_TIMEOUT;
107 _res.retry = 2; /* 2 times seems a reasonable number to me */
108 _res.retrans = time / 2; /* so spend half our alloted time on each try */
116 char *cp = variable_get(VAR_CPIO_VERBOSITY);
118 if (cp && !strcmp(cp, "high"))
120 else if (cp && !strcmp(cp, "medium"))
128 if (!mediaDevice || !mediaVerify() || !DEVICE_INIT(mediaDevice))
129 return DITEM_FAILURE;
130 return DITEM_SUCCESS;
137 DEVICE_SHUTDOWN(mediaDevice);
142 * Return 1 if we successfully found and set the installation type to
146 mediaSetCDROM(dialogMenuItem *self)
152 devs = deviceFind(NULL, DEVICE_TYPE_CDROM);
153 cnt = deviceCount(devs);
155 if (self) /* Interactive? */
156 msgConfirm("No CD/DVD devices found! Please check that your system's\n"
157 "configuration is correct and that the CD/DVD drive is of a supported\n"
158 "type. For more information, consult the hardware guide\n"
160 return DITEM_FAILURE | DITEM_CONTINUE;
166 menu = deviceCreateMenu(&MenuMediaCDROM, DEVICE_TYPE_CDROM, cdromHook, NULL);
168 msgFatal("Unable to create CDROM menu! Something is seriously wrong.");
169 status = dmenuOpenSimple(menu, FALSE);
172 return DITEM_FAILURE;
175 mediaDevice = devs[0];
176 return (mediaDevice ? DITEM_SUCCESS | DITEM_LEAVE_MENU : DITEM_FAILURE);
180 floppyHook(dialogMenuItem *self)
182 return genericHook(self, DEVICE_TYPE_FLOPPY);
186 * Return 1 if we successfully found and set the installation type to
190 mediaSetFloppy(dialogMenuItem *self)
196 devs = deviceFind(NULL, DEVICE_TYPE_FLOPPY);
197 cnt = deviceCount(devs);
199 msgConfirm("No floppy devices found! Please check that your system's configuration\n"
200 "is correct. For more information, consult the hardware guide in the Doc\n"
202 return DITEM_FAILURE | DITEM_CONTINUE;
208 menu = deviceCreateMenu(&MenuMediaFloppy, DEVICE_TYPE_FLOPPY, floppyHook, NULL);
210 msgFatal("Unable to create Floppy menu! Something is seriously wrong.");
211 status = dmenuOpenSimple(menu, FALSE);
214 return DITEM_FAILURE;
217 mediaDevice = devs[0];
219 mediaDevice->private = NULL;
220 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
224 USBHook(dialogMenuItem *self)
226 return genericHook(self, DEVICE_TYPE_USB);
231 * Attempt to use USB as the installation media type.
234 mediaSetUSB(dialogMenuItem *self)
240 devs = deviceFind(NULL, DEVICE_TYPE_USB);
241 cnt = deviceCount(devs);
244 msgConfirm("No USB devices found!");
245 return DITEM_FAILURE | DITEM_CONTINUE;
251 menu = deviceCreateMenu(&MenuMediaUSB, DEVICE_TYPE_USB, USBHook,
254 msgFatal("Unable to create USB menu! Something is " \
256 status = dmenuOpenSimple(menu, FALSE);
259 return DITEM_FAILURE;
262 mediaDevice = devs[0];
264 mediaDevice->private = NULL;
265 msgConfirm("Using USB device: %s", mediaDevice->name);
266 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
270 DOSHook(dialogMenuItem *self)
272 return genericHook(self, DEVICE_TYPE_DOS);
276 * Return 1 if we successfully found and set the installation type to
277 * be a DOS partition.
280 mediaSetDOS(dialogMenuItem *self)
286 devs = deviceFind(NULL, DEVICE_TYPE_DOS);
287 cnt = deviceCount(devs);
289 msgConfirm("No DOS primary partitions found! This installation method is unavailable");
290 return DITEM_FAILURE | DITEM_CONTINUE;
296 menu = deviceCreateMenu(&MenuMediaDOS, DEVICE_TYPE_DOS, DOSHook, NULL);
298 msgFatal("Unable to create DOS menu! Something is seriously wrong.");
299 status = dmenuOpenSimple(menu, FALSE);
302 return DITEM_FAILURE;
305 mediaDevice = devs[0];
306 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
310 * Return 0 if we successfully found and set the installation type to
314 mediaSetFTP(dialogMenuItem *self)
316 static Device ftpDevice;
317 char *cp, hbuf[MAXHOSTNAMELEN], *hostname, *dir;
318 struct addrinfo hints, *res;
322 static Device *networkDev = NULL;
325 cp = variable_get(VAR_FTP_PATH);
326 /* If we've been through here before ... */
327 if (networkDev && cp && msgYesNo("Re-use old FTP site selection values?"))
330 if (!dmenuOpenSimple(&MenuMediaFTP, FALSE))
331 return DITEM_FAILURE;
333 cp = variable_get(VAR_FTP_PATH);
336 return DITEM_FAILURE;
337 else if (!strcmp(cp, "other")) {
338 variable_set2(VAR_FTP_PATH, "ftp://", 0);
339 cp = variable_get_value(VAR_FTP_PATH, "Please specify the URL of a FreeBSD distribution on a\n"
340 "remote ftp site. This site must accept either anonymous\n"
341 "ftp or you should have set an ftp username and password\n"
342 "in the Options screen.\n\n"
343 "A URL looks like this: ftp://<hostname>/<path>\n"
344 "Where <path> is relative to the anonymous ftp directory or the\n"
345 "home directory of the user being logged in as.", 0);
346 if (!cp || !*cp || !strcmp(cp, "ftp://")) {
347 variable_unset(VAR_FTP_PATH);
348 return DITEM_FAILURE;
351 if (urllen >= sizeof(ftpDevice.name)) {
352 msgConfirm("Length of specified URL is %d characters. Allowable maximum is %d.",
353 urllen,sizeof(ftpDevice.name)-1);
354 variable_unset(VAR_FTP_PATH);
355 return DITEM_FAILURE;
358 if (strncmp("ftp://", cp, 6)) {
359 msgConfirm("Sorry, %s is an invalid URL!", cp);
360 variable_unset(VAR_FTP_PATH);
361 return DITEM_FAILURE;
363 SAFE_STRCPY(ftpDevice.name, cp);
364 SAFE_STRCPY(hbuf, cp + 6);
367 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
368 "would you like to skip over it now?") != 0) {
370 DEVICE_SHUTDOWN(networkDev);
371 if (!(networkDev = tcpDeviceSelect())) {
372 variable_unset(VAR_FTP_PATH);
373 return DITEM_FAILURE;
376 if (!DEVICE_INIT(networkDev)) {
378 msgDebug("mediaSetFTP: Net device init failed.\n");
379 variable_unset(VAR_FTP_PATH);
380 return DITEM_FAILURE;
382 if (*hostname == '[' && (cp = index(hostname + 1, ']')) != NULL &&
383 (*++cp == '\0' || *cp == '/' || *cp == ':')) {
388 cp = index(hostname, ':');
389 if (cp != NULL && *cp == ':') {
391 FtpPort = strtol(cp, 0, 0);
395 if ((dir = index(cp ? cp : hostname, '/')) != NULL)
398 msgDebug("hostname = `%s'\n", hostname);
399 msgDebug("dir = `%s'\n", dir ? dir : "/");
400 msgDebug("port # = `%d'\n", FtpPort);
402 if (!ftp_skip_resolve && variable_get(VAR_NAMESERVER)) {
403 msgNotify("Looking up host %s.", hostname);
405 msgDebug("Starting DNS.\n");
408 msgDebug("Looking up hostname, %s, using getaddrinfo(AI_NUMERICHOST).\n", hostname);
409 af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
410 memset(&hints, 0, sizeof(hints));
411 hints.ai_family = af;
412 hints.ai_socktype = SOCK_STREAM;
413 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
414 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
416 msgDebug("Looking up hostname, %s, using getaddrinfo().\n",
418 hints.ai_flags = AI_PASSIVE;
419 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
420 msgConfirm("Cannot resolve hostname `%s'! Are you sure that"
421 " your\nname server, gateway and network interface are"
422 " correctly configured?", hostname);
424 DEVICE_SHUTDOWN(networkDev);
426 variable_unset(VAR_FTP_PATH);
427 return DITEM_FAILURE;
432 msgDebug("Found DNS entry for %s successfully..\n", hostname);
434 variable_set2(VAR_FTP_HOST, hostname, 0);
435 variable_set2(VAR_FTP_DIR, dir ? dir : "/", 0);
436 variable_set2(VAR_FTP_PORT, itoa(FtpPort), 0);
437 ftpDevice.type = DEVICE_TYPE_FTP;
438 ftpDevice.init = mediaInitFTP;
439 ftpDevice.get = mediaGetFTP;
440 ftpDevice.shutdown = mediaShutdownFTP;
441 ftpDevice.private = networkDev;
442 mediaDevice = &ftpDevice;
443 return DITEM_SUCCESS | DITEM_LEAVE_MENU | DITEM_RESTORE;
447 mediaSetFTPActive(dialogMenuItem *self)
449 variable_set2(VAR_FTP_STATE, "active", 0);
450 return mediaSetFTP(self);
454 mediaSetFTPPassive(dialogMenuItem *self)
456 variable_set2(VAR_FTP_STATE, "passive", 0);
457 return mediaSetFTP(self);
460 int mediaSetHTTP(dialogMenuItem *self)
464 char *cp, *idx, hbuf[MAXHOSTNAMELEN], *hostname;
466 int what = DITEM_RESTORE;
469 tmp = ftp_skip_resolve;
470 ftp_skip_resolve = TRUE;
471 result = mediaSetFTP(self);
472 ftp_skip_resolve = tmp;
474 if (DITEM_STATUS(result) != DITEM_SUCCESS)
477 cp = variable_get_value(VAR_HTTP_PROXY,
478 "Please enter the address of the HTTP proxy in this format:\n"
479 " hostname:port (the ':port' is optional, default is 3128)",0);
481 return DITEM_FAILURE;
482 SAFE_STRCPY(hbuf, cp);
484 if (*hostname == '[' && (idx = index(hostname + 1, ']')) != NULL &&
485 (*++idx == '\0' || *idx == ':')) {
489 idx = index(hostname, ':');
490 if (idx == NULL || *idx != ':')
491 HttpPort = 3128; /* try this as default */
494 HttpPort = strtol(idx, 0, 0);
497 variable_set2(VAR_HTTP_HOST, hostname, 0);
498 variable_set2(VAR_HTTP_PORT, itoa(HttpPort), 0);
500 msgDebug("VAR_FTP_PATH : %s\n",variable_get(VAR_FTP_PATH));
501 msgDebug("VAR_HTTP_HOST, _PORT: %s:%s\n",variable_get(VAR_HTTP_HOST),
502 variable_get(VAR_HTTP_PORT));
505 /* mediaDevice has been set by mediaSetFTP(), overwrite partly: */
506 mediaDevice->type = DEVICE_TYPE_HTTP;
507 mediaDevice->init = mediaInitHTTP;
508 mediaDevice->get = mediaGetHTTP;
509 mediaDevice->shutdown = dummyShutdown;
510 return DITEM_SUCCESS | DITEM_LEAVE_MENU | what;
515 mediaSetUFS(dialogMenuItem *self)
517 static Device ufsDevice;
522 cp = variable_get_value(VAR_UFS_PATH, "Enter a fully qualified pathname for the directory\n"
523 "containing the FreeBSD distribution files:", 0);
525 return DITEM_FAILURE;
527 /* If they gave us a CDROM or something, try and pick a better name */
529 strcpy(ufsDevice.name, "ufs");
531 strcpy(ufsDevice.name, st.f_fstypename);
533 ufsDevice.type = DEVICE_TYPE_UFS;
534 ufsDevice.init = dummyInit;
535 ufsDevice.get = mediaGetUFS;
536 ufsDevice.shutdown = dummyShutdown;
537 ufsDevice.private = strdup(cp);
538 mediaDevice = &ufsDevice;
539 return DITEM_LEAVE_MENU;
543 mediaSetNFS(dialogMenuItem *self)
545 static Device nfsDevice;
546 static Device *networkDev = NULL;
548 char hostname[MAXPATHLEN];
552 cp = variable_get_value(VAR_NFS_PATH, "Please enter the full NFS file specification for the remote\n"
553 "host and directory containing the FreeBSD distribution files.\n"
554 "This should be in the format: hostname:/some/freebsd/dir", 0);
556 return DITEM_FAILURE;
557 SAFE_STRCPY(hostname, cp);
558 if (!(idx = index(hostname, ':'))) {
559 msgConfirm("Invalid NFS path specification. Must be of the form:\n"
560 "host:/full/pathname/to/FreeBSD/distdir");
561 return DITEM_FAILURE;
563 pathlen = strlen(hostname);
564 if (pathlen >= sizeof(nfsDevice.name)) {
565 msgConfirm("Length of specified NFS path is %d characters. Allowable maximum is %d.",
566 pathlen,sizeof(nfsDevice.name)-1);
567 variable_unset(VAR_NFS_PATH);
568 return DITEM_FAILURE;
570 SAFE_STRCPY(nfsDevice.name, hostname);
572 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
573 "would you like to skip over it now?") != 0) {
575 DEVICE_SHUTDOWN(networkDev);
576 if (!(networkDev = tcpDeviceSelect()))
577 return DITEM_FAILURE;
579 if (!DEVICE_INIT(networkDev)) {
581 msgDebug("mediaSetNFS: Net device init failed\n");
583 if (variable_get(VAR_NAMESERVER)) {
585 if ((inet_addr(hostname) == INADDR_NONE) && (gethostbyname(hostname) == NULL)) {
586 msgConfirm("Cannot resolve hostname `%s'! Are you sure that your\n"
587 "name server, gateway and network interface are correctly configured?", hostname);
589 DEVICE_SHUTDOWN(networkDev);
591 variable_unset(VAR_NFS_PATH);
592 return DITEM_FAILURE;
596 msgDebug("Found DNS entry for %s successfully..\n", hostname);
599 variable_set2(VAR_NFS_HOST, hostname, 0);
600 nfsDevice.type = DEVICE_TYPE_NFS;
601 nfsDevice.init = mediaInitNFS;
602 nfsDevice.get = mediaGetNFS;
603 nfsDevice.shutdown = mediaShutdownNFS;
604 nfsDevice.private = networkDev;
605 mediaDevice = &nfsDevice;
606 return DITEM_LEAVE_MENU;
610 mediaExtractDistBegin(char *dir, int *fd, int *zpid, int *cpid)
612 int i, pfd[2],qfd[2];
622 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
623 : "/usr/bin/" UNZIPPER;
625 dup2(qfd[0], 0); close(qfd[0]);
626 dup2(pfd[1], 1); close(pfd[1]);
631 open("/dev/null", O_WRONLY);
635 i = execl(unzipper, unzipper, (char *)0);
637 msgDebug("%s command returns %d status\n", unzipper, i);
644 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
646 dup2(pfd[0], 0); close(pfd[0]);
654 close(1); open("/dev/null", O_WRONLY);
657 if (strlen(cpioVerbosity()))
658 i = execl(cpio, cpio, "-idum", cpioVerbosity(), (char *)0);
660 i = execl(cpio, cpio, "-idum", (char *)0);
662 msgDebug("%s command returns %d status\n", cpio, i);
671 mediaExtractDistEnd(int zpid, int cpid)
675 i = waitpid(zpid, &j, 0);
676 /* Don't check exit status - gunzip seems to return a bogus one! */
679 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
682 i = waitpid(cpid, &j, 0);
683 if (i < 0 || WEXITSTATUS(j)) {
685 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
692 mediaExtractDist(char *dir, char *dist, FILE *fp)
694 int i, j, total, seconds, zpid, cpid, pfd[2], qfd[2];
696 struct timeval start, stop;
697 struct sigaction new, old;
704 pipe(pfd); /* read end */
705 pipe(qfd); /* write end */
708 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
709 : "/usr/bin/" UNZIPPER;
713 dup2(qfd[0], 0); close(qfd[0]);
716 dup2(pfd[1], 1); close(pfd[1]);
722 open("/dev/null", O_WRONLY);
724 i = execl(unzipper, unzipper, (char *)0);
726 msgDebug("%s command returns %d status\n", unzipper, i);
731 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
734 dup2(pfd[0], 0); close(pfd[0]);
735 close (qfd[0]); close(qfd[1]);
742 dup2(open("/dev/null", O_WRONLY), 1);
745 if (strlen(cpioVerbosity()))
746 i = execl(cpio, cpio, "-idum", cpioVerbosity(), (char *)0);
748 i = execl(cpio, cpio, "-idum", "--block-size", (char *)0);
750 msgDebug("%s command returns %d status\n", cpio, i);
753 close(pfd[0]); close(pfd[1]);
757 (void)gettimeofday(&start, (struct timezone *)0);
759 /* Make ^C abort the current transfer rather than the whole show */
760 new.sa_handler = handle_intr;
762 (void)sigemptyset(&new.sa_mask);
763 sigaction(SIGINT, &new, &old);
765 while ((i = fread(buf, 1, BUFSIZ, fp)) > 0) {
766 if (check_for_interrupt()) {
767 msgConfirm("Failure to read from media: User interrupt.");
770 if (write(qfd[1], buf, i) != i) {
771 msgConfirm("Write error on transfer to cpio process, try of %d bytes.", i);
775 (void)gettimeofday(&stop, (struct timezone *)0);
776 stop.tv_sec = stop.tv_sec - start.tv_sec;
777 stop.tv_usec = stop.tv_usec - start.tv_usec;
778 if (stop.tv_usec < 0)
779 stop.tv_sec--, stop.tv_usec += 1000000;
780 seconds = stop.tv_sec + (stop.tv_usec / 1000000.0);
784 msgInfo("%10d bytes read from %s dist @ %.1f KB/sec.",
785 total, dist, (total / seconds) / 1024.0);
788 sigaction(SIGINT, &old, NULL); /* restore sigint */
791 i = waitpid(zpid, &j, 0);
792 /* Don't check exit status - gunzip seems to return a bogus one! */
795 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
798 i = waitpid(cpid, &j, 0);
799 if (i < 0 || WEXITSTATUS(j)) {
801 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
808 mediaGetType(dialogMenuItem *self)
810 return ((dmenuOpenSimple(&MenuMedia, FALSE) && mediaDevice) ? DITEM_SUCCESS : DITEM_FAILURE);
813 /* Return TRUE if all the media variables are set up correctly */
818 return (DITEM_STATUS(mediaGetType(NULL)) == DITEM_SUCCESS);
822 /* Set the FTP username and password fields */
824 mediaSetFTPUserPass(dialogMenuItem *self)
828 if (variable_get_value(VAR_FTP_USER, "Please enter the username you wish to login as:", 0)) {
829 DialogInputAttrs |= DITEM_NO_ECHO;
830 pass = variable_get_value(VAR_FTP_PASS, "Please enter the password for this user:", 0);
831 DialogInputAttrs &= ~DITEM_NO_ECHO;
835 return (pass ? DITEM_SUCCESS : DITEM_FAILURE);
838 /* Set CPIO verbosity level */
840 mediaSetCPIOVerbosity(dialogMenuItem *self)
842 char *cp = variable_get(VAR_CPIO_VERBOSITY);
845 msgConfirm("CPIO Verbosity is not set to anything!");
846 return DITEM_FAILURE;
849 if (!strcmp(cp, "low"))
850 variable_set2(VAR_CPIO_VERBOSITY, "medium", 0);
851 else if (!strcmp(cp, "medium"))
852 variable_set2(VAR_CPIO_VERBOSITY, "high", 0);
853 else /* must be "high" - wrap around */
854 variable_set2(VAR_CPIO_VERBOSITY, "low", 0);
856 return DITEM_SUCCESS;
859 /* A generic open which follows a well-known "path" of places to look */
861 mediaGenericGet(char *base, const char *file)
865 snprintf(buf, PATH_MAX, "%s/%s", base, file);
866 if (file_readable(buf))
867 return fopen(buf, "r");
868 snprintf(buf, PATH_MAX, "%s/FreeBSD/%s", base, file);
869 if (file_readable(buf))
870 return fopen(buf, "r");
871 snprintf(buf, PATH_MAX, "%s/releases/%s", base, file);
872 if (file_readable(buf))
873 return fopen(buf, "r");
874 snprintf(buf, PATH_MAX, "%s/%s/%s", base, variable_get(VAR_RELNAME), file);
875 if (file_readable(buf))
876 return fopen(buf, "r");
877 snprintf(buf, PATH_MAX, "%s/releases/%s/%s", base, variable_get(VAR_RELNAME), file);
878 return fopen(buf, "r");