From fa6025fb5c9958d991866c016a06ee2d40a346cc Mon Sep 17 00:00:00 2001 From: bjornstahl Date: Thu, 26 Oct 2023 23:45:18 +0200 Subject: [PATCH] (a12) change appl package format This moves away from exec:ing to tar into a placeholder custom. The need for this is to have a .manifest file for an appl that turns into a global header for the appl with basic information about sandboxing needs (primarily of -net and -terminal should be permitted or not). Then there's a single line header per file, the raw bytes and repeat until eof. Checksumming and compression is not included as the protocol does that already. What is missing in that regard is signature chains and caching of compression to reduce server spinup times, but that's for the optimisation stage. This should also restore functionality for afsrv_net browse/load that was broken during the net/dir_cl refactor. The biggest discrepancy between the two right now is that no NETSTATE events are emitted yet and no tunnel request controls. --- CHANGELOG.md | 2 + src/a12/a12.c | 2 +- src/a12/net/README.md | 41 +++- src/a12/net/dir_cl.c | 122 +++++++----- src/a12/net/dir_srv.c | 3 + src/a12/net/dir_srv_worker.c | 59 ++++-- src/a12/net/dir_supp.c | 303 +++++++++++++++++++++++++++--- src/a12/net/directory.h | 4 + src/frameserver/net/default/net.c | 62 +++--- src/shmif/arcan_shmif_control.c | 2 + 10 files changed, 475 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc09abe68..0bcfe7233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ * directory mode notification of appl updates and sources coming / leaving * directory mode registration of dynamic sinks * directory mode sourcing dynamic sinks (sink-inbound only) + * directory mode source-sink pairing 'reachable source' and 'tunnel' transfer modes + * changed packaging format for appls ## Terminal * SGR reset fix, add CNL / CPL diff --git a/src/a12/a12.c b/src/a12/a12.c index f33a10513..e55886d08 100644 --- a/src/a12/a12.c +++ b/src/a12/a12.c @@ -2256,7 +2256,7 @@ static void process_blob(struct a12_state* S) return; } else - a12int_trace(A12_TRACE_BTRANSFER, "kind=zstd_state:%"PRIu64, decode); + a12int_trace(A12_TRACE_BTRANSFER, "kind=zstd_state:size=%"PRIu64, decode); free_buf = true; } diff --git a/src/a12/net/README.md b/src/a12/net/README.md index 661fe23a8..cbe5d332a 100644 --- a/src/a12/net/README.md +++ b/src/a12/net/README.md @@ -71,10 +71,39 @@ in the path. On normal/default setups this is supported by appl output being directed to a separate 'appltemp' namespace by default, letting applbase be read-only. -If the public key a client is trusted (in keystore), it can also be used as a -state store for storing/restoring the appl execution state. Other (planned) -modes of operation for this feature is to act as a pub/sub for sources/sinks/ -other directories to find eachother. +If the public key a client uses is trusted (in keystore), it can also be used +as a state store for storing/restoring the appl execution state. It can also +be used to dynamically update the hosted appl, letting connected clients switch +automatically: + + arcan-net --push-appl myappl @myserver + +It is also possible to share access to arcan clients dynamically: + + A12_IDENT=me ARCAN_CONNPATH=a12://myserver@ /usr/bin/afsrv_terminal + +Would expose a single instance of 'afsrv\_terminal' as 'me', letting someone +else access it: + + arcan-net --keep-alive @myserver /me + +The above would connect to the directory, wait for the 'me' source to be +announced as available and request to source it. + +This retains end to end encryption of the data stream, and can be used to learn +about public keys from this trusted third party in order to reconnect with +their owners them through some other means later. + +The default form of this creates a direct sink to source outbound connection. +In cases where that network traffic is not possible, the directory server can +act as a tunnel: + + arcan-net --tunnel + +This is permitted by default by a directory server but can be toggled off with +--block-tunnel: + + arcan-net --directory --block-tunnel -l 6680 # Cache @@ -249,8 +278,8 @@ Milestone 3 - big stretch (0.6.x) - [x] enumerate / fetch / execute appl - [x] register as source - [x] register as sink - - [ ] notify on new source/sink - - [ ] relay a12 traffing between source/sink + - [x] notify on new source/sink + - [x] relay a12 traffing between source/sink - [ ] NAT-punch between source/sink - [x] state store/restore - [x] Add to afsrv\_net (x) diff --git a/src/a12/net/dir_cl.c b/src/a12/net/dir_cl.c index 0fa5965cc..669f931db 100644 --- a/src/a12/net/dir_cl.c +++ b/src/a12/net/dir_cl.c @@ -180,8 +180,31 @@ static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag) static void on_cl_event( struct arcan_shmif_cont* cont, int chid, struct arcan_event* ev, void* tag) { + struct ioloop_shared* I = tag; + /* main use would be the appl- runner forwarding messages that direction */ a12int_trace(A12_TRACE_DIRECTORY, "event=%s", arcan_shmif_eventstr(ev, NULL, 0)); + +/* we do have an ongoing transfer (--push-appl) that we wait for an OK or cancel + * before marking that we're ready to shutdown */ + if (I->cbt->in_transfer && + ev->category == EVENT_EXTERNAL && + ev->ext.kind == EVENT_EXTERNAL_STREAMSTATUS){ + if (ev->ext.streamstat.identifier == I->cbt->transfer_id){ + a12int_trace(A12_TRACE_DIRECTORY, + "streamstatus:progress=%f:id=%f", + ev->ext.streamstat.completion, + (int)ev->ext.streamstat.identifier + ); + if (ev->ext.streamstat.completion < 0 || + ev->ext.streamstat.completion >= 0.999){ + I->shutdown = true; + } + } + else + a12int_trace(A12_TRACE_DIRECTORY, + "streamstatus:unknown=%d", (int)ev->ext.streamstat.identifier); + } } static int cleancb( @@ -211,31 +234,6 @@ static bool clean_appldir(const char* name, int basedir) return 0 == nftw(name, cleancb, 32, FTW_DEPTH | FTW_PHYS); } -static bool ensure_appldir(const char* name, int basedir) -{ -/* this should also ensure that we have a correct statedir - * and try to retrieve it if possible */ - if (-1 != basedir){ - fchdir(basedir); - } - - char buf[strlen(name) + sizeof(".new")]; - if (atomic_load(&active_appls.n_active) > 0){ - snprintf(buf, sizeof(buf), "%s.new", name); - name = buf; - } - -/* make sure we don't have a collision */ - clean_appldir(name, basedir); - - if (-1 == mkdir(name, S_IRWXU) || -1 == chdir(name)){ - fprintf(stderr, "Couldn't create [basedir]/%s\n", name); - return false; - } - - return true; -} - struct default_meta { const char* bin; char* key; @@ -321,6 +319,8 @@ static pid_t exec_cpath(struct a12_state* S, if (ctx->key) setenv(ctx->key, ctx->val, 1); + fchdir(dir->clopt->basedir); + setsid(); setenv("XDG_RUNTIME_DIR", "./", 1); dup2(pstdin[0], STDIN_FILENO); @@ -358,7 +358,7 @@ static void swap_appldir(const char* name, int basedir) } nftw(name, cleancb, 32, FTW_DEPTH | FTW_PHYS); - rename(buf, name); + renameat(basedir, buf, basedir, name); } /* @@ -621,6 +621,8 @@ void* appl_runner(void* tag) cbt->state_in_complete = false; } + swap_appldir(cbt->clopt->applname, cbt->clopt->basedir); + /* appl_runner hooks into ios->userfd and attaches the event handler * which translates commands and eventually serializes / forwards state */ handover_exec(S, state_in); @@ -650,26 +652,28 @@ static void mark_xfer_complete(struct ioloop_shared* I, struct a12_bhandler_meta /* signs of foul-play - we recieved completion notices without init.. */ if (!cbt->appl_out){ - fprintf(stderr, "xfer completed on blob without an active state"); + fprintf(stderr, "xfer completed on blob without an active state\n"); I->shutdown = true; return; } -/* EOF the unpack action now */ - if (pclose(cbt->appl_out) != EXIT_SUCCESS){ - fprintf(stderr, "xfer download unpack failed"); +/* extract into 'newname' first, then we swap it before launch */ + char newname[strlen(cbt->clopt->applname) + sizeof(".new")]; + snprintf(newname, sizeof(newname), "%s.new", cbt->clopt->applname); + const char* msg; + if (!extract_appl_pkg(cbt->appl_out, cbt->clopt->basedir, newname, &msg)){ + fprintf(stderr, "unpack appl failed: %s\n", msg); I->shutdown = true; return; } + cbt->appl_out = NULL; cbt->appl_out_complete = false; -/* Setup so it is threadable, though unlikely that useful versus just having - * userfd and multiplex appl-a12 that way. It would be for the case of running - * multiple appls over the same channel so keep that door open. Right now we - * work on the idea that if a new appl is received we should just force the - * other end to reload. We do this by queueing the command, waking the watchdog - * and waiting for it to reply to us via userfd */ +/* if there is already an appl running we wnat to swap it out, in order to do + * that we want to make sure there are no file-system races. this is done by + * sending a lock command, waiting for that to be acknowledged when in the + * handler rename. */ if (atomic_load(&active_appls.n_active) > 0){ fprintf(active_appls.active.pf_stdin, "lock\n"); kill(active_appls.active.pid, SIGUSR1); @@ -677,6 +681,13 @@ static void mark_xfer_complete(struct ioloop_shared* I, struct a12_bhandler_meta return; } +/* Setup so it is threadable, though unlikely that useful versus just having + * userfd and multiplex appl-a12 that way. It would be for the case of running + * multiple appls over the same channel so keep that door open. Right now we + * work on the idea that if a new appl is received we should just force the + * other end to reload. We do this by queueing the command, waking the watchdog + * and waiting for it to reply to us via userfd */ + atomic_fetch_add(&active_appls.n_active, 1); active_appls.active.ios = I; @@ -750,21 +761,26 @@ struct a12_bhandler_res anet_directory_cl_bhandler( * * In the case of an appl we should verify that we wanted hot reloading, * ensure_appldir into new and set atomic-swap on completion. */ - if (!ensure_appldir(cbt->clopt->applname, cbt->clopt->basedir)){ - fprintf(stderr, "Couldn't create temporary appl directory\n"); + if (-1 == cbt->clopt->basedir){ + snprintf(cbt->clopt->basedir_path, PATH_MAX, "%s", "/tmp/appltemp-XXXXXX"); + if (!mkdtemp(cbt->clopt->basedir_path)){ + fprintf(stderr, "Couldn't build a temporary storage base\n"); + return res; + } + cbt->clopt->basedir = open(cbt->clopt->basedir_path, O_DIRECTORY); + } + + char filename[] = "appltemp-XXXXXX"; + int appl_fd = mkstemp(filename); + if (-1 == appl_fd){ + fprintf(stderr, "Couldn't create temporary appl- unpack store\n"); return res; } + unlink(filename); - cbt->appl_out = popen("tar xfm -", "w"); - res.fd = fileno(cbt->appl_out); + cbt->appl_out = fdopen(appl_fd, "rw"); res.flag = A12_BHANDLER_NEWFD; - -/* these should really just enforce basedir and never use relative, it is - * just some initial hack thing that survived. */ - if (-1 != cbt->clopt->basedir) - fchdir(cbt->clopt->basedir); - else - chdir(".."); + res.fd = appl_fd; } break; @@ -841,6 +857,9 @@ static bool cl_got_dir(struct ioloop_shared* I, struct appl_meta* dir) "push-no-match:name=%s", cbt->clopt->outapp.appl.name); } + cbt->transfer_id = cbt->clopt->outapp.identifier; + cbt->in_transfer = true; + a12_enqueue_blob(I->S, cbt->clopt->outapp.buf, cbt->clopt->outapp.buf_sz, @@ -849,7 +868,6 @@ static bool cl_got_dir(struct ioloop_shared* I, struct appl_meta* dir) cbt->clopt->outapp.appl.name ); - I->shutdown = true; return true; } @@ -940,4 +958,12 @@ void anet_directory_cl( a12int_request_dirlist(S, !opts.die_on_list || opts.applname[0]); anet_directory_ioloop(&ioloop); + +/* if we went for setting up the basedir we clean it as well */ + if (opts.basedir_path[0]){ + rmdir(opts.basedir_path); + close(opts.basedir); + opts.basedir = -1; + opts.basedir_path[0] = '\0'; + } } diff --git a/src/a12/net/dir_srv.c b/src/a12/net/dir_srv.c index e13576fdc..2ba9c9087 100644 --- a/src/a12/net/dir_srv.c +++ b/src/a12/net/dir_srv.c @@ -501,6 +501,7 @@ static void handle_bchunk_completion(struct dircl* C, bool ok) * rebuilding the index and notifying listeners though - identity action * so volatile is no concern */ pthread_mutex_unlock(&active_clients.sync); + A12INT_DIRTRACE("dirsv:bchunk_state:appl_update=%d", cur->identifier); anet_directory_shmifsrv_set( (struct anet_dirsrv_opts*) active_clients.opts); } @@ -836,7 +837,9 @@ static void* dircl_process(void* P) handle_bchunk_req(C, (char*) ev.ext.bchunk.extensions, ev.ext.bchunk.input); } +/* bounce-back ack streamsatus */ else if (ev.ext.kind == EVENT_EXTERNAL_STREAMSTATUS){ + shmifsrv_enqueue_event(C->C, &ev, -1); if (C->pending_stream){ C->pending_stream = false; handle_bchunk_completion(C, ev.ext.streamstat.completion >= 1.0); diff --git a/src/a12/net/dir_srv_worker.c b/src/a12/net/dir_srv_worker.c index 807f3fcdb..54ba2489e 100644 --- a/src/a12/net/dir_srv_worker.c +++ b/src/a12/net/dir_srv_worker.c @@ -695,6 +695,23 @@ static struct appl_meta* find_identifier(struct appl_meta* base, unsigned id) return NULL; } +static void pair_enqueue( + struct a12_state* S, struct arcan_shmif_cont *C, struct arcan_event ev) +{ + struct evqueue_entry* rep = malloc(sizeof(struct evqueue_entry)); + + if (shmif_block_synch_request(C, ev, rep, + EVENT_EXTERNAL, + EVENT_EXTERNAL_STREAMSTATUS, + EVENT_EXTERNAL, + EVENT_EXTERNAL_STREAMSTATUS)){ + run_evqueue(S, C, rep); + } + + free_evqueue(rep); + a12_channel_enqueue(S, &ev); +} + static int request_parent_resource( struct a12_state* S, struct arcan_shmif_cont *C, const char* id, bool out) { @@ -765,32 +782,34 @@ static struct a12_bhandler_res srv_bevent( a12int_trace( A12_TRACE_DIRECTORY, "kind=status:completed:identifier=%"PRIu16, M.identifier); if (cbt->in_transfer && M.identifier == cbt->transfer_id){ + struct arcan_event sack = (struct arcan_event){ + .category = EVENT_EXTERNAL, + .ext.kind = EVENT_EXTERNAL_STREAMSTATUS, + .ext.streamstat = { + .completion = 1.0, + .identifier = M.identifier + } + }; + +/* with low enough latency and high enough server load this enqueue can be triggered + * while the event is still in flight, and we shut down before the parent gets to ack + * the update. */ cbt->in_transfer = false; - arcan_shmif_enqueue(cbt->C, - &(struct arcan_event){ - .category = EVENT_EXTERNAL, - .ext.kind = EVENT_EXTERNAL_STREAMSTATUS, - .ext.streamstat = { - .completion = 1.0, - .identifier = M.identifier - } - } - ); + pair_enqueue(cbt->S, cbt->C, sack); } break; case A12_BHANDLER_CANCELLED: if (cbt->in_transfer && M.identifier == cbt->transfer_id){ - cbt->in_transfer = false; - arcan_shmif_enqueue(cbt->C, - &(struct arcan_event){ - .category = EVENT_EXTERNAL, - .ext.kind = EVENT_EXTERNAL_STREAMSTATUS, - .ext.streamstat = { - .completion = -1, - .identifier = M.identifier - } + struct arcan_event sack = (struct arcan_event){ + .category = EVENT_EXTERNAL, + .ext.kind = EVENT_EXTERNAL_STREAMSTATUS, + .ext.streamstat = { + .completion = -1, + .identifier = M.identifier } - ); + }; + cbt->in_transfer = false; + pair_enqueue(cbt->S, cbt->C, sack); } else a12int_trace( diff --git a/src/a12/net/dir_supp.c b/src/a12/net/dir_supp.c index c23f1680a..8b8fc0e06 100644 --- a/src/a12/net/dir_supp.c +++ b/src/a12/net/dir_supp.c @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -151,50 +151,301 @@ FILE* file_to_membuf(FILE* applin, char** out, size_t* out_sz) return applbuf; } -bool build_appl_pkg(const char* name, struct appl_meta* dst, int fd) +static int comp_alpha(const FTSENT** a, const FTSENT** b) { - /* just want directories */ - fchdir(fd); + return strcmp((*a)->fts_name, (*b)->fts_name); +} + +static bool ensure_path(int cdir, const char* path) +{ + char* wrk = strdup(path); + char* tmp = wrk; + bool finished = false; + struct stat s; + + while(1){ + tmp += strspn(tmp, "/"); + tmp += strcspn(tmp, "/"); + + if (!*tmp) + finished = true; + *tmp = '\0'; + + mkdirat(cdir, wrk, S_IRWXU); - struct stat sbuf; - if (-1 == stat(name, &sbuf) || (sbuf.st_mode & S_IFMT) != S_IFDIR){ - a12int_trace(A12_TRACE_DIRECTORY, "kind=build_appl:name=%s:error=not_dir", name); + if (finished) + break; + + *tmp = '/'; + } + + free(wrk); + return finished; +} + +bool extract_appl_pkg(FILE* fin, int cdir, const char* basename, const char** msg) +{ + bool in_file = false; + fseek(fin, 0, SEEK_SET); + +/* permission header, inject as > .manifest. the main point with this file is + * to communicate frameserver needs; terminal? decode against devices? encode? + * net? all of these have different security parameters in the context of lwa + * versus other ones. */ + char line[1024]; + char* lastpath = NULL; + if (!fgets(line, 1024, fin)){ + return false; + } + struct arg_arr* args = arg_unpack(line); + if (!args){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl_fmt:missing=manifest"); + *msg = "broken manifest"; return false; } - chdir(name); - struct appl_meta* res = malloc(sizeof(struct appl_meta)); - if (!res){ - fchdir(fd); + mkdirat(cdir, basename, S_IRWXU); + int bdir = openat(cdir, basename, O_DIRECTORY); + if (-1 == bdir){ + a12int_trace(A12_TRACE_DIRECTORY, "permission=open_basedir"); + *msg = "couldn't open basedir"; return false; } - *res = (struct appl_meta){0}; + int fd = openat(bdir, ".manifest", O_CREAT | O_TRUNC | O_RDWR, 0600); + arg_cleanup(args); + if (-1 == fd){ + a12int_trace(A12_TRACE_ALLOC, "failed_open_manifest"); + *msg = "couldn't create .manifest"; + return false; + } + FILE* fout = fdopen(fd, "w"); + fputs(line, fout); + fclose(fout); + + while(!feof(fin)){ + if (!fgets(line, 1024, fin)){ + break; + } + + size_t len = strlen(line); + if (!len){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl:invalid=entry"); + *msg = "invalid file entry header"; + break; + } - size_t buf_sz; - FILE* cmd = popen("tar cf - .", "r"); + line[len-1] = '\0'; + struct arg_arr* args = arg_unpack(line); + if (!args){ + break; + } - dst->handle = file_to_membuf(cmd, &dst->buf, &buf_sz); - if (cmd) - pclose(cmd); +/* need path, name and size */ + const char* path; + const char* name; + const char* size; + in_file = true; - if (!dst->handle){ - fchdir(fd); - free(res); - return false; + if (!arg_lookup(args, "path", 0, &path) || !path){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl_fmt:missing=path"); + *msg = "broken path entry in file header"; + break; + } + + if (!arg_lookup(args, "name", 0, &name) || !name){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl_fmt:missing=name"); + *msg = "broken name entry in file header"; + break; + } + + if (!arg_lookup(args, "size", 0, &size) || !size){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl_fmt:missing=size"); + *msg = "missing size in file header"; + break; + } + + char* errp = NULL; + size_t ntc = strtoul(size, &errp, 10); + if (errp && *errp != '\0'){ + a12int_trace(A12_TRACE_DIRECTORY, "malformed_appl_fmt:invalid=size"); + *msg = "invalid size entry in file header"; + break; + } + + size_t plen = strlen(path); +/* file comes sorted on path and name, only mkdir chain on new */ + if ((!lastpath || strcmp(path, lastpath)) != 0 && plen > 0){ + free(lastpath); + lastpath = strdup(path); + ensure_path(bdir, path); + } + +/* the other option to extracting like this is parsing into an index of offsets + * and using that with resource resolving calls inside of arcan. the + * precondition to this is the server-side 'shared resource' loading which + * ensures that we have virtualisation of all key api calls. */ + char* fn; + if (!plen) + fn = strdup(name); + else if (-1 == asprintf(&fn, "%s/%s", path, name)){ + fn = NULL; + } + + if (!fn){ + a12int_trace(A12_TRACE_ALLOC, "failed_asprintf_path"); + *msg = "couldn't allocate path"; + break; + } + +/* might want to check for duplicates here, fn shouldn't exist and if it does + * it is a sign that there are collisions in the file itself. */ + int fd = openat(bdir, fn, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); + if (-1 == fd){ + a12int_trace(A12_TRACE_ALLOC, "failed_open_creat=%s", fn); + *msg = "couldn't create file"; + break; + } + free(fn); + fout = fdopen(fd, "w"); + + while (ntc){ + char buf[4096]; + size_t nr = fread(buf, 1, 4096 > ntc ? ntc : 4096, fin); + if (!nr){ + break; + } + fwrite(buf, 1, nr, fout); + ntc -= nr; + } + + if (ntc){ + a12int_trace(A12_TRACE_DIRECTORY, + "malformed_appl:invalid=size:name=%s", fn); + *msg = "truncated / corrupted package"; + break; + } + + fclose(fout); + in_file = false; } - snprintf(dst->appl.name, COUNT_OF(dst->appl.name), "%s", name); - dst->buf_sz = buf_sz; - dst->next = res; + close(bdir); + return feof(fin) && !in_file; +} + +bool build_appl_pkg(const char* name, struct appl_meta* dst, int cdir) +{ + FILE* fpek = NULL; + FTS* fts = NULL; + + int olddir = open(".", O_DIRECTORY); + + char* path[] = {".", NULL}; + fchdir(cdir); + chdir(name); + + if (!(fpek = open_memstream(&dst->buf, &dst->buf_sz))) + goto err; + + if (!(fts = fts_open(path, FTS_PHYSICAL, comp_alpha))) + goto err; + +/* for extended permissions -- net,frameserver,... the .manifest file needs to + * be present, follow the regular arg_arr pack/unpack format and specify which + * ones it needs. */ + FILE* header = fopen(".manifest", "r"); + if (header){ + char buf[256]; + if (!fgets(buf, 256, header)){ + a12int_trace(A12_TRACE_DIRECTORY, "build_appl:error=cant_read_manifest"); + fclose(header); + goto err; + } + + struct arg_arr* args = arg_unpack(buf); + if (!args){ + a12int_trace(A12_TRACE_DIRECTORY, "build_appl:error=cant_parse_manifest"); + fclose(header); + goto err; + } + + fprintf(fpek, "version=1:permission=restricted\n"); + + arg_cleanup(args); + fclose(header); + } + else + fprintf(fpek, "version=1:permission=restricted\n"); + +/* walk and get list of files, lexicographic sort, filter out links, + * cycles, dot files */ + for (FTSENT* cur = fts_read(fts); cur; cur = fts_read(fts)){ +/* possibly allow-list files here, at least make sure it's valid UTF8 as well + * as not any reserved (/, ., :, \t, \n) symbols */ + if (cur->fts_name[0] == '.'){ + continue; + } + + if (cur->fts_info != FTS_F){ + continue; + } + + FILE* fin = fopen(cur->fts_name, "r"); + if (!fin){ + a12int_trace(A12_TRACE_DIRECTORY, + "build_app:error=cant_open:name=%s:path=%s", cur->fts_name, cur->fts_path); + goto err; + } + + /* read all of fin into a memory buffer then */ + char* fbuf; + size_t fbuf_sz; + FILE* fbuf_f = file_to_membuf(fin, &fbuf, &fbuf_sz); + if (!fbuf_f){ + fclose(fin); + goto err; + } + fclose(fin); + fclose(fbuf_f); + cur->fts_path[cur->fts_pathlen - cur->fts_namelen - 1] = '\0'; + fprintf(fpek, + "path=%s:name=%s:size=%zu\n", + strcmp(cur->fts_path, ".") == 0 ? "" : + &cur->fts_path[2], + cur->fts_name, fbuf_sz + ); + cur->fts_path[cur->fts_pathlen - cur->fts_namelen - 1] = '/'; + fflush(fpek); + fwrite(fbuf, fbuf_sz, 1, fpek); + free(fbuf); + } + + fts_close(fts); + fclose(fpek); blake3_hasher hash; blake3_hasher_init(&hash); blake3_hasher_update(&hash, dst->buf, dst->buf_sz); blake3_hasher_finalize(&hash, (uint8_t*)dst->hash, 4); - fchdir(fd); + snprintf(dst->appl.name, COUNT_OF(dst->appl.name), "%s", name); + + dst->next = malloc(sizeof(struct appl_meta)); + *(dst->next) = (struct appl_meta){0}; + fchdir(olddir); + close(olddir); return true; -} +err: + if (fpek) + fclose(fpek); + if (fts) + fts_close(fts); + free(dst->buf); + dst->buf = NULL; + fchdir(olddir); + close(olddir); + return false; +} diff --git a/src/a12/net/directory.h b/src/a12/net/directory.h index 72fe8aae0..7e9433f9d 100644 --- a/src/a12/net/directory.h +++ b/src/a12/net/directory.h @@ -36,6 +36,8 @@ struct anet_dirsrv_opts { struct directory_meta; struct anet_dircl_opts { int basedir; + char basedir_path[PATH_MAX]; + char applname[16]; uint16_t applid; @@ -105,6 +107,8 @@ struct a12_bhandler_res anet_directory_cl_bhandler( * and forward on_event, on_directory, on_userfd */ bool build_appl_pkg(const char* name, struct appl_meta* dst, int dirfd); +bool extract_appl_pkg(FILE* fin, int dirfd, const char* basename, const char** msg); + FILE* file_to_membuf(FILE* applin, char** out, size_t* out_sz); struct ioloop_shared; diff --git a/src/frameserver/net/default/net.c b/src/frameserver/net/default/net.c index 3408edaa2..a49e98aa1 100644 --- a/src/frameserver/net/default/net.c +++ b/src/frameserver/net/default/net.c @@ -506,9 +506,21 @@ static pid_t dircl_exec(struct a12_state* S, buf, NULL /* applname */ }; + char* envv[] = { + "PATH", getenv("PATH"), + "HOME", getenv("HOME"), + "TERM", getenv("TERM"), + "SHELL", getenv("SHELL"), + "ARCAN_LOGPATH", getenv("ARCAN_LOGPATH"), + "ARCAN_RESOURCEPATH", getenv("ARCAN_RESOURCEPATH"), + "ARCAN_STATEPATH", getenv("ARCAN_STATEPATH"), + "XDG_RUNTIME_DIR", getenv("XDG_RUNTIME_DIR"), + NULL, + }; + int* fds[4] = {inf, outf, NULL, &pstdout[1]}; pid_t res = - arcan_shmif_handover_exec_pipe(C, client->segev, lwabin, argv, NULL, 0, fds, 4); + arcan_shmif_handover_exec_pipe(C, client->segev, lwabin, argv, envv, 0, fds, 4); free(lwabin); close(pstdout[1]); @@ -547,6 +559,14 @@ void req_id(struct ioloop_shared* I, uint16_t identifier) client->appl.statefd = -1; } +static void cl_got_dyn(struct a12_state* S, int type, + const char* petname, bool found, uint8_t pubk[static 32], void* tag) +{ + struct arcan_shmif_cont* C = arcan_shmif_primary(SHMIF_INPUT); +/* convert back to NETSTATE and send to arcan */ +/* remember petname and pubk pairing */ +} + /* returning false here would break us out of the ioloop */ static bool dircl_dirent(struct ioloop_shared* I, struct appl_meta* M) { @@ -588,6 +608,12 @@ static bool dircl_dirent(struct ioloop_shared* I, struct appl_meta* M) return true; } +static void find_source(struct arcan_shmif_cont* C, const char* msg) +{ +/* Sweep the known set of sources and pair petname to pubk as we need + * to use the pubk in the source request. */ +} + static void dircl_userfd(struct ioloop_shared* I, bool ok) { /* just flush the regular path unless we're blocked */ @@ -610,6 +636,12 @@ static void dircl_userfd(struct ioloop_shared* I, bool ok) case TARGET_COMMAND_MESSAGE:{ LOG("shmif:message=%s", ev.tgt.message); char* err = NULL; + + if (ev.tgt.message[0] == '/'){ + find_source(C, (char*) &ev.tgt.message[1]); + continue; + } + long id = strtoul(ev.tgt.message, &err, 10); if (*err != '\0' || id < 0 || id > 65535){ LOG("shmif:bad_req_id"); @@ -655,31 +687,16 @@ static int dircl_loop( struct dircl_meta dmeta = { }; - char templ[] = "/tmp/afsrv_net_XXXXXX"; - char* tempdir = mkdtemp(templ); - - if (!tempdir){ - arcan_shmif_last_words(C, "Tempdir couldn't be created"); - LOG("storage directory rejected"); - return EXIT_FAILURE; - } - int dfd = open(tempdir, O_DIRECTORY); - if (-1 == dfd){ - arcan_shmif_last_words(C, "Couldn't open tempdir"); - LOG("storage directory open fail"); - return EXIT_FAILURE; - } - struct anet_dircl_opts clcfg = { .allocator = dircl_alloc, .executor = dircl_exec, - .basedir = dfd, + .basedir = -1, /* let unpack generate tmp folder */ }; struct directory_meta dircfg = { .S = A->state, .clopt = &clcfg, - .state_in = -1 + .state_in = -1, }; struct ioloop_shared ioloop = { @@ -694,15 +711,12 @@ static int dircl_loop( .cbt = &dircfg, }; + A->state->on_discover = cl_got_dyn; + C->user = &dmeta; - a12_set_bhandler(A->state, anet_directory_cl_bhandler, &dircfg); + a12_set_bhandler(A->state, anet_directory_cl_bhandler, &ioloop); anet_directory_ioloop(&ioloop); - if (0 != rmdir(tempdir)){ - LOG("rmdir(%s) failed: %s", tempdir, strerror(errno)); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; } diff --git a/src/shmif/arcan_shmif_control.c b/src/shmif/arcan_shmif_control.c index 435ebd85e..4bbd0d2ec 100644 --- a/src/shmif/arcan_shmif_control.c +++ b/src/shmif/arcan_shmif_control.c @@ -3059,6 +3059,8 @@ static char* spawn_arcan_net(const char* conn_src, int* dsock) pid_t pid = fork(); if (pid == 0){ if (0 == fork()){ + sigaction(SIGINT, &(struct sigaction){}, NULL); + if (weak){ execlp("arcan-net", "arcan-net", "-X", "--ident", ident, "--soft-auth",