imcplugin: Set NACL_FD environment variable
[nativeclient.git] / imcplugin / imcplugin.c
blob7dfe8b4675cd9bb62d614b0c187d413a202661db
2 #include <stdio.h>
3 #include <string.h>
5 #include <fcntl.h>
6 #include <pthread.h>
7 #include <unistd.h>
9 #include <npapi.h>
10 #include <npupp.h>
11 #include <npruntime.h>
13 #include "native_client/intermodule_comm/nacl_imc_c.h"
14 #include "native_client/service_runtime/include/sys/fcntl.h"
15 #include "native_client/service_runtime/nacl_desc_base.h"
16 #include "native_client/service_runtime/nacl_desc_imc.h"
17 #include "native_client/service_runtime/nacl_desc_io.h"
18 #include "native_client/service_runtime/nrd_xfer_lib/nrd_all_modules.h"
19 #include "native_client/service_runtime/nrd_xfer_lib/nrd_xfer.h"
20 #include "native_client/service_runtime/nrd_xfer_lib/nrd_xfer_effector.h"
23 #define print(x)
26 static NPNetscapeFuncs g_funcs;
27 static NPIdentifier ident_launch;
28 static NPIdentifier ident_get_file;
29 static NPIdentifier ident_send;
30 static NPIdentifier ident_length;
32 #define NPN_CreateObject (g_funcs.createobject)
33 #define NPN_RetainObject (g_funcs.retainobject)
34 #define NPN_ReleaseObject (g_funcs.releaseobject)
35 #define NPN_ReleaseVariantValue (g_funcs.releasevariantvalue)
36 #define NPN_InvokeDefault (g_funcs.invokeDefault)
37 #define NPN_GetProperty (g_funcs.getproperty)
38 #define NPN_UTF8FromIdentifier (g_funcs.utf8fromidentifier)
39 #define NPN_GetStringIdentifier (g_funcs.getstringidentifier)
40 #define NPN_GetIntIdentifier (g_funcs.getintidentifier)
41 #define NPN_GetURLNotify (g_funcs.geturlnotify)
42 #define NPN_PluginThreadAsyncCall (g_funcs.pluginthreadasynccall)
45 static void PrintIdentifier(NPIdentifier ident)
47 char *str = NPN_UTF8FromIdentifier(ident);
48 print((" ident = \"%s\"\n", str));
49 free(str);
52 static bool
53 IsType(NPObject *npobj, NPClass *npclass)
55 /* The underscore in "_class" suggests this member is not part of
56 the public API, but it could just be to prevent a clash with the
57 C++ keyword. */
58 return npobj->_class == npclass;
62 /* Mozilla does not provide default behaviour for these NoOp_* methods
63 if NULL is provided instead in the NPClass. */
65 static bool
66 NoOp_HasMethod(NPObject* obj, NPIdentifier methodName)
68 return false;
71 static bool
72 NoOp_HasProperty(NPObject *obj, NPIdentifier propertyName)
74 print(("Obj_HasProperty\n"));
75 PrintIdentifier(propertyName);
76 return false;
79 static bool
80 NoOp_GetProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result)
82 print(("Obj_GetProperty\n"));
83 return false;
87 struct FileObj {
88 struct NPObject header;
89 struct NaClDesc *desc;
90 /* We keep a filename to pass to sel_ldr, but there's a chance the
91 file could be deleted from the cache.
92 TODO: pass FDs to sel_ldr instead. */
93 char *filename;
96 static NPObject *
97 FileObj_Allocate(NPP npp, NPClass *npclass)
99 print(("Obj_Allocate\n"));
100 struct FileObj *obj = malloc(sizeof(struct FileObj));
101 return &obj->header;
104 static void
105 FileObj_Deallocate(NPObject *npobj)
107 struct FileObj *obj = (struct FileObj *) npobj;
108 NaClDescUnref(obj->desc);
109 free(obj->filename);
110 free(obj);
111 print(("FileObj_Deallocate\n"));
114 static NPClass FileObj_Vtable = {
115 .structVersion = NP_CLASS_STRUCT_VERSION,
116 .allocate = FileObj_Allocate,
117 .deallocate = FileObj_Deallocate,
118 .invalidate = NULL,
119 .hasMethod = NoOp_HasMethod,
120 .invoke = NULL,
121 .invokeDefault = NULL,
122 .hasProperty = NoOp_HasProperty,
123 .getProperty = NoOp_GetProperty,
124 .setProperty = NULL,
125 .removeProperty = NULL,
129 struct SendObj {
130 struct NPObject header;
131 NPP plugin;
132 struct NaClDesc *desc;
135 static NPObject *
136 SendObj_Allocate(NPP npp, NPClass *npclass)
138 print(("Obj_Allocate\n"));
139 struct SendObj *obj = malloc(sizeof(struct SendObj));
140 obj->plugin = npp;
141 return &obj->header;
144 static void
145 SendObj_Deallocate(NPObject *npobj)
147 struct SendObj *obj = (struct SendObj *) npobj;
148 NaClDescUnref(obj->desc);
149 free(obj);
150 print(("SendObj_Deallocate\n"));
153 static bool
154 SendObj_HasMethod(NPObject* npobj, NPIdentifier methodName)
156 return methodName == ident_send;
159 static bool
160 ConvertDescArray(NPP plugin, NPObject *array, struct NaClImcTypedMsgHdr *header)
162 NPVariant length_np;
163 if(!NPN_GetProperty(plugin, array, ident_length, &length_np) ||
164 !NPVARIANT_IS_INT32(length_np)) {
165 NPN_ReleaseVariantValue(&length_np);
166 return false;
168 int length = NPVARIANT_TO_INT32(length_np);
169 NPN_ReleaseVariantValue(&length_np);
171 /* Firstly, there will be a limit to the number of capabilities we
172 can send in a message. Secondly, the array is not necessarily a
173 primitive Javascript array; its .length property could be much
174 larger than the amount of space Javascript is normally allowed to
175 allocate. */
176 if(length > 32)
177 return false;
179 struct NaClDesc **descs = malloc(sizeof(struct NaClDesc *) * length);
180 if(descs == NULL)
181 return false;
182 int i;
183 for(i = 0; i < length; i++) {
184 NPIdentifier index_id = NPN_GetIntIdentifier(i);
185 NPVariant element;
186 if(!NPN_GetProperty(plugin, array, index_id, &element) ||
187 !NPVARIANT_IS_OBJECT(element))
188 return false; /* TODO: clear up */
189 NPObject *file_npobj = NPVARIANT_TO_OBJECT(element);
190 if(!IsType(file_npobj, &FileObj_Vtable))
191 return false; /* TODO: clear up */
192 struct FileObj *file_obj = (struct FileObj *) file_npobj;
193 NaClDescRef(file_obj->desc);
194 descs[i] = file_obj->desc;
195 NPN_ReleaseVariantValue(&element);
197 header->ndescv = descs;
198 header->ndesc_length = length;
199 return true;
202 static bool
203 SendObj_Invoke(NPObject *npobj, NPIdentifier methodName,
204 const NPVariant *args, uint32_t argCount, NPVariant *result)
206 struct SendObj *obj = (struct SendObj *) npobj;
207 print(("Obj_Invoke %i\n", argCount));
208 PrintIdentifier(methodName);
209 if(methodName == ident_send) {
210 if(argCount == 2 &&
211 NPVARIANT_IS_STRING(args[0]) &&
212 NPVARIANT_IS_OBJECT(args[1])) {
213 NPString message_data = NPVARIANT_TO_STRING(args[0]);
214 NPObject *caps_array = NPVARIANT_TO_OBJECT(args[1]);
215 struct NaClNrdXferEffector effector;
216 if(!NaClNrdXferEffectorCtor(&effector, obj->desc))
217 return false;
218 struct NaClImcMsgIoVec iovec;
219 struct NaClImcTypedMsgHdr header;
220 if(!ConvertDescArray(obj->plugin, caps_array, &header))
221 return false;
222 /* TODO: allow sending any data, not just UTF-8 */
223 iovec.base = (char *) message_data.utf8characters;
224 iovec.length = message_data.utf8length;
225 header.iov = &iovec;
226 header.iov_length = 1;
227 header.flags = 0;
228 NaClImcSendTypedMessage(obj->desc, (struct NaClDescEffector *) &effector,
229 &header, 0);
230 int i;
231 for(i = 0; i < header.ndesc_length; i++)
232 NaClDescUnref(header.ndescv[i]);
233 free(header.ndescv);
234 effector.base.vtbl->Dtor(&effector.base);
236 VOID_TO_NPVARIANT(*result);
237 return true;
239 return false;
242 static NPClass SendObj_Vtable = {
243 .structVersion = NP_CLASS_STRUCT_VERSION,
244 .allocate = SendObj_Allocate,
245 .deallocate = SendObj_Deallocate,
246 .invalidate = NULL,
247 .hasMethod = SendObj_HasMethod,
248 .invoke = SendObj_Invoke,
249 .invokeDefault = NULL,
250 .hasProperty = NoOp_HasProperty,
251 .getProperty = NoOp_GetProperty,
252 .setProperty = NULL,
253 .removeProperty = NULL,
257 struct LauncherObj {
258 struct NPObject header;
259 NPP plugin;
262 static NPObject *
263 LauncherObj_Allocate(NPP npp, NPClass *npclass)
265 print(("Obj_Allocate\n"));
266 struct LauncherObj *obj = malloc(sizeof(struct LauncherObj));
267 obj->plugin = npp;
268 return &obj->header;
271 static bool
272 LauncherObj_HasMethod(NPObject* obj, NPIdentifier methodName)
274 print(("Obj_HasMethod %i\n", methodName == ident_launch));
275 PrintIdentifier(methodName);
276 return (methodName == ident_launch ||
277 methodName == ident_get_file);
280 struct ReceiveCallbackArgs {
281 NPP plugin;
282 NPObject *callback;
283 char buffer[1024];
284 int size;
287 static void
288 DoReceiveCallback(void *handle)
290 struct ReceiveCallbackArgs *call = handle;
291 NPVariant args[1];
292 NPVariant result;
293 /* TODO: don't assume this data is UTF-8 */
294 STRINGN_TO_NPVARIANT(call->buffer, call->size, args[0]);
295 NPN_InvokeDefault(call->plugin, call->callback, args, 1, &result);
296 NPN_ReleaseVariantValue(&result);
297 free(call);
298 /* We don't do NPN_ReleaseObject() on the callback here because we
299 couldn't do NPN_RetainObject() earlier. */
302 struct ReceiveThreadArgs {
303 NPP plugin;
304 NPObject *callback;
305 struct NaClDesc *desc;
308 static void *
309 ReceiveThread(void *handle)
311 /* We create one thread for every socket we listen on. It would be
312 more efficient to have one thread using poll(). NPAPI doesn't
313 provide a way for us to share the browser's event loop. */
314 struct ReceiveThreadArgs *args = handle;
315 struct NaClNrdXferEffector effector;
316 if(!NaClNrdXferEffectorCtor(&effector, args->desc))
317 return NULL;
318 while(true) {
319 struct ReceiveCallbackArgs *call =
320 malloc(sizeof(struct ReceiveCallbackArgs));
321 if(call == NULL)
322 break;
323 struct NaClImcMsgIoVec iovec;
324 struct NaClImcTypedMsgHdr header;
325 iovec.base = call->buffer;
326 iovec.length = sizeof(call->buffer);
327 header.iov = &iovec;
328 header.iov_length = 1;
329 header.ndescv = NULL;
330 header.ndesc_length = 0;
331 header.flags = 0;
332 int got_bytes =
333 NaClImcRecvTypedMessage(args->desc, (struct NaClDescEffector *) &effector,
334 &header, 0);
335 if(got_bytes < 0) {
336 free(call);
337 break;
339 /* We can't call NPN_RetainObject() here because it is not safe to
340 call it from this thread. We assume that
341 NPN_PluginThreadAsyncCall() schedules functions to be called in
342 order, so that the function is not freed until later. */
343 call->plugin = args->plugin;
344 call->callback = args->callback;
345 call->size = got_bytes;
346 NPN_PluginThreadAsyncCall(args->plugin, DoReceiveCallback, call);
348 effector.base.vtbl->Dtor(&effector.base);
349 NaClDescUnref(args->desc); /* Supposed to be thread safe */
350 NPN_PluginThreadAsyncCall(args->plugin, (void (*)(void *)) NPN_ReleaseObject,
351 args->callback);
352 free(args);
353 return NULL;
356 static bool
357 ConvertArgvArray(NPP plugin, NPObject *array,
358 char ***result_argv, int *result_size)
360 NPVariant length_np;
361 if(!NPN_GetProperty(plugin, array, ident_length, &length_np) ||
362 !NPVARIANT_IS_INT32(length_np)) {
363 NPN_ReleaseVariantValue(&length_np);
364 return false;
366 int length = NPVARIANT_TO_INT32(length_np);
367 NPN_ReleaseVariantValue(&length_np);
369 /* Arbitrary limit */
370 if(length > 100)
371 return false;
373 char **argv = malloc(sizeof(char *) * length);
374 if(argv == NULL)
375 return false;
376 int i;
377 for(i = 0; i < length; i++) {
378 NPIdentifier index_id = NPN_GetIntIdentifier(i);
379 NPVariant element;
380 if(!NPN_GetProperty(plugin, array, index_id, &element) ||
381 !NPVARIANT_IS_STRING(element))
382 return false; /* TODO: clear up */
383 NPString string = NPVARIANT_TO_STRING(element);
384 char *copy = malloc(string.utf8length + 1);
385 if(copy == NULL)
386 return false; /* TODO: clear up */
387 /* TODO: allow sending any data, not just UTF-8 */
388 memcpy(copy, string.utf8characters, string.utf8length);
389 copy[string.utf8length] = 0;
390 argv[i] = copy;
391 NPN_ReleaseVariantValue(&element);
393 *result_argv = argv;
394 *result_size = length;
395 return true;
398 static struct NaClDesc *
399 LaunchProcess(NPP plugin, const char *executable, char **argv, int argv_size,
400 NPObject *receive_callback)
402 int socks[2];
403 if(NaClSocketPair(socks) < 0) {
404 perror("socketpair");
405 return NULL;
407 int pid = fork();
408 if(pid < 0) {
409 NaClClose(socks[0]);
410 NaClClose(socks[1]);
411 perror("fork");
412 return NULL; /* TODO: report error */
414 if(pid == 0) {
415 NaClClose(socks[1]);
416 char fd_buffer[40];
417 snprintf(fd_buffer, sizeof(fd_buffer), "%i:%i", 3, socks[0]);
419 int args_len = 9 + argv_size;
420 const char *args[args_len];
421 int i = 0;
422 args[i++] = "sel_ldr";
423 args[i++] = "-i";
424 args[i++] = fd_buffer;
425 args[i++] = "-E";
426 args[i++] = "NACL_FD=3";
427 args[i++] = "-f";
428 args[i++] = executable;
429 args[i++] = "--";
430 int j;
431 for(j = 0; j < argv_size; j++)
432 args[i++] = argv[j];
433 args[i++] = NULL;
434 assert(i == args_len);
436 execvp("sel_ldr", (char **) args);
437 perror("exec");
438 _exit(1);
440 NaClClose(socks[0]);
441 if(fcntl(socks[1], F_SETFD, FD_CLOEXEC) < 0) {
442 perror("fcntl");
443 return NULL;
446 struct NaClDesc *desc = malloc(sizeof(struct NaClDescImcDesc));
447 if(!NaClDescImcDescCtor((struct NaClDescImcDesc *) desc, socks[1]))
448 return NULL;
450 struct ReceiveThreadArgs *args = malloc(sizeof(struct ReceiveThreadArgs));
451 if(args == NULL)
452 return NULL;
453 NaClDescRef(desc);
454 NPN_RetainObject(receive_callback);
455 args->plugin = plugin;
456 args->desc = desc;
457 args->callback = receive_callback;
458 pthread_t thread_id;
459 pthread_attr_t attr;
460 pthread_attr_init(&attr);
461 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
462 if(pthread_create(&thread_id, &attr, ReceiveThread, args) != 0)
463 return NULL;
465 return desc;
468 static bool
469 LauncherObj_Invoke(NPObject *npobj, NPIdentifier methodName,
470 const NPVariant *args, uint32_t argCount, NPVariant *result)
472 struct LauncherObj *obj = (struct LauncherObj *) npobj;
473 print(("Obj_Invoke %i\n", argCount));
474 PrintIdentifier(methodName);
475 if(methodName == ident_launch) {
476 if(argCount == 3 &&
477 NPVARIANT_IS_OBJECT(args[0]) &&
478 NPVARIANT_IS_OBJECT(args[1]) &&
479 NPVARIANT_IS_OBJECT(args[2])) {
480 NPObject *file_npobj = NPVARIANT_TO_OBJECT(args[0]);
481 NPObject *argv_obj = NPVARIANT_TO_OBJECT(args[1]);
482 NPObject *receive_callback = NPVARIANT_TO_OBJECT(args[2]);
483 char **argv;
484 int argv_size;
485 if(IsType(file_npobj, &FileObj_Vtable) &&
486 ConvertArgvArray(obj->plugin, argv_obj, &argv, &argv_size)) {
487 struct FileObj *file_obj = (struct FileObj *) file_npobj;
488 struct NaClDesc *desc = LaunchProcess(obj->plugin, file_obj->filename,
489 argv, argv_size,
490 receive_callback);
491 int i;
492 for(i = 0; i < argv_size; i++)
493 free(argv[i]);
494 free(argv);
496 if(desc != NULL) {
497 NPObject *send_npobj = NPN_CreateObject(obj->plugin, &SendObj_Vtable);
498 struct SendObj *send_obj = (struct SendObj *) send_npobj;
499 send_obj->desc = desc;
500 OBJECT_TO_NPVARIANT(send_npobj, *result);
501 return true;
505 VOID_TO_NPVARIANT(*result);
506 return true;
508 if(methodName == ident_get_file) {
509 if(argCount == 2 &&
510 NPVARIANT_IS_STRING(args[0]) &&
511 NPVARIANT_IS_OBJECT(args[1])) {
512 /* TODO: do same-origin check (assuming they are useful). */
513 NPString url = NPVARIANT_TO_STRING(args[0]);
514 NPObject *callback = NPVARIANT_TO_OBJECT(args[1]);
515 NPN_RetainObject(callback);
516 /* TODO: I don't think utf8characters is guaranteed to be
517 NULL-terminated. */
518 NPError err = NPN_GetURLNotify(obj->plugin, url.utf8characters,
519 NULL, callback);
520 print(("NPN_GetUrlNotify = %i\n", err));
522 /* TODO: handle errors */
523 VOID_TO_NPVARIANT(*result);
524 return true;
526 return false;
529 static NPClass LauncherObj_Vtable = {
530 .structVersion = NP_CLASS_STRUCT_VERSION,
531 .allocate = LauncherObj_Allocate,
532 .deallocate = NULL,
533 .invalidate = NULL,
534 .hasMethod = LauncherObj_HasMethod,
535 .invoke = LauncherObj_Invoke,
536 .invokeDefault = NULL,
537 .hasProperty = NoOp_HasProperty,
538 .getProperty = NoOp_GetProperty,
539 .setProperty = NULL,
540 .removeProperty = NULL,
544 NPError
545 NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode,
546 int16 argc, char **argn, char **argv, NPSavedData *saved)
548 return NPERR_NO_ERROR;
551 NPError
552 NPP_Destroy(NPP instance, NPSavedData **save)
554 /* TODO: kill sel_ldr subprocesses, and do waitpid() to avoid a zombie */
555 print(("NPP_Destroy\n"));
556 return NPERR_NO_ERROR;
559 NPError
560 NPP_GetValue(NPP instance, NPPVariable variable, void *result)
562 switch(variable) {
563 case NPPVpluginScriptableNPObject:
565 NPObject *obj = NPN_CreateObject(instance, &LauncherObj_Vtable);
566 *(NPObject **) result = obj;
567 break;
569 default:
570 return NPERR_GENERIC_ERROR;
572 return NPERR_NO_ERROR;
575 NPError
576 NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream,
577 NPBool seekable, uint16 *stype)
579 print(("NPP_NewStream type=%s seekable=%i data=%p url=%s\n", type, seekable,
580 stream->notifyData, stream->url));
581 *stype = NP_ASFILEONLY;
582 return NPERR_NO_ERROR;
585 void
586 NPP_StreamAsFile(NPP instance, NPStream *stream, const char *filename)
588 print(("NPP_StreamAsFile \"%s\"\n", filename));
590 /* TODO: this function calls LOG_FATAL on error; not good */
591 struct NaClDesc *desc = (struct NaClDesc *)
592 NaClDescIoDescOpen((char *) filename, NACL_ABI_O_RDONLY, 0);
593 if(desc == NULL)
594 return;
596 NPObject *callback = stream->notifyData;
597 NPObject *file_npobj = NPN_CreateObject(instance, &FileObj_Vtable);
598 struct FileObj *file_obj = (struct FileObj *) file_npobj;
599 file_obj->desc = desc;
600 file_obj->filename = strdup(filename);
601 if(file_obj->filename == NULL)
602 return;
603 NPVariant result;
604 NPVariant args[1];
605 OBJECT_TO_NPVARIANT(file_npobj, args[0]);
606 bool success = NPN_InvokeDefault(instance, callback, args, 1, &result);
607 print(("NPN_InvokeDefault %i\n", success));
608 NPN_ReleaseVariantValue(&result);
609 NPN_ReleaseObject(callback);
610 NPN_ReleaseObject(file_npobj);
613 void
614 NPP_URLNotify(NPP instance, const char *url, NPReason reason, void *notifyData)
616 /* TODO: handle file not found errors */
617 print(("NPP_URLNotify \"%s\"\n", url));
621 NPError
622 NP_Initialize(NPNetscapeFuncs *funcs, NPPluginFuncs *callbacks)
624 print(("NP_Initialize\n"));
625 g_funcs = *funcs;
626 callbacks->newp = NPP_New;
627 callbacks->destroy = NPP_Destroy;
628 callbacks->getvalue = NPP_GetValue;
629 callbacks->newstream = NPP_NewStream;
630 callbacks->asfile = NPP_StreamAsFile;
631 callbacks->urlnotify = NPP_URLNotify;
633 ident_launch = NPN_GetStringIdentifier("launch");
634 ident_get_file = NPN_GetStringIdentifier("get_file");
635 ident_send = NPN_GetStringIdentifier("send");
636 ident_length = NPN_GetStringIdentifier("length");
638 NaClNrdAllModulesInit();
640 return NPERR_NO_ERROR;
643 char *
644 NP_GetMIMEDescription(void)
646 return "application/x-nacl-imc::Native Client IMC plugin";