93e5e0a1c5b52372c9f111297e2973a1e3e06709
[m6w6/ext-http] / php_http_client_pool.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2011, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 PHP_HTTP_API php_http_client_pool_t *php_http_client_pool_init(php_http_client_pool_t *h, php_http_client_pool_ops_t *ops, php_http_resource_factory_t *rf, void *init_arg TSRMLS_DC)
16 {
17 php_http_client_pool_t *free_h = NULL;
18
19 if (!h) {
20 free_h = h = emalloc(sizeof(*h));
21 }
22 memset(h, 0, sizeof(*h));
23
24 h->ops = ops;
25 if (rf) {
26 h->rf = rf;
27 } else if (ops->rsrc) {
28 h->rf = php_http_resource_factory_init(NULL, h->ops->rsrc, h, NULL);
29 }
30 zend_llist_init(&h->clients.attached, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
31 zend_llist_init(&h->clients.finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
32 TSRMLS_SET_CTX(h->ts);
33
34 if (h->ops->init) {
35 if (!(h = h->ops->init(h, init_arg))) {
36 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not initialize request pool");
37 if (free_h) {
38 efree(h);
39 }
40 }
41 }
42
43 return h;
44 }
45
46 PHP_HTTP_API php_http_client_pool_t *php_http_client_pool_copy(php_http_client_pool_t *from, php_http_client_pool_t *to)
47 {
48 if (from->ops->copy) {
49 return from->ops->copy(from, to);
50 }
51
52 return NULL;
53 }
54
55 PHP_HTTP_API void php_http_client_pool_dtor(php_http_client_pool_t *h)
56 {
57 if (h->ops->dtor) {
58 h->ops->dtor(h);
59 }
60
61 zend_llist_clean(&h->clients.finished);
62 zend_llist_clean(&h->clients.attached);
63
64 php_http_resource_factory_free(&h->rf);
65 }
66
67 PHP_HTTP_API void php_http_client_pool_free(php_http_client_pool_t **h) {
68 if (*h) {
69 php_http_client_pool_dtor(*h);
70 efree(*h);
71 *h = NULL;
72 }
73 }
74
75 PHP_HTTP_API STATUS php_http_client_pool_attach(php_http_client_pool_t *h, zval *client)
76 {
77 TSRMLS_FETCH_FROM_CTX(h->ts);
78
79 if (h->ops->attach) {
80 zval *zreq = NULL;
81 php_http_client_object_t *obj;
82 php_http_message_object_t *msg_obj;
83
84 if (SUCCESS != php_http_client_object_handle_request(client, &zreq TSRMLS_CC)) {
85 return FAILURE;
86 }
87
88 obj = zend_object_store_get_object(client TSRMLS_CC);
89 msg_obj = zend_object_store_get_object(zreq TSRMLS_CC);
90
91 if (SUCCESS == h->ops->attach(h, obj->client, msg_obj->message)) {
92 Z_ADDREF_P(client);
93 zend_llist_add_element(&h->clients.attached, &client);
94 return SUCCESS;
95 }
96 }
97
98 return FAILURE;
99 }
100
101 static int php_http_client_pool_compare_handles(void *h1, void *h2)
102 {
103 return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
104 }
105
106
107 PHP_HTTP_API STATUS php_http_client_pool_detach(php_http_client_pool_t *h, zval *client)
108 {
109 TSRMLS_FETCH_FROM_CTX(h->ts);
110
111 if (h->ops->detach) {
112 php_http_client_object_t *obj = zend_object_store_get_object(client TSRMLS_CC);
113
114 if (SUCCESS == h->ops->detach(h, obj->client)) {
115 zend_llist_del_element(&h->clients.finished, client, php_http_client_pool_compare_handles);
116 zend_llist_del_element(&h->clients.attached, client, php_http_client_pool_compare_handles);
117 return SUCCESS;
118 }
119 }
120
121 return FAILURE;
122 }
123
124 PHP_HTTP_API STATUS php_http_client_pool_wait(php_http_client_pool_t *h, struct timeval *custom_timeout)
125 {
126 if (h->ops->wait) {
127 return h->ops->wait(h, custom_timeout);
128 }
129
130 return FAILURE;
131 }
132
133 PHP_HTTP_API int php_http_client_pool_once(php_http_client_pool_t *h)
134 {
135 if (h->ops->once) {
136 return h->ops->once(h);
137 }
138
139 return FAILURE;
140 }
141
142 PHP_HTTP_API STATUS php_http_client_pool_exec(php_http_client_pool_t *h)
143 {
144 if (h->ops->exec) {
145 return h->ops->exec(h);
146 }
147
148 return FAILURE;
149 }
150
151 static void detach(void *r, void *h TSRMLS_DC)
152 {
153 ((php_http_client_pool_t *) h)->ops->detach(h, ((php_http_client_object_t *) zend_object_store_get_object(*((zval **) r) TSRMLS_CC))->client);
154 }
155
156 PHP_HTTP_API void php_http_client_pool_reset(php_http_client_pool_t *h)
157 {
158 if (h->ops->reset) {
159 h->ops->reset(h);
160 } else if (h->ops->detach) {
161 TSRMLS_FETCH_FROM_CTX(h->ts);
162
163 zend_llist_apply_with_argument(&h->clients.attached, detach, h TSRMLS_CC);
164 }
165
166 zend_llist_clean(&h->clients.attached);
167 zend_llist_clean(&h->clients.finished);
168 }
169
170 PHP_HTTP_API STATUS php_http_client_pool_setopt(php_http_client_pool_t *h, php_http_client_pool_setopt_opt_t opt, void *arg)
171 {
172 if (h->ops->setopt) {
173 return h->ops->setopt(h, opt, arg);
174 }
175
176 return FAILURE;
177 }
178
179 PHP_HTTP_API void php_http_client_pool_requests(php_http_client_pool_t *h, zval ***attached, zval ***finished)
180 {
181 zval **handle;
182 int i, count;
183
184 if (attached) {
185 if ((count = zend_llist_count(&h->clients.attached))) {
186 *attached = ecalloc(count + 1 /* terminating NULL */, sizeof(zval *));
187
188 for (i = 0, handle = zend_llist_get_first(&h->clients.attached); handle; handle = zend_llist_get_next(&h->clients.attached)) {
189 Z_ADDREF_PP(handle);
190 (*attached)[i++] = *handle;
191 }
192 } else {
193 *attached = NULL;
194 }
195 }
196
197 if (finished) {
198 if ((count = zend_llist_count(&h->clients.finished))) {
199 *finished = ecalloc(count + 1 /* terminating NULL */, sizeof(zval *));
200
201 for (i = 0, handle = zend_llist_get_first(&h->clients.finished); handle; handle = zend_llist_get_next(&h->clients.finished)) {
202 Z_ADDREF_PP(handle);
203 (*finished)[i++] = *handle;
204 }
205 } else {
206 *finished = NULL;
207 }
208 }
209 }
210
211 /*#*/
212
213 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpClientPool, method, 0, req_args)
214 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpClientPool, method, 0)
215 #define PHP_HTTP_CLIENT_POOL_ME(method, visibility) PHP_ME(HttpClientPool, method, PHP_HTTP_ARGS(HttpClientPool, method), visibility)
216
217 PHP_HTTP_EMPTY_ARGS(__destruct);
218 PHP_HTTP_EMPTY_ARGS(reset);
219
220 PHP_HTTP_BEGIN_ARGS(attach, 1)
221 PHP_HTTP_ARG_OBJ(http\\Client\\AbstractClient, request, 0)
222 PHP_HTTP_END_ARGS;
223
224 PHP_HTTP_BEGIN_ARGS(detach, 1)
225 PHP_HTTP_ARG_OBJ(http\\Client\\AbstractClient, request, 0)
226 PHP_HTTP_END_ARGS;
227
228 PHP_HTTP_EMPTY_ARGS(send);
229 PHP_HTTP_EMPTY_ARGS(once);
230 PHP_HTTP_BEGIN_ARGS(wait, 0)
231 PHP_HTTP_ARG_VAL(timeout, 0)
232 PHP_HTTP_END_ARGS;
233
234 PHP_HTTP_EMPTY_ARGS(valid);
235 PHP_HTTP_EMPTY_ARGS(current);
236 PHP_HTTP_EMPTY_ARGS(key);
237 PHP_HTTP_EMPTY_ARGS(next);
238 PHP_HTTP_EMPTY_ARGS(rewind);
239
240 PHP_HTTP_EMPTY_ARGS(count);
241
242 PHP_HTTP_EMPTY_ARGS(getAttached);
243 PHP_HTTP_EMPTY_ARGS(getFinished);
244
245 PHP_HTTP_BEGIN_ARGS(enablePipelining, 0)
246 PHP_HTTP_ARG_VAL(enable, 0)
247 PHP_HTTP_END_ARGS;
248
249 PHP_HTTP_BEGIN_ARGS(enableEvents, 0)
250 PHP_HTTP_ARG_VAL(enable, 0)
251 PHP_HTTP_END_ARGS;
252
253 static zend_class_entry *php_http_client_pool_class_entry;
254
255 zend_class_entry *php_http_client_pool_get_class_entry(void)
256 {
257 return php_http_client_pool_class_entry;
258 }
259
260 static zend_function_entry php_http_client_pool_method_entry[] = {
261 PHP_HTTP_CLIENT_POOL_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
262 PHP_HTTP_CLIENT_POOL_ME(attach, ZEND_ACC_PUBLIC)
263 PHP_HTTP_CLIENT_POOL_ME(detach, ZEND_ACC_PUBLIC)
264 PHP_HTTP_CLIENT_POOL_ME(send, ZEND_ACC_PUBLIC)
265 PHP_HTTP_CLIENT_POOL_ME(reset, ZEND_ACC_PUBLIC)
266
267 PHP_HTTP_CLIENT_POOL_ME(once, ZEND_ACC_PROTECTED)
268 PHP_HTTP_CLIENT_POOL_ME(wait, ZEND_ACC_PROTECTED)
269
270 /* implements Iterator */
271 PHP_HTTP_CLIENT_POOL_ME(valid, ZEND_ACC_PUBLIC)
272 PHP_HTTP_CLIENT_POOL_ME(current, ZEND_ACC_PUBLIC)
273 PHP_HTTP_CLIENT_POOL_ME(key, ZEND_ACC_PUBLIC)
274 PHP_HTTP_CLIENT_POOL_ME(next, ZEND_ACC_PUBLIC)
275 PHP_HTTP_CLIENT_POOL_ME(rewind, ZEND_ACC_PUBLIC)
276
277 /* implmenents Countable */
278 PHP_HTTP_CLIENT_POOL_ME(count, ZEND_ACC_PUBLIC)
279
280 PHP_HTTP_CLIENT_POOL_ME(getAttached, ZEND_ACC_PUBLIC)
281 PHP_HTTP_CLIENT_POOL_ME(getFinished, ZEND_ACC_PUBLIC)
282
283 PHP_HTTP_CLIENT_POOL_ME(enablePipelining, ZEND_ACC_PUBLIC)
284 PHP_HTTP_CLIENT_POOL_ME(enableEvents, ZEND_ACC_PUBLIC)
285
286 EMPTY_FUNCTION_ENTRY
287 };
288
289 static zend_object_handlers php_http_client_pool_object_handlers;
290
291 extern zend_object_handlers *php_http_client_pool_get_object_handlers(void)
292 {
293 return &php_http_client_pool_object_handlers;
294 }
295
296 static php_http_client_pool_ops_t php_http_client_pool_user_ops = {
297 NULL,
298 NULL,
299 NULL,
300 NULL,
301 NULL,
302 NULL,
303 NULL,
304 NULL,
305 NULL,
306 NULL,
307 NULL,
308 (php_http_new_t) php_http_client_pool_object_new_ex,
309 php_http_client_pool_get_class_entry
310 };
311
312 zend_object_value php_http_client_pool_object_new(zend_class_entry *ce TSRMLS_DC)
313 {
314 return php_http_client_pool_object_new_ex(ce, NULL, NULL TSRMLS_CC);
315 }
316
317 zend_object_value php_http_client_pool_object_new_ex(zend_class_entry *ce, php_http_client_pool_t *p, php_http_client_pool_object_t **ptr TSRMLS_DC)
318 {
319 zend_object_value ov;
320 php_http_client_pool_object_t *o;
321
322 o = ecalloc(1, sizeof(php_http_client_pool_object_t));
323 zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
324 #if PHP_VERSION_ID < 50339
325 zend_hash_copy(((zend_object *) o)->properties, &(ce->default_properties), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));
326 #else
327 object_properties_init((zend_object *) o, ce);
328 #endif
329
330 if (!(o->pool = p)) {
331 o->pool = php_http_client_pool_init(NULL, &php_http_client_pool_user_ops, NULL, NULL TSRMLS_CC);
332 }
333
334 if (ptr) {
335 *ptr = o;
336 }
337
338 ov.handle = zend_objects_store_put(o, NULL, php_http_client_pool_object_free, NULL TSRMLS_CC);
339 ov.handlers = &php_http_client_pool_object_handlers;
340
341 return ov;
342 }
343
344 void php_http_client_pool_object_free(void *object TSRMLS_DC)
345 {
346 php_http_client_pool_object_t *o = (php_http_client_pool_object_t *) object;
347
348 php_http_client_pool_free(&o->pool);
349 zend_object_std_dtor((zend_object *) o TSRMLS_CC);
350 efree(o);
351 }
352
353 static void php_http_client_pool_object_llist2array(zval **req, zval *array TSRMLS_DC)
354 {
355 Z_ADDREF_P(*req);
356 add_next_index_zval(array, *req);
357 }
358
359 PHP_METHOD(HttpClientPool, __destruct)
360 {
361 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
362
363 if (SUCCESS != zend_parse_parameters_none()) {
364 ; /* we always want to clean up */
365 }
366 /* FIXME: move to php_http_client_pool_dtor */
367 php_http_client_pool_reset(obj->pool);
368 }
369
370 PHP_METHOD(HttpClientPool, reset)
371 {
372 if (SUCCESS == zend_parse_parameters_none()) {
373 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
374
375 obj->iterator.pos = 0;
376 php_http_client_pool_reset(obj->pool);
377 }
378 RETVAL_ZVAL(getThis(), 1, 0);
379 }
380
381 PHP_METHOD(HttpClientPool, attach)
382 {
383 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
384 zval *request;
385
386 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_client_get_class_entry())) {
387 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
388 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
389
390 if (obj->iterator.pos > 0 && obj->iterator.pos < zend_llist_count(&obj->pool->clients.attached)) {
391 php_http_error(HE_THROW, PHP_HTTP_E_CLIENT_POOL, "Cannot attach to the HttpClientPool while the iterator is active");
392 } else {
393 php_http_client_pool_attach(obj->pool, request);
394 }
395 } end_error_handling();
396 }
397 } end_error_handling();
398
399 RETVAL_ZVAL(getThis(), 1, 0);
400 }
401
402 PHP_METHOD(HttpClientPool, detach)
403 {
404 RETVAL_FALSE;
405
406 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
407 zval *request;
408
409 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_client_get_class_entry())) {
410 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
411 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
412
413 obj->iterator.pos = -1;
414 php_http_client_pool_detach(obj->pool, request);
415 } end_error_handling();
416 }
417 } end_error_handling();
418
419 RETVAL_ZVAL(getThis(), 1, 0);
420 }
421
422 PHP_METHOD(HttpClientPool, send)
423 {
424 RETVAL_FALSE;
425
426 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
427 if (SUCCESS == zend_parse_parameters_none()) {
428 with_error_handling(EH_THROW, php_http_exception_get_class_entry()) {
429 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
430
431 php_http_client_pool_exec(obj->pool);
432 } end_error_handling();
433 }
434 } end_error_handling();
435
436 RETVAL_ZVAL(getThis(), 1, 0);
437 }
438
439 PHP_METHOD(HttpClientPool, once)
440 {
441 if (SUCCESS == zend_parse_parameters_none()) {
442 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
443
444 if (0 < php_http_client_pool_once(obj->pool)) {
445 RETURN_TRUE;
446 }
447 }
448 RETURN_FALSE;
449 }
450
451 PHP_METHOD(HttpClientPool, wait)
452 {
453 double timeout = 0;
454
455 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|d", &timeout)) {
456 struct timeval timeout_val;
457 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
458
459 timeout_val.tv_sec = (time_t) timeout;
460 timeout_val.tv_usec = PHP_HTTP_USEC(timeout) % PHP_HTTP_MCROSEC;
461
462 RETURN_SUCCESS(php_http_client_pool_wait(obj->pool, timeout > 0 ? &timeout_val : NULL));
463 }
464 RETURN_FALSE;
465 }
466
467 PHP_METHOD(HttpClientPool, valid)
468 {
469 if (SUCCESS == zend_parse_parameters_none()) {
470 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
471
472 RETURN_BOOL(obj->iterator.pos >= 0 && obj->iterator.pos < zend_llist_count(&obj->pool->clients.attached));
473 }
474 RETURN_FALSE;
475 }
476
477 PHP_METHOD(HttpClientPool, current)
478 {
479 if (SUCCESS == zend_parse_parameters_none()) {
480 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
481
482 if (obj->iterator.pos < zend_llist_count(&obj->pool->clients.attached)) {
483 long pos = 0;
484 zval **current = NULL;
485 zend_llist_position lpos;
486
487 for ( current = zend_llist_get_first_ex(&obj->pool->clients.attached, &lpos);
488 current && obj->iterator.pos != pos++;
489 current = zend_llist_get_next_ex(&obj->pool->clients.attached, &lpos));
490 if (current) {
491 RETURN_OBJECT(*current, 1);
492 }
493 }
494 }
495 RETURN_FALSE;
496 }
497
498 PHP_METHOD(HttpClientPool, key)
499 {
500 if (SUCCESS == zend_parse_parameters_none()) {
501 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
502
503 RETURN_LONG(obj->iterator.pos);
504 }
505 RETURN_FALSE;
506 }
507
508 PHP_METHOD(HttpClientPool, next)
509 {
510 if (SUCCESS == zend_parse_parameters_none()) {
511 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
512
513 ++obj->iterator.pos;
514 }
515 }
516
517 PHP_METHOD(HttpClientPool, rewind)
518 {
519 if (SUCCESS == zend_parse_parameters_none()) {
520 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
521
522 obj->iterator.pos = 0;
523 }
524 }
525
526 PHP_METHOD(HttpClientPool, count)
527 {
528 if (SUCCESS == zend_parse_parameters_none()) {
529 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
530
531 RETURN_LONG((long) zend_llist_count(&obj->pool->clients.attached));
532 }
533 }
534
535 PHP_METHOD(HttpClientPool, getAttached)
536 {
537 if (SUCCESS == zend_parse_parameters_none()) {
538 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
539
540 array_init(return_value);
541 zend_llist_apply_with_argument(&obj->pool->clients.attached,
542 (llist_apply_with_arg_func_t) php_http_client_pool_object_llist2array,
543 return_value TSRMLS_CC);
544 return;
545 }
546 RETURN_FALSE;
547 }
548
549 PHP_METHOD(HttpClientPool, getFinished)
550 {
551 if (SUCCESS == zend_parse_parameters_none()) {
552 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
553
554 array_init(return_value);
555 zend_llist_apply_with_argument(&obj->pool->clients.finished,
556 (llist_apply_with_arg_func_t) php_http_client_pool_object_llist2array,
557 return_value TSRMLS_CC);
558 return;
559 }
560 RETURN_FALSE;
561 }
562
563 PHP_METHOD(HttpClientPool, enablePipelining)
564 {
565 zend_bool enable = 1;
566
567 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
568 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
569
570 php_http_client_pool_setopt(obj->pool, PHP_HTTP_CLIENT_POOL_OPT_ENABLE_PIPELINING, &enable);
571 }
572 RETVAL_ZVAL(getThis(), 1, 0);
573 }
574
575 PHP_METHOD(HttpClientPool, enableEvents)
576 {
577 zend_bool enable = 1;
578
579 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
580 php_http_client_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
581
582 php_http_client_pool_setopt(obj->pool, PHP_HTTP_CLIENT_POOL_OPT_USE_EVENTS, &enable);
583 }
584 RETVAL_ZVAL(getThis(), 1, 0);
585 }
586
587 PHP_MINIT_FUNCTION(http_client_pool)
588 {
589 PHP_HTTP_REGISTER_CLASS(http\\Client\\Pool, AbstractPool, http_client_pool, php_http_object_get_class_entry(), ZEND_ACC_EXPLICIT_ABSTRACT_CLASS);
590 php_http_client_pool_class_entry->create_object = php_http_client_pool_object_new;
591 memcpy(&php_http_client_pool_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
592 php_http_client_pool_object_handlers.clone_obj = NULL;
593
594 zend_class_implements(php_http_client_pool_class_entry TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator);
595
596 return SUCCESS;
597 }
598
599
600 /*
601 * Local variables:
602 * tab-width: 4
603 * c-basic-offset: 4
604 * End:
605 * vim600: noet sw=4 ts=4 fdm=marker
606 * vim<600: noet sw=4 ts=4
607 */
608