/* SPDX-License-Identifier: GPL-3.0-or-later */
/*
 * mkfs.c
 *
 * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
 */
#include "mkfs.h"

static int set_working_dir(options_t *opt)
{
	const char *ptr;

	if (opt->packdir != NULL)
		return pushd(opt->packdir);

	ptr = strrchr(opt->infile, '/');
	if (ptr != NULL)
		return pushdn(opt->infile, ptr - opt->infile);

	return 0;
}

static int restore_working_dir(options_t *opt)
{
	if (opt->packdir != NULL || strrchr(opt->infile, '/') != NULL)
		return popd();

	return 0;
}

static int pack_files(data_writer_t *data, fstree_t *fs, options_t *opt)
{
	file_info_t *fi;
	int ret, infd;

	if (set_working_dir(opt))
		return -1;

	while (fs->files != NULL) {
		fi = fs->files;
		fs->files = fi->next;

		if (!opt->quiet)
			printf("packing %s\n", fi->input_file);

		infd = open(fi->input_file, O_RDONLY);
		if (infd < 0) {
			perror(fi->input_file);
			return -1;
		}

		ret = write_data_from_fd(data, fi, infd, 0);
		close(infd);
		if (ret)
			return -1;
	}

	if (data_writer_sync(data))
		return -1;

	return restore_working_dir(opt);
}

static int read_fstree(fstree_t *fs, options_t *opt)
{
	FILE *fp;
	int ret;

	if (opt->infile == NULL)
		return fstree_from_dir(fs, opt->packdir, opt->dirscan_flags);

	fp = fopen(opt->infile, "rb");
	if (fp == NULL) {
		perror(opt->infile);
		return -1;
	}

	if (set_working_dir(opt)) {
		fclose(fp);
		return -1;
	}

	ret = fstree_from_file(fs, opt->infile, fp);

	fclose(fp);

	if (restore_working_dir(opt))
		return -1;

	return ret;
}

int main(int argc, char **argv)
{
	int status = EXIT_FAILURE, ret;
	data_writer_t *data;
	sqfs_super_t super;
	compressor_t *cmp;
	id_table_t idtbl;
	options_t opt;
	fstree_t fs;
	int outfd;

	process_command_line(&opt, argc, argv);

	if (fstree_init(&fs, opt.blksz, opt.fs_defaults))
		return EXIT_FAILURE;

	if (sqfs_super_init(&super, opt.blksz, fs.defaults.st_mtime,
			    opt.compressor)) {
		goto out_fstree;
	}

	if (id_table_init(&idtbl))
		goto out_fstree;

	outfd = open(opt.outfile, opt.outmode, 0644);
	if (outfd < 0) {
		perror(opt.outfile);
		goto out_idtbl;
	}

	if (sqfs_super_write(&super, outfd))
		goto out_outfd;

	if (read_fstree(&fs, &opt))
		goto out_outfd;

	tree_node_sort_recursive(fs.root);

	if (fstree_gen_inode_table(&fs))
		goto out_outfd;

	fstree_gen_file_list(&fs);

	super.inode_count = fs.inode_tbl_size - 2;

#ifdef WITH_SELINUX
	if (opt.selinux != NULL) {
		if (fstree_relabel_selinux(&fs, opt.selinux))
			goto out_outfd;
	}
#endif

	fstree_xattr_deduplicate(&fs);

	cmp = compressor_create(super.compression_id, true, super.block_size,
				opt.comp_extra);
	if (cmp == NULL) {
		fputs("Error creating compressor\n", stderr);
		goto out_outfd;
	}

	ret = cmp->write_options(cmp, outfd);
	if (ret < 0)
		goto out_cmp;

	if (ret > 0) {
		super.flags |= SQFS_FLAG_COMPRESSOR_OPTIONS;
		super.bytes_used += ret;
	}

	data = data_writer_create(&super, cmp, outfd,
				  opt.devblksz, opt.num_jobs);
	if (data == NULL)
		goto out_cmp;

	if (pack_files(data, &fs, &opt))
		goto out_data;

	if (sqfs_serialize_fstree(outfd, &super, &fs, cmp, &idtbl))
		goto out_data;

	if (data_writer_write_fragment_table(data))
		goto out_data;

	if (opt.exportable) {
		if (write_export_table(outfd, &fs, &super, cmp))
			goto out_data;
	}

	if (id_table_write(&idtbl, outfd, &super, cmp))
		goto out_data;

	if (write_xattr(outfd, &fs, &super, cmp))
		goto out_data;

	if (sqfs_super_write(&super, outfd))
		goto out_data;

	if (padd_file(outfd, super.bytes_used, opt.devblksz))
		goto out_data;

	if (!opt.quiet) {
		fstree_gen_file_list(&fs);
		sqfs_print_statistics(&fs, &super);
	}

	status = EXIT_SUCCESS;
out_data:
	data_writer_destroy(data);
out_cmp:
	cmp->destroy(cmp);
out_outfd:
	close(outfd);
out_idtbl:
	id_table_cleanup(&idtbl);
out_fstree:
	fstree_cleanup(&fs);
	return status;
}