#include "fsmap.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "support/nls-enable.h"

struct walk_ext_priv_data {
	char			*path;
	ext2_filsys		fs;
	struct fsmap_format	*format;
};

static int walk_block(ext2_filsys fs  EXT2FS_ATTR((unused)), blk64_t *blocknr,
		      e2_blkcnt_t blockcnt,
		      blk64_t ref64_blk EXT2FS_ATTR((unused)),
		      int ref_offset EXT2FS_ATTR((unused)),
		      void *priv)
{
	struct walk_ext_priv_data *pdata = priv;
	struct fsmap_format *format = pdata->format;

	return format->add_block(fs, *blocknr, blockcnt < 0, format->private);
}

static errcode_t ino_iter_blocks(ext2_filsys fs, ext2_ino_t ino,
				 struct walk_ext_priv_data *pdata)
{
	errcode_t retval;
	struct ext2_inode inode;
	struct fsmap_format *format = pdata->format;

	retval = ext2fs_read_inode(fs, ino, &inode);
	if (retval)
		return retval;

	if (!ext2fs_inode_has_valid_blocks2(fs, &inode))
		return format->inline_data(&(inode.i_block[0]),
					   format->private);

	retval = ext2fs_block_iterate3(fs, ino, 0, NULL, walk_block, pdata);
	if (retval)
		com_err(__func__, retval, _("listing blocks of ino \"%u\""),
			ino);
	return retval;
}

static int is_dir(ext2_filsys fs, ext2_ino_t ino)
{
	struct ext2_inode inode;

	if (ext2fs_read_inode(fs, ino, &inode))
		return 0;
	return S_ISDIR(inode.i_mode);
}

static int walk_ext_dir(ext2_ino_t dir EXT2FS_ATTR((unused)),
			int flags EXT2FS_ATTR((unused)),
			struct ext2_dir_entry *de,
			int offset EXT2FS_ATTR((unused)),
			int blocksize EXT2FS_ATTR((unused)),
			char *buf EXT2FS_ATTR((unused)), void *priv_data)
{
	errcode_t retval;
	struct ext2_inode inode;
	char *filename, *cur_path, *name = de->name;
	int name_len = de->name_len & 0xff;
	struct walk_ext_priv_data *pdata = priv_data;
	struct fsmap_format *format = pdata->format;

	if (!strncmp(name, ".", name_len)
	    || !strncmp(name, "..", name_len)
	    || !strncmp(name, "lost+found", 10))
		return 0;

	if (asprintf(&filename, "%s/%.*s", pdata->path, name_len, name) < 0)
		return -ENOMEM;

	retval = ext2fs_read_inode(pdata->fs, de->inode, &inode);
	if (retval) {
		com_err(__func__, retval, _("reading ino \"%u\""), de->inode);
		goto end;
	}
	format->start_new_file(filename, de->inode, &inode, format->private);
	retval = ino_iter_blocks(pdata->fs, de->inode, pdata);
	if (retval)
		return retval;
	format->end_new_file(format->private);

	retval = 0;
	if (is_dir(pdata->fs, de->inode)) {
		cur_path = pdata->path;
		pdata->path = filename;
		retval = ext2fs_dir_iterate2(pdata->fs, de->inode, 0, NULL,
				    walk_ext_dir, pdata);
		pdata->path = cur_path;
	}

end:
	free(filename);
	return retval;
}

errcode_t fsmap_iter_filsys(ext2_filsys fs, struct fsmap_format *format,
			    const char *file, const char *mountpoint)
{
	struct walk_ext_priv_data pdata;
	errcode_t retval;

	format->private = format->init(file, mountpoint);
	pdata.fs = fs;
	pdata.path = "";
	pdata.format = format;

	retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_ext_dir, &pdata);

	format->cleanup(format->private);
	return retval;
}
