Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

js_queue.c 13.2 KB
Newer Older
1 2
/* Synchronet JavaScript "Queue" Object */

3
/* $Id: js_queue.c,v 1.57 2019/08/22 01:41:23 rswindell Exp $ */
4 5 6 7 8

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
9
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "msg_queue.h"
38
#include "js_request.h"
39 40 41

typedef struct
{
42 43
	char		name[128];
	size_t		size;
44
	uint64		*value;
45 46
} queued_value_t;

47
link_list_t named_queues;
48 49 50 51 52 53 54 55 56 57

/* Queue Destructor */

static void js_finalize_queue(JSContext *cx, JSObject *obj)
{
	msg_queue_t* q;
	list_node_t* n;

	if((q=(msg_queue_t*)JS_GetPrivate(cx,obj))==NULL)
		return;
deuce's avatar
deuce committed
58

59
	if(msgQueueDetach(q)==0 && (n=listFindNode(&named_queues,q,/* length=0 for ptr compare */0))!=NULL)
60
		listRemoveNode(&named_queues,n,FALSE);
61 62 63 64

	JS_SetPrivate(cx, obj, NULL);
}

65
static void js_decode_value(JSContext *cx, JSObject *parent
66
							   ,queued_value_t* v, jsval* rval)
67 68 69
{
	*rval = JSVAL_VOID;

70
	if(v==NULL)
71
		return;
72

73 74
	JS_ReadStructuredClone(cx, v->value, v->size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);

75
	return;
76 77 78 79
}

/* Queue Object Methods */

80 81
extern JSClass js_queue_class;

82
static JSBool
83
js_poll(JSContext *cx, uintN argc, jsval *arglist)
84
{
85 86
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
87 88
	msg_queue_t*	q;
	queued_value_t*	v;
89
	int32 timeout=0;
deuce's avatar
deuce committed
90
	jsrefcount	rc;
91

92 93
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

94
	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
95 96 97
		return(JS_FALSE);
	}

98 99 100 101
	if(argc && JSVAL_IS_NUMBER(argv[0])) { 	/* timeout specified */
		if(!JS_ValueToInt32(cx,argv[0],&timeout))
			return JS_FALSE;
	}
102

103
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
104
	v=msgQueuePeek(q,timeout);
105
	JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
106
	if(v==NULL)
107
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
deuce's avatar
deuce committed
108
	else if(v->name[0])
109
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,v->name)));
110
	else
111
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
112 113 114 115

	return(JS_TRUE);
}

116
static JSBool
117
js_read(JSContext *cx, uintN argc, jsval *arglist)
118
{
119 120
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
121 122 123
	msg_queue_t* q;
	queued_value_t	find_v;
	queued_value_t*	v;
124
	int32 timeout=0;
deuce's avatar
deuce committed
125
	jsrefcount	rc;
126

127 128
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

129
	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
130 131 132
		return(JS_FALSE);
	}

133
	if(argc && JSVAL_IS_STRING(argv[0])) {	/* value named specified */
134
		ZERO_VAR(find_v);
deuce's avatar
deuce committed
135
		JSVALUE_TO_STRBUF(cx, argv[0], find_v.name, sizeof(find_v.name), NULL);
136
		rc=JS_SUSPENDREQUEST(cx);
137
		v=msgQueueFind(q,&find_v,sizeof(find_v.name));
138
		JS_RESUMEREQUEST(cx, rc);
139
	} else {
140 141 142 143
		if(argc && JSVAL_IS_NUMBER(argv[0])) {
			if(!JS_ValueToInt32(cx,argv[0],&timeout))
				return JS_FALSE;
		}
144
		rc=JS_SUSPENDREQUEST(cx);
145
		v=msgQueueRead(q, timeout);
146
		JS_RESUMEREQUEST(cx, rc);
147
	}
148

149
	if(v!=NULL) {
150 151
		jsval	rval;

152
		js_decode_value(cx, obj, v, &rval);
153
		free(v->value);
154
		free(v);
155
		JS_SET_RVAL(cx, arglist, rval);
156
	}
157 158 159 160 161

	return(JS_TRUE);
}

static JSBool
162
js_peek(JSContext *cx, uintN argc, jsval *arglist)
163
{
164 165
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
166 167
	msg_queue_t*	q;
	queued_value_t*	v;
168
	int32 timeout=0;
deuce's avatar
deuce committed
169
	jsrefcount	rc;
170

171 172
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

173
	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
174 175 176
		return(JS_FALSE);
	}

177 178 179 180
	if(argc && JSVAL_IS_NUMBER(argv[0])) { 	/* timeout specified */
		if(!JS_ValueToInt32(cx,argv[0],&timeout))
			return JS_FALSE;
	}
181

182
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
183
	v=msgQueuePeek(q, timeout);
184
	JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
185
	if(v!=NULL) {
186
		jsval	rval;
187
		js_decode_value(cx, obj, v, &rval);
188
		JS_SET_RVAL(cx, arglist, rval);
189
	}
190 191 192 193

	return(JS_TRUE);
}

194
static queued_value_t* js_encode_value(JSContext *cx, jsval val, char* name)
195
{
196 197
	queued_value_t	*v;
	uint64			*serialized;
198

199
	if((v=malloc(sizeof(queued_value_t)))==NULL)
200
		return(NULL);
201
	memset(v,0,sizeof(queued_value_t));
202

203
	if(name!=NULL)
204
		SAFECOPY(v->name,name);
205

rswindell's avatar
rswindell committed
206 207
	if(!JS_WriteStructuredClone(cx, val, &serialized, &v->size, NULL, NULL)) {
		free(v);
208
		return NULL;
rswindell's avatar
rswindell committed
209
	}
210 211
	if((v->value=(uint64 *)malloc(v->size))==NULL) {
		JS_free(cx, serialized);
rswindell's avatar
rswindell committed
212
		free(v);
213 214
		return NULL;
	}
215 216
	memcpy(v->value, serialized, v->size);
	JS_free(cx, serialized);
217 218 219 220 221 222 223 224

	return(v);
}

BOOL js_enqueue_value(JSContext *cx, msg_queue_t* q, jsval val, char* name)
{
	queued_value_t* v;
	BOOL			result;
deuce's avatar
deuce committed
225
	jsrefcount		rc;
226

227
	if((v=js_encode_value(cx,val,name))==NULL)
228 229
		return(FALSE);

230
	rc=JS_SUSPENDREQUEST(cx);
231
	result=msgQueueWrite(q,v,sizeof(queued_value_t));
232
	free(v);
233
	JS_RESUMEREQUEST(cx, rc);
234
	return(result);
235 236 237
}

static JSBool
238
js_write(JSContext *cx, uintN argc, jsval *arglist)
239
{
240 241
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
242 243 244 245 246
	uintN			argn=0;
	msg_queue_t*	q;
	jsval			val;
	char*			name=NULL;

247 248
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

249
	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
250 251 252 253 254
		return(JS_FALSE);
	}

	val = argv[argn++];

deuce's avatar
deuce committed
255
	if(argn < argc) {
256 257
		JSVALUE_TO_MSTRING(cx, argv[argn], name, NULL);
		argn++;
258
		HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
259
	}
260

261
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(js_enqueue_value(cx, q, val, name)));
deuce's avatar
deuce committed
262 263
	if(name)
		free(name);
264 265 266 267 268 269

	return(JS_TRUE);
}

/* Queue Object Properites */
enum {
270 271 272
	 QUEUE_PROP_NAME
	,QUEUE_PROP_DATA_WAITING
	,QUEUE_PROP_READ_LEVEL
273
	,QUEUE_PROP_WRITE_LEVEL
274
	,QUEUE_PROP_OWNER
275
	,QUEUE_PROP_ORPHAN
276 277
};

278
#ifdef BUILD_JSDOCS
279
static char* queue_prop_desc[] = {
280 281 282
	 "name of the queue (if it has one)"
	,"<i>true</i> if data is waiting to be read from queue"
	,"number of values in the read queue"
283
	,"number of values in the write queue"
284
	,"<i>true</i> if current thread is the owner/creator of the queue"
285
	,"<i>true</i> if the owner of the queue has detached from the queue"
286 287 288 289
	,NULL
};
#endif

290
static JSBool js_queue_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
291
{
292
	jsval idval;
293 294
    jsint			tiny;
	msg_queue_t*	q;
deuce's avatar
deuce committed
295
	jsrefcount		rc;
296

297 298
	if((q=(msg_queue_t*)JS_GetPrivate(cx,obj))==NULL)
		return JS_FALSE;
299

300 301
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
302 303

	switch(tiny) {
304
		case QUEUE_PROP_NAME:
deuce's avatar
deuce committed
305
			if(q->name[0])
306 307 308
				*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,q->name));
			break;
		case QUEUE_PROP_DATA_WAITING:
309
			rc=JS_SUSPENDREQUEST(cx);
310
			*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueReadLevel(q)));
311
			JS_RESUMEREQUEST(cx, rc);
312
			break;
313
		case QUEUE_PROP_READ_LEVEL:
314
			rc=JS_SUSPENDREQUEST(cx);
315
			*vp = INT_TO_JSVAL(msgQueueReadLevel(q));
316
			JS_RESUMEREQUEST(cx, rc);
317 318
			break;
		case QUEUE_PROP_WRITE_LEVEL:
319
			rc=JS_SUSPENDREQUEST(cx);
320
			*vp = INT_TO_JSVAL(msgQueueWriteLevel(q));
321
			JS_RESUMEREQUEST(cx, rc);
322
			break;
323 324 325 326 327
		case QUEUE_PROP_OWNER:
			rc=JS_SUSPENDREQUEST(cx);
			*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueOwner(q)));
			JS_RESUMEREQUEST(cx, rc);
			break;
328 329 330 331 332
		case QUEUE_PROP_ORPHAN:
			rc=JS_SUSPENDREQUEST(cx);
			*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(q->flags & MSG_QUEUE_ORPHAN));
			JS_RESUMEREQUEST(cx, rc);
			break;
333 334 335 336 337 338 339 340 341
	}
	return(JS_TRUE);
}

#define QUEUE_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY

static jsSyncPropertySpec js_queue_properties[] = {
/*		 name				,tinyid					,flags,				ver	*/

342 343
	{	"name"				,QUEUE_PROP_NAME		,QUEUE_PROP_FLAGS,	312 },
	{	"data_waiting"		,QUEUE_PROP_DATA_WAITING,QUEUE_PROP_FLAGS,	312 },
344 345
	{	"read_level"		,QUEUE_PROP_READ_LEVEL	,QUEUE_PROP_FLAGS,	312 },
	{	"write_level"		,QUEUE_PROP_WRITE_LEVEL	,QUEUE_PROP_FLAGS,	312 },
346
	{	"owner"				,QUEUE_PROP_OWNER		,QUEUE_PROP_FLAGS,	316 },
347
	{	"orphan"			,QUEUE_PROP_ORPHAN		,QUEUE_PROP_FLAGS,	31702 },
348 349 350 351
	{0}
};

static jsSyncMethodSpec js_queue_functions[] = {
rswindell's avatar
rswindell committed
352
	{"poll",		js_poll,		1,	JSTYPE_UNDEF,	"[timeout=<tt>0</tt>]"
353 354
	,JSDOCSTR("wait for any value to be written to the queue for up to <i>timeout</i> milliseconds "
		"(default: <i>0</i>), returns <i>true</i> or the <i>name</i> (string) of "
355
		"the value waiting (if it has one), or <i>false</i> if no values are waiting")
356 357
	,312
	},
rswindell's avatar
rswindell committed
358
	{"read",		js_read,		1,	JSTYPE_UNDEF,	"[string name] or [timeout=<tt>0</tt>]"
359
	,JSDOCSTR("read a value from the queue, if <i>name</i> not specified, reads next value "
360
		"from the bottom of the queue (waiting up to <i>timeout</i> milliseconds)")
361
	,313
362
	},
rswindell's avatar
rswindell committed
363
	{"peek",		js_peek,		1,	JSTYPE_UNDEF,	"[timeout=<tt>0</tt>]"
364 365 366
	,JSDOCSTR("peek at the value at the bottom of the queue, "
		"wait up to <i>timeout</i> milliseconds for any value to be written "
		"(default: <i>0</i>)")
367
	,313
368
	},
rswindell's avatar
rswindell committed
369
	{"write",		js_write,		1,	JSTYPE_BOOLEAN,	"value [,name=<i>none</i>]"
370 371 372 373 374 375
	,JSDOCSTR("write a value (optionally named) to the queue")
	,312
	},
	{0}
};

376
static JSBool js_queue_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
377 378
{
	char*			name=NULL;
deuce's avatar
deuce committed
379
	JSBool			ret;
deuce's avatar
deuce committed
380

deuce's avatar
deuce committed
381 382 383 384
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
deuce's avatar
deuce committed
385 386
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
387
			HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
388
		}
deuce's avatar
deuce committed
389
	}
deuce's avatar
deuce committed
390

deuce's avatar
deuce committed
391 392 393 394
	ret=js_SyncResolve(cx, obj, name, js_queue_properties, js_queue_functions, NULL, 0);
	if(name)
		free(name);
	return ret;
deuce's avatar
deuce committed
395 396 397 398
}

static JSBool js_queue_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
399
	return(js_queue_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
400 401
}

402
JSClass js_queue_class = {
deuce's avatar
deuce committed
403 404 405 406 407
     "Queue"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_queue_get			/* getProperty	*/
408
	,JS_StrictPropertyStub	/* setProperty	*/
deuce's avatar
deuce committed
409 410 411 412 413 414
	,js_queue_enumerate		/* enumerate	*/
	,js_queue_resolve		/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,js_finalize_queue		/* finalize		*/
};

415 416 417
/* Queue Constructor (creates queue) */

static JSBool
418
js_queue_constructor(JSContext *cx, uintN argc, jsval *arglist)
419
{
420
	JSObject *obj;
421
	jsval *argv=JS_ARGV(cx, arglist);
422 423
	uintN			argn=0;
	char*			name=NULL;
rswindell's avatar
rswindell committed
424
	int32			flags=MSG_QUEUE_BIDIR;
425 426
	msg_queue_t*	q=NULL;
	list_node_t*	n;
deuce's avatar
deuce committed
427
	jsrefcount		rc;
428

429 430
	obj=JS_NewObject(cx, &js_queue_class, NULL, NULL);
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
431

432
#if 0	/* This doesn't appear to be doing anything but leaking memory */
433 434 435 436 437
	if((q=(msg_queue_t*)malloc(sizeof(msg_queue_t)))==NULL) {
		JS_ReportError(cx,"malloc failed");
		return(JS_FALSE);
	}
	memset(q,0,sizeof(msg_queue_t));
438
#endif
439

440 441 442 443
	if(argn<argc && JSVAL_IS_STRING(argv[argn])) {
		JSVALUE_TO_ASTRING(cx, argv[argn], name, sizeof(q->name), NULL);
		argn++;
	}
444

445 446 447 448
	if(argn<argc && JSVAL_IS_NUMBER(argv[argn])) {
		if(!JS_ValueToInt32(cx,argv[argn++],&flags))
			return JS_FALSE;
	}
449

450
	rc=JS_SUSPENDREQUEST(cx);
451
	if(name!=NULL) {
452 453
		listLock(&named_queues);
		for(n=named_queues.first;n!=NULL;n=n->next)
454 455
			if((q=n->data)!=NULL && !stricmp(q->name,name))
				break;
456
		listUnlock(&named_queues);
457 458 459 460 461 462 463 464 465 466 467
		if(n==NULL)
			q=NULL;
	}

	if(q==NULL) {
		q=msgQueueInit(NULL,flags);
		if(name!=NULL)
			SAFECOPY(q->name,name);
		listPushNode(&named_queues,q);
	} else
		msgQueueAttach(q);
468
	JS_RESUMEREQUEST(cx, rc);
469 470 471 472 473 474

	if(!JS_SetPrivate(cx, obj, q)) {
		JS_ReportError(cx,"JS_SetPrivate failed");
		return(JS_FALSE);
	}

475
#ifdef BUILD_JSDOCS
476 477 478 479
	js_DescribeSyncObject(cx,obj,"Class for bi-directional message queues. "
		"Used for inter-thread/module communications.", 312);
	js_DescribeSyncConstructor(cx,obj,"To create a new (named) Queue object: "
		"<tt>var q = new Queue(<i>name</i>)</tt>");
480 481 482 483 484 485
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", queue_prop_desc, JSPROP_READONLY);
#endif

	return(JS_TRUE);
}

Rob Swindell's avatar
Rob Swindell committed
486
JSObject* js_CreateQueueClass(JSContext* cx, JSObject* parent)
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
{
	JSObject*	obj;

	obj = JS_InitClass(cx, parent, NULL
		,&js_queue_class
		,js_queue_constructor
		,0	/* number of constructor args */
		,NULL /* props, specified in constructor */
		,NULL /* funcs, specified in constructor */
		,NULL
		,NULL);

	return(obj);
}

Rob Swindell's avatar
Rob Swindell committed
502
JSObject* js_CreateQueueObject(JSContext* cx, JSObject* parent, char *name, msg_queue_t* q)
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
{
	JSObject*		obj;

	if(name==NULL)
	    obj = JS_NewObject(cx, &js_queue_class, NULL, parent);
	else
		obj = JS_DefineObject(cx, parent, name, &js_queue_class, NULL
			,JSPROP_ENUMERATE|JSPROP_READONLY);

	if(obj==NULL)
		return(NULL);

	if(!JS_SetPrivate(cx, obj, q))
		return(NULL);

	return(obj);
}