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; }