I think I fixed all your suggestions. I don't strictly adhere to kernel normal in the use of comments and I parse command-line arguments without using getopt(3), but the method is robust.
-Luke <A few quick comments from glancing over this: o I definitely don't think camel case will be accepted o I'm pretty sure strtonum(3) is strongly preferred over strtod(3) et al.
/* * 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> 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, uname_pid; int ftp_to_sed[2]; int sed_to_parent[2]; int uname_to_parent[2]; char uname_r[5], uname_m[20], character; int i; double s = 7; int position, num, c, n = 5000; FILE *input; 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, "The argument should less than or equal to 100000000"); } else if (!strcmp(argv[position], "-n")) { ++position; if (strlen(argv[position]) > 3) errx(1, "Integer should be less than or equal to 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(uname_to_parent) == -1) err(1, NULL); // "uname -rm" returns version and architecture like: "5.8 amd64\n" to standard out uname_pid = fork(); if (uname_pid == (pid_t) 0) { /* uname child */ close(uname_to_parent[0]); dup2(uname_to_parent[1], STDOUT_FILENO); /*attaching to pipe(s)*/ execl("/usr/bin/uname","/usr/bin/uname", "-rm", NULL); errx(1, "uname execl() failed."); } if (uname_pid == -1) err(1, NULL); close(uname_to_parent[1]); EV_SET(ke, uname_to_parent[0], EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, NULL); if (kevent(kq, ke, 1, NULL, 0, NULL) == -1) { kill(uname_pid, SIGKILL); errx(1, "kevent register fail."); } i = kevent(kq, NULL, 0, ke, 1, &timeout); if (i == -1) { kill(uname_pid, SIGKILL); errx(1, "kevent uname"); } if (i == 0) { kill(uname_pid, SIGKILL); errx(1, "uname timed out."); } input = fdopen (uname_to_parent[0], "r"); if (input == NULL) err(1, NULL); num = 0; position = -1; while ((c = getc(input)) != EOF) { if (num == 0) { if (++position >= 5) errx(1, "uname_r[] got too long!"); if (c != ' ') uname_r[position] = c; else { uname_r[position] = '\0'; num = 1; position = -1; } } else { if (++position >= 20) errx(1, "uname_m[] got too long!"); if (c != '\n') uname_m[position] = c; else { uname_m[position] = '\0'; break; } } } fclose (input); close(uname_to_parent[0]); if (pipe(ftp_to_sed) == -1) err(1, NULL); ftp_pid = fork(); if (ftp_pid == (pid_t) 0) { /*ftp child*/ close(ftp_to_sed[0]); dup2(ftp_to_sed[1], STDOUT_FILENO); /*attaching to pipe(s)*/ 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) { /* sed child */ close(sed_to_parent[0]); dup2(ftp_to_sed[0], STDIN_FILENO); /*attaching to pipe(s)*/ 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 pipe(s) child attached to*/ 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) err(1, NULL); char line[300]; num = 0; position = 0; struct mirror_st *start, *end, *mTemp1, *mTemp2, *mTemp3; start = end = malloc(sizeof(struct mirror_st)); if (start == NULL) 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 { // there is a space before the newline that is eliminated 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') { // changes ftp listings to http. ftp.html says they can be either line[position++] = 'h'; line[position++] = 't'; continue; } } if ( c != '\n' ) line[position++] = c; else { line[position++] = '\0'; position += num = strlen(uname_r) + 10 + strlen(uname_m) + 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, uname_r, position - c); c += strlcpy(end->mirror + c, "/packages/", position - c); c += strlcpy(end->mirror + c, uname_m, 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; } } } fclose (input); // this is to prevent possible segfault in the next sequence. if (start == end) errx(1, "Too few entries found."); mTemp1 = start; while (mTemp1->next != end) mTemp1 = mTemp1->next; free(end->country_title); free(end); mTemp1->next = NULL; // Eliminate redundant mirrors (no longer need the 'end' value) mTemp1 = start; while (mTemp1->next != NULL) { mTemp2 = mTemp1; do { if (!strcmp(mTemp1->mirror, mTemp2->next->mirror)) { mTemp3 = mTemp2->next; mTemp2 = mTemp2->next = mTemp3->next; free(mTemp3->country_title); free(mTemp3->install_path); free(mTemp3->mirror); free(mTemp3); if (mTemp2 == NULL) break; } else mTemp2 = mTemp2->next; } while ( mTemp2->next != NULL ); mTemp1 = mTemp1->next; } int mirror_num = 0; mTemp1 = start; while (mTemp1 != NULL) { ++mirror_num; mTemp1 = mTemp1->next; } struct mirror_st ** array; if ( (array = calloc(mirror_num, sizeof(struct mirror_st *))) == NULL) errx(1, "calloc failed."); mTemp1 = start; for (c = 0; c < mirror_num; ++c) { array[c] = mTemp1; mTemp1 = mTemp1->next; } qsort(array, mirror_num, sizeof(struct mirror_st *), country_cmp); close(sed_to_parent[0]); for (c = 0; c < mirror_num; ++c) { mTemp1 = array[c]; printf("\n%d : %s : %s\n", (mirror_num - c) - 1, mTemp1->country_title, mTemp1->mirror); ftp_pid = fork(); if (ftp_pid == (pid_t) 0) { /* ping child */ execl("/usr/bin/ftp", "ftp", "-Vmo", "/dev/null", mTemp1->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."); } mTemp1->diff = 0; struct timeval tv_start, tv_end; gettimeofday(&tv_start, NULL); skip: 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); mTemp1->diff = timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0; // Loop until ftp() is dead. goto skip; } if ( ke->data == 0 ) { gettimeofday(&tv_end, NULL); mTemp1->diff = get_time_diff(tv_start, tv_end); } else if ( mTemp1->diff == 0 ) mTemp1->diff = timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0 + 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 < timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0) printf("%f\n\n", array[c]->diff); else if (array[c]->diff == timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0) printf("Timeout\n\n"); else printf("Download Error\n\n"); } if (array[0]->diff >= timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0) 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') { if (--lines == 0 && position == 0) break; // get rid of the lonely newline at the end, if it exists 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 >= timeout.tv_sec + (double)timeout.tv_nsec / 1000000000.0) 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 (run as superuser):\n"); printf("%s\n", buf); } if (argc == 1) printf("%s [-s timeout] [-n maximum_mirrors_written]\n", argv[0]); return 0; }