I fixed the uname(1) call and replaced it with uname(3) I read the style
man page. ran the program through indent.

I ran it through sed because it reduces code complexity. Why re-engineer
the wheel?

I use C because I can use kqueue from a fresh install. You have to use
unaudited packages to use perl or python kqueue. I want the program to be
safe to run as root.

I use kqueue because I like it, but also because the mirror ftp calls need
to have a wait() call that can collect the status and can enforce a timeout
period. ftp can be a bitch that runs without stopping if you let it. I'm
not willing to let it run for hours, unless the user specifically lets the
timeout period be hours, where I've written it to allow that.

-Luke

On Fri, Jan 29, 2016 at 2:19 AM, Nicholas Marriott <
nicholas.marri...@gmail.com> wrote:

> Firstly, I don't think we need this in base and I think there is little
> to no chance of it being taken, even if the code is improved.
>
> Secondly:
>
> - The code is still miles off style(9) and isn't really a consistent
>   style within itself either.
>
> - Forking uname(1)? What? No offence, but that is hilarious :-). Why
>   fork uname(1) for uname(3) but not date(1) for gettimeofday(2)?
>
> - Why would you fork sed either?
>
> I think C is the wrong tool for this. Why not write a shell, perl, or
> python script?
>
> Then if people start to use it you could make a port.
>
>
/*
 * 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 {
                    ++position;

                    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