/* radare - LGPL - Copyright 2014-2024 - pancake */

#include <r_userconf.h>
#include <r_io.h>
#include <r_lib.h>
#include <r_cons.h>

typedef struct {
	char *name;
	ut64 from;
	ut64 to;
	int perm;
} RIOSelfSection;

typedef struct {
	RIOSelfSection self_sections[1024];
	int self_sections_count;
	bool mameio;
} SelfData;

#if DEBUGGER
#if __APPLE__
#include <mach/vm_map.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/mach_interface.h>
#include <mach/mach_traps.h>
#include <mach/mach_types.h>
#include <mach/mach_error.h>
#include <mach/task.h>
#include <mach/task_info.h>

static void macosx_debug_regions(SelfData *data, RIO *io, task_t task, mach_vm_address_t address, int max);

#elif R2__BSD__
#if __FreeBSD__
#include <sys/sysctl.h>
#include <sys/user.h>
#include <libutil.h>
#elif __OpenBSD__ || __NetBSD__
#include <sys/sysctl.h>
#elif __DragonFly__
#include <sys/types.h>
#include <sys/user.h>
#include <sys/sysctl.h>
#include <kvm.h>
#endif
#include <errno.h>
static bool bsd_proc_vmmaps(SelfData *sd, RIO *io, int pid);
#endif
#ifdef __HAIKU__
#include <kernel/image.h>
#endif
#if defined __sun && defined _LP64
#define _STRUCTURED_PROC 1 // to access newer proc data with additional fields
#include <sys/procfs.h>
#include <libproc.h>
#endif
#ifdef _MSC_VER
#include <process.h>  // to compile getpid for msvc windows
#include <psapi.h>
#endif

static int self_in_section(RIODesc *desc, RIO *io, ut64 addr, int *left, int *perm) {
	SelfData *sd = desc->data;
	int i;
	for (i = 0; i < sd->self_sections_count; i++) {
		if (addr >= sd->self_sections[i].from && addr < sd->self_sections[i].to) {
			if (left) {
				*left = sd->self_sections[i].to-addr;
			}
			if (perm) {
				*perm = sd->self_sections[i].perm;
			}
			return true;
		}
	}
	return false;
}

#if __serenity__
static ut64 getnum(char *s) {
	if (s && *s == '"') {
		char *colon = strchr (s, ':');
		if (colon) {
			char *comma = strchr (s, ',');
			if (comma) {
				*comma = 0;
				return r_num_get (NULL, colon + 1);
			}
		}
	}
	return 0;
}

static char *getstr(char *s) {
	if (s && *s == '"') {
		char *colon = strchr (s, ':');
		if (colon) {
			char *comma = strchr (s, ',');
			if (comma) {
				*comma = 0;
				char *q = strchr (colon, '"');
				if (q) {
					char *q2 = strchr (q + 1, '"');
					if (q2) {
						return r_str_ndup (q + 1, q2 - q);
					}
				}
			}
		}
	}
	return NULL;
}

static int serenity_debug_regions(SelfData *sd, RIO *io, int pid) {
	// pid is ignored
	const char *path = "/proc/self/vm";
#if 0
	[ {
		"readable": true,
			"writable": true,
			"executable": true,
			"stack": true,
			"shared": true,
			"syscall": true,
			"purgeable": true,
			"cacheable": true,
			"address": 1234,
			"size": 4096,
			"amount_resident": 4096,
			"amount_dirty": 4096,
			"cow_pages": 0,
			"name": "/bin/cat",
			"vmobject": "/bin/cat",
			"pagemap": "P",
	}, {
		...
	} ]
#endif
	char *pos_c;
	int i, l, perm;
	char line[1024];
	char region[100], region2[100], perms[5];

	int sz;
	sd->self_sections_count = 0;
	char *vmdata = r_file_slurp (path, &sz);
	char *s = r_str_ndup (vmdata, sz);
	char *p = s;
	while (true) {
		char *next = strstr (p, "},");
		if (!next) {
			next = strchr (p, '}');
			if (!next) {
				break;
			}
		}
		char *addr = strstr (p, "\"address\":");
		char *size = strstr (p, "\"size\":");
		char *name = strstr (p, "\"name\":");
		if (addr && size && name) {
			int r = strstr ("\"readable\": true,")? R_PERM_R: 0;
			int w = strstr ("\"writable\": true,")? R_PERM_W: 0;
			int x = strstr ("\"executable\": true,")? R_PERM_X: 0;
			ut64 a = getnum (addr);
			ut64 s = getnum (size);
			char *n = getstr (name);
			sd->self_sections[sd->self_sections_count].from = a;
			sd->self_sections[sd->self_sections_count].to = a + s;
			sd->self_sections[sd->self_sections_count].name = n;
			sd->self_sections[sd->self_sections_count].perm = r|w|x;
			sd->self_sections_count++;
		}
		p = next + 1;
	}
	free (vmdata);
	free (s);

	return true;
}
#endif

static int update_self_regions(RIO *io, int pid, SelfData *sd) {
	sd->self_sections_count = 0;
#if __APPLE__
	mach_port_t task;
	kern_return_t rc;
	rc = task_for_pid (mach_task_self (), pid, &task);
	if (rc) {
		R_LOG_ERROR ("task_for_pid failed");
		return false;
	}
	macosx_debug_regions (sd, io, task, (size_t)1, 1000);
	return true;
#elif __serenity__
	return serenity_debug_regions (sd, io, pid);
#elif __linux__
	char *pos_c;
	int i, l, perm;
	char path[1024], line[1024];
	char region[100], region2[100], perms[5];
	snprintf (path, sizeof (path) - 1, "/proc/%d/maps", pid);
	FILE *fd = r_sandbox_fopen (path, "r");
	if (!fd) {
		return false;
	}

	while (!feof (fd)) {
		line[0]='\0';
		if (!fgets (line, sizeof (line), fd)) {
			break;
		}
		if (line[0] == '\0') {
			break;
		}
		path[0]='\0';
		strcpy (region, "0x");
		if (r_str_scanf (line, "%.s %.s %*s %*s %*s %.[^\n]", sizeof (region) - 2, region + 2, sizeof (perms), perms, sizeof (path), path) < 6) {
			return false;
		}
		pos_c = strchr (region + 2, '-');
		if (pos_c) {
			*pos_c++ = 0;
			memcpy (region2, "0x", 2);
			l = strlen (pos_c);
			memcpy (region2 + 2, pos_c, l);
			region2[2 + l] = 0;
		} else {
			region2[0] = 0;
		}
		perm = 0;
		for (i = 0; i < 4 && perms[i]; i++) {
			switch (perms[i]) {
			case 'r': perm |= R_PERM_R; break;
			case 'w': perm |= R_PERM_W; break;
			case 'x': perm |= R_PERM_X; break;
			}
		}
		sd->self_sections[sd->self_sections_count].from = r_num_get (NULL, region);
		sd->self_sections[sd->self_sections_count].to = r_num_get (NULL, region2);
		sd->self_sections[sd->self_sections_count].name = strdup (path);
		sd->self_sections[sd->self_sections_count].perm = perm;
		sd->self_sections_count++;
		r_num_get (NULL, region2);
	}
	fclose (fd);

	return true;
#elif R2__BSD__
	return bsd_proc_vmmaps(sd, io, pid);
#elif __HAIKU__
	image_info ii;
	int32 cookie = 0;

	while (get_next_image_info (0, &cookie, &ii) == B_OK) {
		sd->self_sections[sd->self_sections_count].from = (ut64)(size_t)ii.text;
		sd->self_sections[sd->self_sections_count].to = (ut64)(size_t)((char*)ii.text + ii.text_size);
		sd->self_sections[sd->self_sections_count].name = strdup (ii.name);
		sd->self_sections[sd->self_sections_count].perm = 0;
		sd->self_sections_count++;
	}
	return true;
#elif __sun && defined _LP64
	char path[PATH_MAX];
	int err;
	pid_t self = r_sys_getpid ();
	struct ps_prochandle *Pself = Pgrab(self, PGRAB_RDONLY, &err);

	if (!Pself) {
		return false;
	}

	snprintf (path, sizeof (path), "/proc/%d/map",  self);
	size_t hint = (1 << 20);
	int fd = open (path, O_RDONLY);

	if (fd == -1) {
		return false;
	}

	ssize_t rd;
	prmap_t *c, *map = malloc (hint);

	if (!map) {
		return false;
	}

	while (hint > 0 && (rd = pread (fd, map, hint, 0)) == hint) {
		hint <<= 1;
		prmap_t *tmp = realloc (map, hint);
		if (!tmp) {
			free (map);
			return false;
		}

		map = tmp;
	}

	for (c = map; rd > 0; c ++, rd -= sizeof (prmap_t)) {
		char name[PATH_MAX];
		Pobjname (Pself, c->pr_vaddr, name, sizeof (name));

		if (name[0] == '\0') {
			// If no name, it is an anonymous map
			strcpy (name, "[anon]");
		}

		int perm = 0;

		if ((c->pr_mflags & MA_READ)) {
			perm |= R_PERM_R;
		}
		if ((c->pr_mflags & MA_WRITE)) {
			perm |= R_PERM_W;
		}
		if ((c->pr_mflags & MA_EXEC)) {
			perm |= R_PERM_X;
		}

		sd->self_sections[sd->self_sections_count].from = (ut64)c->pr_vaddr;
		sd->self_sections[sd->self_sections_count].to = (ut64)(c->pr_vaddr + c->pr_size);
		sd->self_sections[sd->self_sections_count].name = strdup (name);
		sd->self_sections[sd->self_sections_count].perm = perm;
		sd->self_sections_count++;
	}

	free (map);
	close (fd);
	return true;
#else
#ifdef _MSC_VER
	int perm;
	const size_t name_size = 1024;
	PVOID to = NULL;
	MEMORY_BASIC_INFORMATION mbi;
	HANDLE h = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid);
	LPTSTR name = calloc (name_size, sizeof (TCHAR));
	if (!name) {
		R_LOG_ERROR ("io_self/update_self_regions: Failed to allocate memory");
		CloseHandle (h);
		return false;
	}
	while (VirtualQuery (to, &mbi, sizeof (mbi))) {
		to = (PBYTE) mbi.BaseAddress + mbi.RegionSize;
		perm = 0;
		perm |= mbi.Protect & PAGE_READONLY ? R_PERM_R : 0;
		perm |= mbi.Protect & PAGE_READWRITE ? R_PERM_RW : 0;
		perm |= mbi.Protect & PAGE_EXECUTE ? R_PERM_X : 0;
		perm |= mbi.Protect & PAGE_EXECUTE_READ ? R_PERM_RX : 0;
		perm |= mbi.Protect & PAGE_EXECUTE_READWRITE ? R_PERM_RWX : 0;
		perm = mbi.Protect & PAGE_NOACCESS ? 0 : perm;
		if (perm && !r_w32_GetMappedFileName (h, (LPVOID) mbi.BaseAddress, name, name_size)) {
			name[0] = '\0';
		}
		sd->self_sections[sd->self_sections_count].from = (ut64) mbi.BaseAddress;
		sd->self_sections[sd->self_sections_count].to = (ut64) to;
		sd->self_sections[sd->self_sections_count].name = r_sys_conv_win_to_utf8 (name);
		sd->self_sections[sd->self_sections_count].perm = perm;
		sd->self_sections_count++;
		name[0] = '\0';
	}
	free (name);
	CloseHandle (h);
	return true;
#else
	#warning not yet implemented for this platform
#endif
	return false;
#endif
}

static bool __plugin_open(RIO *io, const char *file, bool many) {
	return r_str_startswith (file, "self://");
}

static void self_data_free(SelfData *sd) {
	if (sd) {
		int i;
		for (i = 0; i < sd->self_sections_count; i++) {
			free (sd->self_sections[i].name);
		}
		free (sd);
	}
}

static RIODesc *__open(RIO *io, const char *file, int rw, int mode) {
	if (r_sandbox_enable (0)) {
		return NULL;
	}
	int pid = r_sys_getpid ();
	SelfData *sd = R_NEW0 (SelfData);
	if (update_self_regions (io, pid, sd)) {
		return r_io_desc_new (io, &r_io_plugin_self, file, rw, mode, sd);
	}
	self_data_free (sd);
	return NULL;
}

static bool __close(RIODesc *desc) {
	self_data_free (desc->data);
	desc->data = NULL;
	return true;
}

static int __read(RIO *io, RIODesc *desc, ut8 *buf, int len) {
	int left, perm;
	if (self_in_section (desc, io, io->off, &left, &perm)) {
		if (perm & R_PERM_R) {
			int newlen = R_MIN (len, left);
			ut8 *ptr = (ut8*)(size_t)io->off;
			memcpy (buf, ptr, newlen);
			return newlen;
		}
	}
	memset (buf, io->Oxff, len);
	return 0;
}

static int __write(RIO *io, RIODesc *desc, const ut8 *buf, int len) {
	if (desc->perm & R_PERM_W) {
		int left, perm;
		if (self_in_section (desc, io, io->off, &left, &perm)) {
			int newlen = R_MIN (len, left);
			ut8 *ptr = (ut8*)(size_t)io->off;
			if (newlen > 0) {
				memcpy (ptr, buf, newlen);
			}
			return newlen;
		}
	}
	return -1;
}

static ut64 __lseek(RIO *io, RIODesc *fd, ut64 offset, int whence) {
	switch (whence) {
	case R_IO_SEEK_SET:
		io->off = offset;
		return offset;
	case R_IO_SEEK_CUR:
		io->off += offset;
		return io->off;
	case R_IO_SEEK_END:
		if (sizeof (void*) == 8) {
			io->off = UT64_MAX;
		} else {
			io->off = UT32_MAX;
		}
		// UT64_MAX means error
		return UT64_MAX - 1;
	}
	return offset;
}

static void got_alarm(int sig) {
#if !defined(R2__WINDOWS__)
	// !!! may die if not running from r2preload !!! //
	kill (r_sys_getpid (), SIGUSR1);
#endif
}

static char *__system(RIO *io, RIODesc *fd, const char *cmd) {
	SelfData *sd = fd->data;
	if (!strcmp (cmd, "pid")) {
		return r_str_newf ("%d", fd->fd);
	}
	if (r_str_startswith (cmd, "pid")) {
		/* do nothing here */
#if !defined(R2__WINDOWS__)
	} else if (r_str_startswith (cmd, "kill")) {
		if (r_sandbox_enable (false)) {
			R_LOG_ERROR ("This is unsafe, so disabled by the sandbox");
			return NULL;
		}
		/* do nothing here */
		kill (r_sys_getpid (), SIGKILL);
#endif
	} else if (r_str_startswith (cmd, "call ")) {
		size_t cbptr = 0;
		if (r_sandbox_enable (false)) {
			R_LOG_ERROR ("This is unsafe, so disabled by the sandbox");
			return NULL;
		}
		ut64 result = 0;
		char *argv = strdup (cmd + 5);
		int argc = r_str_word_set0 (argv);
		if (argc == 0) {
			R_LOG_ERROR ("Usage: :call [fcnptr] [args..]");
			free (argv);
			return NULL;
		}
		const char *sym = r_str_word_get0 (argv, 0);
		if (sym) {
			const char *symbol = cmd + 6;
			void *lib = r_lib_dl_open (NULL, false);
			void *ptr = r_lib_dl_sym (lib, symbol);
			if (ptr) {
				cbptr = (ut64)(size_t)ptr;
			} else {
				cbptr = r_num_math (NULL, symbol);
			}
			r_lib_dl_close (lib);
		}
		if (argc == 1) {
			size_t (*cb)() = (size_t(*)())cbptr;
			if (cb) {
				result = cb ();
			} else {
				R_LOG_ERROR ("callback not defined");
			}
		} else if (argc == 2) {
			size_t (*cb)(size_t a0) = (size_t(*)(size_t))cbptr;
			if (cb) {
				ut64 a0 = r_num_math (NULL, r_str_word_get0 (argv, 1));
				result = cb (a0);
			} else {
				R_LOG_ERROR ("callback not defined");
			}
		} else if (argc == 3) {
			size_t (*cb)(size_t a0, size_t a1) = (size_t(*)(size_t,size_t))cbptr;
			ut64 a0 = r_num_math (NULL, r_str_word_get0 (argv, 1));
			ut64 a1 = r_num_math (NULL, r_str_word_get0 (argv, 2));
			if (cb) {
				result = cb (a0, a1);
			} else {
				R_LOG_ERROR ("callback not defined");
			}
		} else if (argc == 4) {
			size_t (*cb)(size_t a0, size_t a1, size_t a2) = \
				(size_t(*)(size_t,size_t,size_t))cbptr;
			ut64 a0 = r_num_math (NULL, r_str_word_get0 (argv, 1));
			ut64 a1 = r_num_math (NULL, r_str_word_get0 (argv, 2));
			ut64 a2 = r_num_math (NULL, r_str_word_get0 (argv, 3));
			if (cb) {
				result = cb (a0, a1, a2);
			} else {
				R_LOG_ERROR ("callback not defined");
			}
		} else if (argc == 5) {
			size_t (*cb)(size_t a0, size_t a1, size_t a2, size_t a3) = \
				(size_t(*)(size_t,size_t,size_t,size_t))cbptr;
			ut64 a0 = r_num_math (NULL, r_str_word_get0 (argv, 1));
			ut64 a1 = r_num_math (NULL, r_str_word_get0 (argv, 2));
			ut64 a2 = r_num_math (NULL, r_str_word_get0 (argv, 3));
			ut64 a3 = r_num_math (NULL, r_str_word_get0 (argv, 4));
			if (cb) {
				result = cb (a0, a1, a2, a3);
			} else {
				R_LOG_ERROR ("callback not defined");
			}
		} else if (argc == 6) {
			size_t (*cb)(size_t a0, size_t a1, size_t a2, size_t a3, size_t a4) = \
				(size_t(*)(size_t,size_t,size_t,size_t,size_t))cbptr;
			ut64 a0 = r_num_math (NULL, r_str_word_get0 (argv, 1));
			ut64 a1 = r_num_math (NULL, r_str_word_get0 (argv, 2));
			ut64 a2 = r_num_math (NULL, r_str_word_get0 (argv, 3));
			ut64 a3 = r_num_math (NULL, r_str_word_get0 (argv, 4));
			ut64 a4 = r_num_math (NULL, r_str_word_get0 (argv, 5));
			if (cb) {
				result = cb (a0, a1, a2, a3, a4);
			} else {
				R_LOG_ERROR ("No callback defined");
			}
		} else {
			R_LOG_ERROR ("Unsupported number of arguments in call");
		}
		R_LOG_INFO ("RES %"PFMT64d, result);
		free (argv);
#if !defined(R2__WINDOWS__) && !defined (__serenity__)
	} else if (r_str_startswith (cmd, "alarm ")) {
		struct itimerval tmout;
		int secs = atoi (cmd + 6);
		R_RETURN_VAL_IF_FAIL (secs >= 0, NULL);

		tmout.it_value.tv_sec = secs;
		tmout.it_value.tv_usec = 0;
		r_sys_signal (SIGALRM, got_alarm);
		setitimer (ITIMER_REAL, &tmout, NULL);
#else
#ifdef _MSC_VER
#pragma message ("self:// alarm is not implemented for this platform yet")
#else
#warning "self:// alarm is not implemented for this platform yet"
#endif
#endif
	} else if (r_str_startswith (cmd, "dlsym ")) {
		const char *symbol = cmd + 6;
		void *lib = r_lib_dl_open (NULL, false);
		void *ptr = r_lib_dl_sym (lib, symbol);
		char *s = r_str_newf ("(%s) 0x%08"PFMT64x"\n", symbol, (ut64)(size_t)ptr);
		r_lib_dl_close (lib);
		return s;
	} else if (!strcmp (cmd, "mameio")) {
		void *lib = r_lib_dl_open (NULL, false);
		void *ptr = r_lib_dl_sym (lib, "_ZN12device_debug2goEj");
	//	void *readmem = dlsym (lib, "_ZN23device_memory_interface11memory_readE16address_spacenumjiRy");
		if (ptr) {
			R_LOG_TODO ("MAME IO is not yet implemented");
			sd->mameio = true;
		} else {
			R_LOG_ERROR ("This process is not a MAME!");
		}
		r_lib_dl_close (lib);
	} else if (!strcmp (cmd, "maps") || r_str_startswith (cmd, "dm")) {
		int i;
		RStrBuf *sb = r_strbuf_new ("");
		for (i = 0; i < sd->self_sections_count; i++) {
			r_strbuf_appendf (sb, "0x%08"PFMT64x" - 0x%08"PFMT64x" %s %s\n",
				sd->self_sections[i].from, sd->self_sections[i].to,
				r_str_rwx_i (sd->self_sections[i].perm),
				sd->self_sections[i].name);
		}
		return r_strbuf_drain (sb);
	} else if (*cmd == '?') {
		return strdup ("Usage: :[cmd] [args]\n"
		" :pid               show getpid()\n"
		" :maps              show map regions (same as :dm)\n"
		" :kill              commit suicide\n"
#if !defined(R2__WINDOWS__)
		" :alarm [secs]      setup alarm signal to raise r2 prompt\n"
#endif
		" :dlsym [sym]       dlopen\n"
		" :call [sym] [...]  nativelly call a function\n"
		" :mameio            enter mame IO mode\n");
	}
	return NULL;
}

RIOPlugin r_io_plugin_self = {
	.meta = {
		.name = "self",
		.desc = "Read memory from self",
		.author = "pancake",
		.license = "LGPL-3.0-only",
	},
	.uris = "self://",
	.open = __open,
	.close = __close,
	.read = __read,
	.check = __plugin_open,
	.seek = __lseek,
	.system = __system,
	.write = __write,
};

#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
	.type = R_LIB_TYPE_IO,
	.data = &r_io_plugin_mach,
	.version = R2_VERSION
};
#endif

#if __APPLE__
// mach/mach_vm.h not available for iOS
kern_return_t mach_vm_region_recurse (
	vm_map_t target_task,
	mach_vm_address_t *address,
	mach_vm_size_t *size,
	natural_t *depth,
	vm_region_recurse_info_t info,
	mach_msg_type_number_t *infoCnt
);

static const char *share_mode[] = {
	"null",
	"cow",
	"private",
	"empty",
	"shared",
	"true shared",
	"prv aliased",
	"shm aliased",
	"large",
};

// TODO: unify that implementation in a single reusable place
static void macosx_debug_regions(SelfData *sd, RIO *io, task_t task, mach_vm_address_t address, int max) {
	kern_return_t kret;
	struct vm_region_submap_info_64 info;
	mach_vm_size_t size;

	natural_t nsubregions = 1;
	mach_msg_type_number_t count;

	int num_printed = 0;

	for (;;) {
		count = VM_REGION_SUBMAP_INFO_COUNT_64;
		kret = mach_vm_region_recurse (task, &address, &size, &nsubregions,
				(vm_region_recurse_info_t) &info, &count);
		if (kret != KERN_SUCCESS) {
			if (!num_printed) {
				R_LOG_ERROR ("mach_vm_region_recurse: %d - %s", kret, mach_error_string (kret));
			}
			break;
		}

		if (!info.is_submap) {
			int print_size;
			char *print_size_unit;
			int perm = 0;

			// eprintf (num_printed? "   ... ": "Region ");
			//findListOfBinaries(task, prev_address, prev_size);
			/* Quick hack to show size of segment, which GDB does not */
			print_size = size;
			if (print_size > 1024) { print_size /= 1024; print_size_unit = "K"; }
			if (print_size > 1024) { print_size /= 1024; print_size_unit = "M"; }
			if (print_size > 1024) { print_size /= 1024; print_size_unit = "G"; }
			/* End Quick hack */
			R_LOG_DEBUG ("%p - %p [%d%s](%x/%x; %d, %s, %u p. res, %u p. swp, %u p. drt, %u ref)",
					(void*)(size_t)(address),
					(void*)(size_t)(address + size),
					print_size,
					print_size_unit,
					info.protection,
					info.max_protection,
					info.inheritance,
					share_mode[info.share_mode],
					info.pages_resident,
					info.pages_swapped_out,
					info.pages_dirtied,
					info.ref_count);

			if (info.protection & VM_PROT_READ) {
				perm |= R_PERM_R;
			}
			if (info.protection & VM_PROT_WRITE) {
				perm |= R_PERM_W;
			}
			if (info.protection & VM_PROT_EXECUTE) {
				perm |= R_PERM_X;
			}

			sd->self_sections[sd->self_sections_count].from = address;
			sd->self_sections[sd->self_sections_count].to = address+size;
			sd->self_sections[sd->self_sections_count].perm = perm;
			sd->self_sections_count++;
#if 0
			if (nsubregions > 1) {
				eprintf (" (%d sub-regions)", nsubregions);
			}
			eprintf ("\n");
#endif
			num_printed++;
			address += size;
			size = 0;
		} else {
			nsubregions++;
		}

		if ((max > 0) && (num_printed >= max)) {
			// eprintf ("Max %d num_printed %d\n", max, num_printed);
			break;
		}
	}
}
#elif R2__BSD__
static bool bsd_proc_vmmaps(SelfData *sd, RIO *io, int pid) {
#if __FreeBSD__
	size_t size;
	bool ret = false;
	int mib[4] = {
		CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, pid
	};
	int s = sysctl (mib, 4, NULL, &size, NULL, 0);
	if (s == -1) {
		R_LOG_ERROR ("sysctl failed: %s", strerror (errno));
		return false;
	}

	size = size * 4 / 3;
	ut8 *p = malloc (size);
	if (p) {
		s = sysctl (mib, 4, p, &size, NULL, 0);
		if (s == -1) {
			R_LOG_ERROR ("sysctl failed: %s", strerror (errno));
			goto exit;
		}
		ut8 *p_start = p;
		ut8 *p_end = p + size;

		while (p_start < p_end) {
			struct kinfo_vmentry *entry = (struct kinfo_vmentry *)p_start;
			size_t sz = entry->kve_structsize;
			int perm = 0;
			if (sz == 0) {
				break;
			}

			if (entry->kve_protection & KVME_PROT_READ) {
				perm |= R_PERM_R;
			}
			if (entry->kve_protection & KVME_PROT_WRITE) {
				perm |= R_PERM_W;
			}
			if (entry->kve_protection & KVME_PROT_EXEC) {
				perm |= R_PERM_X;
			}
#if 0
			if (entry->kve_path[0] != '\0') {
				R_LOG_INFO (" %p - %p %s (%s)",
						(void *)entry->kve_start,
						(void *)entry->kve_end,
						r_str_rwx_i (perm),
						entry->kve_path);
			}
#endif
			sd->self_sections[sd->self_sections_count].from = entry->kve_start;
			sd->self_sections[sd->self_sections_count].to = entry->kve_end;
			sd->self_sections[sd->self_sections_count].name = strdup (entry->kve_path);
			sd->self_sections[sd->self_sections_count].perm = perm;
			sd->self_sections_count++;
			p_start += sz;
		}

		ret = true;
	} else {
		R_LOG_ERROR ("buffer allocation failed");
	}

exit:
	free (p);
	return ret;
#elif __OpenBSD__
	size_t size = sizeof (struct kinfo_vmentry);
	struct kinfo_vmentry entry = { .kve_start = 0 };
	ut64 endq = 0;
	int mib[3] = {
		CTL_KERN, KERN_PROC_VMMAP, pid
	};
	int s = sysctl (mib, 3, &entry, &size, NULL, 0);
	if (s == -1) {
		R_LOG_ERROR ("sysctl failed: %s", strerror (errno));
		return false;
	}
	endq = size;

	while (sysctl (mib, 3, &entry, &size, NULL, 0) != -1) {
		int perm = 0;
		if (entry.kve_end == endq) {
			break;
		}

		if (entry.kve_protection & KVE_PROT_READ) {
			perm |= R_PERM_R;
		}
		if (entry.kve_protection & KVE_PROT_WRITE) {
			perm |= R_PERM_W;
		}
		if (entry.kve_protection & KVE_PROT_EXEC) {
			perm |= R_PERM_X;
		}
		R_LOG_DEBUG ("%p - %p %s [off. %" PFMT64u "]",
				(void *)entry.kve_start,
				(void *)entry.kve_end,
				r_str_rwx_i (perm),
				(ut64)entry.kve_offset);

		sd->self_sections[sd->self_sections_count].from = entry.kve_start;
		sd->self_sections[sd->self_sections_count].to = entry.kve_end;
		sd->self_sections[sd->self_sections_count].perm = perm;
		sd->self_sections_count++;
		entry.kve_start = entry.kve_start + 1;
	}

	return true;
#elif __NetBSD__
	size_t size;
	bool ret = false;
	int mib[5] = {
		CTL_VM, VM_PROC, VM_PROC_MAP, pid, sizeof (struct kinfo_vmentry)
	};
	int s = sysctl (mib, 5, NULL, &size, NULL, 0);
	if (s == -1) {
		R_LOG_ERROR ("sysctl failed: %s", strerror (errno));
		return false;
	}

	size = size * 4 / 3;
	ut8 *p = malloc (size);
	if (p) {
		s = sysctl (mib, 5, p, &size, NULL, 0);
		if (s == -1) {
			R_LOG_ERROR ("sysctl failed: %s", strerror (errno));
			goto exit;
		}
		ut8 *p_start = p;
		ut8 *p_end = p + size;

		while (p_start < p_end) {
			struct kinfo_vmentry *entry = (struct kinfo_vmentry *)p_start;
			size_t sz = sizeof (*entry);
			int perm = 0;
			if (sz == 0) {
				break;
			}

			if (entry->kve_protection & KVME_PROT_READ) {
				perm |= R_PERM_R;
			}
			if (entry->kve_protection & KVME_PROT_WRITE) {
				perm |= R_PERM_W;
			}
			if (entry->kve_protection & KVME_PROT_EXEC) {
				perm |= R_PERM_X;
			}

			if (entry->kve_path[0] != '\0') {
				R_LOG_DEBUG ("%p - %p %s (%s)",
					(void *)entry->kve_start,
					(void *)entry->kve_end,
				 	r_str_rwx_i (perm),
					entry->kve_path);
			}

			sd->self_sections[sd->self_sections_count].from = entry->kve_start;
			sd->self_sections[sd->self_sections_count].to = entry->kve_end;
			sd->self_sections[sd->self_sections_count].name = strdup (entry->kve_path);
			sd->self_sections[sd->self_sections_count].perm = perm;
			sd->self_sections_count++;
			p_start += sz;
		}

		ret = true;
	} else {
		R_LOG_ERROR ("buffer allocation failed");
	}

exit:
	free (p);
	return ret;
#elif __DragonFly__
	struct kinfo_proc *proc;
	struct vmspace vs;
	struct vm_map *map;
	struct vm_map_entry entry, *ep;
	struct proc p;
	int nm;
	char e[_POSIX2_LINE_MAX];

	kvm_t *k = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, e);
	if (!k) {
		R_LOG_ERROR ("kvm_openfiles: %s", e);
		return false;
	}

	proc = kvm_getprocs (k, KERN_PROC_PID, pid, &nm);

	kvm_read (k, (uintptr_t)proc->kp_paddr, (ut8 *)&p, sizeof (p));
	kvm_read (k, (uintptr_t)p.p_vmspace, (ut8 *)&vs, sizeof (vs));

	map = &vs.vm_map;
	ep = kvm_vm_map_entry_first (k, map, &entry);

	while (ep) {
		int perm = 0;
		if (entry.protection & VM_PROT_READ) {
			perm |= R_PERM_R;
		}
		if (entry.protection & VM_PROT_WRITE) {
			perm |= R_PERM_W;
		}
		if (entry.protection & VM_PROT_EXECUTE) {
			perm |= R_PERM_X;
		}
		R_LOG_DEBUG ("%p - %p %s [off. %" PFMT64u "]",
				(void *)entry.ba.start,
				(void *)entry.ba.end,
				r_str_rwx_i (perm),
				(ut64)entry.ba.offset);

		sd->self_sections[sd->self_sections_count].from = entry.ba.start;
		sd->self_sections[sd->self_sections_count].to = entry.ba.end;
		sd->self_sections[sd->self_sections_count].perm = perm;
		sd->self_sections_count++;
		ep = kvm_vm_map_entry_next (k, ep, &entry);
	}

	kvm_close (k);
	return true;
#endif
}
#endif

#else // DEBUGGER
RIOPlugin r_io_plugin_self = {
	.meta = {
		.name = "self",
		.desc = "read memory from myself using 'self://' (UNSUPPORTED)",
	},
};

#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
	.type = R_LIB_TYPE_IO,
	.data = &r_io_plugin_mach,
	.version = R2_VERSION
};
#endif
#endif
