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