/* kvm.cc: provides support for libkvm in multiple BSDs
**
** this is really the only way to get a thing that works on
** OpenBSD AND NetBSD.
**
** Much of this file is taken from the fstat source code in
** NetBSD.
*/

#include <kvm.h>

namespace animia::internal::kvm {

static bool GetFilename(kvm_t* kvm, struct vnode *vp, std::string& name) {
	struct vnode vn;
	if (!kvm_read(kvm, vp, &vn, sizeof(*vn)))
		return 0;

	struct filestat fst;
	const char* type = vfilestat(vn, &fst);
	if (type == dead)
		return false;

	for (DEVS* d = devs; d != NULL; d = d->next) {
		if (d->fsid == fst->fsid && d->ino == fst->fileid) {
			name = d->name;
			break;
		}
	}

	return true;
}

static bool GetFilePath(kvm_t* kvm, fdfile_t* fp, std::string& path) {
	struct file file;
	fdfile_t fdfile;

	if (!kvm_read(kvm, fp, &fdfile, sizeof(fdfile)))
		return false;

	if (!fdfile.ff_file)
		return false;

	if (!kvm_read(fdfile.ff_file, &file, sizeof(file)))
		return false;

	if (file.f_type != DTYPE_VNODE)
		return false;

	return GetFilename(kvm, file.f_data, path);
}

static bool OpenFiles(kvm_t* kvm, struct kinfo_proc* p, open_file_proc_t open_file_proc) {
	if (p->proc->p_fd == 0 || p->proc->p_cwdi == 0)
		return false;

	struct filedesc filed;
	if (!kvm_read(kvm, p->proc->p_fd, &filed, sizeof(filed)))
		return false;

	if (filed.fd_lastfile == -1)
		return false;

	struct cwdinfo cwdi;
	if (!kvm_read(kvm, p->proc->p_cwdi, &cwdi, sizeof(cwdi)))
		return false;

	struct fdtab dt;
	if (!kvm_read(kvm, filed.fd_dt, &dt, sizeof(dt)))
		return false;

	/* check for corrupted files? */
	if ((unsigned)filed.fd_lastfile >= dt.dt_nfiles || filed.fd_freefile > filed.fd_lastfile + 1)
		return false;

	/* open files */
	std::unique_ptr<fdfile_t*[]> ofiles = nullptr;
	{
		ofiles.reset(malloc((filed.fd_lastfile + 1) * sizeof(fdfile_t*)));
		if (!ofiles.get())
			return false;
	}

	if (!kvm_read(kvm, &filed.fd_dt->dt_ff, ofiles.get(), filed.fd_lastfile + 1 * (sizeof(fdfile_t*))))
		return false;

	for (int i = 0; i <= filed.fd_lastfile; i++) {
		if (!ofiles[i])
			continue;
		std::string name;
		GetFilePath(kvm, ofiles[i], name);
		if (!open_file_proc(p->proc->p_pid, name))
			return false;
	}

	return true;
}

bool EnumerateOpenProcesses(process_proc_t process_proc) {
	char errbuf[_POSIX2_LINE_MAX];
	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
	if (!kernel)
		return false;

	int entries = 0;
	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &nentries);
	if (!kinfo)
		return false;

	for (int i = 0; i < entries; i++)
		if (!process_proc({kinfo[i].p_pid, kinfo[i].p_comm}))
			return false;

	return true;
}

bool EnumerateOpenFiles(std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
	if (!kernel)
		return false;

	for (const auto& pid : pids) {
		int cnt;
		struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_PID, pid, &cnt);
		if (!kinfo)
			return false;

		for (int i = 0; i < cnt; i++) {
			OpenFiles(kernel, kinfo, open_file_proc);
		}
	}
}

}
