Files
ports/misc/github-copilot-cli-legacy/files/launcher.c
T

188 lines
4.5 KiB
C

/*
* launcher.c - GitHub Copilot CLI binary flavor launcher
*
* Extracts the bundled copilot JS files and node runtime to a version-specific
* cache directory and runs them. The bundle (xz-compressed tar) contains the
* full node binary so no system node installation is required.
*
* Node.js SEA (Single Executable Application) cannot be used because
* postject_find_resource() has no FreeBSD code path.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef PORTVERSION
#define PORTVERSION "unknown"
#endif
#define CACHE_SUBDIR "github-copilot-cli/v" PORTVERSION
extern const char _binary_copilot_bundle_txz_start[];
extern const char _binary_copilot_bundle_txz_end[];
static int
makedirs(const char *path)
{
char buf[1024];
char *p;
struct stat st;
if (stat(path, &st) == 0)
return (0);
snprintf(buf, sizeof(buf), "%s", path);
for (p = buf + 1; *p != '\0'; p++) {
if (*p == '/') {
*p = '\0';
if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0 &&
errno != EEXIST)
return (-1);
*p = '/';
}
}
return (mkdir(buf, 0755) == 0 || errno == EEXIST ? 0 : -1);
}
static int
extract_bundle(const char *destdir)
{
char tmpdir[1024], archpath[1024];
const char *data;
size_t size;
FILE *f;
pid_t pid;
int status;
/* Write bundle to a temp archive file */
snprintf(archpath, sizeof(archpath), "%s/.bundle.txz.tmp", destdir);
data = _binary_copilot_bundle_txz_start;
size = (size_t)(_binary_copilot_bundle_txz_end -
_binary_copilot_bundle_txz_start);
f = fopen(archpath, "wb");
if (f == NULL) {
fprintf(stderr, "copilot: cannot write bundle: %s\n",
strerror(errno));
return (-1);
}
if (fwrite(data, 1, size, f) != size) {
fclose(f);
unlink(archpath);
fprintf(stderr, "copilot: bundle write failed\n");
return (-1);
}
fclose(f);
/* Extract into a temp subdir, then atomically rename */
snprintf(tmpdir, sizeof(tmpdir), "%s.extracting", destdir);
makedirs(tmpdir);
pid = fork();
if (pid < 0) {
unlink(archpath);
return (-1);
}
if (pid == 0) {
execl("/usr/bin/tar", "tar", "-xJf", archpath, "-C", tmpdir,
(char *)NULL);
_exit(127);
}
waitpid(pid, &status, 0);
unlink(archpath);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
fprintf(stderr, "copilot: bundle extraction failed\n");
/* cleanup tmpdir */
pid = fork();
if (pid == 0) {
execl("/bin/rm", "rm", "-rf", tmpdir, (char *)NULL);
_exit(1);
}
if (pid > 0)
waitpid(pid, NULL, 0);
return (-1);
}
/* Atomic rename: tmpdir -> destdir */
if (rename(tmpdir, destdir) != 0) {
/* Another process may have already created destdir - that's ok */
if (errno != EEXIST && errno != ENOTEMPTY) {
fprintf(stderr, "copilot: rename failed: %s\n",
strerror(errno));
return (-1);
}
/* Use existing destdir; clean up tmpdir */
pid = fork();
if (pid == 0) {
execl("/bin/rm", "rm", "-rf", tmpdir, (char *)NULL);
_exit(1);
}
if (pid > 0)
waitpid(pid, NULL, 0);
}
return (0);
}
int
main(int argc, char *argv[])
{
const char *xdg, *home;
char cache_dir[1024], node_path[1080], script_path[1080];
char **new_argv;
struct stat st;
int i;
/* Determine cache directory */
xdg = getenv("XDG_CACHE_HOME");
home = getenv("HOME");
if (xdg != NULL && xdg[0] != '\0')
snprintf(cache_dir, sizeof(cache_dir), "%s/" CACHE_SUBDIR, xdg);
else if (home != NULL && home[0] != '\0')
snprintf(cache_dir, sizeof(cache_dir),
"%s/.cache/" CACHE_SUBDIR, home);
else {
fprintf(stderr,
"copilot: HOME or XDG_CACHE_HOME must be set\n");
return (1);
}
snprintf(node_path, sizeof(node_path), "%s/node", cache_dir);
snprintf(script_path, sizeof(script_path), "%s/index.js", cache_dir);
/* Extract bundle if not already done (check for node binary) */
if (stat(node_path, &st) != 0) {
if (makedirs(cache_dir) != 0) {
fprintf(stderr, "copilot: cannot create %s: %s\n",
cache_dir, strerror(errno));
return (1);
}
if (extract_bundle(cache_dir) != 0)
return (1);
}
/* Build argument vector for the bundled node */
new_argv = malloc((size_t)(argc + 2) * sizeof(char *));
if (new_argv == NULL) {
fprintf(stderr, "copilot: malloc failed\n");
return (1);
}
new_argv[0] = node_path;
new_argv[1] = script_path;
for (i = 1; i < argc; i++)
new_argv[i + 1] = argv[i];
new_argv[argc + 1] = NULL;
execv(node_path, new_argv);
fprintf(stderr, "copilot: cannot exec %s: %s\n", node_path,
strerror(errno));
return (1);
}