/*
 * Copyright (c) 2013, 2016, 2022 Jonas 'Sortie' Termansen.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * tix-rmdiff.c
 * Compare removed files between two trees.
 */

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "util.h"

static DIR* fdopendupdir(int fd)
{
	int newfd = dup(fd);
	if ( newfd < 0 )
		return NULL;
	DIR* result = fdopendir(newfd);
	if ( !result )
		return close(newfd), (DIR*) NULL;
	return result;
}

static void rmdiff(int tree_a, const char* tree_a_path,
                   int tree_b, const char* tree_b_path,
                   const char* relpath)
{
	DIR* dir_a = fdopendupdir(tree_a);
	if ( !dir_a )
		err(1, "fdopendupdir: %s", tree_a_path);
	struct dirent* entry;
	while ( (entry = readdir(dir_a)) )
	{
		if ( !strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") )
			continue;

		char* subrelpath = relpath[0] ? join_paths(relpath, entry->d_name) :
		                   strdup(entry->d_name);
		if ( !subrelpath )
			err(1, "malloc");

		int diropenflags = O_RDONLY | O_DIRECTORY | O_NOFOLLOW;
		int subtree_b = openat(tree_b, entry->d_name, diropenflags);
		if ( 0 <= subtree_b )
		{
			char* subtree_b_path = join_paths(tree_b_path, entry->d_name);
			if ( !subtree_b_path )
				err(1, "malloc");
			int subtree_a = openat(tree_a, entry->d_name, diropenflags);
			if ( subtree_a < 0 )
			{
				if ( !(errno == ENOTDIR || errno == ELOOP || errno == ENOENT) )
					err(1, "%s/%s", tree_b_path, entry->d_name);
				free(subtree_b_path);
				close(subtree_b);
				free(subrelpath);
				continue;
			}
			char* subtree_a_path = join_paths(tree_a_path, entry->d_name);
			if ( !subtree_a_path )
				err(1, "malloc");
			rmdiff(subtree_a, subtree_a_path, subtree_b, subtree_b_path,
			       subrelpath);
			free(subtree_a_path);
			close(subtree_a);
			free(subtree_b_path);
			close(subtree_b);
			free(subrelpath);
			continue;
		}
		else if ( errno == ENOENT )
		{
			printf("rm -rf -- '");
			for ( size_t i = 0; subrelpath[i]; i++ )
				if ( subrelpath[i] == '\'' )
					printf("'\\''");
				else
					putchar(subrelpath[i]);
			printf("'\n");
		}

		free(subrelpath);
	}
	closedir(dir_a);
}

int main(int argc, char* argv[])
{
	int opt;
	while ( (opt = getopt(argc, argv, "")) != -1 )
	{
		switch ( opt )
		{
		default: return 1;
		}
	}

	if ( argc - optind < 2 )
		errx(1, "expected two directories");

	if ( 2 < argc - optind )
		errx(1, "unexpected extra operand");

	const char* tree_a_path = argv[optind + 0];
	int tree_a = open(tree_a_path, O_RDONLY | O_DIRECTORY);
	if ( tree_a < 0 )
		err(1, "%s", tree_a_path);

	const char* tree_b_path = argv[optind + 1];
	int tree_b = open(tree_b_path, O_RDONLY | O_DIRECTORY);
	if ( tree_b < 0 )
		err(1, "%s", tree_b_path);

	rmdiff(tree_a, tree_a_path, tree_b, tree_b_path, "");

	if ( ferror(stdout) || fflush(stdout) == EOF )
		err(1, "stdout");

	return 0;
}
