/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA  02111-1307  USA
*
*/
/**
 * holdon - A *NIX command line filter.
 *
 * Copyright (C) Richard Whitty 2007
 * All rights reserved.
 * Email: cs6rlw@csc.liv.ac.uk
 *
 * The reason for writing this became apparent as I was messing with bash.
 * All to often, to remove an entry from a file, you'd have to do something like:
 *
 *		$ grep -v $remove $list > temp
 *		$ mv temp $list
 *
 * Wouldn't it be nice if we could redirect something back to the file it came from,
 * and have it work.
 * Well... now you can.
 *
 * Usage:
 *
 *		$ holdon <filename>
 *
 * it writes the contents of stdin to file, upon receipt of EOF.
 * If no file is supplied it just echoes it to stdout (not sure what use that
 * would be though)
 *
 *		$ grep -v $remove $list | holdon $list
 *
 * Assuming holdon is in your PATH somewhere. It's worth noting that this is
 * simply syntactic sugar, and using the temp file would probably be more
 * efficient. I just think they're ugly.
 *
 * To compile:
 *		$ gcc -o holdon holdon.c
 */
/*
 Note that this is almost definately slower than using a temp file.
 also it will quite happily eat all your memory+swap if you give it the chance.
 The return value of the malloc call in read_it_in() is not checked for 0. I
 justify this because Linux will never return null due to overcommitting. (It
 allocates the page on the first write to it). Possibly the best action it
 could take would be to segfault (hence exiting non-zero) and not modifying the
 destination file.
 */
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

#define BUF_SIZE 4096
/* default mode for the file (well, not that there's an option to change it. */
#define FILE_MODE 0600
#define FILE_FLAGS (O_WRONLY|O_CREAT|O_TRUNC)
/* simple list */
typedef struct listnode {
	char *string;
	int len;
	struct listnode *next;
} listnode;

listnode *head=NULL;
listnode *tail=NULL;
void free_list();
void kill_list(int output);
void add_list_element(char *v,int len);

/**
 * Frees the list for cleanup purposes.
 * (no output)
 * this is called only in case of an error in kill_list, and we want to save
 * grace.
 */
void free_list() {
	listnode *current;
	while(head!=NULL) {
		current=head;
		head=head->next;
		free(current->string);
		free(current);
	}
}

/**
 * Deletes all the contents of the list, outputting them at the same time.
 * @param output the file descriptor to send the data to.
 */
void kill_list(int output) {
	int count;
	listnode *current;
	current=head;
	while(head!=NULL) {
		current=head;
		if((count=write(output,current->string,current->len))==-1) {
			fprintf(stderr,"Error writing to file: %s\n",strerror(errno));
			free_list();
			exit(1);
		}
		head=head->next;
		free(current->string);
		free(current);
	}
}

/**
 * Adds data onto the list.
 * @param v data to add.
 * @param len length in bytes.
 */
void add_list_element(char *v,int len) {
	listnode *n = (listnode *) malloc(sizeof(listnode));
	n->string = v;
	n->next=NULL;
	n->len=len;
	if(tail) {
		tail->next=n;
		tail=n;
	} else {
		head=tail=n;
	}
}

/**
 * Reads from a file descriptor until the EOF or end of buffer.
 * Same parameters and return values as read(2).
 */
int my_read(int fd,char *buf,int len) {
	int done=0,count=1;
	while(done<len && count>0 ) {
		count= read(fd,buf+done,len-done);
		if(count>0)
			done+=count;
	}
	return count<0 ? count : done;
}

/**
 * Reads it from stdin and throws it onto a queue.
 * @return 0 on success (EOF reached) -1 on failure (errno set).
 */
int read_it_in() {
	char *buf=NULL;
	int count =-1;
	do {
		buf   = malloc(BUF_SIZE);
		count = my_read(0,buf,BUF_SIZE);
		if(count<1)
			break;
		add_list_element(buf,count);
	} while(count>0);
	return count;
}

/**
 * one argument - the file name.
 * if ommitted, then stdout is good enough.
 */
int main(int argc,char **argv) {
	int output=-1;
	if(argc<2 || argc>3) {
		fprintf(stderr,"Usage: %s <filename\n",argv[0]);
		return 1;
	}
	if(strncmp(argv[1],"--version",9)==0) {
		printf("holdon 0.1 - (C) Richard Whitty 2007\n");
		return 0;
	}
	/* Handle stdout */
	if(argv[1][0]=='-'&& argv[1][1]==0)
		output=1;
	/* if there was an error reading it in, die. */
	if(read_it_in()==-1) {
		return 1;
	}
	/* finally we can open the destination file and start printing. */
	/* if it's not directed at stdout, that is. */
	if(output==-1)
	if((output=open(argv[1],FILE_FLAGS,FILE_MODE))==-1) {
		fprintf(stderr,"Error opening output file: %s\n",strerror(errno));
		return 1;
	}
	/* free the memory and close the file. */
	kill_list(output);
	if(output!=1)
		close(output);
	return 0;
}

