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 DOSHook(dialogMenuItem *self)
226 return genericHook(self, DEVICE_TYPE_DOS);
230 * Return 1 if we successfully found and set the installation type to
231 * be a DOS partition.
234 mediaSetDOS(dialogMenuItem *self)
240 devs = deviceFind(NULL, DEVICE_TYPE_DOS);
241 cnt = deviceCount(devs);
243 msgConfirm("No DOS primary partitions found! This installation method is unavailable");
244 return DITEM_FAILURE | DITEM_CONTINUE;
250 menu = deviceCreateMenu(&MenuMediaDOS, DEVICE_TYPE_DOS, DOSHook, NULL);
252 msgFatal("Unable to create DOS menu! Something is seriously wrong.");
253 status = dmenuOpenSimple(menu, FALSE);
256 return DITEM_FAILURE;
259 mediaDevice = devs[0];
260 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
264 tapeHook(dialogMenuItem *self)
266 return genericHook(self, DEVICE_TYPE_TAPE);
270 * Return 1 if we successfully found and set the installation type to
274 mediaSetTape(dialogMenuItem *self)
280 devs = deviceFind(NULL, DEVICE_TYPE_TAPE);
281 cnt = deviceCount(devs);
283 msgConfirm("No tape drive devices found! Please check that your system's configuration\n"
284 "is correct. For more information, consult the hardware guide in the Doc\n"
286 return DITEM_FAILURE | DITEM_CONTINUE;
292 menu = deviceCreateMenu(&MenuMediaTape, DEVICE_TYPE_TAPE, tapeHook, NULL);
294 msgFatal("Unable to create tape drive menu! Something is seriously wrong.");
295 status = dmenuOpenSimple(menu, FALSE);
298 return DITEM_FAILURE;
301 mediaDevice = devs[0];
305 val = msgGetInput("/var/tmp", "Please enter the name of a temporary directory containing\n"
306 "sufficient space for holding the contents of this tape (or\n"
307 "tapes). The contents of this directory will be removed\n"
308 "after installation, so be sure to specify a directory that\n"
309 "can be erased afterwards!\n");
313 mediaDevice->private = strdup(val);
315 return (mediaDevice ? DITEM_LEAVE_MENU : DITEM_FAILURE);
319 * Return 0 if we successfully found and set the installation type to
323 mediaSetFTP(dialogMenuItem *self)
325 static Device ftpDevice;
326 char *cp, hbuf[MAXHOSTNAMELEN], *hostname, *dir;
327 struct addrinfo hints, *res;
331 static Device *networkDev = NULL;
334 cp = variable_get(VAR_FTP_PATH);
335 /* If we've been through here before ... */
336 if (networkDev && cp && msgYesNo("Re-use old FTP site selection values?"))
339 if (!dmenuOpenSimple(&MenuMediaFTP, FALSE))
340 return DITEM_FAILURE;
342 cp = variable_get(VAR_FTP_PATH);
345 return DITEM_FAILURE;
346 else if (!strcmp(cp, "other")) {
347 variable_set2(VAR_FTP_PATH, "ftp://", 0);
348 cp = variable_get_value(VAR_FTP_PATH, "Please specify the URL of a FreeBSD distribution on a\n"
349 "remote ftp site. This site must accept either anonymous\n"
350 "ftp or you should have set an ftp username and password\n"
351 "in the Options screen.\n\n"
352 "A URL looks like this: ftp://<hostname>/<path>\n"
353 "Where <path> is relative to the anonymous ftp directory or the\n"
354 "home directory of the user being logged in as.", 0);
355 if (!cp || !*cp || !strcmp(cp, "ftp://")) {
356 variable_unset(VAR_FTP_PATH);
357 return DITEM_FAILURE;
360 if (urllen >= sizeof(ftpDevice.name)) {
361 msgConfirm("Length of specified URL is %d characters. Allowable maximum is %d.",
362 urllen,sizeof(ftpDevice.name)-1);
363 variable_unset(VAR_FTP_PATH);
364 return DITEM_FAILURE;
367 if (strncmp("ftp://", cp, 6)) {
368 msgConfirm("Sorry, %s is an invalid URL!", cp);
369 variable_unset(VAR_FTP_PATH);
370 return DITEM_FAILURE;
372 SAFE_STRCPY(ftpDevice.name, cp);
373 SAFE_STRCPY(hbuf, cp + 6);
376 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
377 "would you like to skip over it now?") != 0) {
379 DEVICE_SHUTDOWN(networkDev);
380 if (!(networkDev = tcpDeviceSelect())) {
381 variable_unset(VAR_FTP_PATH);
382 return DITEM_FAILURE;
385 if (!DEVICE_INIT(networkDev)) {
387 msgDebug("mediaSetFTP: Net device init failed.\n");
388 variable_unset(VAR_FTP_PATH);
389 return DITEM_FAILURE;
391 if (*hostname == '[' && (cp = index(hostname + 1, ']')) != NULL &&
392 (*++cp == '\0' || *cp == '/' || *cp == ':')) {
397 cp = index(hostname, ':');
398 if (cp != NULL && *cp == ':') {
400 FtpPort = strtol(cp, 0, 0);
404 if ((dir = index(cp ? cp : hostname, '/')) != NULL)
407 msgDebug("hostname = `%s'\n", hostname);
408 msgDebug("dir = `%s'\n", dir ? dir : "/");
409 msgDebug("port # = `%d'\n", FtpPort);
411 if (!ftp_skip_resolve && variable_get(VAR_NAMESERVER)) {
412 msgNotify("Looking up host %s.", hostname);
414 msgDebug("Starting DNS.\n");
417 msgDebug("Looking up hostname, %s, using getaddrinfo(AI_NUMERICHOST).\n", hostname);
418 af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
419 memset(&hints, 0, sizeof(hints));
420 hints.ai_family = af;
421 hints.ai_socktype = SOCK_STREAM;
422 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
423 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
425 msgDebug("Looking up hostname, %s, using getaddrinfo().\n",
427 hints.ai_flags = AI_PASSIVE;
428 if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
429 msgConfirm("Cannot resolve hostname `%s'! Are you sure that"
430 " your\nname server, gateway and network interface are"
431 " correctly configured?", hostname);
433 DEVICE_SHUTDOWN(networkDev);
435 variable_unset(VAR_FTP_PATH);
436 return DITEM_FAILURE;
441 msgDebug("Found DNS entry for %s successfully..\n", hostname);
443 variable_set2(VAR_FTP_HOST, hostname, 0);
444 variable_set2(VAR_FTP_DIR, dir ? dir : "/", 0);
445 variable_set2(VAR_FTP_PORT, itoa(FtpPort), 0);
446 ftpDevice.type = DEVICE_TYPE_FTP;
447 ftpDevice.init = mediaInitFTP;
448 ftpDevice.get = mediaGetFTP;
449 ftpDevice.shutdown = mediaShutdownFTP;
450 ftpDevice.private = networkDev;
451 mediaDevice = &ftpDevice;
452 return DITEM_SUCCESS | DITEM_LEAVE_MENU | DITEM_RESTORE;
456 mediaSetFTPActive(dialogMenuItem *self)
458 variable_set2(VAR_FTP_STATE, "active", 0);
459 return mediaSetFTP(self);
463 mediaSetFTPPassive(dialogMenuItem *self)
465 variable_set2(VAR_FTP_STATE, "passive", 0);
466 return mediaSetFTP(self);
469 int mediaSetHTTP(dialogMenuItem *self)
473 char *cp, *idx, hbuf[MAXHOSTNAMELEN], *hostname;
475 int what = DITEM_RESTORE;
478 tmp = ftp_skip_resolve;
479 ftp_skip_resolve = TRUE;
480 result = mediaSetFTP(self);
481 ftp_skip_resolve = tmp;
483 if (DITEM_STATUS(result) != DITEM_SUCCESS)
486 cp = variable_get_value(VAR_HTTP_PROXY,
487 "Please enter the address of the HTTP proxy in this format:\n"
488 " hostname:port (the ':port' is optional, default is 3128)",0);
490 return DITEM_FAILURE;
491 SAFE_STRCPY(hbuf, cp);
493 if (*hostname == '[' && (idx = index(hostname + 1, ']')) != NULL &&
494 (*++idx == '\0' || *idx == ':')) {
498 idx = index(hostname, ':');
499 if (idx == NULL || *idx != ':')
500 HttpPort = 3128; /* try this as default */
503 HttpPort = strtol(idx, 0, 0);
506 variable_set2(VAR_HTTP_HOST, hostname, 0);
507 variable_set2(VAR_HTTP_PORT, itoa(HttpPort), 0);
509 msgDebug("VAR_FTP_PATH : %s\n",variable_get(VAR_FTP_PATH));
510 msgDebug("VAR_HTTP_HOST, _PORT: %s:%s\n",variable_get(VAR_HTTP_HOST),
511 variable_get(VAR_HTTP_PORT));
514 /* mediaDevice has been set by mediaSetFTP(), overwrite partly: */
515 mediaDevice->type = DEVICE_TYPE_HTTP;
516 mediaDevice->init = mediaInitHTTP;
517 mediaDevice->get = mediaGetHTTP;
518 mediaDevice->shutdown = dummyShutdown;
519 return DITEM_SUCCESS | DITEM_LEAVE_MENU | what;
524 mediaSetUFS(dialogMenuItem *self)
526 static Device ufsDevice;
531 cp = variable_get_value(VAR_UFS_PATH, "Enter a fully qualified pathname for the directory\n"
532 "containing the FreeBSD distribution files:", 0);
534 return DITEM_FAILURE;
536 /* If they gave us a CDROM or something, try and pick a better name */
538 strcpy(ufsDevice.name, "ufs");
540 strcpy(ufsDevice.name, st.f_fstypename);
542 ufsDevice.type = DEVICE_TYPE_UFS;
543 ufsDevice.init = dummyInit;
544 ufsDevice.get = mediaGetUFS;
545 ufsDevice.shutdown = dummyShutdown;
546 ufsDevice.private = strdup(cp);
547 mediaDevice = &ufsDevice;
548 return DITEM_LEAVE_MENU;
552 mediaSetNFS(dialogMenuItem *self)
554 static Device nfsDevice;
555 static Device *networkDev = NULL;
557 char hostname[MAXPATHLEN];
561 cp = variable_get_value(VAR_NFS_PATH, "Please enter the full NFS file specification for the remote\n"
562 "host and directory containing the FreeBSD distribution files.\n"
563 "This should be in the format: hostname:/some/freebsd/dir", 0);
565 return DITEM_FAILURE;
566 SAFE_STRCPY(hostname, cp);
567 if (!(idx = index(hostname, ':'))) {
568 msgConfirm("Invalid NFS path specification. Must be of the form:\n"
569 "host:/full/pathname/to/FreeBSD/distdir");
570 return DITEM_FAILURE;
572 pathlen = strlen(hostname);
573 if (pathlen >= sizeof(nfsDevice.name)) {
574 msgConfirm("Length of specified NFS path is %d characters. Allowable maximum is %d.",
575 pathlen,sizeof(nfsDevice.name)-1);
576 variable_unset(VAR_NFS_PATH);
577 return DITEM_FAILURE;
579 SAFE_STRCPY(nfsDevice.name, hostname);
581 if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
582 "would you like to skip over it now?") != 0) {
584 DEVICE_SHUTDOWN(networkDev);
585 if (!(networkDev = tcpDeviceSelect()))
586 return DITEM_FAILURE;
588 if (!DEVICE_INIT(networkDev)) {
590 msgDebug("mediaSetNFS: Net device init failed\n");
592 if (variable_get(VAR_NAMESERVER)) {
594 if ((inet_addr(hostname) == INADDR_NONE) && (gethostbyname(hostname) == NULL)) {
595 msgConfirm("Cannot resolve hostname `%s'! Are you sure that your\n"
596 "name server, gateway and network interface are correctly configured?", hostname);
598 DEVICE_SHUTDOWN(networkDev);
600 variable_unset(VAR_NFS_PATH);
601 return DITEM_FAILURE;
605 msgDebug("Found DNS entry for %s successfully..\n", hostname);
608 variable_set2(VAR_NFS_HOST, hostname, 0);
609 nfsDevice.type = DEVICE_TYPE_NFS;
610 nfsDevice.init = mediaInitNFS;
611 nfsDevice.get = mediaGetNFS;
612 nfsDevice.shutdown = mediaShutdownNFS;
613 nfsDevice.private = networkDev;
614 mediaDevice = &nfsDevice;
615 return DITEM_LEAVE_MENU;
619 mediaExtractDistBegin(char *dir, int *fd, int *zpid, int *cpid)
621 int i, pfd[2],qfd[2];
631 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
632 : "/usr/bin/" UNZIPPER;
634 dup2(qfd[0], 0); close(qfd[0]);
635 dup2(pfd[1], 1); close(pfd[1]);
640 open("/dev/null", O_WRONLY);
644 i = execl(unzipper, unzipper, (char *)0);
646 msgDebug("%s command returns %d status\n", unzipper, i);
653 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
655 dup2(pfd[0], 0); close(pfd[0]);
663 close(1); open("/dev/null", O_WRONLY);
666 if (strlen(cpioVerbosity()))
667 i = execl(cpio, cpio, "-idum", cpioVerbosity(), "--block-size", mediaTapeBlocksize(), (char *)0);
669 i = execl(cpio, cpio, "-idum", "--block-size", mediaTapeBlocksize(), (char *)0);
671 msgDebug("%s command returns %d status\n", cpio, i);
680 mediaExtractDistEnd(int zpid, int cpid)
684 i = waitpid(zpid, &j, 0);
685 /* Don't check exit status - gunzip seems to return a bogus one! */
688 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
691 i = waitpid(cpid, &j, 0);
692 if (i < 0 || WEXITSTATUS(j)) {
694 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
701 mediaExtractDist(char *dir, char *dist, FILE *fp)
703 int i, j, total, seconds, zpid, cpid, pfd[2], qfd[2];
705 struct timeval start, stop;
706 struct sigaction new, old;
713 pipe(pfd); /* read end */
714 pipe(qfd); /* write end */
717 char *unzipper = RunningAsInit ? "/stand/" UNZIPPER
718 : "/usr/bin/" UNZIPPER;
722 dup2(qfd[0], 0); close(qfd[0]);
725 dup2(pfd[1], 1); close(pfd[1]);
731 open("/dev/null", O_WRONLY);
733 i = execl(unzipper, unzipper, (char *)0);
735 msgDebug("%s command returns %d status\n", unzipper, i);
740 char *cpio = RunningAsInit ? "/stand/cpio" : "/usr/bin/cpio";
743 dup2(pfd[0], 0); close(pfd[0]);
744 close (qfd[0]); close(qfd[1]);
751 dup2(open("/dev/null", O_WRONLY), 1);
754 if (strlen(cpioVerbosity()))
755 i = execl(cpio, cpio, "-idum", cpioVerbosity(), "--block-size", mediaTapeBlocksize(), (char *)0);
757 i = execl(cpio, cpio, "-idum", "--block-size", mediaTapeBlocksize(), (char *)0);
759 msgDebug("%s command returns %d status\n", cpio, i);
762 close(pfd[0]); close(pfd[1]);
766 (void)gettimeofday(&start, (struct timezone *)0);
768 /* Make ^C abort the current transfer rather than the whole show */
769 new.sa_handler = handle_intr;
771 (void)sigemptyset(&new.sa_mask);
772 sigaction(SIGINT, &new, &old);
774 while ((i = fread(buf, 1, BUFSIZ, fp)) > 0) {
775 if (check_for_interrupt()) {
776 msgConfirm("Failure to read from media: User interrupt.");
779 if (write(qfd[1], buf, i) != i) {
780 msgConfirm("Write error on transfer to cpio process, try of %d bytes.", i);
784 (void)gettimeofday(&stop, (struct timezone *)0);
785 stop.tv_sec = stop.tv_sec - start.tv_sec;
786 stop.tv_usec = stop.tv_usec - start.tv_usec;
787 if (stop.tv_usec < 0)
788 stop.tv_sec--, stop.tv_usec += 1000000;
789 seconds = stop.tv_sec + (stop.tv_usec / 1000000.0);
793 msgInfo("%10d bytes read from %s dist @ %.1f KB/sec.",
794 total, dist, (total / seconds) / 1024.0);
797 sigaction(SIGINT, &old, NULL); /* restore sigint */
800 i = waitpid(zpid, &j, 0);
801 /* Don't check exit status - gunzip seems to return a bogus one! */
804 msgDebug("wait for %s returned status of %d!\n", UNZIPPER, i);
807 i = waitpid(cpid, &j, 0);
808 if (i < 0 || WEXITSTATUS(j)) {
810 msgDebug("cpio returned error status of %d!\n", WEXITSTATUS(j));
817 mediaGetType(dialogMenuItem *self)
819 return ((dmenuOpenSimple(&MenuMedia, FALSE) && mediaDevice) ? DITEM_SUCCESS : DITEM_FAILURE);
822 /* Return TRUE if all the media variables are set up correctly */
827 return (DITEM_STATUS(mediaGetType(NULL)) == DITEM_SUCCESS);
831 /* Set the FTP username and password fields */
833 mediaSetFTPUserPass(dialogMenuItem *self)
837 if (variable_get_value(VAR_FTP_USER, "Please enter the username you wish to login as:", 0)) {
838 DialogInputAttrs |= DITEM_NO_ECHO;
839 pass = variable_get_value(VAR_FTP_PASS, "Please enter the password for this user:", 0);
840 DialogInputAttrs &= ~DITEM_NO_ECHO;
844 return (pass ? DITEM_SUCCESS : DITEM_FAILURE);
847 /* Set CPIO verbosity level */
849 mediaSetCPIOVerbosity(dialogMenuItem *self)
851 char *cp = variable_get(VAR_CPIO_VERBOSITY);
854 msgConfirm("CPIO Verbosity is not set to anything!");
855 return DITEM_FAILURE;
858 if (!strcmp(cp, "low"))
859 variable_set2(VAR_CPIO_VERBOSITY, "medium", 0);
860 else if (!strcmp(cp, "medium"))
861 variable_set2(VAR_CPIO_VERBOSITY, "high", 0);
862 else /* must be "high" - wrap around */
863 variable_set2(VAR_CPIO_VERBOSITY, "low", 0);
865 return DITEM_SUCCESS;
868 /* A generic open which follows a well-known "path" of places to look */
870 mediaGenericGet(char *base, const char *file)
874 snprintf(buf, PATH_MAX, "%s/%s", base, file);
875 if (file_readable(buf))
876 return fopen(buf, "r");
877 snprintf(buf, PATH_MAX, "%s/FreeBSD/%s", base, file);
878 if (file_readable(buf))
879 return fopen(buf, "r");
880 snprintf(buf, PATH_MAX, "%s/releases/%s", base, file);
881 if (file_readable(buf))
882 return fopen(buf, "r");
883 snprintf(buf, PATH_MAX, "%s/%s/%s", base, variable_get(VAR_RELNAME), file);
884 if (file_readable(buf))
885 return fopen(buf, "r");
886 snprintf(buf, PATH_MAX, "%s/releases/%s/%s", base, variable_get(VAR_RELNAME), file);
887 return fopen(buf, "r");