Whoops, got rid of putting in a null character when I should have left it
in.

-Luke
/*
 * Copyright (c) 2016 Luke N. Small
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


/*
 * Special thanks to Dan Mclaughlin for the ftp to sed idea
 *
 * ftp -o - http://www.openbsd.org/ftp.html | \
 * sed -n \
 *  -e 's:</a>$::' \
 * 	-e 's:	<strong>\([^<]*\)<.*:\1:p' \
 * 	-e 's:^\(	[hfr].*\):\1:p'
 */


#define EVENT_NOPOLL
#define EVENT_NOSELECT

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/event.h>
#include <signal.h>
#include <string.h>
#include <sys/utsname.h>

struct mirror_st {
        char           *country_title;
        char           *mirror;
        char           *install_path;
        double 		diff;
        struct mirror_st *next;
};

int
ftp_cmp(const void *a, const void *b)
{
        struct mirror_st **one = (struct mirror_st **) a;
        struct mirror_st **two = (struct mirror_st **) b;

        if ((*one)->diff < (*two)->diff)
            return -1;
        if ((*one)->diff > (*two)->diff)
            return 1;
        return 0;
}

int
country_cmp(const void *a, const void *b)
{
        struct mirror_st **one = (struct mirror_st **) a;
        struct mirror_st **two = (struct mirror_st **) b;

        //list the USA mirrors first, it will subsort correctly
            int8_t temp = !strncmp("USA", (*one)->country_title, 3);
        if (temp != !strncmp("USA", (*two)->country_title, 3)) {
            if (temp)
                return -1;
            return 1;
        }
        return strcmp((*one)->country_title, (*two)->country_title);
}


double
get_time_diff(struct timeval a, struct timeval b)
{
        long 		sec;
        long 		usec;
        sec = b.tv_sec - a.tv_sec;
        usec = b.tv_usec - a.tv_usec;
        if (usec < 0) {
            --sec;
            usec += 1000000;
        }
        return sec + ((double) usec / 1000000.0);
}

void
manpage(char *a)
{
        errx(1, "%s [-s timeout] [-n maximum_mirrors_written]", a);
}


int
main(int argc, char *argv[])
{
        pid_t 		ftp_pid, sed_pid;
        int 		ftp_to_sed[2];
        int 		sed_to_parent[2];
        char 		character;
        int 		i;
        double 		s = 7;
        int 		position , num, c, n = 5000;
        FILE           *input;
        struct utsname 	name;
        if (uname(&name) == -1)
            err(1, NULL);

        if (argc > 1) {
            if (argc % 2 == 0)
                manpage(argv[0]);

            position = 0;
            while (++position < argc) {
                if (strlen(argv[position]) != 2)
                    manpage(argv[0]);

                if (!strcmp(argv[position], "-s")) {
                    ++position;
                    c = -1;
                    i = 0;
                    while ((character = argv[position][++c]) != '\0') {
                        if (character == '.')
                            ++i;

                        if (((character < '0' || character > '9')
                             && character != '.') || i > 1) {

                            if (character == '-')
                                errx(1, "No negative numbers.");
                            errx(1, "Incorrect floating point format.");
                        }
                    }
                    errno = 0;
                    strtod(argv[position], NULL);
                    if (errno == ERANGE)
                        err(1, NULL);
                    if ((s = strtod(argv[position], NULL)) > 100000000.0)
                        errx(1, "-s should less than or equal to 100000000");
                } else if (!strcmp(argv[position], "-n")) {
                    ++position;
                    if (strlen(argv[position]) > 3)
                        errx(1, "Integer should be <= 3 digits long.");
                    c = -1;
                    n = 0;
                    while ((character = argv[position][++c]) != '\0') {
                        if (character < '0' || character > '9') {
                            if (character == '.')
                                errx(1, "No decimal points.");
                            if (character == '-')
                                errx(1, "No negative numbers.");
                            errx(1, "Incorrect integer format.");
                        }
                        n = n * 10 + (int) (character - '0');
                    }
                } else
                    manpage(argv[0]);
            }
        }
        struct kevent 	ke[1];
        struct timespec timeout;

        timeout.tv_sec = (int) s;
        timeout.tv_nsec = (int) ((s - (double) timeout.tv_sec) * 1000000000);

        int 		kq = kqueue();
        if (kq == -1)
            errx(1, "kq!");


        if (pipe(ftp_to_sed) == -1)
            err(1, NULL);


        ftp_pid = fork();
        if (ftp_pid == (pid_t) 0) {
            close(ftp_to_sed[0]);
            dup2(ftp_to_sed[1], STDOUT_FILENO);
            execl("/usr/bin/ftp", "ftp", "-Vo", "-",
                "http://www.openbsd.org/ftp.html";, NULL);
            errx(1, "ftp execl() failed.");
        }
        if (ftp_pid == -1)
            err(1, NULL);

        close(ftp_to_sed[1]);

        if (pipe(sed_to_parent) == -1)
            err(1, NULL);

        sed_pid = fork();
        if (sed_pid == (pid_t) 0) {
            close(sed_to_parent[0]);
            dup2(ftp_to_sed[0], STDIN_FILENO);
            dup2(sed_to_parent[1], STDOUT_FILENO);
            execl("/usr/bin/sed", "sed", "-n",
                "-e", "s:</a>$::",
                "-e", "s:\t<strong>\\([^<]*\\)<.*:\\1:p",
                "-e", "s:^\\(\t[hfr].*\\):\\1:p", NULL);
            kill(ftp_pid, SIGKILL);
            errx(1, "sed execl() failed.");
        }
        if (sed_pid == -1) {
            kill(ftp_pid, SIGKILL);
            err(1, NULL);
        }
        EV_SET(ke, sed_to_parent[0], EVFILT_READ, EV_ADD | EV_ONESHOT,
               0, 0, NULL);
        if (kevent(kq, ke, 1, NULL, 0, NULL) == -1) {
            kill(ftp_pid, SIGKILL);
            kill(sed_pid, SIGKILL);
            errx(1, "sed_to_parent kevent register fail.");
        }
        close(ftp_to_sed[0]);
        close(sed_to_parent[1]);

        i = kevent(kq, NULL, 0, ke, 1, &timeout);
        if (i == -1) {
            kill(ftp_pid, SIGKILL);
            kill(sed_pid, SIGKILL);
            errx(1, "kevent, timeout may be too large");
        }
        if (i == 0) {
            kill(ftp_pid, SIGKILL);
            kill(sed_pid, SIGKILL);
            errx(1, "timed out fetching openbsd.org/ftp.html.");
        }
        input = fdopen(sed_to_parent[0], "r");
        if (input == NULL) {
            kill(ftp_pid, SIGKILL);
            kill(sed_pid, SIGKILL);
            errx(1, "input = fdopen (sed_to_parent[0], \"r\") failed.");
        }
        char 		line     [300];
        num = 0;
        position = 0;
        struct mirror_st *start, *end, *m_temp1, *m_temp2, *m_temp3;
        start = end = malloc(sizeof(struct mirror_st));
        if (start == NULL) {
            kill(ftp_pid, SIGKILL);
            kill(sed_pid, SIGKILL);
            errx(1, "Malloc failed.");
        }
        start->next = NULL;

        while ((c = getc(input)) != EOF) {
            if (position >= 300) {
                kill(ftp_pid, SIGKILL);
                kill(sed_pid, SIGKILL);
                errx(1, "line[] got too long!");
            }
            if (num == 0) {
                if (c != '\n')
                    line[position++] = c;
                else {
                    line[position++] = '\0';
                    end->country_title = malloc(position);
                    if (end->country_title == NULL) {
                        kill(ftp_pid, SIGKILL);
                        kill(sed_pid, SIGKILL);
                        errx(1, "malloc failed.");
                    }
                    strlcpy(end->country_title, line, position);

                    position = 0;
                    num = 1;
                }
            } else {
                if (position == 0) {
                    if ((c != 'h') && (c != 'f') && (c != 'r'))
                        continue;
                    else if (c == 'r')
                        break;
                    else if (c == 'f') {
                        /*
                         * This changes ftp listings to http.
                         * ftp.html says they can be either one.
                         */
                        line[position++] = 'h';
                        line[position++] = 't';
                        continue;
                    }
                }
                if (c != '\n')
                    line[position++] = c;
                else {
                    line[position++] = '\0';

                    position += num = strlen(name.release) + 10
                        + strlen(name.machine) + 7;

                    end->mirror = malloc(position);
                    if (end->mirror == NULL) {
                        kill(ftp_pid, SIGKILL);
                        kill(sed_pid, SIGKILL);
                        errx(1, "malloc failed.");
                    }
                    c = strlcpy(end->mirror, line, position);
                    c += strlcpy(end->mirror + c, name.release, position - c);
                    c += strlcpy(end->mirror + c, "/packages/", position - c);
                    c += strlcpy(end->mirror + c, name.machine, position - c);
                    strlcpy(end->mirror + c, "/SHA256", position - c);

                    position += 15 - num;
                    end->install_path = malloc(position);
                    if (end->install_path == NULL) {
                        kill(ftp_pid, SIGKILL);
                        kill(sed_pid, SIGKILL);
                        errx(1, "malloc failed.");
                    }
                    c = strlcpy(end->install_path, line,
                            position);

                    strlcpy(end->install_path + c, "%c/packages/%a/",
                        position - c);


                    end = end->next = malloc(sizeof(struct mirror_st));
                    if (end == NULL) {
                        kill(ftp_pid, SIGKILL);
                        kill(sed_pid, SIGKILL);
                        errx(1, "malloc failed.");
                    }
                    end->next = NULL;

                    position = 0;
                    num = 0;
                }
            }
        }

        if (c != 'r')
            errx(1, "rsync file listings not found.");

        fclose(input);

        /* this is to prevent possible segfault in the next sequence. */
        if (start == end)
            errx(1, "No mirrors found.");

        m_temp1 = start;
        while (m_temp1->next != end)
            m_temp1 = m_temp1->next;
        free(end->country_title);
        free(end);
        m_temp1->next = NULL;

        /* Eliminate redundant mirrors (no longer need the 'end' value) */
        m_temp1 = start;
        while (m_temp1->next != NULL) {
            m_temp2 = m_temp1;
            do {
                if (!strcmp(m_temp1->mirror, m_temp2->next->mirror)) {
                    m_temp3 = m_temp2->next;
                    m_temp2 = m_temp2->next = m_temp3->next;

                    free(m_temp3->country_title);
                    free(m_temp3->install_path);
                    free(m_temp3->mirror);
                    free(m_temp3);

                    if (m_temp2 == NULL)
                        break;
                } else
                    m_temp2 = m_temp2->next;

            } while (m_temp2->next != NULL);
            m_temp1 = m_temp1->next;
        }

        close(sed_to_parent[0]);

        int 		mirror_num = 0;
        m_temp1 = start;
        while (m_temp1 != NULL) {
            ++mirror_num;
            m_temp1 = m_temp1->next;
        }

        struct mirror_st **array;

        if ((array = calloc(mirror_num, sizeof(struct mirror_st *))) == NULL)
            errx(1, "calloc failed.");

        m_temp1 = start;
        for (c = 0; c < mirror_num; ++c) {
            array[c] = m_temp1;
            m_temp1 = m_temp1->next;
        }

        qsort(array, mirror_num, sizeof(struct mirror_st *), country_cmp);

        for (c = 0; c < mirror_num; ++c) {
            m_temp1 = array[c];

            printf("\n%d : %s  :  %s\n", (mirror_num - c) - 1,
                   m_temp1->country_title, m_temp1->mirror);

            ftp_pid = fork();
            if (ftp_pid == (pid_t) 0) {
                execl("/usr/bin/ftp", "ftp", "-Vmo", "/dev/null",
                      m_temp1->mirror, NULL);
                errx(1, "ftp execl() failed.");
            }
            if (ftp_pid == -1)
                err(1, NULL);
            EV_SET(ke, ftp_pid, EVFILT_PROC, EV_ADD | EV_ONESHOT,
                   NOTE_EXIT, 0, NULL);
            if (kevent(kq, ke, 1, NULL, 0, NULL) == -1) {
                kill(ftp_pid, SIGKILL);
                errx(1, "kevent register fail.");
            }
            m_temp1->diff = 0;
            struct timeval 	tv_start, tv_end;
            gettimeofday(&tv_start, NULL);

            /* Loop until ftp() is dead and *ke is populated */
            for (;;) {
                i = kevent(kq, NULL, 0, ke, 1, &timeout);
                if (i == -1) {
                    kill(ftp_pid, SIGKILL);
                    errx(1, "kevent");
                }
                if (i == 0) {
                    printf("\n");
                    kill(ftp_pid, SIGKILL);
                    m_temp1->diff = s;
                } else
                    break;
            }

            if (ke->data == 0) {
                gettimeofday(&tv_end, NULL);
                m_temp1->diff = get_time_diff(tv_start, tv_end);
            } else if (m_temp1->diff == 0)
                m_temp1->diff = s + 1;
        }

        qsort(array, mirror_num, sizeof(struct mirror_st *), ftp_cmp);

        printf("\n\n");

        for (c = mirror_num - 1; c >= 0; --c) {
            printf("%d : %s:\n\t%s : ", c, array[c]->country_title,
                   array[c]->install_path);

            if (array[c]->diff < s)
                printf("%f\n\n", array[c]->diff);
            else if (array[c]->diff == s)
                printf("Timeout\n\n");
            else
                printf("Download Error\n\n");
        }

        if (array[0]->diff >= s)
            errx(1, "No mirrors found within timeout period.");

        char           *buf;
        int 		lines;
        int 		total_length = 0;
        int 		copy = 0;

        for (position = 0; position < mirror_num; ++position) {
            total_length += strlen("installpath += \n")
                + strlen(array[position]->install_path);
        }

        if ((input = fopen("/etc/pkg.conf", "r")) == NULL) {
            buf = (char *) malloc(total_length + 1);
            if (buf == NULL)
                errx(1, "Malloc failed.");
        } else {
            fseek(input, 0, SEEK_END);
            num = ftell(input);
            fseek(input, 0, SEEK_SET);
            lines = 0;
            buf = (char *) malloc(num + total_length + 11 + 1);
            if (buf == NULL)
                errx(1, "malloc failed.");
            fread(buf, 1, num, input);
            fclose(input);
            for (c = 0; c < num; ++c) {
                if (buf[c] == '\n')
                    ++lines;
            }

            position = 0;
            for (c = 0; c < num; ++c) {
                if (buf[c] == '\n') {
                    --lines;
                    position = 0;
                } else if (position++ == 0) {
                    if (!strncmp(buf + c, "installpath", 11)) {
                        if (lines) {
                            while (buf[++c] != '\n');
                            --lines;
                            position = 0;
                            continue;
                        } else
                            break;
                    }
                }
                buf[copy++] = buf[c];
            }
            total_length += num;
        }

        copy += strlcpy(buf + copy, "installpath = ", total_length - copy);
        copy += strlcpy(buf + copy, array[0]->install_path,
            total_length - copy);

        if (n < mirror_num)
            mirror_num = n;

        position = 1;
        while (position < mirror_num) {
            /* Eliminates dowload error and timeout mirrors */
            if (array[position]->diff >= s)
                break;
            copy += strlcpy(buf + copy, "\ninstallpath += ",
                total_length - copy);

            copy += strlcpy(buf + copy, array[position++]->install_path,
                total_length - copy);
        }
        copy += strlcpy(buf + copy, "\n", total_length - copy);


        input = fopen("/etc/pkg.conf", "w");
        if (input != NULL) {
            fwrite(buf, 1, copy, input);
            fclose(input);
            printf("\n\nEdit out all PKG_PATH environment variable exports ");
            printf("and run \"unset PKG_PATH\".\n\n/etc/pkg.conf:\n%s\n", buf);
        } else {
            printf("\n\nThis could have been the contents of /etc/pkg.conf");
            printf(" (run as superuser):\n%s\n", buf);
        }

        if (argc == 1)
            printf("%s [-s timeout] [-n maximum_mirrors_written]\n", argv[0]);

        return 0;
}

Reply via email to