#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>

/* Cette utilitaire permet de couper un gros fichiers en une suite de petits
	fichiers, facilement transportable par disquette. Le fonctionnement
	est assez semblable a celui de ARJ, sauf qu'il n'y a pas de
	compression. Les fichiers generes sont archive.0, archive.1 ...
	ou archive est le nom de l'archive que l'on a choisi

	Auteur: Benoit Papillault
	Creation: Vendredi 14 Mars 1997
	Derniere Modification: Dimanche 30 Mars 1997

	Historique:
	* la version 1.0 utilisait fseek() ce qui provoque des erreurs lors
	de la reconstitution des fichiers originaux.
	* la version 2.0 corrige l'erreur du a fseek() et propose une option
	pour preciser la taille des fichiers generes
	* la version 3.0 permet egalement de preciser la taille du buffer
	interne utilise par le programme.

	20/10/1997: ajout de perror() lorsque l'on n'arrive pas a ouvrir
	l'archive destinatrice.


*/

#define TAILLE_FICHIER_GENERE_PAR_DEFAUT        1400L*1024L
#ifndef	__MSDOS__
#define TAILLE_BUFFER_INTERNE_PAR_DEFAUT        1400*1024
#else
#define	TAILLE_BUFFER_INTERNE_PAR_DEFAUT		50*1024
/* toutes valeurs inferieures a 65535 */
#endif

/* librement modifiable au gre du compilateur, Visual C++ rajoute un underscore */
#ifdef	_WIN32
#define	stat	_stat
#endif

/* Les options */

long taille_fichier_genere=0;
/* la taille maximum de chaque fichier genere (en octets) */
const char format[] = "%s.%03d";
/* le format des noms de fichiers generes */

unsigned int taille_buffer_interne=0;
/* la taille du buffer utilise en interne, doit etre de type unsigned int car
	passe en argument a la fonction malloc */

int debug=0;
/* pour activer le mode debug */

/* affiche le message precisant les options */

void usage(void)
{
	printf("Usage:\tdisquette [-<option>...] <command> <archive_name> [<file_names>...]\n");
	printf("<command>\n");
	printf("  a:Add files to archive\n");
	printf("  e:Extract files from archive\n");
	printf("  l:List contents of archive\n");
	printf("<option>\n");
	printf("  v:size in bytes of each parts of the archive\n");
	printf("  d:debug mode\n");
	printf("  b:size in bytes of the internal buffer used\n");
	printf("  h:display this help message\n");
	printf("decoupe v3.0 beta, Auteur: Benoit Papillault, Mars 1997\n");
}

/* renvoie la taille du fichier ou -1 en cas d'erreur */

long filesize(const char *file)
{
	struct stat buf;

	if (stat(file,&buf)!=0)
		return -1;

	return buf.st_size;
}


/* ============ structure precedant chaque bloc =======================

4 octets:       offset du bloc dans le fichier original, poids forts d'abord
4 octets:       taille du bloc, poids forts d'abord
1 octet:        longeur du nom du fichier
n octets:       le nom du fichier
n octets:       la taille du bloc

*/

/* copier (taille_bloc) octets du fichier (src) dans le fichier (dst)
	en utilisant le (buffer) de taille (taille_buffer). retourne
	le nombre d'octets copies (en gros). taille_buffer doit etre d'un
	type compatible avec size_t car passe en parametres a fread. on choisi
	unsigned int (cas de fread sous MS-DOS/BC++ 3.1). item pour reste
	qui est < taille_buffer.
*/

long copier(FILE *src,FILE *dst,long taille_bloc,unsigned char *buffer,
	unsigned int taille_buffer)
{
	long	i,n,result;
	unsigned int reste;

	result = 0;
	n = taille_bloc/taille_buffer;
	for (i=0;i<n;i++)
	{
		if (fread(buffer,1,taille_buffer,src)!=taille_buffer)
		{
			if (debug)
			printf("copier: impossible de lire %u octets\n",
				taille_buffer);
			return result;
		}
		if (fwrite(buffer,1,taille_buffer,dst)!=taille_buffer)
		{
			if (debug)
			printf("copier: impossible d'ecrire %u octets\n",
				taille_buffer);
			return result;
		}
		if (debug)
		printf("copier: copie de %u octets reussie\n",taille_buffer);
		result += taille_buffer;
	}
	reste = taille_bloc%taille_buffer;
	if (reste != 0)
	{
		if (fread(buffer,1,reste,src)!=reste)
		{
			if (debug)
			printf("copier: impossible de lire %u octets\n",reste);
			return result;
		}
		if (fwrite(buffer,1,reste,dst)!=reste)
		{
			if (debug)
			printf("copier: impossible d'ecrire %u octets\n",reste);
			return result;
		}
		if (debug)
		printf("copier: copie de %u octets reussie\n",reste);
		result += reste;
	}
	return result;
}

/* mets les fichiers de la liste (fichiers) dans l'archive de nom (archive)
	le nombre de fichiers a mettre est n
*/

void decoupe(const char *archive,int n,char **fichiers)
{
	char	*nom_archive;
	int		i,numero;
	FILE	*src,*dst;
	unsigned char *buffer;
	long	taille_fichier,taille_bloc,offset_bloc,taille_generee;
	long	result;

/*
	taille_fichier : la taille du fichier source restant a copier
	taille_generee : la taille de l'archive generee
	taille_bloc : la taille du bloc que l'on va copier
*/

	numero = strlen(archive);
	nom_archive = (char *)malloc(numero+5);
	if (nom_archive == NULL)
	{
		printf("impossible d'allouer %d octets\n",numero+5);
		return;
	}
	buffer = (unsigned char *)malloc(taille_buffer_interne);
	if (buffer == NULL)
	{
		printf("impossible d'allouer %u octets\n",
			taille_buffer_interne);
		return;
	}

	numero = 0;
	taille_generee = 0;
	dst = NULL;
	for (i=0;i<n;i++)
	{
		taille_fichier = filesize(fichiers[i]);
		if (taille_fichier == -1)
		{
			printf("impossible d'obtenir la taille du fichier %s\n",
				fichiers[i]);
			continue;
		}
		printf("%10ld %s\n",taille_fichier,fichiers[i]);
		src = fopen(fichiers[i],"rb");
		if (src == NULL)
		{
			printf("impossible d'ouvrir %s\n",fichiers[i]);
			continue;
		}
		offset_bloc = 0;
		do
		{
			if (taille_generee == taille_fichier_genere)
			{
				if (debug)
				printf("fermeture de %s\n",nom_archive);
				fclose(dst);
				dst = NULL;
			}

			if (dst == NULL)
			{
				sprintf(nom_archive,format,archive,numero);
				dst = fopen(nom_archive,"wb");
				if (dst == NULL)
				{
					perror(nom_archive);
					exit(-1);
				}
				taille_generee = 0;
				numero++;

				if (debug)
				printf("creation de %s\n",nom_archive);
			}

			if (taille_generee+9+strlen(fichiers[i])+taille_fichier
				> taille_fichier_genere)
			{
				taille_bloc = taille_fichier_genere - 9
					- strlen(fichiers[i]) - taille_generee;
			}
			else
				taille_bloc = taille_fichier;

			buffer[0] = (unsigned char)(offset_bloc>>24)&0xff;
			buffer[1] = (unsigned char)(offset_bloc>>16)&0xff;
			buffer[2] = (unsigned char)(offset_bloc>>8)&0xff;
			buffer[3] = (unsigned char)(offset_bloc)&0xff;
			buffer[4] = (unsigned char)(taille_bloc>>24)&0xff;
			buffer[5] = (unsigned char)(taille_bloc>>16)&0xff;
			buffer[6] = (unsigned char)(taille_bloc>>8)&0xff;
			buffer[7] = (unsigned char)(taille_bloc)&0xff;
			buffer[8] = (unsigned char)strlen(fichiers[i]);

			if (fwrite(buffer,1,9,dst)!=9)
			{
				printf("impossible d'ecrire dans %s\n",
					nom_archive);
				break;
			}
			taille_generee += 9;
			if (fwrite(fichiers[i],1,buffer[8],dst)!=buffer[8])
			{
				printf("impossible d'ecrire dans %s\n",
					nom_archive);
				break;
			}
			taille_generee += buffer[8];

			result = copier(src,dst,taille_bloc,buffer,
				taille_buffer_interne);

			taille_fichier -= result;
			taille_generee += result;
			offset_bloc += taille_bloc;

			if (result != taille_bloc)
			{
				printf("erreur de copie\n");
				break;
			}
		}
		while (taille_fichier>0);

		fclose(src);
	}
	if (dst != NULL)
	{
		if (debug)
		printf("fermeture de %s\n",nom_archive);
		fclose(dst);
	}
	free(nom_archive);
	free(buffer);
}

void recolle(const char *archive)
{
	char	*nom_fichier,*nom_precedent;
	int	n,l;
	long	taille,taille_bloc,offset_bloc,taille_totale,result;
	unsigned char *buffer;
	FILE	*src,*dst;

/*
	taille : taille de l'archive restant a lire
*/

	n = strlen(archive);
	nom_fichier = (char *)malloc(n+5);
	if (nom_fichier == NULL)
	{
		printf("impossible d'allouer %d octets\n",n+5);
		return;
	}

	buffer = (unsigned char *)malloc(taille_buffer_interne);
	if (buffer == NULL)
	{
		printf("impossible d'allouer %d octets\n",
			taille_buffer_interne);
		free(nom_fichier);
		return;
	}
	nom_precedent = NULL;
	taille_totale = 0;
	dst = NULL;
	for (n=0;;n++)
	{
		sprintf(nom_fichier,format,archive,n);
		taille = filesize(nom_fichier);
		if (taille == -1)
		{
			if (debug)
			printf("Impossible d'obtenir la taille de %s\n",
				nom_fichier);
			break;
		}
		if (debug)
		printf("lecture de %s (%ld octets)\n",nom_fichier,taille);
		src = fopen(nom_fichier,"rb");
		if (src == NULL)
		{
			printf("impossible d'ouvrir %s\n",nom_fichier);
			break;
		}

		do
		{
			if (taille<9)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}
			if (fread(buffer,1,9,src)!=9)
			{
				printf("impossible de lire l'entete\n");
				break;
			}

			taille -= 9;
			offset_bloc = buffer[0]*16777216L + buffer[1]*65536L
				+ buffer[2]*256L + buffer[3];
			taille_bloc = buffer[4]*16777216L + buffer[5]*65536L
				+ buffer[6]*256L + buffer[7];
			l = buffer[8];

			if (taille<l)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}
			if (fread(buffer,1,l,src)!=l)
			{
				printf("impossible de lire le nom du fichier original\n");
				break;
			}
			taille -= l;

			buffer[l] = 0;
			if (debug)
				printf("%s (offset=%ld,taille=%ld)\n",
					buffer,offset_bloc,taille_bloc);


	/* on affiche le nom du fichier original que s'il est different
		du dernier nom de fichier (stocke dans nom_precedent)
	 */
			if (nom_precedent != NULL)
				if (strcmp(nom_precedent,(char *)buffer)!=0)
				{
					printf("%10ld %s\n",taille_totale,
						nom_precedent);
					taille_totale = 0;
					fclose(dst);
					dst = NULL;
				}

			if (dst == NULL)
			{
				dst = fopen((char *)buffer,"wb");
				if (dst == NULL)
				{
					printf("impossible d'ouvrir le fichier %s\n",
						buffer);
					break;
				}
			}

			taille_totale += taille_bloc;
			nom_precedent = (char *)realloc(nom_precedent,l+1);
			strcpy(nom_precedent,(char *)buffer);

			if (taille<taille_bloc)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}

			result = copier(src,dst,taille_bloc,buffer,
				taille_buffer_interne);
			if (result != taille_bloc)
			{
				printf("erreur de copie\n");
				break;
			}
			taille -= taille_bloc;
		}
		while (taille>0);

		fclose(src);
	}
	if (nom_precedent!=NULL)
	{
		printf("%10ld %s\n",taille_totale,nom_precedent);
		free(nom_precedent);
	}
	free(buffer);
	free(nom_fichier);
}

void lister_disquette(const char *archive)
{
	char	*nom_fichier,*nom_precedent;
	int		n,l;
	long	taille,taille_bloc,offset_bloc;
	long    taille_totale;
	unsigned char *buffer;
	FILE	*fp;

	n = strlen(archive);
	nom_fichier = (char *)malloc(n+5);
	if (nom_fichier == NULL)
	{
		printf("impossible d'allouer %d octets\n",n+5);
		return;
	}

	buffer = (unsigned char *)malloc(taille_buffer_interne);
	if (buffer == NULL)
	{
		printf("impossible d'allouer %d octets\n",
			taille_buffer_interne);
		free(nom_fichier);
		return;
	}
	nom_precedent = NULL;
	taille_totale = 0;
	for (n=0;;n++)
	{
		sprintf(nom_fichier,format,archive,n);
		taille = filesize(nom_fichier);
		if (taille == -1)
		{
			if (debug)
			printf("Impossible d'obtenir la taille de %s\n",nom_fichier);
			break;
		}
		if (debug)
		printf("lecture de %s (%ld octets)\n",nom_fichier,taille);
		fp = fopen(nom_fichier,"rb");
		if (fp == NULL)
		{
			perror(nom_fichier);
			break;
		}

		do
		{
			if (taille<9)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}
			if (fread(buffer,1,9,fp)!=9)
			{
				printf("impossible de lire l'entete\n");
				break;
			}

			taille -= 9;
			offset_bloc = buffer[0]*16777216L + buffer[1]*65536L
				+ buffer[2]*256L + buffer[3];
			taille_bloc = buffer[4]*16777216L + buffer[5]*65536L
				+ buffer[6]*256L + buffer[7];
			l = buffer[8];

			if (taille<l)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}
			if (fread(buffer,1,l,fp)!=l)
			{
				printf("impossible de lire le nom du fichier original\n");
				break;
			}
			taille -= l;

			buffer[l] = 0;
			if (debug)
				printf("%s (offset=%ld,taille=%ld)\n",
					buffer,offset_bloc,taille_bloc);


	/* on affiche le nom du fichier original que s'il est different
		du dernier nom de fichier (stocke dans nom_precedent)
	 */
			if (nom_precedent != NULL)
				if (strcmp(nom_precedent,(char *)buffer)!=0)
				{
					printf("%10ld %s\n",taille_totale,
						nom_precedent);
					taille_totale = 0;
				}

			taille_totale += taille_bloc;
			nom_precedent = (char *)realloc(nom_precedent,l+1);
			strcpy(nom_precedent,(char *)buffer);

			if (taille<taille_bloc)
			{
				printf("%s: fichier corrompu\n",nom_fichier);
				break;
			}
			if (fseek(fp,taille_bloc,SEEK_CUR)!=0)
			{
				printf("erreur de deplacement dans %s\n",
					nom_fichier);
				break;
			}
			taille -= taille_bloc;
		}
		while (taille>0);

		fclose(fp);
	}
	if (nom_precedent!=NULL)
	{
		printf("%10ld %s\n",taille_totale,nom_precedent);
		free(nom_precedent);
	}
	free(buffer);
	free(nom_fichier);
}

int interpreter_option(int *argc,char ***argv)
{
	long	n;
	unsigned int nbuf;

	for (;;)
	{
		if (*argc <= 1)
			break;

		if ((*argv)[1][0] == '-')
		{
			switch ((*argv)[1][1])
			{
			case 'v':
				n = strtol((*argv)[1]+2,NULL,10);
				if (n < 100)
				{
					printf("%s:invalid number\n",(*argv)[1]+2);
					return -1;
				}
				taille_fichier_genere=n;
				(*argc)--;
				(*argv)++;
				continue;
			case 'd':
				debug = 1;
				(*argc)--;
				(*argv)++;
				continue;
			case 'b':
				nbuf = atoi((*argv)[1]+2);
				if (nbuf<256)
				{
					printf("%s:invalid number\n",(*argv)[1]+2);
					return -1;
				}
				taille_buffer_interne = nbuf;
				(*argc)--;
				(*argv)++;
				continue;
			case 'h':
				return -1;
			default:
				break;
			}
		}
		break;
	}
	if (taille_fichier_genere == 0)
		taille_fichier_genere = TAILLE_FICHIER_GENERE_PAR_DEFAUT;
	if (taille_buffer_interne == 0)
		taille_buffer_interne = TAILLE_BUFFER_INTERNE_PAR_DEFAUT;
	return 0;
}

int submain(int argc,char *argv[])
{
	if (interpreter_option(&argc,&argv)!=0)
	{
		usage();
		return -1;
	}

	if (argc < 2 )
	{
		usage();
		return -1;
	}

	if (strcmp(argv[1],"a")==0 && argc>3)
	{
		decoupe(argv[2],argc-3,argv+3);
		return 0;
	}

	if (strcmp(argv[1],"e")==0 && argc==3)
	{
		recolle(argv[2]);
		return 0;
	}

	if (strcmp(argv[1],"l")==0 && argc==3)
	{
		lister_disquette(argv[2]);
		return 0;
	}

	printf("%s:unknown command\n",argv[1]);
	usage();
	return -1;
}

int main(int argc,char *argv[])
{
	int result;

	result = submain(argc,argv);

	return result;
}
	
