/*
UFS2 text undelete
		Lisence: GPL	(C)2004/12/10	nabe@abk

Sorry, Comment is ja_JP.eucJP.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// for mkdir, chdir
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// for UFS Filesystem
#include <sys/param.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/dir.h>
#include <ufs/ffs/fs.h>

#define uchar	unsigned char
#define uint	unsigned int
#define uint64	unsigned long long

#define SuperBlock_offset	0x10000
#define FileName_bufsize	0x100000	// 1MB
#define FineName_maxsize	1024		// 1024 chars
#define MaxPathDepth		256		// 最大 dir 数

#define DIR_MODE		0755		// Directory permission

#define ascii_th	100	// 100 byte
#define OUT_DIR		"__out/"
#define OUT_DIR2	"__nofullpath/"
#define INDEX_FILE	"__index.txt"
#define LOSTDIR_fmt	"__inode.%d/"		// %d = inode

#define DIRLIST_FILE	".dir"
#define DIRNAME_FILE	".name"

// ----------------------------------------------------------------------------
struct node_info {
	uint	up_inode;
	uchar	is_dir;		// 0:file 1:directory
	char	*name;
};

// ----------------------------------------------------------------------------
uint	block_size;
uint64	blocks;
uint64	file_size;
uint64	st_block;
uint	ncg;		// number of cg(cylinder group)
uint	inodes;
FILE	*fp;
char	*namebuf;
uint	namebuf_p;
uint	namebuf_size;
char	ascii_tbl[0x200];

uchar	*buf;		// File Reading buffer

struct direct		*dir;
struct ufs2_dinode	*inode;
struct fs		sblock;
struct node_info	*i_info;

//-----------------------------------------------------------------------------
inline int is_directory(char *buf) {
	return (   0x0104000c == *(uint *)(buf+0x04)
		&& 0x0000002e == *(uint *)(buf+0x08)
		&& 0x00002e2e == *(uint *)(buf+0x14) );
}

inline struct direct *next_dir(struct direct *dir) {
	static struct direct *old_dir, *new_dir;
	old_dir = dir;
	new_dir  = (struct direct *)((char *)dir + ((dir->d_namlen + 8 + 4) & 0x1fc));
	if (new_dir->d_ino <= inodes) return new_dir;

	// inode 番号が異常なときは reclen を信用する
	return (struct direct *)((char *)old_dir + old_dir->d_reclen);
}

//-----------------------------------------------------------------------------
// ディレクトリ構造を解析する
//-----------------------------------------------------------------------------
void init_namebuf(uint size) {
	namebuf      = (char *)malloc(size);
	namebuf_p    = 0;
	namebuf_size = size;
}
char *save_name_to_buf(char *s, uint length, uint dir_flag) {
	char *p;
	if (namebuf_p + length + 2 > namebuf_size) {
		fprintf(stderr, "\n\nOverflow of name buffer\nPlease recompile with setting FileName_bufsize\n");
		exit(1);
	}
	p = namebuf + namebuf_p;
	strncpy(p, s, length);
	if (dir_flag) p[length++] = '/';	// directory
	p[length] = 0;	// string terminate
	namebuf_p += length + 1;
	return p;
}

void save_directory_infomation(char *filename) {
	uint i;
	FILE *fp;
	char s[FineName_maxsize];

	for(i=0; i<inodes; i++) i_info[i].name = i_info[i].name - (uint)namebuf;
	sprintf(s, "%s" DIRLIST_FILE, filename);
	fp = fopen(s, "w");
	fwrite(i_info, sizeof(struct node_info), inodes, fp);
	fclose(fp);
	for(i=0; i<inodes; i++) i_info[i].name = i_info[i].name + (uint)namebuf;

	sprintf(s, "%s" DIRNAME_FILE, filename);
	fp = fopen(s, "w");
	fwrite(namebuf, 1, FileName_bufsize, fp);
	fclose(fp);
}
// 0:false 1:true
int load_directory_infomation(char *filename) {
	uint i;
	FILE *fp;
	char s[FineName_maxsize];

	sprintf(s, "%s" DIRLIST_FILE, filename);
	fp = fopen(s, "r");
	if (!fp) return 0;
	fread(i_info, sizeof(struct node_info), inodes, fp);
	fclose(fp);
	for(i=0; i<inodes; i++) i_info[i].name = i_info[i].name + (uint)namebuf;

	sprintf(s, "%s" DIRNAME_FILE, filename);
	fp = fopen(s, "r");
	fread(namebuf, 1, FileName_bufsize, fp);
	if (!fp) return 0;
	fclose(fp);

	return 1;
}


void analyze_directory(FILE *fp) {
	uint64 i;
	uint k,x;

	// st_block = 282272;
	for(i=st_block; i<blocks; i++) {
		if (!((uint)i & 0x3f)) printf("Directocy analyze : %lld / %lld\r", i, blocks);
		fseek(fp, i*block_size, SEEK_SET);
		fread(buf, 1, block_size, fp);
		// if (i>280000) printf("\n");

		// found directory
		if (is_directory(buf)) {
			// "." dir
			dir = (struct direct *) buf;
			k   = dir->d_ino;
			dir = next_dir(dir);

			// ".." dir
			i_info[k].up_inode = dir->d_ino;
			dir = next_dir(dir);

			while(dir->d_ino) {
				x = dir->d_ino;		// inode number
				i_info[x].up_inode = k;	// 親
				if (dir->d_type == DT_DIR) i_info[x].is_dir = 1;
				// 名前の設定
				i_info[x].name = save_name_to_buf(dir->d_name, dir->d_namlen, i_info[x].is_dir);
				dir = next_dir(dir);
			}
		}
	}
	printf("Directocy analyzed                    \n");
	/*
	for(i=2; i<inodes; i++)
		if (i_info[i].up_inode) printf("[%3lld] = up to %3d\n", i, i_info[i].up_inode);
	*/
}

inline void get_pathname(char *fnbuf, uint inode) {
	static uint nstack[MaxPathDepth];
	uint p;
	char *s,s2[16];

	if (inode==2) {		// is Root
		*fnbuf = 0; return;
	}
	p = 0;
	while(inode) {
		if (i_info[inode].is_dir) nstack[p++] = inode;
		inode = i_info[inode].up_inode;
		if (inode == 2) break;	// is Root
		if(p >= MaxPathDepth) {
			printf("direoctory chain looped?\n");
			sprintf(fnbuf, OUT_DIR2 LOSTDIR_fmt, nstack[0]);
			mkdir(fnbuf, DIR_MODE);	// directory 作成
			return;
		}
	}
	if (!inode) {
		fnbuf = strcpy(fnbuf, OUT_DIR2);
	} else	*fnbuf = 0;
	// directory 連結
	while(p) {
		s = i_info[nstack[--p]].name;
		if (!s) {
			sprintf(s2, LOSTDIR_fmt, nstack[--p]);
			s = s2;
		}
		fnbuf = strcat(fnbuf, s);
		mkdir(fnbuf, DIR_MODE);	// directory 作成
	}
}

//-----------------------------------------------------------------------------
// テキスト判別用テーブルの作成
//-----------------------------------------------------------------------------
void init_ascii_tbl() {
	int i;

	// 第 1byte
	for (i=0; i<=0xff; i++) ascii_tbl[i] = 0;
	// ASCII
	ascii_tbl['\t'] = 1;
	ascii_tbl[0x0A] = 1;
	ascii_tbl[0x0d] = 1;
	ascii_tbl[0x0e] = 1;	/* JIS shift IN */
	ascii_tbl[0x0f] = 1;	/* JIS shift OUT */
	ascii_tbl[0x1b] = 1;	/* ESC sequence */
	for (i=0x20; i<0x80; i++) ascii_tbl[i] = 1;

	/* Shift-JIS or EUC */
	for (i=0x81; i<0xa0; i++) ascii_tbl[i] = 2;
	/* 半角カタカナ or EUC */
	for (i=0xa0; i<0xe0; i++) ascii_tbl[i] = 1;
	/* Shift-JIS or EUC */
	for (i=0xe0; i<0x100;i++) ascii_tbl[i] = 2;

	// 2byte
	for (i=0x100; i<=0x1ff;i++) ascii_tbl[i] = 0;
	/* Shift-JIS 第 2byte */
	for (i=0x140; i<=0x17e;i++) ascii_tbl[i] = 1;
	for (i=0x180; i<=0x1fc;i++) ascii_tbl[i] = 1;
	/* EUC 第 2byte */
	for (i=0x1a1; i<=0x1fe;i++) ascii_tbl[i] = 1;
}
// ----------------------------------------------------------------------------

int main(int argc, char *argv[]) {
	uint64 i, txt_stblock;
	uint j;
	uint x;
	FILE *save_fp, *index_fp;
	char *p;
	char imgfile[FineName_maxsize];
	char str[FineName_maxsize], outdir[FineName_maxsize];
	save_fp = index_fp = NULL;

	//////////////////////////////////////////////////////////////
	// 引数解析
	//////////////////////////////////////////////////////////////
	if (argc < 2) {
		printf("Usage: %s UFS2-dump-image-file\n", argv[0]); return 1;
	}

	// 文字テーブル初期化
	init_ascii_tbl();

	// ファイルオープン
	 p = argv[1];
	fp = fopen(p, "r");
	if (!fp) {
		fprintf(stderr, "file open error!!\n"); exit(1);
	}
	// ファイル名のみ取り出す
	x=j=0;
	while(p[j]) {
		if (p[j++]=='/') x = j;
	}
	strncpy(imgfile, &p[x], FineName_maxsize);	// without path

	//////////////////////////////////////////////////////////////
	// スーパーブロックを読み出す
	//////////////////////////////////////////////////////////////
	fseek(fp, SuperBlock_offset, SEEK_SET);		// offset 0x10000
	fread(&sblock, 1, sizeof(sblock), fp);

	block_size = sblock.fs_fsize;		// ブロックサイズ
	blocks     = sblock.fs_size;		// ブロック数
	file_size  = block_size * blocks;
	st_block   = sblock.fs_dblkno;		// 最初のデータ blockオフセット
	ncg        = sblock.fs_ncg;		// シリンダグループ数
	inodes     = sblock.fs_ipg * ncg;	// 全 inode 数

	// ファイル読み込みバッファ確保
	buf = calloc(block_size * 4, 1);	// 余分に確保
	buf[block_size] = 0xa1;			// for TEXT check

	// 表示
	printf("Image file  : %s\n", str);
	printf("file size   : %lld\n", file_size);
	printf("block size  : %d\n", block_size);
	printf("blocks      : %lld\n", blocks);
	printf("start block : %lld\n", st_block);
	printf("n of cg     : %d\n", ncg);
	printf("n of inode  : %d\n", inodes);

	//////////////////////////////////////////////////////////////
	// (1pass) ディレクトリ構造の解析
	//////////////////////////////////////////////////////////////
	init_namebuf(FileName_bufsize);
	i_info = (struct node_info *)calloc(inodes, sizeof(struct node_info));

	if (!load_directory_infomation(imgfile) ) {
		analyze_directory(fp);
		save_directory_infomation(imgfile);
	}

	//////////////////////////////////////////////////////////////
	// directory 作成
	//////////////////////////////////////////////////////////////
	mkdir(OUT_DIR, DIR_MODE);
	chdir(OUT_DIR);		// 作業ディレクトリ

	mkdir(OUT_DIR2, DIR_MODE);

	//////////////////////////////////////////////////////////////
	// (2pass) ファイル解析
	//////////////////////////////////////////////////////////////
	txt_stblock = 0;
	strcpy(outdir, OUT_DIR2);	// 出力 dir
	for(i=st_block; i<blocks; i++) {
		if (!((uint)i & 0x3f)) printf("%lld / %lld\r", i, blocks);
		fseek(fp, i*block_size, SEEK_SET);
		fread(buf, 1, block_size, fp);

		// found directory
		if (is_directory(buf)) {
			dir = (struct direct *) buf;
			get_pathname(outdir, dir->d_ino);

			// index file open
			sprintf(str, "%s" INDEX_FILE, outdir);
			if (index_fp) fclose(index_fp);
			index_fp = fopen(str, "w");
			if (!index_fp) {
				printf("file open error : %s\n", str); exit(1);
			}
			printf("[%lld] found directory %s\n", i, outdir);
			fprintf(index_fp,"block number=%lld, Directory=%s\n\n", i, outdir);

			dir = next_dir(dir);	// "."  を skip
			dir = next_dir(dir);	// ".." を skip
			while(dir->d_ino) {
				x = dir->d_namlen;	// 名前の長さ
				*str = 0;
				strncpy(str, dir->d_name, x);
				str[x] = 0;
				printf("inode=%d, type=%x, name=%s\n", dir->d_ino, dir->d_type, str);
				if (dir->d_type != DT_DIR)
					fprintf(index_fp, "inode=%d, file name=%s\n" , dir->d_ino, str);
				else	fprintf(index_fp, "inode=%d, directory=%s/\n", dir->d_ino, str);
				dir = next_dir(dir);
			}
			j = 0;	// 必須
		} else {
			// ascii counter
			j=0;
			j+=ascii_tbl[buf[j]];
			j+=ascii_tbl[buf[j]];
			j+=ascii_tbl[buf[j]];
			for (   ; j<block_size; ) {
				if (! ascii_tbl[buf[j]]) break;
				if (  ascii_tbl[buf[j]]==1) {
					j++; continue;	// Ascii
				}
				if (! ascii_tbl[buf[j] + 0x100]) break;
				j++;
			}
		}

		// テキストファイルと見なす
		if (txt_stblock || j>=ascii_th) {
			if (!txt_stblock) {
				sprintf(str, "%s%06lld.txt", outdir, i);
				save_fp  = fopen(str, "w");
				if (!save_fp) {
					printf("file open error : %s\n", str); exit(1);
				}
				txt_stblock = i;
			}
			if (j > block_size) j = block_size;
			
			fwrite(buf, 1, j, save_fp);	// j byte write
			
			if (j < block_size) {
				printf("[%lld] wrote %s%06lld.txt, %lld byte\n", txt_stblock, outdir, i,(i-txt_stblock)*block_size + j);
				fclose(save_fp);
				txt_stblock = 0;
			}
		}
	}

	if (index_fp) fclose(index_fp);
	fclose(fp);

	return 0;
}

