// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

#include <stdbool.h>
#include <string.h>

#include "drgn_internal.h"
#include "dwarf_constants.h"
#include "path.h"
#include "util.h"

bool path_iterator_next(struct path_iterator *it, const char **component_ret,
			size_t *component_len_ret)
{
	while (it->num_components) {
		struct nstring *cur = &it->components[it->num_components - 1];
		while (cur->len > 0) {
			if (cur->str[cur->len - 1] == '/') {
				if (cur->len == 1) {
					/*
					 * This is an absolute path. Emit an
					 * empty component. Components joined
					 * before this one and remaining ".."
					 * components are not meaningful.
					 */
					it->num_components = 0;
					it->dot_dot = 0;
					*component_ret = "";
					*component_len_ret = 0;
					return true;
				}
				/* Skip redundant slashes. */
				cur->len--;
				continue;
			}

			size_t component_start = cur->len - 1;
			while (component_start > 0 &&
			       cur->str[component_start - 1] != '/')
				component_start--;
			size_t component_len = cur->len - component_start;
			cur->len = component_start;
			if (component_len == 1 &&
			    cur->str[component_start] == '.') {
				/* Skip "." components. */
			} else if (component_len == 2 &&
				   cur->str[component_start] == '.' &&
				   cur->str[component_start + 1] == '.') {
				/* Count ".." components. */
				it->dot_dot++;
			} else if (it->dot_dot) {
				it->dot_dot--;
			} else {
				*component_ret = &cur->str[component_start];
				*component_len_ret = component_len;
				return true;
			}
		}
		it->num_components--;
	}

	if (it->dot_dot) {
		/*
		 * Leftover ".." components must be above the current
		 * directory.
		 */
		it->dot_dot--;
		*component_ret = "..";
		*component_len_ret = 2;
		return true;
	}
	return false;
}

bool path_ends_with(struct path_iterator *haystack,
		    struct path_iterator *needle)
{
	for (;;) {
		const char *h_component, *n_component;
		size_t h_component_len, n_component_len;

		if (!path_iterator_next(needle, &n_component, &n_component_len))
			return true;

		if (!path_iterator_next(haystack, &h_component,
					&h_component_len))
			return false;

		if (h_component_len != n_component_len ||
		    memcmp(h_component, n_component, h_component_len) != 0)
			return false;
	}
}

bool die_matches_filename(Dwarf_Die *die, const char *filename)
{
	if (!filename || !filename[0])
		return true;

	struct nstring die_components[2];
	struct path_iterator die_path = {
		.components = die_components,
	};

	Dwarf_Die cu_die;
	Dwarf_Attribute attr_mem, *attr;
	attr = dwarf_attr_integrate(dwarf_diecu(die, &cu_die, NULL, NULL),
				    DW_AT_comp_dir, &attr_mem);
	const char *path = dwarf_formstring(attr);
	if (path) {
		die_path.components[die_path.num_components].str = path;
		die_path.components[die_path.num_components].len = strlen(path);
		die_path.num_components++;
	}

	path = dwarf_decl_file(die);
	if (!path)
		return false;
	/*
	 * If the declaration file name is absolute, the compilation directory
	 * component will be ignored.
	 */
	die_path.components[die_path.num_components].str = path;
	die_path.components[die_path.num_components].len = strlen(path);
	die_path.num_components++;

	struct path_iterator needle = {
		.components = (struct nstring []){
			{ filename, strlen(filename) }
		},
		.num_components = 1,
	};

	return path_ends_with(&die_path, &needle);
}

LIBDRGN_PUBLIC bool drgn_filename_matches(const char *haystack,
					  const char *needle)
{
	struct path_iterator haystack_path = {
		.components = (struct nstring []){
			{ haystack, strlen(haystack) }
		},
		.num_components = 1,
	};
	struct path_iterator needle_path = {
		.components = (struct nstring []){
			{ needle, strlen(needle) }
		},
		.num_components = 1,
	};
	return path_ends_with(&haystack_path, &needle_path);
}
