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

Reply via email to