radare2/libr/main/r2pm.c
2022-04-13 01:25:05 +02:00

581 lines
13 KiB
C

/* radare - LGPL - Copyright 2021 - pancake */
#include <r_main.h>
static int r_main_r2pm_sh(int argc, const char **argv) {
int i;
RStrBuf *sb = r_strbuf_new ("r2pm.sh");
for (i = 1; i < argc; i++) {
r_strbuf_appendf (sb, " %s", argv[i]);
}
char *cmd = r_strbuf_drain (sb);
int res = r_sandbox_system (cmd, 1);
free (cmd);
return res;
}
static const char *helpmsg = \
"Usage: r2pm [-flags] [pkgs...]\n"\
"Commands:\n"\
" -c ([git/dir]) clear source cache (GITDIR)\n"\
" -ci <pkgname> clean + install\n"\
" -cp clean the user's home plugin directory\n"\
" -d,doc [pkgname] show documentation for given package\n"\
" -f force operation (install, uninstall, ..)\n"\
" -gi <pkg> global install (system-wide)\n"\
" -h show this message\n"\
" -H variable show value of given variable\n"\
" -I information about repository and installed packages\n"\
" -i <pkgname> install or update package in your home (pkgname=all)\n"\
" -l list installed pkgs\n"\
" -r [cmd ...args] run shell command with R2PM_BINDIR in PATH\n"\
" -s [<keyword>] search in database\n"\
" -uci <pkgname> uninstall + clean + install\n"\
" -ui <pkgname> uninstall + install\n"\
" -u <pkgname> r2pm -u baleful (See -f to force uninstall)\n"\
" -U r2pm -U (upgrade all outdated packages)\n"\
" -v show version\n"\
"Environment:\n"\
" SUDO=sudo use this tool as sudo\n"\
" R2PM_PLUGDIR=${R2PM_PLUGDIR}\n"\
" R2PM_BINDIR=${R2PM_BINDIR}\n"\
" R2PM_OFFLINE=0 disabled by default, avoid init/update calls if set to !=0\n"\
" R2PM_NATIVE=0 set to 1 to use the native C codepath for r2pm\n"\
" R2PM_DBDIR=${R2PM_DBDIR}\n"\
" R2PM_GITDIR=${R2PM_GITDIR}\n"\
" R2PM_GITSKIP=${R2PM_GITSKIP}\n";
typedef struct r_r2pm_t {
bool help;
bool clean;
bool force;
bool global;
bool list;
bool init;
bool run;
bool doc;
bool search;
bool version;
bool info;
bool install;
bool uninstall;
} R2Pm;
static int git_pull(const char *dir) {
char *s = r_str_newf ("cd %s\ngit pull", dir);
int rc = r_sandbox_system (s, 1);
free (s);
return rc;
}
static int git_clone(const char *dir, const char *url) {
char *cmd = r_str_newf ("git clone --depth=10 --recursive %s %s", url, dir);
int rc = r_sandbox_system (cmd, 1);
free (cmd);
return rc;
}
static bool r2pm_actionword(R2Pm *r2pm, const char *action) {
if (!strcmp (action, "init") || !strcmp (action, "update")) {
r2pm->init = true;
} else if (!strcmp (action, "help")) {
r2pm->help = true;
} else if (!strcmp (action, "info")) {
r2pm->info = true;
} else if (!strcmp (action, "help")) {
r2pm->help = true;
} else {
return false;
}
return true;
}
static char *r2pm_bindir(void) {
return r_str_home (".local/share/radare2/prefix/bin");
}
static char *r2pm_gitdir(void) {
return r_str_home (".local/share/radare2/r2pm/git");
}
static char *r2pm_dbdir(void) {
return r_str_home (".local/share/radare2/r2pm/db");
}
static char *r2pm_pkgdir(void) {
return r_str_home (".local/share/radare2/r2pm/pkg");
}
typedef enum {
TT_OPT_QUOTED_LINE,
TT_CODEBLOCK,
TT_ENDQUOTE,
} R2pmTokenType;
static char *r2pm_get(const char *file, const char *token, R2pmTokenType type) {
char *res = NULL;
char *dbdir = r2pm_dbdir ();
char *path = r_str_newf ("%s/%s", dbdir, file);
free (dbdir);
char *data = r_file_slurp (path, NULL);
if (!data) {
free (path);
return NULL;
}
const char *needle = token; // "\nR2PM_DESC ";
char *descptr = strstr (data, needle);
if (descptr) {
char *nl = NULL;
switch (type) {
case TT_OPT_QUOTED_LINE:
descptr += strlen (needle);
nl = strchr (descptr, '\n');
if (nl) {
*nl = 0;
nl--;
if (*nl == '"') {
*nl = 0;
}
}
if (*descptr == '"') {
descptr++;
}
res = strdup (descptr);
break;
case TT_ENDQUOTE:
nl = strchr (descptr + strlen (token), '\n');
if (nl) {
char *begin = nl + 1;
char *eoc = strstr (begin, "\n\"\n");
if (eoc) {
return r_str_ndup (begin, eoc-begin);
} else {
eprintf ("Cannot find end of thing\n");
return NULL;
}
}
break;
case TT_CODEBLOCK:
nl = strchr (descptr + strlen (token), '\n');
if (nl) {
char *begin = nl + 1;
char *eoc = strstr (begin, "\n}\n");
if (eoc) {
return r_str_ndup (begin, eoc-begin);
} else {
eprintf ("Cannot find end of thing\n");
return NULL;
}
}
break;
}
}
free (data);
return res;
}
static char *r2pm_desc(const char *file) {
return r2pm_get (file, "\nR2PM_DESC ", TT_OPT_QUOTED_LINE);
}
static int r2pm_list(void) {
char *path = r2pm_pkgdir ();
RList *files = r_sys_dir (path);
free (path);
if (!files) {
return 1;
}
RListIter *iter;
const char *file;
r_list_foreach (files, iter, file) {
if (*file != '.') {
printf ("%s\n", file);
}
}
r_list_free (files);
return 0;
}
static int r2pm_update(void) {
char *gpath = r2pm_gitdir ();
char *pmpath = r_str_newf ("%s/%s", gpath, "radare2-pm");
r_sys_mkdirp (gpath);
if (r_file_exists (pmpath)) {
if (git_pull (pmpath) != 0) {
eprintf ("Error\n");
free (pmpath);
free (gpath);
return 1;
}
} else {
const char *giturl = "https://github.com/radareorg/radare2-pm";
git_clone (pmpath, giturl);
}
// copy files from git into db
char *dbpath = r2pm_dbdir ();
r_sys_mkdirp (dbpath);
RList *files = r_sys_dir (pmpath);
if (files) {
RListIter *iter;
const char *file;
r_list_foreach (files, iter, file) {
if (*file != '.') {
char *src = r_str_newf ("%s/%s", pmpath, file);
char *dst = r_str_newf ("%s/%s", dbpath, file);
if (!r_file_copy (src, dst)) {
eprintf ("Warning: Cannot copy '%s' into '%s'.\n", file, dbpath);
}
free (src);
free (dst);
}
}
r_list_free (files);
}
free (pmpath);
free (gpath);
free (dbpath);
return 0;
}
static void r2pm_setenv(void) {
r_sys_setenv ("MAKE", "make");
char *r2_plugdir = r_str_home (R2_HOME_PLUGINS);
r_sys_setenv ("R2PM_PLUGDIR", r2_plugdir);
free (r2_plugdir);
char *r2_prefix = r_str_home (R2_HOME_DATADIR "/prefix");
r_sys_setenv ("R2PM_PREFIX", r2_prefix);
free (r2_prefix);
}
static int r2pm_install_pkg(const char *pkg) {
printf ("Installing %s ...\n", pkg);
char *script = r2pm_get (pkg, "\nR2PM_INSTALL() {", TT_CODEBLOCK);
if (!script) {
eprintf ("Cannot parse package\n");
return 1;
}
r2pm_setenv ();
char *srcdir = r2pm_gitdir ();
char *s = r_str_newf ("cd '%s/%s'\nexport MAKE=make\nR2PM_FAIL(){\n echo $@\n}\n%s", srcdir, pkg, script);
int res = r_sandbox_system (s, 1);
free (s);
free (srcdir);
return res;
}
static int r2pm_doc_pkg(const char *pkg) {
char *docstr = r2pm_get (pkg, "\nR2PM_DOC=\"", TT_ENDQUOTE);
if (docstr) {
printf ("%s\n", docstr);
free (docstr);
return 0;
}
eprintf ("Cannot find documentation for '%s'\n", pkg);
return 1;
}
static int r2pm_clean_pkg(const char *pkg) {
printf ("Cleaning %s ...\n", pkg);
// TODO. make clean/mrproper instead maybe better?
char *srcdir = r2pm_gitdir ();
if (R_STR_ISNOTEMPTY (srcdir)) {
char *d = r_file_new (srcdir, pkg, NULL);
if (d && r_file_exists (d)) {
eprintf ("rm -rf '%s'\n", d);
r_file_rm_rf (d);
}
free (d);
}
free (srcdir);
return 0;
}
static int r2pm_uninstall_pkg(const char *pkg) {
printf ("Uninstalling %s ...\n", pkg);
char *script = r2pm_get (pkg, "\nR2PM_UNINSTALL() {", TT_CODEBLOCK);
if (!script) {
eprintf ("Cannot parse package\n");
return 1;
}
r2pm_setenv ();
char *srcdir = r2pm_gitdir ();
char *s = r_str_newf ("cd %s/%s\nexport MAKE=make\nR2PM_FAIL(){\n echo $@\n}\n%s",
srcdir, pkg, script);
int res = r_sandbox_system (s, 1);
free (s);
free (srcdir);
return res;
}
static int r2pm_clone(const char *pkg) {
char *pkgdir = r2pm_gitdir ();
char *srcdir = r_file_new (pkgdir, pkg, NULL);
free (pkgdir);
if (r_file_is_directory (srcdir)) {
git_pull (srcdir);
} else {
char *url = r2pm_get (pkg, "\nR2PM_GIT ", TT_OPT_QUOTED_LINE);
if (url) {
git_clone (srcdir, url);
free (url);
} else {
char *url = r2pm_get (pkg, "\nR2PM_TGZ", TT_OPT_QUOTED_LINE);
eprintf ("TODO: wget tarball from '%s'\n", url);
free (srcdir);
free (url);
return 1;
}
}
free (srcdir);
return 0;
}
static int r2pm_install(RList *targets, bool uninstall, bool clean) {
RListIter *iter;
const char *t;
int rc = 0;
r_list_foreach (targets, iter, t) {
if (uninstall) {
r2pm_uninstall_pkg (t);
}
if (clean) {
r2pm_clean_pkg (t);
}
r2pm_clone (t);
rc |= r2pm_install_pkg (t);
}
return rc;
}
static int r2pm_doc(RList *targets) {
RListIter *iter;
const char *t;
int rc = 0;
r_list_foreach (targets, iter, t) {
rc |= r2pm_doc_pkg (t);
}
return rc;
}
static int r2pm_clean(RList *targets) {
RListIter *iter;
const char *t;
int rc = 0;
r_list_foreach (targets, iter, t) {
rc |= r2pm_clean_pkg (t);
}
return rc;
}
static int r2pm_uninstall(RList *targets) {
RListIter *iter;
const char *t;
int rc = 0;
r_list_foreach (targets, iter, t) {
rc |= r2pm_uninstall_pkg (t);
}
return rc;
}
static bool is_valid_package(const char *dbdir, const char *pkg) {
if (*pkg == '.') {
return false;
}
char *script = r2pm_get (pkg, "\nR2PM_INSTALL() {", TT_CODEBLOCK);
if (!script) {
eprintf ("Warning: Unable to find R2PM_INSTALL script in '%s'\n", pkg);
return false;
}
free (script);
return true;
}
static int count_available(void) {
char *dbdir = r2pm_dbdir ();
RListIter *iter;
const char *c;
RList *dbfiles = r_sys_dir (dbdir);
int count = 0;
r_list_foreach (dbfiles, iter, c) {
if (is_valid_package (dbdir, c)) {
count ++;
}
}
r_list_free (dbfiles);
free (dbdir);
return count;
}
static int count_installed(void) {
char *dbdir = r2pm_pkgdir ();
RListIter *iter;
const char *c;
RList *dbfiles = r_sys_dir (dbdir);
int count = 0;
r_list_foreach (dbfiles, iter, c) {
if (*c != '.') {
count ++;
}
}
r_list_free (dbfiles);
free (dbdir);
return count;
}
static int r2pm_info(void) {
const int installed_packages = count_installed ();
const int available_packages = count_available ();
printf ("Installed %d packages of %d in database\n",
installed_packages, available_packages);
return 0;
}
static int r2pm_search(const char *grep) {
char *path = r2pm_dbdir ();
RList *files = r_sys_dir (path);
free (path);
if (!files) {
return 1;
}
RListIter *iter;
const char *file;
r_list_foreach (files, iter, file) {
if (*file != '.') {
char *desc = r2pm_desc (file);
if (!grep || (strstr (desc, grep) || strstr (file, grep))) {
eprintf ("%s%s%s\n", file, r_str_pad (' ', 20 - strlen (file)), desc);
}
free (desc);
}
}
r_list_free (files);
return 0;
}
static int r_main_r2pm_c(int argc, const char **argv) {
R2Pm r2pm = {0};
RGetopt opt;
r_getopt_init (&opt, argc, argv, "cdiIhflgrsu");
if (opt.ind < argc) {
// TODO: deprecate, only use flags imho
r2pm_actionword (&r2pm, argv[opt.ind]);
}
int i, c;
while ((c = r_getopt_next (&opt)) != -1) {
switch (c) {
case 'c':
r2pm.clean = true;
break;
case 'i':
r2pm.install = true;
break;
case 'd':
r2pm.doc = true;
break;
case 'I':
r2pm.info = true;
break;
case 'u':
r2pm.uninstall = true;
break;
case 'f':
r2pm.force = true;
break;
case 'l':
r2pm.list = true;
break;
case 's':
r2pm.search = true;
break;
case 'r':
r2pm.run = true;
break;
case 'g':
r2pm.global = true;
break;
case 'h':
r2pm.help = true;
break;
}
}
if (r2pm.help || argc == 1) {
printf ("%s", helpmsg);
return 0;
}
if (r2pm.init) {
return r2pm_update ();
}
if (r2pm.run) {
char *opath = r_sys_getenv ("PATH");
if (opath) {
char *bindir = r2pm_bindir ();
const char *sep = R_SYS_ENVSEP;
char *newpath = r_str_newf ("%s%s%s", bindir, sep, opath);
r_sys_setenv ("PATH", newpath);
free (newpath);
free (opath);
free (bindir);
}
int i;
RStrBuf *sb = r_strbuf_new ("");
for (i = opt.ind; i < argc; i++) {
r_strbuf_appendf (sb, " %s", argv[i]);
}
char *cmd = r_strbuf_drain (sb);
int res = r_sandbox_system (cmd, 1);
free (cmd);
return res;
}
RList *targets = r_list_newf (free);
for (i = opt.ind; i < argc; i++) {
r_list_append (targets, strdup (argv[i]));
}
int res = -1;
if (r2pm.search) {
res = r2pm_search (argv[opt.ind]);
} else if (r2pm.info) {
res = r2pm_info ();
} else if (r2pm.doc) {
res = r2pm_doc (targets);
} else if (r2pm.install) {
res = r2pm_install (targets, r2pm.uninstall, r2pm.clean);
} else if (r2pm.uninstall) {
res = r2pm_uninstall (targets);
} else if (r2pm.clean) {
res = r2pm_clean (targets);
} else if (r2pm.list) {
res = r2pm_list ();
}
r_list_free (targets);
if (res != -1) {
return res;
}
if (opt.ind == 1) {
return 0;
}
eprintf ("TODO\n");
#if __WINDOWS__
bool use_c_impl = true;
#else
bool use_c_impl = r_sys_getenv_asbool ("R2PM_NATIVE");
#endif
if (use_c_impl) {
return 1;
}
return r_main_r2pm_sh (argc, argv);
}
R_API int r_main_r2pm(int argc, const char **argv) {
#if __WINDOWS__
bool use_c_impl = true;
#else
bool use_c_impl = r_sys_getenv_asbool ("R2PM_NATIVE");
#endif
if (use_c_impl) {
return r_main_r2pm_c (argc, argv);
}
return r_main_r2pm_sh (argc, argv);
}