镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 08:52:45 +08:00
584 行
20 KiB
C
584 行
20 KiB
C
/* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */
|
|
#include "pdm.h"
|
|
#include "pmm.h"
|
|
#include "sha256.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <direct.h>
|
|
#include <io.h>
|
|
#include <inttypes.h>
|
|
#define PMM_MKDIR(p) _mkdir(p)
|
|
#define RMDIR(p) _rmdir(p)
|
|
#define PMM_POPEN_READ(cmd) _popen(cmd, "r")
|
|
#define PMM_PCLOSE_READ(p) _pclose(p)
|
|
#define GETCWD(p,n) (_getcwd((p),(n)))
|
|
#define CHDIR(p) _chdir(p)
|
|
#else
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#define PMM_MKDIR(p) mkdir((p), 0755)
|
|
#define RMDIR(p) rmdir(p)
|
|
#define PMM_POPEN_READ(cmd) popen(cmd, "r")
|
|
#define PMM_PCLOSE_READ(p) pclose(p)
|
|
#define GETCWD(p,n) getcwd((p),(n))
|
|
#define CHDIR(p) chdir(p)
|
|
#endif
|
|
|
|
/* ---------- small helpers ---------- */
|
|
|
|
static int ends_with(const char *s, const char *suf) {
|
|
size_t sl = strlen(s), xl = strlen(suf);
|
|
return sl >= xl && strcmp(s + sl - xl, suf) == 0;
|
|
}
|
|
|
|
static const char *base_name(const char *path) {
|
|
const char *a = strrchr(path, '/');
|
|
const char *b = strrchr(path, '\\');
|
|
const char *p = (a > b) ? a : b;
|
|
return p ? p + 1 : path;
|
|
}
|
|
|
|
static void mkdir_p(const char *path) {
|
|
char tmp[1024];
|
|
strncpy(tmp, path, sizeof(tmp) - 1);
|
|
tmp[sizeof(tmp) - 1] = '\0';
|
|
for (char *p = tmp + 1; *p; p++) {
|
|
if (*p == '/' || *p == '\\') {
|
|
char c = *p; *p = '\0'; PMM_MKDIR(tmp); *p = c;
|
|
}
|
|
}
|
|
PMM_MKDIR(tmp);
|
|
}
|
|
|
|
/* Convert backslashes to forward slashes (so cmd/bash don't treat "C:\x" as host). */
|
|
static void rmtree(const char *dir) {
|
|
/* Pure-C recursive delete: no shell, so no path-flavour (bsdtar/GNU) issues. */
|
|
#ifdef _WIN32
|
|
char patt[1200];
|
|
snprintf(patt, sizeof(patt), "%s\\*", dir);
|
|
struct _finddata_t fd;
|
|
intptr_t h = _findfirst(patt, &fd);
|
|
if (h != -1) {
|
|
do {
|
|
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
|
|
if (fd.attrib & _A_SUBDIR) rmtree(full);
|
|
else remove(full);
|
|
} while (_findnext(h, &fd) == 0);
|
|
_findclose(h);
|
|
}
|
|
RMDIR(dir);
|
|
#else
|
|
DIR *d = opendir(dir);
|
|
if (!d) return;
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
|
|
struct stat st;
|
|
if (stat(full, &st) == 0 && S_ISDIR(st.st_mode)) rmtree(full);
|
|
else remove(full);
|
|
}
|
|
closedir(d);
|
|
RMDIR(dir);
|
|
#endif
|
|
}
|
|
|
|
/* KEY: value lookup in control text; returns malloc'd value or NULL. */
|
|
static char *control_get(const char *text, const char *key) {
|
|
size_t klen = strlen(key);
|
|
const char *line = text;
|
|
while (line && *line) {
|
|
const char *eol = strchr(line, '\n');
|
|
size_t ll = eol ? (size_t)(eol - line) : strlen(line);
|
|
if (ll > klen && strncasecmp(line, key, klen) == 0 && line[klen] == ':') {
|
|
const char *v = line + klen + 1;
|
|
while (v < line + ll && (*v == ' ' || *v == '\t')) v++;
|
|
size_t vl = (size_t)(line + ll - v);
|
|
while (vl && (v[vl-1] == '\r' || v[vl-1] == ' ')) vl--;
|
|
char *out = malloc(vl + 1);
|
|
memcpy(out, v, vl);
|
|
out[vl] = '\0';
|
|
return out;
|
|
}
|
|
line = eol ? eol + 1 : NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static char *read_file(const char *path, long *len) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return NULL;
|
|
fseek(f, 0, SEEK_END);
|
|
long n = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
char *buf = malloc((size_t)n + 1);
|
|
size_t rd = fread(buf, 1, (size_t)n, f);
|
|
fclose(f);
|
|
buf[rd] = '\0';
|
|
if (len) *len = (long)rd;
|
|
return buf;
|
|
}
|
|
|
|
/* ---------- pack ---------- */
|
|
|
|
int pdm_pack(const char *dir, const char *out) {
|
|
char cp_[1100];
|
|
snprintf(cp_, sizeof(cp_), "%s/pdm-control", dir);
|
|
char *control = read_file(cp_, NULL);
|
|
if (!control) {
|
|
fprintf(stderr, "pdm: %s has no pdm-control file\n", dir);
|
|
return -1;
|
|
}
|
|
char *pkg = control_get(control, "Package");
|
|
char *ver = control_get(control, "Version");
|
|
if (!pkg || !*pkg) {
|
|
fprintf(stderr, "pdm: pdm-control missing 'Package:' field\n");
|
|
free(control); return -1;
|
|
}
|
|
if (!ver || !*ver) ver = strdup("0.0.0");
|
|
|
|
/* Output defaults to <Package>_<Version>.pdm in the current directory.
|
|
* Everything below uses paths relative to the CWD so the shell never sees a
|
|
* Windows drive-letter path (which MSYS bash would mangle). */
|
|
char outbuf[1100];
|
|
if (!out) {
|
|
snprintf(outbuf, sizeof(outbuf), "%s_%s.pdm", pkg, ver);
|
|
out = outbuf;
|
|
}
|
|
|
|
const char *stage = ".pdm-stage";
|
|
char cmd[2800];
|
|
rmtree(stage);
|
|
PMM_MKDIR(stage);
|
|
|
|
/* control.tar.gz (contains pdm-control; packed from the source dir) */
|
|
snprintf(cmd, sizeof(cmd), "tar -czf \"%s/control.tar.gz\" -C \"%s\" pdm-control", stage, dir);
|
|
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (control) failed\n"); rmtree(stage); return -1; }
|
|
|
|
/* data.tar.gz (everything except pdm-control and the scratch dir) */
|
|
snprintf(cmd, sizeof(cmd),
|
|
"tar -czf \"%s/data.tar.gz\" -C \"%s\" --exclude pdm-control --exclude %s .",
|
|
stage, dir, stage);
|
|
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (data) failed\n"); rmtree(stage); return -1; }
|
|
|
|
/* sha256sums of the two members */
|
|
char sums_path[1100], hex[128];
|
|
snprintf(sums_path, sizeof(sums_path), "%s/sha256sums", stage);
|
|
FILE *sf = fopen(sums_path, "w");
|
|
if (sf) {
|
|
char mp[1200];
|
|
snprintf(mp, sizeof(mp), "%s/control.tar.gz", stage);
|
|
if (pmm_sha256_file(mp, hex) == 0) fprintf(sf, "%s control.tar.gz\n", hex);
|
|
snprintf(mp, sizeof(mp), "%s/data.tar.gz", stage);
|
|
if (pmm_sha256_file(mp, hex) == 0) fprintf(sf, "%s data.tar.gz\n", hex);
|
|
fclose(sf);
|
|
}
|
|
|
|
/* outer archive */
|
|
snprintf(cmd, sizeof(cmd), "tar -cf \"%s\" -C \"%s\" control.tar.gz data.tar.gz sha256sums", out, stage);
|
|
int rc = system(cmd);
|
|
rmtree(stage);
|
|
if (rc != 0) { fprintf(stderr, "pdm: failed to write %s\n", out); return -1; }
|
|
|
|
printf("pdm: packed %s (%s %s) -> %s\n", dir, pkg, ver, out);
|
|
free(control); free(pkg);
|
|
if (strcmp(ver, "0.0.0") != 0) free(ver);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------- install ---------- */
|
|
|
|
static void db_dirs(char *db, size_t dbs, char *root, size_t roots) {
|
|
char home[900];
|
|
pmm_config_dir(home, sizeof(home) - 32);
|
|
snprintf(db, dbs, "%s/installed", home);
|
|
mkdir_p(db);
|
|
if (pmm_flat_mode()) snprintf(root, roots, "%s", home); /* -p <path> */
|
|
else snprintf(root, roots, "%s/root", home);
|
|
mkdir_p(root);
|
|
}
|
|
|
|
static int copy_file(const char *src, const char *dst) {
|
|
FILE *in = fopen(src, "rb");
|
|
if (!in) return -1;
|
|
FILE *out = fopen(dst, "wb");
|
|
if (!out) { fclose(in); return -1; }
|
|
char buf[65536];
|
|
size_t n;
|
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
|
|
if (fwrite(buf, 1, n, out) != n) { fclose(in); fclose(out); return -1; }
|
|
fclose(in);
|
|
fclose(out);
|
|
return 0;
|
|
}
|
|
|
|
/* Recursively find the first .exe under dir (for the app icon); malloc'd path or NULL. */
|
|
static char *find_first_exe(const char *dir) {
|
|
#ifdef _WIN32
|
|
char patt[1300];
|
|
snprintf(patt, sizeof(patt), "%s\\*", dir);
|
|
struct _finddata_t fd;
|
|
intptr_t h = _findfirst(patt, &fd);
|
|
if (h != -1) {
|
|
do {
|
|
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
|
|
if ((fd.attrib & _A_SUBDIR)) {
|
|
char *r = find_first_exe(full);
|
|
if (r) { _findclose(h); return r; }
|
|
} else {
|
|
size_t l = strlen(fd.name);
|
|
if (l > 4 && strcasecmp(fd.name + l - 4, ".exe") == 0) {
|
|
char *r = malloc(1400);
|
|
if (r) snprintf(r, 1400, "%s\\%s", dir, fd.name);
|
|
_findclose(h);
|
|
return r;
|
|
}
|
|
}
|
|
} while (_findnext(h, &fd) == 0);
|
|
_findclose(h);
|
|
}
|
|
#else
|
|
DIR *d = opendir(dir);
|
|
if (d) {
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (e->d_name[0] == '.') continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
|
|
struct stat st;
|
|
if (stat(full, &st) == 0) {
|
|
if (S_ISDIR(st.st_mode)) { char *r = find_first_exe(full); if (r) { closedir(d); return r; } }
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
/* Recursively sum file sizes under dir, in bytes. */
|
|
static unsigned long long dir_size(const char *dir) {
|
|
unsigned long long total = 0;
|
|
#ifdef _WIN32
|
|
char patt[1300];
|
|
snprintf(patt, sizeof(patt), "%s\\*", dir);
|
|
struct _finddata_t fd;
|
|
intptr_t h = _findfirst(patt, &fd);
|
|
if (h != -1) {
|
|
do {
|
|
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
|
|
if (fd.attrib & _A_SUBDIR) total += dir_size(full);
|
|
else total += (unsigned long long)fd.size;
|
|
} while (_findnext(h, &fd) == 0);
|
|
_findclose(h);
|
|
}
|
|
#else
|
|
DIR *d = opendir(dir);
|
|
if (d) {
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (e->d_name[0] == '.') continue;
|
|
char full[1400];
|
|
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
|
|
struct stat st;
|
|
if (stat(full, &st) == 0) {
|
|
if (S_ISDIR(st.st_mode)) total += dir_size(full);
|
|
else total += (unsigned long long)st.st_size;
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
#endif
|
|
return total;
|
|
}
|
|
|
|
static char saved_cwd[1400];
|
|
static int chdir_save(const char *dir) {
|
|
if (!GETCWD(saved_cwd, sizeof(saved_cwd))) saved_cwd[0] = '\0';
|
|
return CHDIR(dir) == 0 ? 0 : -1;
|
|
}
|
|
static void chdir_restore(void) {
|
|
if (saved_cwd[0]) CHDIR(saved_cwd);
|
|
}
|
|
|
|
/* Move everything under <dir>/bin up into <dir>, then remove bin/. (C-level, so
|
|
* it works on Windows cmd too where `mv` doesn't exist.) */
|
|
static void flatten_bin(const char *dir) {
|
|
char bindir[1200];
|
|
snprintf(bindir, sizeof(bindir), "%s/%s", dir, "bin");
|
|
#ifdef _WIN32
|
|
char patt[1300];
|
|
snprintf(patt, sizeof(patt), "%s\\*", bindir);
|
|
struct _finddata_t fd;
|
|
intptr_t h = _findfirst(patt, &fd);
|
|
if (h != -1) {
|
|
do {
|
|
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
|
|
char src[1400], dst[1400];
|
|
snprintf(src, sizeof(src), "%s\\%s", bindir, fd.name);
|
|
snprintf(dst, sizeof(dst), "%s\\%s", dir, fd.name);
|
|
rename(src, dst);
|
|
} while (_findnext(h, &fd) == 0);
|
|
_findclose(h);
|
|
}
|
|
RMDIR(bindir);
|
|
#else
|
|
DIR *d = opendir(bindir);
|
|
if (d) {
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (e->d_name[0] == '.') continue;
|
|
char src[1400], dst[1400];
|
|
snprintf(src, sizeof(src), "%s/%s", bindir, e->d_name);
|
|
snprintf(dst, sizeof(dst), "%s/%s", dir, e->d_name);
|
|
rename(src, dst);
|
|
}
|
|
closedir(d);
|
|
rmdir(bindir);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int pdm_info(const char *pdmfile) {
|
|
printf("pdm: %s\n", base_name(pdmfile));
|
|
/* Copy into the pm dir + chdir, then use relative names (works with both
|
|
* MSYS GNU tar and System32 bsdtar). */
|
|
char home[1024];
|
|
pmm_config_dir(home, sizeof(home));
|
|
char info_tmp[1400];
|
|
snprintf(info_tmp, sizeof(info_tmp), "%s/_pmm_info.pdm", home);
|
|
if (copy_file(pdmfile, info_tmp) != 0) return -1;
|
|
if (chdir_save(home) != 0) { remove(info_tmp); return -1; }
|
|
char cmd[2800];
|
|
snprintf(cmd, sizeof(cmd), "tar -xOf _pmm_info.pdm control.tar.gz | tar -xzOf - pdm-control");
|
|
int rc = system(cmd);
|
|
chdir_restore();
|
|
remove(info_tmp);
|
|
return rc == 0 ? 0 : -1;
|
|
}
|
|
|
|
int pdm_install_file(const char *pdmfile) {
|
|
if (!ends_with(pdmfile, ".pdm")) {
|
|
fprintf(stderr, "pdm: '%s' is not a .pdm file\n", pdmfile);
|
|
return -1;
|
|
}
|
|
char home[1024], db[1024], root[1024], cmd[2600];
|
|
pmm_config_dir(home, sizeof(home));
|
|
int flat = pmm_flat_mode(); /* -p <path>: deploy directly into that dir */
|
|
snprintf(db, sizeof(db), "%s/installed", home);
|
|
mkdir_p(db);
|
|
if (flat)
|
|
snprintf(root, sizeof(root), "%s", home);
|
|
else
|
|
snprintf(root, sizeof(root), "%s/root", home);
|
|
mkdir_p(root);
|
|
|
|
/* Copy the package into the PM dir, chdir there, and run tar with ONLY
|
|
* relative paths. Relative paths are understood identically by MSYS GNU tar
|
|
* and System32 bsdtar, so we don't have to guess a drive-letter style. */
|
|
char tmpname[1400], tmprel[] = "_pmm_install.pdm";
|
|
snprintf(tmpname, sizeof(tmpname), "%s/_pmm_install.pdm", home);
|
|
if (copy_file(pdmfile, tmpname) != 0) {
|
|
fprintf(stderr, "pdm: cannot read %s\n", pdmfile);
|
|
return -1;
|
|
}
|
|
if (chdir_save(home) != 0) { remove(tmpname); return -1; }
|
|
if (system(NULL) == 0) { fprintf(stderr, "pdm: cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; }
|
|
|
|
const char *stage = "installed/.stage";
|
|
rmtree(stage);
|
|
PMM_MKDIR(stage);
|
|
|
|
/* extract members (all relative) */
|
|
snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", tmprel, stage);
|
|
if (system(cmd) != 0) {
|
|
fprintf(stderr, "pdm: not a valid .pdm archive: %s\n", pdmfile);
|
|
chdir_restore(); remove(tmpname); return -1;
|
|
}
|
|
|
|
/* verify member checksums (relative to pm dir) */
|
|
char hex[128];
|
|
FILE *sf = fopen("installed/.stage/sha256sums", "r");
|
|
if (sf) {
|
|
char line[256], fname[128], expect[128];
|
|
while (fgets(line, sizeof(line), sf)) {
|
|
if (sscanf(line, "%127s %127s", expect, fname) != 2) continue;
|
|
char mp[1200];
|
|
snprintf(mp, sizeof(mp), "installed/.stage/%s", fname);
|
|
if (pmm_sha256_file(mp, hex) != 0 || strcasecmp(hex, expect) != 0) {
|
|
fprintf(stderr, "pdm: CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile);
|
|
fclose(sf); chdir_restore(); remove(tmpname); return -1;
|
|
}
|
|
}
|
|
fclose(sf);
|
|
printf("pdm: sha256 checksums OK\n");
|
|
} else {
|
|
printf("pdm: warning: no sha256sums member, skipping integrity check\n");
|
|
}
|
|
|
|
/* read control */
|
|
snprintf(cmd, sizeof(cmd), "tar -xzOf \"%s/control.tar.gz\" pdm-control", stage);
|
|
FILE *cf = PMM_POPEN_READ(cmd);
|
|
if (!cf) {
|
|
fprintf(stderr, "pdm: missing control.tar.gz in %s\n", pdmfile);
|
|
chdir_restore(); remove(tmpname); return -1;
|
|
}
|
|
char ctl[4096];
|
|
size_t got = fread(ctl, 1, sizeof(ctl) - 1, cf);
|
|
PMM_PCLOSE_READ(cf);
|
|
ctl[got] = '\0';
|
|
char *pkg = control_get(ctl, "Package");
|
|
char *ver = control_get(ctl, "Version");
|
|
if (!pkg || !*pkg) {
|
|
fprintf(stderr, "pdm: package has no Package: field\n");
|
|
chdir_restore(); remove(tmpname); return -1;
|
|
}
|
|
|
|
/* extract data into root (relative target: cwd is already the pm dir) */
|
|
const char *tgt = flat ? "." : "root";
|
|
snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt);
|
|
if (system(cmd) != 0) {
|
|
fprintf(stderr, "pdm: data extraction failed\n");
|
|
chdir_restore(); remove(tmpname); return -1;
|
|
}
|
|
|
|
/* flat mode (-p <path>): move bin/* up into the address itself, drop bin/ */
|
|
if (flat) {
|
|
flatten_bin("."); /* cwd = the install address in flat mode */
|
|
}
|
|
|
|
/* record file list + control in db */
|
|
snprintf(cmd, sizeof(cmd), "%s/%s.info", db, pkg);
|
|
FILE *dbf = fopen(cmd, "wb"); /* binary: keep \n, avoid Windows CRLF translation */
|
|
if (dbf) {
|
|
fprintf(dbf, "Package: %s\nVersion: %s\nSource: %s\n\n%s\n",
|
|
pkg, ver ? ver : "0.0.0", base_name(pdmfile), ctl);
|
|
fprintf(dbf, "Files:\n");
|
|
char ltcmd[2600];
|
|
snprintf(ltcmd, sizeof(ltcmd), "tar -tzf \"%s/data.tar.gz\"", stage);
|
|
FILE *tf = PMM_POPEN_READ(ltcmd);
|
|
if (tf) {
|
|
char ln[2048];
|
|
while (fgets(ln, sizeof(ln), tf)) {
|
|
if (flat) { /* reflection of the flatten: strip a leading bin/ */
|
|
const char *q = ln;
|
|
while (*q==' '||*q=='\t') q++;
|
|
if (q[0]=='.'&&q[1]=='/'&&strncasecmp(q+2,"bin/",4)==0) { ln[0]='\0'; continue; }
|
|
}
|
|
fputs(ln, dbf);
|
|
}
|
|
PMM_PCLOSE_READ(tf);
|
|
} else {
|
|
fprintf(stderr, "pdm: warning: could not record file list\n");
|
|
}
|
|
fclose(dbf);
|
|
}
|
|
|
|
rmtree(stage);
|
|
chdir_restore();
|
|
remove(tmpname);
|
|
printf("pdm: installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root);
|
|
|
|
/* register in the Windows per-user "Installed Apps" (Uninstall) hive,
|
|
* like an MSI installer would, so it shows in Settings > Installed apps */
|
|
{
|
|
char rootpkg[1400];
|
|
snprintf(rootpkg, sizeof(rootpkg), "%s/%s", root, pkg);
|
|
char *icon = find_first_exe(rootpkg);
|
|
const char *pub = control_get(ctl, "Maintainer");
|
|
const char *self = pmm_self_path();
|
|
pmm_reg_uninstall(pkg, ver ? ver : "", pub ? pub : "",
|
|
root, icon, self && *self ? self : "pmm",
|
|
dir_size(rootpkg));
|
|
if (pub) free((char *)pub);
|
|
free(icon);
|
|
}
|
|
|
|
pmm_add_to_path(); /* auto-put PMM dirs (e.g. root/nodejs) on PATH */
|
|
|
|
#ifndef _WIN32
|
|
/* On Linux/macOS, mark anything in <root>/bin executable. Windows doesn't
|
|
* record unix exec bits, so ELF/scripts packaged on Windows lose +x; restore
|
|
* them at install time so e.g. a Linux Node.js binary actually runs. */
|
|
{
|
|
char cx[1600];
|
|
snprintf(cx, sizeof(cx), "chmod +x \"%s/bin\"/* 2>/dev/null || true", root);
|
|
system(cx);
|
|
}
|
|
#endif
|
|
|
|
free(pkg); if (ver) free(ver);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------- list / remove ---------- */
|
|
|
|
void pdm_list_installed(void) {
|
|
char db[1024], root[1024];
|
|
db_dirs(db, sizeof(db), root, sizeof(root));
|
|
#ifdef _WIN32
|
|
char cmd[1200];
|
|
snprintf(cmd, sizeof(cmd), "dir /b \"%s\\*.info\" 2>nul", db);
|
|
#else
|
|
char cmd[1200];
|
|
snprintf(cmd, sizeof(cmd), "ls -1 \"%s\"/*.info 2>/dev/null", db);
|
|
#endif
|
|
printf("installed .pdm packages (root: %s):\n", root);
|
|
fflush(stdout);
|
|
system(cmd);
|
|
}
|
|
|
|
int pdm_remove(const char *name) {
|
|
char db[1024], root[1024];
|
|
db_dirs(db, sizeof(db), root, sizeof(root));
|
|
char ipath[1100];
|
|
snprintf(ipath, sizeof(ipath), "%s/%s.info", db, name);
|
|
long len = 0;
|
|
char *info = read_file(ipath, &len);
|
|
if (!info) {
|
|
fprintf(stderr, "pdm: package '%s' is not installed\n", name);
|
|
return -1;
|
|
}
|
|
char *files = strstr(info, "Files:\n");
|
|
int n = 0;
|
|
if (files) {
|
|
char path[1024];
|
|
char *line = files + 7;
|
|
while (*line) {
|
|
char *eol = strchr(line, '\n');
|
|
if (!eol) eol = line + strlen(line);
|
|
size_t ll = (size_t)(eol - line);
|
|
while (ll && (line[ll-1] == '\r' || line[ll-1] == ' ')) ll--;
|
|
if (ll > 0 && ll < sizeof(path)) {
|
|
memcpy(path, line, ll); path[ll] = '\0';
|
|
/* normalize "./x" -> "x" */
|
|
char *p = path;
|
|
while (p[0] == '.' && p[1] == '/') p += 2;
|
|
char full[1200];
|
|
snprintf(full, sizeof(full), "%s/%s", root, p);
|
|
remove(full);
|
|
n++;
|
|
}
|
|
line = *eol ? eol + 1 : eol;
|
|
}
|
|
}
|
|
remove(ipath);
|
|
free(info);
|
|
printf("pdm: removed %s (%d files)\n", name, n);
|
|
pmm_reg_uninstall_clear(name); /* drop it from Installed Apps */
|
|
return 0;
|
|
}
|