/*	clipboard version 2.0
 *	a simple command-line tool to get or set the clipboard
 *	usage: clipboard #prints the current scrap
 *	[some command] | clipboard #changes the scrap to whatever comes in off the pipe
 *	copyright 2004 Mac-arena the Bored Zo.
 *  this version released 14 April 2004 (changes since 2.0rc8: now using mmap to 'read' regular files; translatenewlines uses a translation table or switch instead of if-else-if; assorted hole-plugs)
 *	http://boredzo.fourx.org/clipboard/
 *	this program is provided AS-IS with NO WARRANTY AT ALL. any damage occurring as a result of compiling and/or using this program (including, but not limited to, clobbering of whatever happened to be on the clipboard) is entirely your own fault.
 *	this program requires Mac OS X 10.0 or later.
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <Carbon/Carbon.h>
#include <sys/stat.h>

double version = 2.0;
char subver[5] = "";

void donothing(void *donothing) {
	return;
}

#define NO_FREE donothing
#define HANDLE_ERROR(err, msg, buf, free) {\
	if((err) != noErr) {\
		fprintf(stderr, (msg), (err));\
		if((buf) != NULL)\
			(free)((buf));\
		return (err);\
	}\
}
#define HANDLE_ERROR_RETURN_VOID(err, msg, buf, free) {\
	if((err) != noErr) {\
		fprintf(stderr, (msg), (err));\
		if((buf) != NULL)\
			(free)((buf));\
		return;\
	}\
}
#define HANDLE_NULL(err, msg, int, buf) {\
	if((buf) == NULL) {\
		fprintf(stderr, (msg), (int));\
		return (err);\
	}\
}
#define HANDLE_NULL_RETURN_VOID(err, msg, int, buf) {\
	if((buf) == NULL) {\
		fprintf(stderr, (msg), (int));\
		return;\
	}\
}

#ifdef DEBUG
#define MALLOC(bytes) (fprintf(stderr, "Allocating %u bytes on line %i\n", (bytes), __LINE__), malloc((bytes)))
#else
#define MALLOC(bytes) malloc((bytes))
#endif

#ifndef BUFSIZE
#define BUFSIZE 65536U
#endif

enum {
	kbufsize = BUFSIZE //default buffer size
	/* users of clipboard 1.0.x may remember this being 1024 before.
	 * I changed it to 4096 after remembering that the Carbon documentation recommends 4 K chunks for file I/O.
	 * I changed it to 64 K after experiencing crashes with some files (particularly the Undermac File, which is over 300 K). it didn't help, but I do think this bufsize is saner.
	 * it can be overridden semipermanently by redefining BUFSIZE at the compiler, or temporarily by specifying -s at the command-line.
	 */
};
unsigned long gbufsize = kbufsize;

#ifndef NOARGSUPPORT
#ifdef TOCLIPSUPPORT
Boolean toclip_mode = false; //when true, support toclip's arguments (e.g. change behaviour of -x)
#endif
enum {
	kNoImplicitPaste,   //false
	kYesImplicitPaste,  //true
	kMaybeImplicitPaste = -1
} implicitpaste = kMaybeImplicitPaste;

//I had to code my own system for parsing numbers because Darwin's @*$*#!
//  strtoul accepts negative constants. THIS properly returns 0.
#define CHTOINT(c) ((c) - '0')
#ifndef USELOCALE
#	define ISWITHIN(c, start, stop) ((c) >= (start) && (c) <= (stop))
#	define ISDIGIT(c) ISWITHIN(c, '0', '9')
#	ifdef OLD_DASHS_FLAG
#		define ISUPPER(c) ISWITHIN(c, 'A', 'Z')
#		define ISLOWER(c) ISWITHIN(c, 'a', 'z')
#		define ISALPHA(c) (ISUPPER(c) || ISLOWER(c))
#		define ISSPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '\f')
#	endif
#else
#	define ISDIGIT(c) isdigit(c)
#	ifdef OLD_DASH_FLAG
#		define ISUPPER(c) isdigit(c)
#		define ISLOWER(c) islower(c)
#		define ISALPHA(c) isalpha(c)
#		define ISSPACE(c) isspace(c)
#	endif
#endif
#ifdef OLD_DASHS_FLAG
inline char mydigittoint(const char ch, const char base) {
	if(ISDIGIT(ch)) return CHTOINT(ch);
	if(base > 10)
		if(ISUPPER(ch)) return (ch - 'A') + 10;
		else if(ISLOWER(ch)) return (ch - 'a') + 10;
	return 0;
}
inline Boolean myisdigit(const char ch, const char base) {
	if(ISDIGIT(ch))
		return true;
	else if(base > 10 && ISALPHA(ch))
		return true;
	else
		return false;
}
unsigned long mystrtoul(const char *instr, const char base) {
	register const char *ch = instr;
	register unsigned long out = 0UL;
	register char base_ = base;

	while(ISSPACE(*ch)) ++ch;
	while(*ch == '+') ++ch;
	while(ISSPACE(*ch)) ++ch;

	if(base_ == 0) {
		if(ISDIGIT(*ch) && CHTOINT(*ch) == 0) {
			base_ = *++ch == 'x' ? 16 : 8;
			if(base_ == 16)
				++ch;
		} else
			base_ = 10;
#ifdef DEBUG
		printf("base_: %i\n", base_);
#endif
	}

	while(myisdigit(*ch, base_)) {
#ifdef DEBUG
		printf("out: %lu\n", out);
		printf("(int)ch: %i (%c)\n", mydigittoint(*ch, base_), *ch);
#endif
		out = out * base_ + mydigittoint(*(ch++), base_);
	}

	return out;
}
#endif
//end of the above

void printhelp(const char *argv0) {
#ifdef TOCLIPSUPPORT
	printf("%s%.1f%s\n%s%s%s",
		   "clipboard version ",
		   version, subver,
		   "copyright 2004 Mac-arena the Bored Zo\n\
usage: ", argv0, " [options]\n\
options:\n\
\t-h,-?\t--help\n\t\t\tprint this help and exit\n\
\t-V\t--version\n\t\t\tprint version and exit\n\
\t-r,-v");
	if(toclip_mode)
		fputs(",-x,-e", stdout);
	fputs("\t--paste\n\t\t\tprint the clipboard to stdout\n\
\t-w,-c\t--copy\n\t\t\tcopy from stdin to the clipboard\n", stdout);
	if(!toclip_mode)
		fputs("\t-x\t--cut\n\t\t\tsame as copy\n", stdout);
	printf("%s%u%s", "\t-b\t--clear\n\t\t\tclear the clipboard\n\t-sNUM\n\t\t\tchange the bufsize to NUM bytes from the default of ", kbufsize, "\n\
\t-t\t--translate-newlines\n\t\t\ttranslate CR<->LF (default=on)\n\
\t-T\t--no-translate-newlines\n\t\t\tdon't translate CR<->LF\n\
\t-f\n\t\t\tignored (toclip compatibility)\n\
\n\
you can also specify up to one file after each option to read from or write to that file.\n\
invoking the program as 'toclip' changes options, adding -e for paste and changing -x to paste.\n"
	);
#else
	printf("%s%.1f%s\n%s%s%s%u%s",
		   "clipboard version ",
		   version, subver,
		   "copyright 2004 Mac-arena the Bored Zo\n\
usage: ", argv0, " [options]\n\
options:\n\
\t-h,-?\t--help\n\t\t\tprint this help and exit\n\
\t-V\t--version\n\t\t\tprint version and exit\n\
\t-r,-v\t--paste\n\t\t\tprint the clipboard to stdout\n\
\t-w,-c\t--copy\n\t\t\tcopy from stdin to the clipboard\n\
\t-x\t--cut\n\t\t\tsame as copy\n\
\t-b\t--clear\n\t\t\tclear the clipboard\n\t-sNUM\n\t\t\tchange the bufsize to NUM bytes from the default of ", kbufsize, "\n\
\t-t\t--translate-newlines\n\t\t\ttranslate CR<->LF (default=on)\n\
\t-T\t--no-translate-newlines\n\t\t\tdon't translate CR<->LF\n\
\t-f\n\t\t\tignored (toclip compatibility)\n\
\n\
you can also specify up to one file after each option to read from or write to that file.\n"
	);
#endif
}
void printver(void) {
	printf("%s%.1f%s\n", "clipboard ", version, subver);
#if defined(DEBUG) || !defined(NO_ESCALATE) || !(defined(NOARGSUPPORT) || defined(NOCLIPCAT))
	fputs("Compiled with: \n", stdout);
#endif
#ifdef DEBUG
	fputs("\tDebugging messages\n", stdout);
#endif
#ifndef NO_ESCALATE
	fputs("\tEscalating allocation\n", stdout);
#endif
#ifdef USE_FD
	fputs("\tFile-descriptor I/O\n", stdout);
#endif
#ifndef OLDFILEREAD
	fputs("\tMemory-mapped input\n", stdout);
#endif
#ifdef TOCLIPSUPPORT
	fputs("\t`toclip' emulation capability\n", stdout);
#endif
#ifndef NOARGSUPPORT
	fputs("\tArgument support\n", stdout);
#endif
#ifndef NOCLIPCAT
	fputs("\tClipboard concatenation\n", stdout);
#endif
#ifdef TRANS_NL_SWITCH
	fputs("\tSwitch-based newline translation\n", stdout);
#endif
}

#endif

//default value for translatenewlines.
Boolean g_translatenewlines = true;

#ifndef NOARGSUPPORT
void get_th(int i, char *buf) {
	switch(i % 10) {
		case 1:
			buf[0] = 's';
			buf[1] = 't';
			break;
		case 2:
			buf[0] = 'n';
			buf[1] = 'd';
			break;
		case 3:
			buf[0] = 'r';
			buf[1] = 'd';
			break;
		default:
			buf[0] = 't';
			buf[1] = 'h';
	}
	buf[2] = '\0';
}

//subcommand functions.
#ifdef USE_FD
void copy(int file, Boolean b_translatenewlines);
void paste(int file, Boolean b_translatenewlines);
void clear(int file, Boolean b_translatenewlines);
#else
void copy(FILE *file, Boolean b_translatenewlines);
void paste(FILE *file, Boolean b_translatenewlines);
void clear(FILE *file, Boolean b_translatenewlines);
#endif

char funcnames[3][6] = {
	"copy",
	"paste",
	"clear"
};

#ifdef USE_FD
char *getnameforfunc(void (*func)(int file, Boolean translatenewlines)) {
#else
char *getnameforfunc(void (*func)(FILE *file, Boolean translatenewlines)) {
#endif
	if(func == copy)
		return funcnames[0];
	else if(func == paste)
		return funcnames[1];
	else if(func == clear)
		return funcnames[2];
	else
		return NULL;
}

/*const char paste_name[] = "paste";
 *const char  copy_name[] = "copy";
 *const char clear_name[] = "clear";
 */

typedef struct subcommand {
#ifdef USE_FD
	void (*command)(int file, Boolean translatenewlines);
	int file;
#else
	void (*command)(FILE *file, Boolean translatenewlines);
	FILE *file;
#endif
	const char *pathname;
//	const char *name;
	unsigned translatenewlines :1;
	unsigned align :31;
} Subcommand;

void add_copy_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname);
void add_paste_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname);
void add_clear_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname);

int parseargs(int argc, const char *argv[], Subcommand **outsubs) {
	//return value: number of subcommands parsed.
	Subcommand *subs = MALLOC(sizeof(Subcommand));
	int lastsub = -1; //index of last subcmd
	Boolean parseargs = true;
	Boolean translate = g_translatenewlines;
	const char *pathname = NULL;
	const char *progname = argv[0];

	while(--argc) {
		const char *arg = *++argv;

		if(parseargs && arg[0] == '-') {
			if(arg[1] == '-') {
				//long arguments or end-of-args
				if(!strcmp(arg, "--"))
					parseargs = false;
				else if(!strcmp(arg, "--copy") || !strcmp(arg, "--cut"))
					add_copy_subcmd(&subs, lastsub++, arg, &translate, &pathname);
				else if(!strcmp(arg, "--paste"))
					add_paste_subcmd(&subs, lastsub++, arg, &translate, &pathname);
				else if(!strcmp(arg, "--clear"))
					add_clear_subcmd(&subs, lastsub++, arg, &translate, &pathname);
				else if(!strcmp(arg, "--translate-newlines"))
					translate = true;
				else if(!strcmp(arg, "--no-translate-newlines"))
					translate = false;
				else if(!strcmp(arg, "--help")) {
					printhelp(progname);
					free(subs);
					exit(0);
				} else if(!strcmp(arg, "--version")) {
					printver();
					free(subs);
					exit(0);
				} else {
					fprintf(stderr, "Unrecognised argument `%s'\n", arg);
					*outsubs = NULL;
					free(subs);
					return 0;
				}
			} else {
				const char *ch = &arg[1];

				if(*ch == '\0') {
					subs[lastsub].pathname = NULL;
					if(lastsub > -1) {
						if(subs[lastsub].command == paste)
#ifdef USE_FD
							subs[lastsub].file = STDOUT_FILENO;
#else
							subs[lastsub].file = stdout;
#endif
						else
#ifdef USE_FD
							subs[lastsub].file = STDIN_FILENO;
#else
							subs[lastsub].file = stdin;
#endif
					} //work out some kind of else for this
				} else while(*ch) {
					Boolean bail = false;

					switch(*ch) {
						case 'x':
#ifdef TOCLIPSUPPORT
							if(toclip_mode) {
								add_paste_subcmd(&subs, lastsub++, arg, &translate, &pathname);
								break;
							}
#endif
							//else continue
						case 'c':
						case 'w':
							//cut/copy/write to the clipboard
							add_copy_subcmd(&subs, lastsub++, arg, &translate, &pathname);
							break;
						case 'f':
							//nothing special
							break;
						case 'v':
						case 'r':
#ifdef TOCLIPSUPPORT
						case 'e':
							//'e' is for toclip compatibility
#endif
							//paste/read from the clipboard
							add_paste_subcmd(&subs, lastsub++, arg, &translate, &pathname);
							break;
						case 'b':
							//clear the clipboard
							add_clear_subcmd(&subs, lastsub++, arg, &translate, &pathname);
							break;
						case 't':
							if(lastsub > -1 && subs != NULL)
								subs[lastsub].translatenewlines = true;
							else
								translate = true;
							break;
						case 'T':
							if(lastsub > -1 && subs != NULL)
								subs[lastsub].translatenewlines = false;
							else
								translate = false;
							break;
						case 'h':
						case '?':
							printhelp(progname);
							free(subs);
							exit(0);
							break;
						case 'V':
							printver();
							free(subs);
							exit(0);
							break;
/***
						case 'e':
//							implicitpaste = kYesImplicitPaste;
							add_paste_subcmd(&subs, lastsub++, arg, &translate, &pathname);
							break;
***/
						case 's':
							if(*++ch != '\0') {
								const char *numarg = ch;
#ifndef OLD_DASHS_FLAG
								while(*ch && !ISDIGIT(*ch) && *ch != '-') ++ch;
								if(*ch == '+') ++ch;
								gbufsize = ISDIGIT(*ch) ? strtoul(ch, NULL, 0) : 0;
#else
								gbufsize = mystrtoul(ch, 0);
#endif
#ifdef DEBUG
								printf("Bufsize argument: %s\n", numarg);
								printf("New gbufsize: %u (taken from %s)\n", gbufsize, ch);
#endif
								if(gbufsize < 1) {
									fprintf(stderr, "Invalid value %s for buffer size; using %u\n", numarg, kbufsize);
									gbufsize = kbufsize;
								}
							} else
								fprintf(stderr, "Usage of the -s flag is -sNUM (NUM being an amount of bytes)\n");
							while(*ch != '\0') ++ch; //wind past the end of the argument
							--ch;
							break;
						default:
							fprintf(stderr, "Unrecognised flag -%c\n", *ch);
							bail = true;
							break;
					} //switch(*ch)
					if(bail) {
						*outsubs = NULL;
						free(subs);
						return 0;
					}
					++ch;
				} //while(*ch)
			} //if(arg[1] != '-')
		//if(parseargs && arg[0] == '-')
		} else {
			//!parseargs || arg[0] != '-'
			//assume arg is a pathname and add it to the last subcommand read (if it doesn't have one already), or clone that subcommand and add it to that.
			if(lastsub >= 0) {
				if(subs[lastsub].pathname == NULL)
					subs[lastsub].pathname = arg;
				else {
#ifdef DEBUG
					fprintf(stderr, "new subcommand array length: %u (%i)\n", sizeof(Subcommand) * (lastsub + 1), lastsub + 1);
#endif
					subs = realloc(subs, sizeof(Subcommand) * (1 + ++lastsub));
					if(subs == NULL) {
						char th[3] = "th";
						get_th(lastsub, th);
						fprintf(stderr, "Couldn't allocate %i%s subcommand, pathname (%s) - errno = %i\n", lastsub, th, arg, errno);
						return 0;
					}
					subs[lastsub].command = subs[lastsub-1].command;
					subs[lastsub].translatenewlines = translate;
					translate = g_translatenewlines;
					subs[lastsub].file = NULL;
					subs[lastsub].pathname = arg;
				}
			} else
				pathname = arg;
		}

		//recognise -[tT] even if no subcommands existed.
		if(lastsub == -1)
			g_translatenewlines = translate;
	} //while(argc)

	*outsubs = subs;
	if(pathname != NULL)
		if(subs != NULL && lastsub > -1) {
			if(subs[lastsub].pathname == NULL)
				subs[lastsub].pathname = pathname;
			else {
#ifdef DEBUG
				fprintf(stderr, "new subcommand array length: %u\n", sizeof(Subcommand) * (lastsub + 1));
#endif
				subs = realloc(subs, sizeof(Subcommand) * (1 + ++lastsub));
				if(subs == NULL) {
					char th[3] = "th";
					get_th(lastsub, th);
					fprintf(stderr, "Couldn't allocate %i%s subcommand, pathname (%s) - errno = %i\n", lastsub, th, pathname, errno);
					return 0;
				}
				subs[lastsub].command = subs[lastsub-1].command;
				subs[lastsub].translatenewlines = translate;
				translate = g_translatenewlines;
				subs[lastsub].file = NULL;
				subs[lastsub].pathname = pathname;
			}
		} else
			add_copy_subcmd(&subs, ++lastsub, pathname, &translate, &pathname);

	return lastsub + 1;
} //int parseargs(int argc, char *argv[], Subcommand **outsubs)

void add_copy_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname) {
//	int lastsub2 = lastsub;
//	while(lastsub2 >= 0) {
//		fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub2, (*subs)[lastsub2].name, (*subs)[lastsub2].pathname);
//		--lastsub2;
//	}
	
	(*subs) = realloc(*subs, sizeof(Subcommand) * (1 + ++lastsub));
#ifdef DEBUG
	fprintf(stderr, "new subcommand array length: %u (%i)\n", sizeof(Subcommand) * lastsub, lastsub);
#endif
	if(*subs == NULL) {
		char th[3] = "th";
		get_th(lastsub, th);
		fprintf(stderr, "Couldn't allocate %i%s subcommand, copy (%s) - errno = %i\n", lastsub, th, arg, errno);
		return;
	}
	(*subs)[lastsub].translatenewlines = *translate;
	*translate = g_translatenewlines;
//	(*subs)[lastsub].name = copy_name;
	(*subs)[lastsub].command = copy;
	(*subs)[lastsub].pathname = *pathname;
#ifdef USE_FD
	(*subs)[lastsub].file = (*pathname != NULL ? NULL : STDIN_FILENO);
#else
	(*subs)[lastsub].file = (*pathname != NULL ? NULL : stdin);
#endif
//	while(lastsub >= 0) {
//	  fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub, (*subs)[lastsub].name, (*subs)[lastsub].pathname);
//	  --lastsub;
//	}
	*pathname = NULL;
}
void add_paste_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname) {
//	int lastsub2 = lastsub;
//	while(lastsub2 >= 0) {
//		fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub2, (*subs)[lastsub2].name, (*subs)[lastsub2].pathname);
//		--lastsub2;
//	}
	
	(*subs) = realloc(*subs, sizeof(Subcommand) * (1 + ++lastsub));
#ifdef DEBUG
	fprintf(stderr, "new subcommand array length: %u (%i)\n", sizeof(Subcommand) * lastsub, lastsub);
#endif
	if(*subs == NULL) {
		char th[3] = "th";
		get_th(lastsub, th);
		fprintf(stderr, "Couldn't allocate %i%s subcommand, paste (%s) - errno = %i\n", lastsub, th, arg, errno);
		return;
	}

#ifdef DEBUG
	printf("implicitpaste (in add_paste_subcmd): %i\n", implicitpaste);
#endif
	if(implicitpaste == kMaybeImplicitPaste)
		implicitpaste = kNoImplicitPaste; //this is an explicit paste

	(*subs)[lastsub].translatenewlines = *translate;
	*translate = g_translatenewlines;
//	(*subs)[lastsub].name = paste_name;
	(*subs)[lastsub].command = paste;
	(*subs)[lastsub].pathname = *pathname;
#ifdef USE_FD
	(*subs)[lastsub].file = (*pathname != NULL ? NULL : STDOUT_FILENO);
#else
	(*subs)[lastsub].file = (*pathname != NULL ? NULL : stdout);
#endif
	*pathname = NULL;
}
void add_clear_subcmd(Subcommand **subs, int lastsub, const char *arg, Boolean *translate, const char **pathname) {
//	int lastsub2 = lastsub;
//	fputs("---\n", stderr);
//	while(lastsub2 >= 0) {
//		fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub2, (*subs)[lastsub2].name, (*subs)[lastsub2].pathname);
//		--lastsub2;
//	}
	
	//clear doesn't take any options, so any options that have been read before it belong to the previous subcommand.
	if(lastsub > -1) {
		if((*subs)[lastsub].translatenewlines)
			(*subs)[lastsub].translatenewlines = *translate;
		if((*subs)[lastsub].pathname == NULL)
			(*subs)[lastsub].pathname = *pathname;
#ifdef USE_FD
		(*subs)[lastsub].file = (*pathname != NULL ? NULL : STDOUT_FILENO);
#else
		(*subs)[lastsub].file = (*pathname != NULL ? NULL : stdout);
#endif
	}
	*pathname = NULL;
	*translate = g_translatenewlines;

	(*subs) = realloc(*subs, sizeof(Subcommand) * (1 + ++lastsub));
#ifdef DEBUG
	fprintf(stderr, "new subcommand array length: %u (%i)\n", sizeof(Subcommand) * lastsub, lastsub);
#endif
	if(*subs == NULL) {
		char th[3] = "th";
		get_th(lastsub, th);
		fprintf(stderr, "Couldn't allocate %i%s subcommand, clear (%s) - errno = %i\n", lastsub, th, arg, errno);
		return;
	}
//	lastsub2 = lastsub;
//	while(lastsub2 >= 0) {
//		fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub2, (*subs)[lastsub2].name, (*subs)[lastsub2].pathname);
//		--lastsub2;
//	}
//	(*subs)[lastsub].name = clear_name;
	(*subs)[lastsub].command = clear;
#ifdef USE_FD
	(*subs)[lastsub].file = -1;
#else
	(*subs)[lastsub].file = NULL;
#endif
	(*subs)[lastsub].pathname = NULL;
	(*subs)[lastsub].translatenewlines = true;
//	while(lastsub >= 0) {
//	  fprintf(stderr, "Subcommand %i name = \"%s\" and pathname = \"%s\"\n", lastsub, (*subs)[lastsub].name, (*subs)[lastsub].pathname);
//	  --lastsub;
//	}
}
#endif //!defined(NOARGSUPPORT)

#ifdef USE_FD
ssize_t readfile(int file, char **bufptr) {
	ssize_t bufsize = 0;
#else
size_t readfile(FILE *file, char **bufptr) {
	size_t bufsize = 0;
#endif
	char *buf = NULL;

#ifdef USE_FD
	if(file == STDIN_FILENO) {
#else
	if(file == stdin) {
#endif
		ssize_t amtread = gbufsize;
		register char *newbuf = NULL, *oldbuf = NULL;
#ifndef NO_ESCALATE
		unsigned long oldincrement = gbufsize;
		unsigned long readsize = gbufsize;
#else
#	define readsize gbufsize
#endif
		unsigned char shift = 0;
#ifdef DEBUG
		unsigned long numallocs = 1;
#endif

		buf = newbuf = MALLOC(gbufsize);
#ifdef DEBUG
		printf("Initial address of block (%u bytes): %p\n", gbufsize, newbuf);
		printf("Allocations so far: %u\n", numallocs);
#endif
#ifdef USE_FD
		while(amtread > 0 && newbuf != NULL) {
#else
		while(!(feof(file) || ferror(file)) && newbuf != NULL) {
#endif
#ifdef USE_FD
#	ifdef DEBUG
			printf("Reading %u bytes to %p (%p + %i)\n", readsize >> shift, &newbuf[bufsize], newbuf, bufsize);
#	endif
			amtread = read(file, &newbuf[bufsize], readsize >> shift);
#else
#	ifdef DEBUG
			printf("Reading %u bytes to %p (%p + %u)\n", readsize >> shift, &newbuf[bufsize], newbuf, bufsize);
#	endif
			amtread = fread(&newbuf[bufsize], sizeof(char), readsize >> shift, file);
#endif
			/*	the way that works is fairly simple:
			 *	first run: &newbuf[0]
			 *	second run: &newbuf[gbufsize * 1]
			 *	third run: &newbuf[gbufsize * 2]
			 *	...
			 */
			bufsize += amtread;
#ifdef DEBUG
			printf("Total bytes read: %i\nThis time: %i\n", bufsize, amtread);
			fputs("---\n", stdout);
#endif
#ifndef NO_ESCALATE
			if(amtread < (readsize >> shift))
				readsize -= amtread;
			else {
				gbufsize = readsize <<= 1;
#endif
//				if(!shift) shift = 1;
#ifdef DEBUG
				printf("Reallocing newbuf %p by %u bytes to %i bytes\n", newbuf, gbufsize, bufsize + gbufsize); fflush(stdout);
				printf("Allocations so far: %u\n", ++numallocs);
#endif
				oldbuf = newbuf;
				newbuf = realloc(newbuf, bufsize + gbufsize);
#ifdef DEBUG
				printf("Return value of realloc: %p\n", newbuf); fflush(stdout);
#endif
#ifndef NO_ESCALATE
//				gbufsize <<= 1;
			}
#endif

			if(newbuf == NULL) {
				free(oldbuf); //note: on Darwin, free(NULL) is safe, so even if oldbuf is NULL (which would be odd since oldbuf is assigned before the first realloc, and the loop itself guards against newbuf being NULL), this is not a problem.
				*bufptr = NULL;
				return 0;
			} //if(newbuf == NULL)
		} //while(!(feof(file) || ferror(file)) && newbuf != NULL)
#ifdef DEBUG
#	ifdef USE_FD
		printf("Current bufsize: %i\n", bufsize + gbufsize);
#	else
		printf("Current bufsize: %u\n", bufsize + gbufsize);
#	endif
#endif
#ifndef NO_ESCALATE
		gbufsize = oldincrement;
#endif
		HANDLE_NULL(3, "Couldn't get memory for file buffer - initial malloc failed\n", bufsize, buf);
		buf = realloc(newbuf, bufsize);
		if(buf == NULL)
			buf = newbuf;
#ifdef DEBUG
		else
#	ifdef USE_FD
			printf("New bufsize (trimmed): %i\n", bufsize);
#	else
			printf("New bufsize (trimmed): %u\n", bufsize);
#	endif
#endif
		//when amtread is 0, file is exhausted, so we're finished.
	} else {
		//reading from a regular file, not stdin.
		struct stat statresult;
		int retval = 0;

#ifdef USE_FD
#	ifdef DEBUG
		fprintf(stderr, "Performing fstat on %s\n", file == STDIN_FILENO ? "stdin" : "file");
#	endif
		retval = fstat(file, &statresult);

#else

#	ifdef DEBUG
		fprintf(stderr, "Performing fstat on %s\n", file == stdin ? "stdin" : "file");
#	endif
		retval = fstat(fileno(file), &statresult);
#endif
		if(retval) {
			fprintf(stderr, "fstat returned nonzero - return value is %i and errno is %i\n", retval, errno);
			return 0;
		}

		bufsize = statresult.st_size;
#ifdef DEBUG
#	ifdef USE_FD
		fprintf(stderr, "Size is %i\n", bufsize);
#	else
		fprintf(stderr, "Size is %u\n", bufsize);
#	endif
#endif

		//successfully determined the size of the file. now try allocating and reading.

#ifndef OLDFILEREAD
		//use mmap.
#	ifdef USE_FD
#		define fd (file)
#	else
#		define fd fileno(file)
#	endif
		buf = mmap(NULL, bufsize + 1, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE, fd, 0);
		if(buf == (char *)-1) {
#ifdef USE_FD
			fprintf(stderr, "Could not mmap %i bytes from %s: errno == %i\n", bufsize + 1, file == STDIN_FILENO ? "stdin" : "file", errno);
#else
			fprintf(stderr, "Could not mmap %u bytes from %s: errno == %i\n", bufsize + 1, file == stdin ? "stdin" : "file", errno);
#endif
			*bufptr = NULL;
			return 0;
		}
		madvise(buf, bufsize + 1, MADV_SEQUENTIAL);
#else
		buf = MALLOC(bufsize + 1);
		if(buf == NULL) {
#ifdef USE_FD
			fprintf(stderr, "Could not allocate %i bytes of memory for %s: errno == %i\n", bufsize + 1, file == STDIN_FILENO ? "stdin" : "file", errno);
#else
			fprintf(stderr, "Could not allocate %u bytes of memory for %s: errno == %i\n", bufsize + 1, file == stdin ? "stdin" : "file", errno);
#endif
			*bufptr = NULL;
			return 0;
		}
		madvise(buf, bufsize + 1, MADV_SEQUENTIAL);

#ifdef USE_FD
		bufsize = read(file, buf, bufsize);
		if(bufsize < 1) {
			fprintf(stderr, "read returned %i!\n", bufsize);
#else
		bufsize = fread(buf, sizeof(char), bufsize, file);
		if(bufsize == 0) {
			fprintf(stderr, "fread returned %u!\n", bufsize);
			fprintf(stderr, "Hit EOF: %i\n", feof(file));
			fprintf(stderr, "Error encountered: %i\n", ferror(file));
#endif
		}
#endif
	}

/*
	if(buf != NULL)
		buf[bufsize] = '\0';
*/

	*bufptr = buf;
	return bufsize;
}

#ifdef USE_FD
void printbuf(const char *buf, Size scrapsize, int file, Boolean translatenewlines) {
#else
void printbuf(const char *buf, Size scrapsize, FILE *file, Boolean translatenewlines) {
#endif
	//print the contents of the buffer, quietly translating newlines and carriage returns.
	char newline = (translatenewlines ? '\r' : '\n'); //used at the end of the function for comparison
#ifdef DEBUG
	size_t total = 0;
	fprintf(stderr, "Paste request for %zu bytes\n", scrapsize);
#endif

	if(translatenewlines) {
		register const char *slicestart = NULL;
		register size_t slicelen = 0;
		register const char *cur = buf;
		register size_t pastelen = scrapsize;

		for(; pastelen; ++cur) {
			const char lf = '\n', cr = '\r';

			if(*cur != lf && *cur != cr) {
				if(slicestart == NULL)
					slicestart = cur;
				if(++slicelen == pastelen)
					break;
			} else {
#ifdef USE_FD
				write(file, slicestart, slicelen);
#else
				fwrite(slicestart, sizeof(char), slicelen, file);
#endif
				pastelen -= slicelen + 1; //1 for the newline.
#ifdef DEBUG
				total += slicelen;
				++total;
#endif

				slicestart = NULL;
				slicelen = 0;
				switch(*cur) {
					case '\n':
#ifdef USE_FD
						write(file, &cr, sizeof(char));
#else
						fputc(cr, file);
#endif

						break;
					case '\r':
#ifdef USE_FD
						write(file, &lf, sizeof(char));
#else
						fputc(lf, file);
#endif
						break;
				}
			}
		}

		if(slicestart != NULL && *slicestart && slicelen > 0)
#ifdef DEBUG
		{
			total += slicelen;
#endif
#ifdef USE_FD
			write(file, slicestart, slicelen);
#else
			fwrite(slicestart, sizeof(char), slicelen, file);
#endif
#ifdef DEBUG
		}
#endif
	} else //if(!translatenewlines)
#ifdef DEBUG
	{
		total += scrapsize;
#endif
#ifdef USE_FD
		write(file, buf, scrapsize);
#else
		fwrite(buf, sizeof(char), scrapsize, file);
#endif
#ifdef DEBUG
	}
#endif

	if(scrapsize > 0 && buf[scrapsize-1] != newline
#ifdef USE_FD
	   && isatty(file))
	{
		const char lf = '\n';
		write(file, &lf, sizeof(char));
#ifdef DEBUG
		++total;
#endif
	}
#else
	   && isatty(fileno(file)))
#ifdef DEBUG
	{
		++total;
#endif
		fputc('\n', file);
#ifdef DEBUG
	}
#endif
#endif
#ifdef DEBUG
	fprintf(stderr, "Pasted %zu bytes\n", total);
#endif
}

void translatenewlines(char *buf, size_t bufsize) {
	/*iterate over buf, translating newlines therein*/
	register char *cur = buf;
	register size_t rbufsize = bufsize;
#ifndef TRANS_NL_SWITCH
	char translate[256];
	char *t_buf = translate;

	/*populate the 'translate' buffer*/ {
		unsigned char i = 0;
		register char *t_char = t_buf;

		do {
			*(t_char++) = i;
		} while(++i);

		t_buf['\n'] = '\r';
		t_buf['\r'] = '\n';
	}
#endif

	for(; rbufsize; --rbufsize) {
#ifndef TRANS_NL_SWITCH
		*cur = t_buf[*cur];
#else
		switch(*cur) {
			case '\n':
				*cur = '\r'; break;
			case '\r':
				*cur = '\n'; break;
		}
#endif

		++cur;
	}
}

OSStatus writescrap(char *buf, size_t bufsize, ScrapRef scrap) {
	//note: to save memory, writescrap DOES change the buffer it's writing (if translatenewlines is not false).
	OSStatus err = noErr;
	ScrapFlavorFlags flags = kScrapFlavorMaskNone;

	err = PutScrapFlavor(scrap, kScrapFlavorTypeText, flags, bufsize, buf);
	HANDLE_ERROR(err, "PutScrapFlavor failed with error %li\n", buf, free);

	return err;
}

#ifndef NOCLIPCAT

enum { kMaxNumBufs = 64 };
char *bufs[kMaxNumBufs];
size_t bufsizes[kMaxNumBufs];
signed int numbufs = 0;

void copy_bufs(void) {
#ifdef DEBUG
	fprintf(stderr, "copy_bufs entered\n");
#endif
	if(numbufs > 1) {
		size_t totalsize = 0;
		size_t oldtotal = 0;
#define cursize oldtotal /*alias for clarity purposes*/
		register signed int i = numbufs;
		char *catbuf = NULL;
		OSStatus err = noErr;
		ScrapRef scrap = kScrapRefNone;

#ifdef DEBUG
		fprintf(stderr, "Performing clipcat (numbufs == %u)\n", numbufs);
#endif
		err = ClearCurrentScrap();
		if(err != noErr) {
			fprintf(stderr, "Couldn't ClearCurrentScrap (in copy_bufs) - error %li\n", err);
			return;
		}
		err = GetCurrentScrap(&scrap);
		if(err != noErr) {
			fprintf(stderr, "Couldn't GetCurrentScrap (in copy_bufs) - error %li\n", err);
			return;
		}

		while(i) {
			totalsize += bufsizes[--i];
#ifdef DEBUG
			printf("i: %i; bufsize: %u; totalsize: %u\n", i, bufsizes[i], totalsize);
#endif
			if(totalsize >= oldtotal) {
				//the condition is an attempt to guard against overflow
				oldtotal = totalsize;
			} else
				break;
		}

		if(i) return; //if i > 0, we overflowed

#ifdef DEBUG
		printf("totalsize: %u\n", totalsize);
#endif
		catbuf = MALLOC(totalsize);
		if(catbuf == NULL) return;

		cursize = 0;
#ifdef DEBUG
		printf("catbuf: %p\n", catbuf);
#endif
		while(i < numbufs) {
			//we use memcpy instead of strncat because it's legal for clipboard data to contain nulls - strncat would bail with the buffer half-copied.
#ifdef DEBUG
			printf("Copying to %u bytes to position %u (address %p)\n", bufsizes[i], cursize, &catbuf[cursize]);
#endif
			memcpy(&catbuf[cursize], bufs[i], bufsizes[i]);
			cursize += bufsizes[i];
			++i;
		}

		writescrap(catbuf, cursize, scrap);
		free(catbuf);
	} //if numbufs >= 0
#ifdef DEBUG
	fprintf(stderr, "copy_bufs exiting\n");
#endif
} //void copy_bufs(void)

void free_bufs(void) {
	while(numbufs--) {
		if(bufs[numbufs] != NULL) {
#ifdef OLDFILEREAD
			free(bufs[numbufs]);
#else
			munmap(bufs[numbufs], bufsizes[numbufs]);
#endif
			bufs[numbufs] = NULL;
		}
		bufsizes[numbufs] = 0;
	}
}

#endif //defined(CLIPCAT)

#ifdef USE_FD
void copy(int file, Boolean b_translatenewlines) {
#else
void copy(FILE *file, Boolean b_translatenewlines) {
#endif
	char *buf = NULL;
	size_t bufsize = 0;
	OSStatus err = noErr;
	ScrapRef scrap = kScrapRefNone;

	err = ClearCurrentScrap();
	if(err != noErr) {
		fprintf(stderr, "Couldn't ClearCurrentScrap (in copy) - error %li\n", err);
		return;
	}
	err = GetCurrentScrap(&scrap);
	if(err != noErr) {
		fprintf(stderr, "Couldn't GetCurrentScrap (in copy) - error %li\n", err);
		return;
	}

	bufsize = readfile(file, &buf);
#ifdef DEBUG
	fprintf(stderr, "Read %zu bytes\n", bufsize);
#endif

	if(buf != NULL) {
		if(b_translatenewlines)
			translatenewlines(buf, bufsize);
		writescrap(buf, bufsize, scrap);

#ifndef NOCLIPCAT
		if(numbufs > -1)
			if(numbufs < kMaxNumBufs) {
				bufsizes[numbufs] = bufsize;
				bufs[numbufs++] = buf;
			} else
				fprintf(stderr, "Maximum number of copies (%u) exceeded (%u) - concatenation will not occur\n", kMaxNumBufs, numbufs);

#else
		free(buf);
#endif
	}
}
#ifdef USE_FD
void paste(int file, Boolean b_translatenewlines) {
#else
void paste(FILE *file, Boolean b_translatenewlines) {
#endif
	char *buf = NULL;
	Size scrapsize = 0;
	ScrapRef curscrap = kScrapRefNone;
	OSStatus err = noErr;

	err = GetCurrentScrap(&curscrap);
	HANDLE_ERROR_RETURN_VOID(err, "GetCurrentScrap failed with error %li\n", NULL, NO_FREE);

	err = GetScrapFlavorSize(curscrap, kScrapFlavorTypeText, &scrapsize);
#ifdef DEBUG
	fprintf(stderr, "Scrap flavour size: %zu\n", scrapsize);
#endif
//	if(err != noErr) {
		switch(err) {
			case noScrapErr:
			case noTypeErr:
				fprintf(stderr, "The clipboard is empty (error %li).\n", err);
				return;
			case noErr:
				break;
			default:
				fprintf(stderr, "GetScrapFlavorSize failed with error %li\n", err);
				return/* err*/;
		}
//	}

	buf = MALLOC(scrapsize + 1);
	HANDLE_NULL_RETURN_VOID(1, "Could not allocate %u bytes for scrap contents\n", scrapsize + 1, buf);

	err = GetScrapFlavorData(curscrap, kScrapFlavorTypeText, &scrapsize, buf);
#ifdef DEBUG
	fprintf(stderr, "Got %zu bytes\n", scrapsize);
#endif
	HANDLE_ERROR_RETURN_VOID(err, "GetScrapFlavorData failed with error %li\n", buf, free);

	buf[scrapsize] = '\0'; //null-terminate

	printbuf(buf, scrapsize, file, b_translatenewlines);

#ifndef NOARGSUPPORT
	free(buf);
#endif
}
#ifdef USE_FD
void clear(int file, Boolean translatenewlines) {
#else
void clear(FILE *file, Boolean translatenewlines) {
#endif
	ClearCurrentScrap();
}

#ifndef NOARGSUPPORT
int main(int argc, const char *argv[]) {
#else
int main(void) {
#endif
	OSStatus err = noErr;
	size_t bufsize = 0;
	size_t amtread = gbufsize;
	char *buf = NULL;
	Boolean noblock = true; //only if stdout is a tty. see below.
#ifndef NOARGSUPPORT
	Subcommand *subs = NULL;
	unsigned int numcmds = 0;

#	ifdef TOCLIPSUPPORT
		//if invoked as toclip, set toclip_mode to true.
		toclip_mode = !strcmp(argv[0], "toclip");
#	endif
#else
	ScrapRef curscrap = kScrapRefNone;
#endif

#ifndef NOARGSUPPORT
#ifdef DEBUG
	fprintf(stderr, "sizeof(Subcommand): %u\n", sizeof(Subcommand));
#endif
	if(argc > 1)
		numcmds = parseargs(argc, argv, &subs);
	if(numcmds > 0U && subs != NULL) {
		Subcommand *cursub = subs;
		while(numcmds--) {
//		  fprintf(stderr, "Performing %s subcommand\n", cursub->name);

#ifdef USE_FD
			if( (cursub->file >= 0 || cursub->file == (cursub->command == paste ? STDOUT_FILENO : STDIN_FILENO)) && cursub->pathname != NULL && strcmp(cursub->pathname, "-")) {
				cursub->file = open(cursub->pathname, (cursub->command == paste ? O_WRONLY : O_RDONLY) | O_EXLOCK);
#else
#	ifdef DEBUG
			printf("File is %p (=stdin: %u; =stdout: %u); pathname is %p\n", cursub->file, cursub->file == stdin, cursub->file == stdout, cursub->pathname);
#	endif
			if( (cursub->file == NULL || cursub->file == (cursub->command == paste ? stdout : stdin)) && cursub->pathname != NULL && strcmp(cursub->pathname, "-")) {
				cursub->file = fopen(cursub->pathname, (cursub->command == paste ? "w" : "r"));
				//if USE_FD is defined, the lock was obtained during open().
#	ifdef DEBUG
				fprintf(stderr, "Value of cursub->file: %p\n", cursub->file);
#	endif
				if(cursub->file == NULL)
				  perror("cursub->file is NULL; errno");
#	ifdef DEBUG
				fprintf(stderr, "Locking file %s\n", cursub->pathname);
#	endif
				if(flock(fileno(cursub->file), LOCK_EX))
					fprintf(stderr, "Couldn't lock file %s: errno %i\n", cursub->pathname, errno);
#endif
			}

			if(cursub->command == NULL)
				fputs("That's strange. The function for that subcommand didn't get sent back to the main function.\n", stderr);
			else
				cursub->command(cursub->file, cursub->translatenewlines);

#ifdef USE_FD
			if(cursub->file >= 0 && cursub->file != STDIN_FILENO && cursub->file != STDOUT_FILENO) {
#else
			if(cursub->file != NULL && cursub->file != stdin && cursub->file != stdout) {
#endif
#ifdef DEBUG
				printf("Unlocking file %s\n", cursub->pathname);
#endif DEBUG
#ifdef USE_FD
				if(flock(cursub->file, LOCK_UN))
#else
				if(flock(fileno(cursub->file), LOCK_UN))
#endif
					fprintf(stderr, "Couldn't unlock file %s: errno %i\n", cursub->pathname, errno);
#ifdef USE_FD
				close(cursub->file);
#else
				fclose(cursub->file);
#endif
			}

			++cursub;
		}

#ifndef NOCLIPCAT
		copy_bufs();
		free_bufs();
#endif

#ifdef DEBUG
		printf("implicitpaste: %i\n", implicitpaste);
#endif DEBUG
		if(implicitpaste && !isatty(STDOUT_FILENO))
#ifdef USE_FD
			paste(STDOUT_FILENO, g_translatenewlines);
#else
			paste(stdout, g_translatenewlines);
#endif
	} else {
		Boolean recvpipe = !isatty(STDIN_FILENO);

		/* if receiving from a pipe, copy stdin to the clipboard.
		 * if not receiving from a pipe, or if sending to a pipe, paste the clipboard to stdout.
		 */

#ifdef DEBUG
		fputs("No subcommands found\n", stderr);
#endif

		if(recvpipe) {
#ifdef DEBUG
			fputs("Copying\n", stderr);
#endif
#ifdef USE_FD
			copy(STDIN_FILENO, g_translatenewlines);
#else
			copy(stdin, g_translatenewlines);
#endif
		}
		if(!recvpipe || !isatty(STDOUT_FILENO)) {
#ifdef DEBUG
			fputs("Pasting\n", stderr);
#endif
#ifdef USE_FD
			paste(STDOUT_FILENO, g_translatenewlines);
#else
			paste(stdout, g_translatenewlines);
#endif
		}
	}
	if(subs != NULL)
		free(subs);

#else //if defined(NOARGSUPPORT)

	err = ClearCurrentScrap();
	if(err != noErr) {
		fprintf(stderr, "Couldn't ClearCurrentScrap (in main) - error %li\n", err);
		return err;
	}
	err = GetCurrentScrap(&curscrap);
	if(err != noErr) {
		fprintf(stderr, "Couldn't GetCurrentScrap (in main) - error %li\n", err);
		return err;
	}

	buf = MALLOC(gbufsize);
	if(buf == NULL) {
		fprintf(stderr, "malloc(%u) for reading standard input returned NULL. errno was %i. Please tell the maintainer.\n", gbufsize, errno);
		exit(2);
	}

	if(!isatty(STDIN_FILENO)) {
		while(amtread > 0 && buf != NULL) {
#ifdef USE_FD
			amtread = read(STDIN_FILENO, &buf[bufsize], gbufsize);
#else
			amtread = fread(&buf[bufsize], sizeof(char), gbufsize, stdin);
#endif
			/*	the way that works is fairly simple:
			 *	first run: &buf[0]
			 *	second run: &buf[gbufsize * 1]
			 *	third run: &buf[gbufsize * 2]
			 *	...
			 */
			bufsize += amtread;
			buf = realloc(buf, bufsize);
			HANDLE_NULL(3, "Couldn't get more memory for stdin buffer - ran out at %i bytes\n", bufsize, buf);
		}
		//when amtread is 0, stdin is exhausted, so we're finished.
	}

	if(bufsize == 0) {
		//paste.
#ifdef USE_FD
		paste(STDOUT_FILENO, g_translatenewlines);
#else
		paste(stdout, g_translatenewlines);
#endif
		//paste freed the buffer for us.
	} else {
		//copy.
		if(g_translatenewlines)
			translatenewlines(buf, bufsize);
		err = writescrap(buf, bufsize, curscrap);
	}
#endif

	free(buf);

	return err;
}

