/*
  Bypassa le restrizioni imposte sulle quote utente.
  Memorizza i dati di un file creando molti file e sfruttandone
  per la memorizzazione i 255 byte disponibili per il filename
  
  TODO
  Sostituire il file temporaneo con una pipe
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>

#define STORE 1
#define EXTRACT 2

void
usage(char *prog)
{
	fprintf(stderr, "%s -s -f filename\n"
		"\tmemorizza un file nel filesystem\n"
		"%s -x -f filename\n"
	    "\trecupera un file precedentemente memorizzato\n", prog, prog);
	exit(-1);
}

int
main(int argc, char **argv)
{
	pid_t           pid;
	DIR            *dirp;
	struct dirent  *dentry;
	int             fd, fdpipe[2], mode = 0, index = 0, firstline = 1,
	                len, opt, n;
	char           *filename = NULL, *p, buf[256];

	opterr = 0;
	while ((opt = getopt(argc, argv, "sxf:")) != -1)
		switch (opt) {
		case 's':
			if (!mode) {
				mode = STORE;
				break;
			} else
				usage(argv[0]);
		case 'x':
			if (!mode) {
				mode = EXTRACT;
				break;
			} else
				usage(argv[0]);
		case 'f':
			filename = strdup(optarg);
			break;
		default:
			usage(argv[0]);
		}

	if (optind < argc)
		usage(argv[0]);

	if (!mode)
		usage(argv[0]);

	if (!filename)
		usage(argv[0]);

	if (pipe(fdpipe) == -1) {
		perror("error");
		exit(-1);
	}
	switch (mode) {
	case STORE:
		if ((pid = fork()) == -1) {
			perror("error");
			exit(-1);
		} else if (pid == 0) {	/* child */
			close(fdpipe[0]);

			/* 
			  duplica il descrittore della pipe sullo
			  standard output
			 */
			if (dup2(fdpipe[1], STDOUT_FILENO) == -1) {
				perror("error");
				exit(-1);
			}
			/*
			  encoda il file che viene passato al processo parent
			  attraverso il descrittore della pipe che e' stato
			  duplicato su stdout
			 */
			execlp("uuencode", "uuencode", filename, filename, NULL);
			fprintf(stderr, "error: can't execute uuencode\n");
			exit(-1);
		}
		/* parent */
		close(fdpipe[1]);

		/* tronca la lunghezza del filename a 50 caratteri */
		snprintf(buf, 50, ".%s", filename);
		/* aggiunge un progressivo al nome del file */
		snprintf(buf + strlen(buf), 20, "%d-", index++);
		len = strlen(buf);

		/*
		  legge dal descrittore della pipe l'output di uuencode
		  che runna come processo child
		 */
		while ((n = read(fdpipe[0], buf + len, 255 - len)) >= 0) {
			/* create file */
			len += n;
			
			/* attende di avere almeno 255 caratteri nel buffer.
			   accetta meno di 255 caratteri sono nel caso in cui
			   legge EOF dalla pipe (n==0)
			 */
			if (len == 255 || n == 0) {
				buf[len] = 0;
				p = buf;
				
				/*
				  skippa la prima riga di intestazione dell'encoding:
				  begin 644 foo.bar
				 */
				if (firstline == 1) {
				
					/* skippa oltre l'intestazione di uuencode */
					if ((p = strchr(p, '\n')) != NULL) {
						/* 
						  Il carattere '/' non puo' essere utilizzato all'interno di un
						  nome di file percio' lo sostituisce con il carattere 'a' che
						  rappresenta un carattere ASCII non utilizzato nella codifica
						  uuencode
						 */
						while ((p = strchr(p, '/')) != NULL)
							*p = 'a';
						firstline = 0;
					}
				} else {
					/* 
					  Il carattere '/' non puo' essere utilizzato all'interno di un
					  nome di file percio' lo sostituisce con il carattere 'a' che
					  rappresenta un carattere ASCII non utilizzato nella codifica
					  uuencode
					 */
					while ((p = strchr(p, '/')) != NULL)
						*p = 'a';
				}
				
				/* crea i filename che contengono i dati del file originale encodati */
				if (open(buf, O_RDONLY | O_CREAT | O_EXCL) == -1) {
					perror("error");
					exit(-1);
				}
				snprintf(buf, 50, ".%s", filename);
				/* aumenta il progressivo del file */
				snprintf(buf + strlen(buf), 20, "%d-", index++);
				len = strlen(buf);
				/* EOF */
				if (n == 0)
					break;
			}
		}
		if (n == -1) {
			perror("error");
			exit(-1);
		}
		break;
	case EXTRACT:
		if ((dirp = opendir(".")) == NULL) {
			perror("error");
			exit(-1);
		}
		/* crea un file temporaneo che conterra' il file originale encodato */
		if ((fd = open("tmpfile", O_WRONLY | O_CREAT, S_IRWXU)) == -1) {
			perror("error");
			exit(-1);
		}
		snprintf(buf, 50, ".%s", filename);
		snprintf(buf + strlen(buf), 20, "%d-", index++);
		
		/*
		  sfoglia tutti i file contenuti nella directory locale alla ricerca
		  dei nomi dei file nei quali sono stati inseriti i dati encodati
		 */
		while ((dentry = readdir(dirp)) != NULL) {
			/*
			  cerca il nome del file che corrisponde al file e il
			  progressivo desiderati
			 */
			if (strncmp(buf, dentry->d_name, strlen(buf)) == 0) {
				p = dentry->d_name + strlen(buf);
				/*
				  skippa la prima riga di intestazione dell'encoding:
				  begin 644 foo.bar
				 */
				if (firstline == 1) {
					if ((p = strchr(p, '\n')) != NULL) {
						/* 
						  Il carattere 'a' che in fase di memorizzazione e' stato
						  sostituito a '/' viene ripristinato all'interno della
						  codifica
						 */
						while ((p = strchr(p, 'a')) != NULL)
							*p = '/';
						firstline = 0;
					}
				} else {
					/* 
					  Il carattere 'a' che in fase di memorizzazione e' stato
					  sostituito a '/' viene ripristinato all'interno della
					  codifica
					 */
					while ((p = strchr(p, 'a')) != NULL)
						*p = '/';
				}
				/*
				  scrive il file originale encodato nel file temporaneo creato
				  in precedenza
				 */
				if (write(fd, dentry->d_name + strlen(buf), strlen(dentry->d_name + strlen(buf))) != strlen(dentry->d_name + strlen(buf))) {
					fprintf(stderr, "write error\n");
					exit(-1);
				}
				snprintf(buf, 50, ".%s", filename);
				snprintf(buf + strlen(buf), 20, "%d-", index++);
				rewinddir(dirp);
			}
		}
		close(fd);
		if ((pid = fork()) == -1) {
			perror("error");
			exit(-1);
		} else if (pid == 0) {	/* child */
			/*
			  decodifica il file temporaneo che contiene il file originale
			  encodato
			 */
			execlp("uudecode", "uudecode", "tmpfile", NULL);
			fprintf(stderr, "error: can't execute uudecode\n");
			exit(-1);
		}
		/* parent */
		waitpid(-1, NULL, 0);
		break;
	}
	unlink("tmpfile");
	return 0;
}
