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.
style within itself either.
- Forking uname(1)? What? No offence, but that is hilarious :-). Why
I think C is the wrong tool for this. Why not write a shell, perl, or
Then if people start to use it you could make a port.
Post by Luke SmallI 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
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);
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;
}