// SPDX-License-Identifier: GPL-2.0
#include "debug.h"
#include "dsos.h"
#include "dso.h"
#include "util.h"
#include "vdso.h"
#include "namespaces.h"
#include <errno.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <symbol.h> // filename__read_build_id
#include <unistd.h>

void dsos__init(struct dsos *dsos)
{
	init_rwsem(&dsos->lock);

	dsos->cnt = 0;
	dsos->allocated = 0;
	dsos->dsos = NULL;
	dsos->sorted = true;
}

static void dsos__purge(struct dsos *dsos)
{
	down_write(&dsos->lock);

	for (unsigned int i = 0; i < dsos->cnt; i++) {
		struct dso *dso = dsos->dsos[i];

		dso__set_dsos(dso, NULL);
		dso__put(dso);
	}

	zfree(&dsos->dsos);
	dsos->cnt = 0;
	dsos->allocated = 0;
	dsos->sorted = true;

	up_write(&dsos->lock);
}

void dsos__exit(struct dsos *dsos)
{
	dsos__purge(dsos);
	exit_rwsem(&dsos->lock);
}


static int __dsos__for_each_dso(struct dsos *dsos,
				int (*cb)(struct dso *dso, void *data),
				void *data)
{
	for (unsigned int i = 0; i < dsos->cnt; i++) {
		struct dso *dso = dsos->dsos[i];
		int err;

		err = cb(dso, data);
		if (err)
			return err;
	}
	return 0;
}

struct dsos__read_build_ids_cb_args {
	bool with_hits;
	bool have_build_id;
};

static int dsos__read_build_ids_cb(struct dso *dso, void *data)
{
	struct dsos__read_build_ids_cb_args *args = data;
	struct nscookie nsc;
	struct build_id bid = { .size = 0, };

	if (args->with_hits && !dso__hit(dso) && !dso__is_vdso(dso))
		return 0;
	if (dso__has_build_id(dso)) {
		args->have_build_id = true;
		return 0;
	}
	nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
	if (filename__read_build_id(dso__long_name(dso), &bid) > 0) {
		dso__set_build_id(dso, &bid);
		args->have_build_id = true;
	} else if (errno == ENOENT && dso__nsinfo(dso)) {
		char *new_name = dso__filename_with_chroot(dso, dso__long_name(dso));

		if (new_name && filename__read_build_id(new_name, &bid) > 0) {
			dso__set_build_id(dso, &bid);
			args->have_build_id = true;
		}
		free(new_name);
	}
	nsinfo__mountns_exit(&nsc);
	return 0;
}

bool dsos__read_build_ids(struct dsos *dsos, bool with_hits)
{
	struct dsos__read_build_ids_cb_args args = {
		.with_hits = with_hits,
		.have_build_id = false,
	};

	dsos__for_each_dso(dsos, dsos__read_build_ids_cb, &args);
	return args.have_build_id;
}

static int __dso__cmp_long_name(const char *long_name, const struct dso_id *id,
				const struct dso *b)
{
	int rc = strcmp(long_name, dso__long_name(b));
	return rc ?: dso_id__cmp(id, dso__id_const(b));
}

static int __dso__cmp_short_name(const char *short_name, const struct dso_id *id,
				 const struct dso *b)
{
	int rc = strcmp(short_name, dso__short_name(b));
	return rc ?: dso_id__cmp(id, dso__id_const(b));
}

static int dsos__cmp_long_name_id_short_name(const void *va, const void *vb)
{
	const struct dso *a = *((const struct dso **)va);
	const struct dso *b = *((const struct dso **)vb);
	int rc = strcmp(dso__long_name(a), dso__long_name(b));

	if (!rc) {
		rc = dso_id__cmp(dso__id_const(a), dso__id_const(b));
		if (!rc)
			rc = strcmp(dso__short_name(a), dso__short_name(b));
	}
	return rc;
}

struct dsos__key {
	const char *long_name;
	const struct dso_id *id;
};

static int dsos__cmp_key_long_name_id(const void *vkey, const void *vdso)
{
	const struct dsos__key *key = vkey;
	const struct dso *dso = *((const struct dso **)vdso);

	return __dso__cmp_long_name(key->long_name, key->id, dso);
}

/*
 * Find a matching entry and/or link current entry to RB tree.
 * Either one of the dso or name parameter must be non-NULL or the
 * function will not work.
 */
static struct dso *__dsos__find_by_longname_id(struct dsos *dsos,
					       const char *name,
					       const struct dso_id *id,
					       bool write_locked)
	SHARED_LOCKS_REQUIRED(dsos->lock)
{
	struct dsos__key key = {
		.long_name = name,
		.id = id,
	};
	struct dso **res;

	if (dsos->dsos == NULL)
		return NULL;

	if (!dsos->sorted) {
		if (!write_locked) {
			struct dso *dso;

			up_read(&dsos->lock);
			down_write(&dsos->lock);
			dso = __dsos__find_by_longname_id(dsos, name, id,
							  /*write_locked=*/true);
			up_write(&dsos->lock);
			down_read(&dsos->lock);
			return dso;
		}
		qsort(dsos->dsos, dsos->cnt, sizeof(struct dso *),
		      dsos__cmp_long_name_id_short_name);
		dsos->sorted = true;
	}

	res = bsearch(&key, dsos->dsos, dsos->cnt, sizeof(struct dso *),
		      dsos__cmp_key_long_name_id);
	if (!res)
		return NULL;

	return dso__get(*res);
}

int __dsos__add(struct dsos *dsos, struct dso *dso)
{
	if (dsos->cnt == dsos->allocated) {
		unsigned int to_allocate = 2;
		struct dso **temp;

		if (dsos->allocated > 0)
			to_allocate = dsos->allocated * 2;
		temp = realloc(dsos->dsos, sizeof(struct dso *) * to_allocate);
		if (!temp)
			return -ENOMEM;
		dsos->dsos = temp;
		dsos->allocated = to_allocate;
	}
	if (!dsos->sorted) {
		dsos->dsos[dsos->cnt++] = dso__get(dso);
	} else {
		int low = 0, high = dsos->cnt - 1;
		int insert = dsos->cnt; /* Default to inserting at the end. */

		while (low <= high) {
			int mid = low + (high - low) / 2;
			int cmp = dsos__cmp_long_name_id_short_name(&dsos->dsos[mid], &dso);

			if (cmp < 0) {
				low = mid + 1;
			} else {
				high = mid - 1;
				insert = mid;
			}
		}
		memmove(&dsos->dsos[insert + 1], &dsos->dsos[insert],
			(dsos->cnt - insert) * sizeof(struct dso *));
		dsos->cnt++;
		dsos->dsos[insert] = dso__get(dso);
	}
	dso__set_dsos(dso, dsos);
	return 0;
}

int dsos__add(struct dsos *dsos, struct dso *dso)
{
	int ret;

	down_write(&dsos->lock);
	ret = __dsos__add(dsos, dso);
	up_write(&dsos->lock);
	return ret;
}

struct dsos__find_id_cb_args {
	const char *name;
	const struct dso_id *id;
	struct dso *res;
};

static int dsos__find_id_cb(struct dso *dso, void *data)
{
	struct dsos__find_id_cb_args *args = data;

	if (__dso__cmp_short_name(args->name, args->id, dso) == 0) {
		args->res = dso__get(dso);
		return 1;
	}
	return 0;

}

static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, const struct dso_id *id,
				   bool cmp_short, bool write_locked)
	SHARED_LOCKS_REQUIRED(dsos->lock)
{
	struct dso *res;

	if (cmp_short) {
		struct dsos__find_id_cb_args args = {
			.name = name,
			.id = id,
			.res = NULL,
		};

		__dsos__for_each_dso(dsos, dsos__find_id_cb, &args);
		return args.res;
	}
	res = __dsos__find_by_longname_id(dsos, name, id, write_locked);
	return res;
}

struct dso *dsos__find(struct dsos *dsos, const char *name, bool cmp_short)
{
	struct dso *res;

	down_read(&dsos->lock);
	res = __dsos__find_id(dsos, name, &dso_id_empty, cmp_short, /*write_locked=*/false);
	up_read(&dsos->lock);
	return res;
}

static void dso__set_basename(struct dso *dso)
{
	char *base, *lname;
	int tid;

	if (perf_pid_map_tid(dso__long_name(dso), &tid)) {
		if (asprintf(&base, "[JIT] tid %d", tid) < 0)
			return;
	} else {
	      /*
	       * basename() may modify path buffer, so we must pass
               * a copy.
               */
		lname = strdup(dso__long_name(dso));
		if (!lname)
			return;

		/*
		 * basename() may return a pointer to internal
		 * storage which is reused in subsequent calls
		 * so copy the result.
		 */
		base = strdup(basename(lname));

		free(lname);

		if (!base)
			return;
	}
	dso__set_short_name(dso, base, true);
}

static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
{
	struct dso *dso = dso__new_id(name, id);

	if (dso != NULL) {
		/*
		 * The dsos lock is held on entry, so rename the dso before
		 * adding it to avoid needing to take the dsos lock again to say
		 * the array isn't sorted.
		 */
		dso__set_basename(dso);
		__dsos__add(dsos, dso);
	}
	return dso;
}

static struct dso *__dsos__findnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
	SHARED_LOCKS_REQUIRED(dsos->lock)
{
	struct dso *dso = __dsos__find_id(dsos, name, id, false, /*write_locked=*/true);

	if (dso)
		__dso__improve_id(dso, id);

	return dso ? dso : __dsos__addnew_id(dsos, name, id);
}

struct dso *dsos__findnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
{
	struct dso *dso;
	down_write(&dsos->lock);
	dso = __dsos__findnew_id(dsos, name, id);
	up_write(&dsos->lock);
	return dso;
}

struct dsos__fprintf_buildid_cb_args {
	FILE *fp;
	bool (*skip)(struct dso *dso, int parm);
	int parm;
	size_t ret;
};

static int dsos__fprintf_buildid_cb(struct dso *dso, void *data)
{
	struct dsos__fprintf_buildid_cb_args *args = data;
	char sbuild_id[SBUILD_ID_SIZE];

	if (args->skip && args->skip(dso, args->parm))
		return 0;
	build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
	args->ret += fprintf(args->fp, "%-40s %s\n", sbuild_id, dso__long_name(dso));
	return 0;
}

size_t dsos__fprintf_buildid(struct dsos *dsos, FILE *fp,
			       bool (*skip)(struct dso *dso, int parm), int parm)
{
	struct dsos__fprintf_buildid_cb_args args = {
		.fp = fp,
		.skip = skip,
		.parm = parm,
		.ret = 0,
	};

	dsos__for_each_dso(dsos, dsos__fprintf_buildid_cb, &args);
	return args.ret;
}

struct dsos__fprintf_cb_args {
	FILE *fp;
	size_t ret;
};

static int dsos__fprintf_cb(struct dso *dso, void *data)
{
	struct dsos__fprintf_cb_args *args = data;

	args->ret += dso__fprintf(dso, args->fp);
	return 0;
}

size_t dsos__fprintf(struct dsos *dsos, FILE *fp)
{
	struct dsos__fprintf_cb_args args = {
		.fp = fp,
		.ret = 0,
	};

	dsos__for_each_dso(dsos, dsos__fprintf_cb, &args);
	return args.ret;
}

static int dsos__hit_all_cb(struct dso *dso, void *data __maybe_unused)
{
	dso__set_hit(dso);
	return 0;
}

int dsos__hit_all(struct dsos *dsos)
{
	return dsos__for_each_dso(dsos, dsos__hit_all_cb, NULL);
}

struct dso *dsos__findnew_module_dso(struct dsos *dsos,
				     struct machine *machine,
				     struct kmod_path *m,
				     const char *filename)
{
	struct dso *dso;

	down_write(&dsos->lock);

	dso = __dsos__find_id(dsos, m->name, &dso_id_empty, /*cmp_short=*/true,
			      /*write_locked=*/true);
	if (dso) {
		up_write(&dsos->lock);
		return dso;
	}
	/*
	 * Failed to find the dso so create it. Change the name before adding it
	 * to the array, to avoid unnecessary sorts and potential locking
	 * issues.
	 */
	dso = dso__new_id(m->name, /*id=*/NULL);
	if (!dso) {
		up_write(&dsos->lock);
		return NULL;
	}
	dso__set_basename(dso);
	dso__set_module_info(dso, m, machine);
	dso__set_long_name(dso,	strdup(filename), true);
	dso__set_kernel(dso, DSO_SPACE__KERNEL);
	__dsos__add(dsos, dso);

	up_write(&dsos->lock);
	return dso;
}

static int dsos__find_kernel_dso_cb(struct dso *dso, void *data)
{
	struct dso **res = data;
	/*
	 * The cpumode passed to is_kernel_module is not the cpumode of *this*
	 * event. If we insist on passing correct cpumode to is_kernel_module,
	 * we should record the cpumode when we adding this dso to the linked
	 * list.
	 *
	 * However we don't really need passing correct cpumode.  We know the
	 * correct cpumode must be kernel mode (if not, we should not link it
	 * onto kernel_dsos list).
	 *
	 * Therefore, we pass PERF_RECORD_MISC_CPUMODE_UNKNOWN.
	 * is_kernel_module() treats it as a kernel cpumode.
	 */
	if (!dso__kernel(dso) ||
	    is_kernel_module(dso__long_name(dso), PERF_RECORD_MISC_CPUMODE_UNKNOWN))
		return 0;

	*res = dso__get(dso);
	return 1;
}

struct dso *dsos__find_kernel_dso(struct dsos *dsos)
{
	struct dso *res = NULL;

	dsos__for_each_dso(dsos, dsos__find_kernel_dso_cb, &res);
	return res;
}

int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data), void *data)
{
	int err;

	down_read(&dsos->lock);
	err = __dsos__for_each_dso(dsos, cb, data);
	up_read(&dsos->lock);
	return err;
}
