1 /*
2 * Copyright 2014-2016, Björn Ståhl
3 * License: 3-Clause BSD, see COPYING file in arcan source repository.
4 * Reference: http://arcan-fe.com
5 */
6
7 #include <stdio.h>
8 #include <stdint.h>
9 #include <stdbool.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <errno.h>
14 #include <assert.h>
15 #include <dirent.h>
16
17 #include <sys/types.h>
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <sys/socket.h>
21 #include <sys/uio.h>
22 #include <sys/time.h>
23 #include <signal.h>
24 #include <fcntl.h>
25 #include <time.h>
26 #include <dlfcn.h>
27
28 #include <arcan_shmif.h>
29 #include "frameserver.h"
30
31 #ifndef AFSRV_CHAINLOADER
close_logdev()32 static void close_logdev()
33 {
34 fflush(stderr);
35 }
36
toggle_logdev(const char * prefix)37 static void toggle_logdev(const char* prefix)
38 {
39 const char* const logdir = getenv("ARCAN_FRAMESERVER_LOGDIR");
40
41 if (!prefix || !logdir)
42 return;
43
44 char timeb[16];
45 time_t t = time(NULL);
46 struct tm* basetime = localtime(&t);
47 strftime(timeb, sizeof(timeb)-1, "%y%m%d_%H%M", basetime);
48
49 size_t logbuf_sz = strlen(logdir) +
50 sizeof("/fsrv__yymmddhhss__65536.txt") + strlen(prefix);
51 char* logbuf = malloc(logbuf_sz);
52
53 snprintf(logbuf, logbuf_sz,
54 "%s/fsrv_%s_%s_%d.txt", logdir, prefix, timeb, (int)getpid());
55 if (!freopen(logbuf, "a", stderr)){
56 if (!freopen("/dev/null", "a", stderr))
57 fclose(stderr);
58 }
59 }
60 #endif
61
dumpargs(int argc,char ** argv)62 static void dumpargs(int argc, char** argv)
63 {
64 printf("invalid number of arguments (%d):\n", argc);
65 printf("[1 mode] : %s\n", argc > 1 && argv[1] ? argv[1] : "");
66 printf("environment (ARCAN_ARG) : %s\n",
67 getenv("ARCAN_ARG") ? getenv("ARCAN_ARG") : "");
68 printf("environment (ARCAN_SOCKIN_FD) : %s\n",
69 getenv("ARCAN_SOCKIN_FD") ? getenv("ARCAN_SOCKIN_FD") : "");
70 printf("environment (ARCAN_CONNPATH) : %s\n",
71 getenv("ARCAN_CONNPATH") ? getenv("ARCAN_CONNPATH") : "");
72 printf("environment (ARCAN_CONNKEY) : %s\n",
73 getenv("ARCAN_CONKEY") ? getenv("ARCAN_CONNKEY") : "");
74 }
75
76 #if defined(_DEBUG) && !defined(__APPLE__) && !defined(__BSD)
77
dump_links(const char * path)78 void dump_links(const char* path)
79 {
80 DIR* dp;
81 struct dirent64* dirp;
82
83 if ((dp = opendir(path)) == NULL)
84 return;
85
86 int fd = dirfd(dp);
87
88 while((dirp = readdir64(dp)) != NULL){
89 if (strcmp(dirp->d_name, ".") == 0)
90 continue;
91 else if (strcmp(dirp->d_name, "..") == 0)
92 continue;
93
94 char buf[256];
95 buf[255] = '\0';
96
97 ssize_t nr = readlinkat(fd, dirp->d_name, buf, sizeof(buf)-1);
98 if (-1 == nr)
99 continue;
100
101 buf[nr] = '\0';
102 fprintf(stdout, "\t%s\n", buf);
103 }
104
105 closedir(dp);
106 }
107 #endif
108
109 /*
110 * When built as a chainloader we select a different binary (our own
111 * name or afsrv if we're arcan_frameserver) + _mode and just pass
112 * the environment onwards. The afsrv_ split permits the parent to run
113 * with a different set of frameservers for debugging/testing/etc. purposes.
114 *
115 * The chainloading approach is to get a process separated spot for
116 * implementing monitoring, sandboxing and other environment related factors
117 * where we might have temporarily inflated privileges.
118 */
119 #ifdef AFSRV_CHAINLOADER
main(int argc,char ** argv)120 int main(int argc, char** argv)
121 {
122 if (2 != argc){
123 dumpargs(argc, argv);
124 return EXIT_FAILURE;
125 }
126
127 if (!argv[0])
128 return EXIT_FAILURE;
129
130 size_t i = strlen(argv[0])-1;
131 for (; i && argv[0][i] == '/'; i--) argv[0][i] = 0;
132 for (; i && argv[0][i-1] != '/'; i--);
133 if (i)
134 argv[0][i-1] = '\0';
135
136 const char* dirn = argv[0];
137 const char* base = argv[0] + i;
138 const char* mode = argv[1];
139
140 if (strcmp(base, "arcan_frameserver") == 0)
141 base = "afsrv";
142
143 size_t bin_sz = strlen(dirn) + strlen(base) + strlen(argv[1]) + 3;
144 char newarg[ bin_sz ];
145 snprintf(newarg, bin_sz, "%s/%s_%s", dirn, base, argv[1]);
146
147 /*
148 * the sweet-spot for adding in privilege/uid/gid swapping and setting up mode-
149 * specific sandboxing, package format mount (or other compatibility / loading)
150 * etc. When that is done, ofc. add more stringent control over what newarg
151 * turns out to be.
152 */
153
154 /* we no longer need the mode argument */
155 argv[1] = NULL;
156 argv[0] = newarg;
157
158 setsid();
159
160 execv(newarg, argv);
161
162 return EXIT_FAILURE;
163 }
164
165 #else
166 typedef int (*mode_fun)(struct arcan_shmif_cont*, struct arg_arr*);
167
launch_mode(const char * modestr,mode_fun fptr,enum ARCAN_SEGID id,enum ARCAN_FLAGS flags,char * altarg)168 int launch_mode(const char* modestr,
169 mode_fun fptr, enum ARCAN_SEGID id, enum ARCAN_FLAGS flags, char* altarg)
170 {
171 char* debug = getenv("ARCAN_FRAMESERVER_DEBUGSTALL");
172
173 if (!debug)
174 toggle_logdev(modestr);
175
176 struct arg_arr* arg = NULL;
177 struct arcan_shmif_cont con = arcan_shmif_open_ext(flags,
178 &arg, (struct shmif_open_ext){.type = id}, sizeof(struct shmif_open_ext));
179
180 if (!arg && altarg)
181 arg = arg_unpack(altarg);
182
183 if (debug){
184 arcan_shmif_signal(&con, SHMIF_SIGVID);
185 int sleeplen = strtoul(debug, NULL, 10);
186
187 struct arcan_event ev = {
188 .ext.kind = ARCAN_EVENT(IDENT)
189 };
190 arcan_shmif_enqueue(&con, &ev);
191 snprintf(
192 (char*)ev.ext.message.data,
193 sizeof(ev.ext.message.data), "debugstall:%d:%zu", sleeplen, (size_t)getpid()
194 );
195
196 if (sleeplen <= 0){
197 fprintf(stdout, "\x1b[1mARCAN_FRAMESERVER_DEBUGSTALL,\x1b[0m "
198 "spin-waiting for debugger.\n \tAttach to pid: "
199 "\x1b[32m%d\x1b[39m\x1b[0m and break out of loop"
200 " (set loop = 0)\n", getpid()
201 );
202
203 volatile int loop = 1;
204 while(loop == 1);
205 }
206 else{
207 fprintf(stdout,"\x1b[1mARCAN_FRAMESERVER_DEBUGSTALL set, waiting %d s.\n"
208 "\tfor debugging/tracing, attach to pid: \x1b[32m%d\x1b[39m\x1b[0m\n",
209 sleeplen, (int) getpid());
210 sleep(sleeplen);
211 }
212 }
213
214 return fptr(con.addr ? &con : NULL, arg);
215 }
216
main(int argc,char ** argv)217 int main(int argc, char** argv)
218 {
219 #ifdef DEFAULT_FSRV_MODE
220 char* fsrvmode = DEFAULT_FSRV_MODE;
221 char* argstr = argc > 1 ? argv[1] : NULL; /* optional */
222 #else
223 /* non-split mode arguments require [mode + opt-arg ] */
224 char* fsrvmode = argv[1];
225 char* argstr = argc > 2 ? argv[2] : NULL;
226 #endif
227
228 /*
229 * Monitor for descriptor leaks from parent
230 */
231 #if defined(_DEBUG) && !defined(__APPLE__) && !defined(__BSD)
232 DIR* dp;
233 struct dirent64* dirp;
234 if ((dp = opendir("/proc/self/fd")) != NULL){
235 size_t desc_count = 0;
236
237 while((dirp = readdir64(dp)) != NULL) {
238 if (strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0)
239 desc_count++;
240 }
241 closedir(dp);
242
243 /* stdin, stdout, stderr, [connection socket], any more
244 * and we should be suspicious about descriptor leakage */
245 if (desc_count > 5){
246 fprintf(stdout, "\x1b[1msuspicious amount (%zu)"
247 "of descriptors open, investigate.\x1b[0m\n", desc_count);
248
249 dump_links("/proc/self/fd");
250 }
251 }
252 #endif
253
254 /*
255 * These are enabled based on build-system toggles,
256 * a global define, FRAMESERVER_MODESTRING includes a space
257 * separated list of enabled frameserver archetypes.
258 */
259 #ifdef ENABLE_FSRV_DECODE
260 if (strcmp(fsrvmode, "decode") == 0)
261 return launch_mode("decode",
262 afsrv_decode, SEGID_MEDIA,
263 SHMIF_MANUAL_PAUSE | SHMIF_NOACTIVATE_RESIZE, argstr
264 );
265 #endif
266
267 #ifdef ENABLE_FSRV_TERMINAL
268 if (strcmp(fsrvmode, "terminal") == 0)
269 return launch_mode("terminal", afsrv_terminal, SEGID_TERMINAL, 0, argstr);
270 #endif
271
272 #ifdef ENABLE_FSRV_ENCODE
273 if (strcmp(fsrvmode, "encode") == 0)
274 return launch_mode("encode", afsrv_encode, SEGID_ENCODER, 0, argstr);
275 #endif
276
277 #ifdef ENABLE_FSRV_REMOTING
278 if (strcmp(fsrvmode, "remoting") == 0)
279 return launch_mode("remoting", afsrv_remoting, SEGID_REMOTING, 0, argstr);
280 #endif
281
282 #ifdef ENABLE_FSRV_GAME
283 if (strcmp(fsrvmode, "game") == 0)
284 return launch_mode("game", afsrv_game,
285 SEGID_GAME, SHMIF_NOACTIVATE_RESIZE, argstr);
286 #endif
287
288 #ifdef ENABLE_FSRV_AVFEED
289 if (strcmp(fsrvmode, "avfeed") == 0)
290 return launch_mode("avfeed", afsrv_avfeed,
291 SEGID_MEDIA, SHMIF_DISABLE_GUARD, argstr);
292 #endif
293
294 /*
295 * NET- is a bit special in that it encompasses multiple submodes
296 * and may need to support multiple more (p2p-node etc.)
297 * which may need different IDs, so we do a preliminary arg-unpack
298 * in beforehand.
299 */
300 #ifdef ENABLE_FSRV_NET
301 if (strcmp(fsrvmode, "net") == 0){
302 struct arg_arr* tmp = arg_unpack(getenv("ARCAN_ARG"));
303 const char* rk;
304
305 if (!tmp)
306 tmp = arg_unpack(argstr);
307
308 enum ARCAN_SEGID id;
309 mode_fun fptr;
310 const char* modestr = NULL;
311
312 if (tmp && arg_lookup(tmp, "mode", 0, &rk)){
313 if (strcmp(rk, "client") == 0){
314 id = SEGID_NETWORK_CLIENT;
315 fptr = afsrv_netcl;
316 modestr = "client";
317 }
318 else if (strcmp(rk, "server") == 0){
319 id = SEGID_NETWORK_SERVER;
320 fptr = afsrv_netsrv;
321 modestr = "server";
322 }
323 else{
324 fprintf(stdout, "frameserver_net, invalid ARCAN_ARG env:\n"
325 "must have mode=modev set to client or server.\n");
326 return EXIT_FAILURE;
327 }
328 }
329 /* will invalidate all aliases from _lookup */
330 arg_cleanup(tmp);
331
332 if (!modestr){
333 fprintf(stdout, "frameserver_net, invalid ARCAN_ARG env:\n"
334 "must have mode=modev set to client or server.\n");
335 return EXIT_FAILURE;
336 }
337
338 return launch_mode(modestr, fptr, id, 0, argstr);
339 }
340 #endif
341
342 printf("frameserver launch failed, unsupported mode (%s)\n", fsrvmode);
343 dumpargs(argc, argv);
344
345 return EXIT_FAILURE;
346 }
347 #endif
348