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 if (!variable_get(VAR_NONINTERACTIVE))
266 msgConfirm("Using USB device: %s", mediaDevice->name);
267 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
271 DOSHook(dialogMenuItem *self)
273 return genericHook(self, DEVICE_TYPE_DOS);
277 * Return 1 if we successfully found and set the installation type to
278 * be a DOS partition.
281 mediaSetDOS(dialogMenuItem *self)
287 devs = deviceFind(NULL, DEVICE_TYPE_DOS);
288 cnt = deviceCount(devs);
290 msgConfirm("No DOS primary partitions found! This installation method is unavailable");
291 return DITEM_FAILURE | DITEM_CONTINUE;
297 menu = deviceCreateMenu(&MenuMediaDOS, DEVICE_TYPE_DOS, DOSHook, NULL);
299 msgFatal("Unable to create DOS menu! Something is seriously wrong.");
300 status = dmenuOpenSimple(menu, FALSE);
303 return DITEM_FAILURE;
306 mediaDevice = devs[0];
307 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
311 * Return 0 if we successfully found and set the installation type to
315 mediaSetFTP(dialogMenuItem *self)
317 static Device ftpDevice;
318 char *cp, hbuf[MAXHOSTNAMELEN], *hostname, *dir;
319 struct addrinfo hints, *res;
323 static Device *networkDev = NULL;
326 cp = variable_get(VAR_FTP_PATH);
327 /* If we've been through here before ... */
328 if (networkDev && cp && msgYesNo("Re-use old FTP site selection values?"))
331 if (!dmenuOpenSimple(&MenuMediaFTP, FALSE))
332 return DITEM_FAILURE;
334 cp = variable_get(VAR_FTP_PATH);
337 return DITEM_FAILURE;
338 else if (!strcmp(cp, "other")) {
339 variable_set2(VAR_FTP_PATH, "ftp://", 0);
340 cp = variable_get_value(VAR_FTP_PATH, "Please specify the URL of a FreeBSD distribution on a\n"
341 "remote ftp site. This site must accept either anonymous\n"
342 "ftp or you should have set an ftp username and password\n"
343 "in the Options screen.\n\n"
344 "A URL looks like this: ftp://<hostname>/<path>\n"
345 "Where <path> is relative to the anonymous ftp directory or the\n"
346 "home directory of the user being logged in as.", 0);
347 if (!cp || !*cp || !strcmp(cp, "ftp://")) {
348 variable_unset(VAR_FTP_PATH);
349 return DITEM_FAILURE;
352 if (urllen >= sizeof(ftpDevice.name)) {
353 msgConfirm("Length of specified URL is %d characters. Allowable maximum is %d.",
354 urllen,sizeof(ftpDevice.name)-1);
355 variable_unset(VAR_FTP_PATH);
356 return DITEM_FAILURE;
359 if (strncmp("ftp://", cp, 6)) {
360 msgConfirm("Sorry, %s is an invalid URL!", cp);
361 variable_unset(VAR_FTP_PATH);
362 return DITEM_FAILURE;
364 SAFE_STRCPY(ftpDevice.name, cp);
365 SAFE_STRCPY(hbuf, cp + 6);
368 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
369 "would you like to skip over it now?") != 0) {
371 DEVICE_SHUTDOWN(networkDev);
372 if (!(networkDev = tcpDeviceSelect())) {
373 variable_unset(VAR_FTP_PATH);
374 return DITEM_FAILURE;
377 if (!DEVICE_INIT(networkDev)) {
379 msgDebug("mediaSetFTP: Net device init failed.\n");
380 variable_unset(VAR_FTP_PATH);
381 return DITEM_FAILURE;
383 if (*hostname == '[' && (cp = index(hostname + 1, ']')) != NULL &&
384 (*++cp == '\0' || *cp == '/' || *cp == ':')) {
389 cp = index(hostname, ':');
390 if (cp != NULL && *cp == ':') {
392 FtpPort = strtol(cp, 0, 0);
396 if ((dir = index(cp ? cp : hostname, '/')) != NULL)
399 msgDebug("hostname = `%s'\n", hostname);
400 msgDebug("dir = `%s'\n", dir ? dir : "/");
401 msgDebug("port # = `%d'\n", FtpPort);
403 if (!ftp_skip_resolve && variable_get(VAR_NAMESERVER)) {
404 msgNotify("Looking up host %s.", hostname);
406 msgDebug("Starting DNS.\n");
409 msgDebug("Looking up hostname, %s, using getaddrinfo(AI_NUMERICHOST).\n", hostname);
410 af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
411 memset(&hints, 0, sizeof(hints));
412 hints.ai_family = af;
413 hints.ai_socktype = SOCK_STREAM;
414 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
415 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
417 msgDebug("Looking up hostname, %s, using getaddrinfo().\n",
419 hints.ai_flags = AI_PASSIVE;
420 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
421 msgConfirm("Cannot resolve hostname `%s'! Are you sure that"
422 " your\nname server, gateway and network interface are"
423 " correctly configured?", hostname);
425 DEVICE_SHUTDOWN(networkDev);
427 variable_unset(VAR_FTP_PATH);
428 return DITEM_FAILURE;
433 msgDebug("Found DNS entry for %s successfully..\n", hostname);
435 variable_set2(VAR_FTP_HOST, hostname, 0);
436 variable_set2(VAR_FTP_DIR, dir ? dir : "/", 0);
437 variable_set2(VAR_FTP_PORT, itoa(FtpPort), 0);
438 ftpDevice.type = DEVICE_TYPE_FTP;
439 ftpDevice.init = mediaInitFTP;
440 ftpDevice.get = mediaGetFTP;
441 ftpDevice.shutdown = mediaShutdownFTP;
442 ftpDevice.private = networkDev;
443 mediaDevice = &ftpDevice;
444 return DITEM_SUCCESS | DITEM_LEAVE_MENU | DITEM_RESTORE;
448 mediaSetFTPActive(dialogMenuItem *self)
450 variable_set2(VAR_FTP_STATE, "active", 0);
451 return mediaSetFTP(self);
455 mediaSetFTPPassive(dialogMenuItem *self)
457 variable_set2(VAR_FTP_STATE, "passive", 0);
458 return mediaSetFTP(self);
461 int mediaSetHTTP(dialogMenuItem *self)
465 char *cp, *idx, hbuf[MAXHOSTNAMELEN], *hostname;
467 int what = DITEM_RESTORE;
470 tmp = ftp_skip_resolve;
471 ftp_skip_resolve = TRUE;
472 result = mediaSetFTP(self);
473 ftp_skip_resolve = tmp;
475 if (DITEM_STATUS(result) != DITEM_SUCCESS)
478 cp = variable_get_value(VAR_HTTP_PROXY,
479 "Please enter the address of the HTTP proxy in this format:\n"
480 " hostname:port (the ':port' is optional, default is 3128)",0);
482 return DITEM_FAILURE;
483 SAFE_STRCPY(hbuf, cp);
485 if (*hostname == '[' && (idx = index(hostname + 1, ']')) != NULL &&
486 (*++idx == '\0' || *idx == ':')) {
490 idx = index(hostname, ':');
491 if (idx == NULL || *idx != ':')
492 HttpPort = 3128; /* try this as default */
495 HttpPort = strtol(idx, 0, 0);
498 variable_set2(VAR_HTTP_HOST, hostname, 0);
499 variable_set2(VAR_HTTP_PORT, itoa(HttpPort), 0);
501 msgDebug("VAR_FTP_PATH : %s\n",variable_get(VAR_FTP_PATH));
502 msgDebug("VAR_HTTP_HOST, _PORT: %s:%s\n",variable_get(VAR_HTTP_HOST),
503 variable_get(VAR_HTTP_PORT));
506 /* mediaDevice has been set by mediaSetFTP(), overwrite partly: */
507 mediaDevice->type = DEVICE_TYPE_HTTP;
508 mediaDevice->init = mediaInitHTTP;
509 mediaDevice->get = mediaGetHTTP;
510 mediaDevice->shutdown = dummyShutdown;
511 return DITEM_SUCCESS | DITEM_LEAVE_MENU | what;
516 mediaSetUFS(dialogMenuItem *self)
518 static Device ufsDevice;
523 cp = variable_get_value(VAR_UFS_PATH, "Enter a fully qualified pathname for the directory\n"
524 "containing the FreeBSD distribution files:", 0);
526 return DITEM_FAILURE;
528 /* If they gave us a CDROM or something, try and pick a better name */
530 strcpy(ufsDevice.name, "ufs");
532 strcpy(ufsDevice.name, st.f_fstypename);
534 ufsDevice.type = DEVICE_TYPE_UFS;
535 ufsDevice.init = dummyInit;
536 ufsDevice.get = mediaGetUFS;
537 ufsDevice.shutdown = dummyShutdown;
538 ufsDevice.private = strdup(cp);
539 mediaDevice = &ufsDevice;
540 return DITEM_LEAVE_MENU;
544 mediaSetNFS(dialogMenuItem *self)
546 static Device nfsDevice;
547 static Device *networkDev = NULL;
549 char hostname[MAXPATHLEN];
553 cp = variable_get_value(VAR_NFS_PATH, "Please enter the full NFS file specification for the remote\n"
554 "host and directory containing the FreeBSD distribution files.\n"
555 "This should be in the format: hostname:/some/freebsd/dir", 0);
557 return DITEM_FAILURE;
558 SAFE_STRCPY(hostname, cp);
559 if (!(idx = index(hostname, ':'))) {
560 msgConfirm("Invalid NFS path specification. Must be of the form:\n"
561 "host:/full/pathname/to/FreeBSD/distdir");
562 return DITEM_FAILURE;
564 pathlen = strlen(hostname);
565 if (pathlen >= sizeof(nfsDevice.name)) {
566 msgConfirm("Length of specified NFS path is %d characters. Allowable maximum is %d.",
567 pathlen,sizeof(nfsDevice.name)-1);
568 variable_unset(VAR_NFS_PATH);
569 return DITEM_FAILURE;
571 SAFE_STRCPY(nfsDevice.name, hostname);
573 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
574 "would you like to skip over it now?") != 0) {
576 DEVICE_SHUTDOWN(networkDev);
577 if (!(networkDev = tcpDeviceSelect()))
578 return DITEM_FAILURE;
580 if (!DEVICE_INIT(networkDev)) {
582 msgDebug("mediaSetNFS: Net device init failed\n");
584 if (variable_get(VAR_NAMESERVER)) {
586 if ((inet_addr(hostname) == INADDR_NONE) && (gethostbyname(hostname) == NULL)) {
587 msgConfirm("Cannot resolve hostname `%s'! Are you sure that your\n"
588 "name server, gateway and network interface are correctly configured?", hostname);
590 DEVICE_SHUTDOWN(networkDev);
592 variable_unset(VAR_NFS_PATH);
593 return DITEM_FAILURE;
597 msgDebug("Found DNS entry for %s successfully..\n", hostname);
600 variable_set2(VAR_NFS_HOST, hostname, 0);
601 nfsDevice.type = DEVICE_TYPE_NFS;
602 nfsDevice.init = mediaInitNFS;
603 nfsDevice.get = mediaGetNFS;
604 nfsDevice.shutdown = mediaShutdownNFS;
605 nfsDevice.private = networkDev;
606 mediaDevice = &nfsDevice;
607 return DITEM_LEAVE_MENU;
611 mediaExtractDistBegin(char *dir, int *fd, int *zpid, int *cpid)
613 int i, pfd[2],qfd[2];
623 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
624 : "/usr/bin/" UNZIPPER;
626 dup2(qfd[0], 0); close(qfd[0]);
627 dup2(pfd[1], 1); close(pfd[1]);
632 open("/dev/null", O_WRONLY);
636 i = execl(unzipper, unzipper, (char *)0);
638 msgDebug("%s command returns %d status\n", unzipper, i);
645 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
647 dup2(pfd[0], 0); close(pfd[0]);
655 close(1); open("/dev/null", O_WRONLY);
658 if (strlen(cpioVerbosity()))
659 i = execl(cpio, cpio, "-idum", cpioVerbosity(), (char *)0);
661 i = execl(cpio, cpio, "-idum", (char *)0);
663 msgDebug("%s command returns %d status\n", cpio, i);
672 mediaExtractDistEnd(int zpid, int cpid)
676 i = waitpid(zpid, &j, 0);
677 /* Don't check exit status - gunzip seems to return a bogus one! */
680 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
683 i = waitpid(cpid, &j, 0);
684 if (i < 0 || WEXITSTATUS(j)) {
686 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
693 mediaExtractDist(char *dir, char *dist, FILE *fp)
695 int i, j, total, seconds, zpid, cpid, pfd[2], qfd[2];
697 struct timeval start, stop;
698 struct sigaction new, old;
705 pipe(pfd); /* read end */
706 pipe(qfd); /* write end */
709 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
710 : "/usr/bin/" UNZIPPER;
714 dup2(qfd[0], 0); close(qfd[0]);
717 dup2(pfd[1], 1); close(pfd[1]);
723 open("/dev/null", O_WRONLY);
725 i = execl(unzipper, unzipper, (char *)0);
727 msgDebug("%s command returns %d status\n", unzipper, i);
732 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
735 dup2(pfd[0], 0); close(pfd[0]);
736 close (qfd[0]); close(qfd[1]);
743 dup2(open("/dev/null", O_WRONLY), 1);
746 if (strlen(cpioVerbosity()))
747 i = execl(cpio, cpio, "-idum", cpioVerbosity(), (char *)0);
749 i = execl(cpio, cpio, "-idum", "--block-size", (char *)0);
751 msgDebug("%s command returns %d status\n", cpio, i);
754 close(pfd[0]); close(pfd[1]);
758 (void)gettimeofday(&start, (struct timezone *)0);
760 /* Make ^C abort the current transfer rather than the whole show */
761 new.sa_handler = handle_intr;
763 (void)sigemptyset(&new.sa_mask);
764 sigaction(SIGINT, &new, &old);
766 while ((i = fread(buf, 1, BUFSIZ, fp)) > 0) {
767 if (check_for_interrupt()) {
768 msgConfirm("Failure to read from media: User interrupt.");
771 if (write(qfd[1], buf, i) != i) {
772 msgConfirm("Write error on transfer to cpio process, try of %d bytes.", i);
776 (void)gettimeofday(&stop, (struct timezone *)0);
777 stop.tv_sec = stop.tv_sec - start.tv_sec;
778 stop.tv_usec = stop.tv_usec - start.tv_usec;
779 if (stop.tv_usec < 0)
780 stop.tv_sec--, stop.tv_usec += 1000000;
781 seconds = stop.tv_sec + (stop.tv_usec / 1000000.0);
785 msgInfo("%10d bytes read from %s dist @ %.1f KB/sec.",
786 total, dist, (total / seconds) / 1024.0);
789 sigaction(SIGINT, &old, NULL); /* restore sigint */
792 i = waitpid(zpid, &j, 0);
793 /* Don't check exit status - gunzip seems to return a bogus one! */
796 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
799 i = waitpid(cpid, &j, 0);
800 if (i < 0 || WEXITSTATUS(j)) {
802 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
809 mediaGetType(dialogMenuItem *self)
811 return ((dmenuOpenSimple(&MenuMedia, FALSE) && mediaDevice) ? DITEM_SUCCESS : DITEM_FAILURE);
814 /* Return TRUE if all the media variables are set up correctly */
819 return (DITEM_STATUS(mediaGetType(NULL)) == DITEM_SUCCESS);
823 /* Set the FTP username and password fields */
825 mediaSetFTPUserPass(dialogMenuItem *self)
829 if (variable_get_value(VAR_FTP_USER, "Please enter the username you wish to login as:", 0)) {
830 DialogInputAttrs |= DITEM_NO_ECHO;
831 pass = variable_get_value(VAR_FTP_PASS, "Please enter the password for this user:", 0);
832 DialogInputAttrs &= ~DITEM_NO_ECHO;
836 return (pass ? DITEM_SUCCESS : DITEM_FAILURE);
839 /* Set CPIO verbosity level */
841 mediaSetCPIOVerbosity(dialogMenuItem *self)
843 char *cp = variable_get(VAR_CPIO_VERBOSITY);
846 msgConfirm("CPIO Verbosity is not set to anything!");
847 return DITEM_FAILURE;
850 if (!strcmp(cp, "low"))
851 variable_set2(VAR_CPIO_VERBOSITY, "medium", 0);
852 else if (!strcmp(cp, "medium"))
853 variable_set2(VAR_CPIO_VERBOSITY, "high", 0);
854 else /* must be "high" - wrap around */
855 variable_set2(VAR_CPIO_VERBOSITY, "low", 0);
857 return DITEM_SUCCESS;
860 /* A generic open which follows a well-known "path" of places to look */
862 mediaGenericGet(char *base, const char *file)
866 snprintf(buf, PATH_MAX, "%s/%s", base, file);
867 if (file_readable(buf))
868 return fopen(buf, "r");
869 snprintf(buf, PATH_MAX, "%s/FreeBSD/%s", base, file);
870 if (file_readable(buf))
871 return fopen(buf, "r");
872 snprintf(buf, PATH_MAX, "%s/releases/%s", base, file);
873 if (file_readable(buf))
874 return fopen(buf, "r");
875 snprintf(buf, PATH_MAX, "%s/%s/%s", base, variable_get(VAR_RELNAME), file);
876 if (file_readable(buf))
877 return fopen(buf, "r");
878 snprintf(buf, PATH_MAX, "%s/releases/%s/%s", base, variable_get(VAR_RELNAME), file);
879 return fopen(buf, "r");