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",