New PHP5 APC - version 3.0.19, using PHP5 5.2.0-8+etch11,
[php5-apc.git] / apc_main.c
1 /*
2   +----------------------------------------------------------------------+
3   | APC                                                                  |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2008 The PHP Group                                     |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Daniel Cowgill <dcowgill@communityconnect.com>              |
16   |          Rasmus Lerdorf <rasmus@php.net>                             |
17   |          Arun C. Murthy <arunc@yahoo-inc.com>                        |
18   |          Gopal Vijayaraghavan <gopalv@yahoo-inc.com>                 |
19   +----------------------------------------------------------------------+
20
21    This software was contributed to PHP by Community Connect Inc. in 2002
22    and revised in 2005 by Yahoo! Inc. to add support for PHP 5.1.
23    Future revisions and derivatives of this source code must acknowledge
24    Community Connect Inc. as the original contributor of this module by
25    leaving this note intact in the source code.
26
27    All other licensing and usage conditions are those of the PHP Group.
28
29  */
30
31 /* $Id: apc_main.c,v 3.103.2.7 2008/05/11 18:57:00 rasmus Exp $ */
32
33 #include "apc_php.h"
34 #include "apc_main.h"
35 #include "apc.h"
36 #include "apc_lock.h"
37 #include "apc_cache.h"
38 #include "apc_compile.h"
39 #include "apc_globals.h"
40 #include "apc_sma.h"
41 #include "apc_stack.h"
42 #include "apc_zend.h"
43 #include "SAPI.h"
44 #if PHP_API_VERSION <= 20020918
45 #if HAVE_APACHE
46 #ifdef APC_PHP4_STAT
47 #undef XtOffsetOf
48 #include "httpd.h"
49 #endif
50 #endif
51 #endif
52
53 /* {{{ module variables */
54
55 /* pointer to the original Zend engine compile_file function */
56 typedef zend_op_array* (zend_compile_t)(zend_file_handle*, int TSRMLS_DC);
57 static zend_compile_t *old_compile_file;
58
59 /* }}} */
60
61 /* {{{ get/set old_compile_file (to interact with other extensions that need the compile hook) */
62 static zend_compile_t* set_compile_hook(zend_compile_t *ptr)
63 {
64     zend_compile_t *retval = old_compile_file;
65
66     if (ptr != NULL) old_compile_file = ptr;
67     return retval;
68 }
69 /* }}} */
70
71 /* {{{ install_function */
72 static int install_function(apc_function_t fn TSRMLS_DC)
73 {
74     zend_function *func;
75     int status;
76     
77     func = apc_copy_function_for_execution(fn.function);
78     
79     status =  zend_hash_add(EG(function_table),
80                       fn.name,
81                       fn.name_len+1,
82                       func, 
83                       sizeof(fn.function[0]),
84                       NULL);
85
86     efree(func);
87
88     if (status == FAILURE) {
89         /* apc_eprint("Cannot redeclare %s()", fn.name); */
90     }
91
92     return status;
93 }
94 /* }}} */
95
96 /* {{{ install_class */
97 static int install_class(apc_class_t cl TSRMLS_DC)
98 {
99     zend_class_entry* class_entry = cl.class_entry;
100     zend_class_entry* parent = NULL;
101     int status;
102 #ifdef ZEND_ENGINE_2
103     zend_class_entry** allocated_ce = NULL;
104 #endif
105
106
107     /* Special case for mangled names. Mangled names are unique to a file.
108      * There is no way two classes with the same mangled name will occur,
109      * unless a file is included twice. And if in case, a file is included
110      * twice, all mangled name conflicts can be ignored and the class redeclaration
111      * error may be deferred till runtime of the corresponding DECLARE_CLASS
112      * calls.
113      */
114
115     if(cl.name_len != 0 && cl.name[0] == '\0') {
116         if(zend_hash_exists(CG(class_table), cl.name, cl.name_len+1)) {
117             return SUCCESS;
118         }
119     }
120     
121 #ifdef ZEND_ENGINE_2    
122     /*
123      * XXX: We need to free this somewhere...
124      */
125     allocated_ce = apc_php_malloc(sizeof(zend_class_entry*));    
126
127     if(!allocated_ce) {
128         return FAILURE;
129     }
130
131     *allocated_ce = 
132 #endif        
133     class_entry =
134         apc_copy_class_entry_for_execution(cl.class_entry,
135                                            cl.is_derived);
136
137
138     /* restore parent class pointer for compile-time inheritance */
139     if (cl.parent_name != NULL) {
140 #ifdef ZEND_ENGINE_2    
141         zend_class_entry** parent_ptr = NULL;
142         /*
143          * zend_lookup_class has to be due to presence of __autoload, 
144          * just looking up the EG(class_table) is not enough in php5!
145          * Even more dangerously, thanks to __autoload and people using
146          * class names as filepaths for inclusion, this has to be case
147          * sensitive. zend_lookup_class automatically does a case_fold
148          * internally, but passes the case preserved version to __autoload.
149          * Aside: Do NOT pass *strlen(cl.parent_name)+1* because 
150          * zend_lookup_class does it internally anyway!
151          */
152         status = zend_lookup_class(cl.parent_name,
153                                     strlen(cl.parent_name),
154                                     &parent_ptr TSRMLS_CC);
155 #else
156         status = zend_hash_find(EG(class_table),
157                                 cl.parent_name,
158                                 strlen(cl.parent_name)+1,
159                                 (void**) &parent);
160 #endif
161         if (status == FAILURE) {
162             if(APCG(report_autofilter)) {
163                 apc_wprint("Dynamic inheritance detected for class %s", cl.name);
164             }
165             class_entry->parent = NULL;
166             return status;
167         }
168         else {
169 #ifdef ZEND_ENGINE_2            
170             parent = *parent_ptr;
171 #endif 
172             class_entry->parent = parent;
173 #ifdef ZEND_ENGINE_2
174             zend_do_inheritance(class_entry, parent TSRMLS_CC);
175 #else
176             zend_do_inheritance(class_entry, parent);
177 #endif
178         }
179
180
181     }
182
183 #ifdef ZEND_ENGINE_2                           
184     status = zend_hash_add(EG(class_table),
185                            cl.name,
186                            cl.name_len+1,
187                            allocated_ce,
188                            sizeof(zend_class_entry*),
189                            NULL);
190 #else                           
191     status = zend_hash_add(EG(class_table),
192                            cl.name,
193                            cl.name_len+1,
194                            class_entry,
195                            sizeof(zend_class_entry),
196                            NULL);
197 #endif                           
198
199     if (status == FAILURE) {
200         apc_eprint("Cannot redeclare class %s", cl.name);
201     } 
202     return status;
203 }
204 /* }}} */
205
206 /* {{{ uninstall_class */
207 static int uninstall_class(apc_class_t cl TSRMLS_DC)
208 {
209     int status;
210
211 #ifdef ZEND_ENGINE_2                           
212     status = zend_hash_del(EG(class_table),
213                            cl.name,
214                            cl.name_len+1);
215 #else                           
216     status = zend_hash_del(EG(class_table),
217                            cl.name,
218                            cl.name_len+1);
219 #endif                           
220     if (status == FAILURE) {
221         apc_eprint("Cannot delete class %s", cl.name);
222     } 
223     return status;
224 }
225 /* }}} */
226
227 /* {{{ compare_file_handles */
228 static int compare_file_handles(void* a, void* b)
229 {
230     zend_file_handle* fh1 = (zend_file_handle*)a;
231     zend_file_handle* fh2 = (zend_file_handle*)b;
232     return (fh1->type == fh2->type && 
233             fh1->filename == fh2->filename &&
234             fh1->opened_path == fh2->opened_path);
235 }
236 /* }}} */
237
238 /* {{{ cached_compile */
239 static zend_op_array* cached_compile(zend_file_handle* h,
240                                         int type TSRMLS_DC)
241 {
242     apc_cache_entry_t* cache_entry;
243     int i, ii;
244
245     cache_entry = (apc_cache_entry_t*) apc_stack_top(APCG(cache_stack));
246     assert(cache_entry != NULL);
247
248     if (cache_entry->data.file.classes) {
249         for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) {
250             if(install_class(cache_entry->data.file.classes[i] TSRMLS_CC) == FAILURE) {
251                 goto default_compile;
252             }
253         }
254     }
255
256     if (cache_entry->data.file.functions) {
257         for (i = 0; cache_entry->data.file.functions[i].function != NULL; i++) {
258             install_function(cache_entry->data.file.functions[i] TSRMLS_CC);
259         }
260     }
261
262
263     return apc_copy_op_array_for_execution(NULL, cache_entry->data.file.op_array TSRMLS_CC);
264
265 default_compile:
266
267     if(APCG(report_autofilter)) {
268         apc_wprint("Autofiltering %s", h->opened_path);
269     }
270
271     if(cache_entry->data.file.classes) {
272         for(ii = 0; ii < i ; ii++) {
273             uninstall_class(cache_entry->data.file.classes[ii] TSRMLS_CC);
274         }
275     }
276     
277     apc_stack_pop(APCG(cache_stack)); /* pop out cache_entry */
278     
279     apc_cache_release(apc_cache, cache_entry);
280
281     /* cannot free up cache data yet, it maybe in use */
282    
283     zend_llist_del_element(&CG(open_files), h, compare_file_handles); /* We leak fds without this hack */
284
285     h->type = ZEND_HANDLE_FILENAME;
286
287     return NULL;
288 }
289 /* }}} */
290
291 /* {{{ my_compile_file
292    Overrides zend_compile_file */
293 static zend_op_array* my_compile_file(zend_file_handle* h,
294                                                int type TSRMLS_DC)
295 {
296     apc_cache_key_t key;
297     apc_cache_entry_t* cache_entry;
298     zend_op_array* op_array;
299     int num_functions, num_classes, ret;
300     zend_op_array* alloc_op_array;
301     apc_function_t* alloc_functions;
302     apc_class_t* alloc_classes;
303     time_t t;
304     char *path;
305     size_t mem_size;
306
307     if (!APCG(enabled) || apc_cache_busy(apc_cache)) { 
308         return old_compile_file(h, type TSRMLS_CC);
309     }
310
311     /* check our regular expression filters */
312     if (APCG(filters) && apc_compiled_filters) {
313         int ret = apc_regex_match_array(apc_compiled_filters, h->filename);
314         if(ret == APC_NEGATIVE_MATCH || (ret != APC_POSITIVE_MATCH && !APCG(cache_by_default))) {
315             return old_compile_file(h, type TSRMLS_CC);
316         }
317     } else if(!APCG(cache_by_default)) {
318         return old_compile_file(h, type TSRMLS_CC);
319     }
320
321 #if PHP_API_VERSION < 20041225
322 #if HAVE_APACHE && defined(APC_PHP4_STAT)
323     t = ((request_rec *)SG(server_context))->request_time;
324 #else 
325     t = time(0);
326 #endif
327 #else 
328     t = sapi_get_request_time(TSRMLS_C);
329 #endif
330
331 #ifdef __DEBUG_APC__
332     fprintf(stderr,"1. h->opened_path=[%s]  h->filename=[%s]\n", h->opened_path?h->opened_path:"null",h->filename);
333 #endif
334
335     /* try to create a cache key; if we fail, give up on caching */
336     if (!apc_cache_make_file_key(&key, h->filename, PG(include_path), t TSRMLS_CC)) {
337         return old_compile_file(h, type TSRMLS_CC);
338     }
339
340
341     if(!APCG(force_file_update)) {
342         /* search for the file in the cache */
343         cache_entry = apc_cache_find(apc_cache, key, t);
344     } else {
345         cache_entry = NULL;
346     }
347
348     if (cache_entry != NULL) {
349         int dummy = 1;
350         if (h->opened_path == NULL) {
351             h->opened_path = estrdup(cache_entry->data.file.filename);
352         }
353         zend_hash_add(&EG(included_files), h->opened_path, strlen(h->opened_path)+1, (void *)&dummy, sizeof(int), NULL);
354
355         zend_llist_add_element(&CG(open_files), h); /* We leak fds without this hack */
356
357         apc_stack_push(APCG(cache_stack), cache_entry);
358         op_array = cached_compile(h, type TSRMLS_CC);
359         if(op_array) {
360 #ifdef APC_FILEHITS
361             /* If the file comes from the cache, add it to the global request file list */
362             add_next_index_string(APCG(filehits), h->filename, 1);
363 #endif
364             return op_array;
365         }
366         if(APCG(report_autofilter)) {
367             apc_wprint("Recompiling %s", h->opened_path);
368         }
369         /* TODO: check what happens with EG(included_files) */
370     }
371     
372     /* remember how many functions and classes existed before compilation */
373     num_functions = zend_hash_num_elements(CG(function_table));
374     num_classes   = zend_hash_num_elements(CG(class_table));
375     
376     /* compile the file using the default compile function */
377     op_array = old_compile_file(h, type TSRMLS_CC);
378     if (op_array == NULL) {
379         return NULL;
380     }
381     /*
382      * Basically this will cause a file only to be cached on a percentage 
383      * of the attempts.  This is to avoid cache slams when starting up a
384      * very busy server or when modifying files on a very busy live server.
385      * There is no point having many processes all trying to cache the same
386      * file at the same time.  By introducing a chance of being cached
387      * we theoretically cut the cache slam problem by the given percentage.
388      * For example if apc.slam_defense is set to 66 then 2/3 of the attempts
389      * to cache an uncached file will be ignored.
390      */
391     if(APCG(slam_defense)) {
392         if(APCG(slam_rand)==-1) {
393             APCG(slam_rand) = (int)(100.0*rand()/(RAND_MAX+1.0));
394         }
395         if(APCG(slam_rand) < APCG(slam_defense)) {
396             return op_array;
397         }
398     }
399
400     /* Make sure the mtime reflects the files last known mtime in the case of fpstat==0 */
401     if(key.type == APC_CACHE_KEY_FPFILE) {
402         apc_fileinfo_t fileinfo;
403         struct stat *tmp_buf = NULL;
404         if(!strcmp(SG(request_info).path_translated, h->filename)) {
405             tmp_buf = sapi_get_stat(TSRMLS_C);  /* Apache has already done this stat() for us */
406         }
407         if(tmp_buf) { 
408             fileinfo.st_buf.sb = *tmp_buf;
409         } else {
410             if (apc_search_paths(h->filename, PG(include_path), &fileinfo) != 0) {
411 #ifdef __DEBUG_APC__
412                 fprintf(stderr,"Stat failed %s - bailing (%s) (%d)\n",filename,SG(request_info).path_translated);
413 #endif
414                 return op_array;
415             }
416         }
417         key.mtime = fileinfo.st_buf.sb.st_mtime;
418     }
419
420     HANDLE_BLOCK_INTERRUPTIONS();
421
422 #if NONBLOCKING_LOCK_AVAILABLE
423     if(APCG(write_lock)) {
424         if(!apc_cache_write_lock(apc_cache)) {
425             HANDLE_UNBLOCK_INTERRUPTIONS();
426             return op_array;
427         }
428     }
429 #endif
430
431     mem_size = 0;
432     APCG(mem_size_ptr) = &mem_size;
433     if(!(alloc_op_array = apc_copy_op_array(NULL, op_array, apc_sma_malloc, apc_sma_free TSRMLS_CC))) {
434         apc_cache_expunge(apc_cache,t);
435         apc_cache_expunge(apc_user_cache,t);
436         APCG(mem_size_ptr) = NULL;
437 #if NONBLOCKING_LOCK_AVAILABLE
438         if(APCG(write_lock)) {
439             apc_cache_write_unlock(apc_cache);
440         }
441 #endif
442         HANDLE_UNBLOCK_INTERRUPTIONS();
443         return op_array;
444     }
445     
446     if(!(alloc_functions = apc_copy_new_functions(num_functions, apc_sma_malloc, apc_sma_free TSRMLS_CC))) {
447         apc_free_op_array(alloc_op_array, apc_sma_free);
448         apc_cache_expunge(apc_cache,t);
449         apc_cache_expunge(apc_user_cache,t);
450         APCG(mem_size_ptr) = NULL;
451 #if NONBLOCKING_LOCK_AVAILABLE
452         if(APCG(write_lock)) {
453             apc_cache_write_unlock(apc_cache);
454         }
455 #endif
456         HANDLE_UNBLOCK_INTERRUPTIONS();
457         return op_array;
458     }
459     if(!(alloc_classes = apc_copy_new_classes(op_array, num_classes, apc_sma_malloc, apc_sma_free TSRMLS_CC))) {
460         apc_free_op_array(alloc_op_array, apc_sma_free);
461         apc_free_functions(alloc_functions, apc_sma_free);
462         apc_cache_expunge(apc_cache,t);
463         apc_cache_expunge(apc_user_cache,t);
464         APCG(mem_size_ptr) = NULL;
465 #if NONBLOCKING_LOCK_AVAILABLE
466         if(APCG(write_lock)) {
467             apc_cache_write_unlock(apc_cache);
468         }
469 #endif
470         HANDLE_UNBLOCK_INTERRUPTIONS();
471         return op_array;
472     }
473
474     path = h->opened_path;
475     if(!path) path=h->filename;
476
477 #ifdef __DEBUG_APC__
478     fprintf(stderr,"2. h->opened_path=[%s]  h->filename=[%s]\n", h->opened_path?h->opened_path:"null",h->filename);
479 #endif
480
481     if(!(cache_entry = apc_cache_make_file_entry(path, alloc_op_array, alloc_functions, alloc_classes))) {
482         apc_free_op_array(alloc_op_array, apc_sma_free);
483         apc_free_functions(alloc_functions, apc_sma_free);
484         apc_free_classes(alloc_classes, apc_sma_free);
485         apc_cache_expunge(apc_cache,t);
486         apc_cache_expunge(apc_user_cache,t);
487         APCG(mem_size_ptr) = NULL;
488 #if NONBLOCKING_LOCK_AVAILABLE
489         if(APCG(write_lock)) {
490             apc_cache_write_unlock(apc_cache);
491         }
492 #endif
493         HANDLE_UNBLOCK_INTERRUPTIONS();
494         return op_array;
495     }
496     APCG(mem_size_ptr) = NULL;
497     cache_entry->mem_size = mem_size;
498
499     if ((ret = apc_cache_insert(apc_cache, key, cache_entry, t)) != 1) {
500         apc_cache_free_entry(cache_entry);
501         if(ret==-1) {
502             apc_cache_expunge(apc_cache,t);
503             apc_cache_expunge(apc_user_cache,t);
504         }
505     }
506
507 #if NONBLOCKING_LOCK_AVAILABLE
508     if(APCG(write_lock)) {
509         apc_cache_write_unlock(apc_cache);
510     }
511 #endif
512     HANDLE_UNBLOCK_INTERRUPTIONS();
513
514     return op_array;
515 }
516 /* }}} */
517
518 /* {{{ module init and shutdown */
519
520 int apc_module_init(int module_number TSRMLS_DC)
521 {
522     /* apc initialization */
523 #if APC_MMAP
524     apc_sma_init(APCG(shm_segments), APCG(shm_size)*1024*1024, APCG(mmap_file_mask));
525 #else
526     apc_sma_init(APCG(shm_segments), APCG(shm_size)*1024*1024, NULL);
527 #endif
528     apc_cache = apc_cache_create(APCG(num_files_hint), APCG(gc_ttl), APCG(ttl));
529     apc_user_cache = apc_cache_create(APCG(user_entries_hint), APCG(gc_ttl), APCG(user_ttl));
530
531     apc_compiled_filters = apc_regex_compile_array(APCG(filters));
532
533     /* override compilation */
534     old_compile_file = zend_compile_file;
535     zend_compile_file = my_compile_file;
536     REGISTER_LONG_CONSTANT("\000apc_magic", (long)&set_compile_hook, CONST_PERSISTENT | CONST_CS);
537
538     APCG(initialized) = 1;
539     return 0;
540 }
541
542 int apc_module_shutdown(TSRMLS_D)
543 {
544     if (!APCG(initialized))
545         return 0;
546
547     /* restore compilation */
548     zend_compile_file = old_compile_file;
549
550     /* 
551      * In case we got interrupted by a SIGTERM or something else during execution
552      * we may have cache entries left on the stack that we need to check to make
553      * sure that any functions or classes these may have added to the global function
554      * and class tables are removed before we blow away the memory that hold them.
555      * 
556      * This is merely to remove memory leak warnings - as the process is terminated
557      * immediately after shutdown. The following while loop can be removed without
558      * affecting anything else.
559      */
560     while (apc_stack_size(APCG(cache_stack)) > 0) {
561         int i;
562         apc_cache_entry_t* cache_entry = (apc_cache_entry_t*) apc_stack_pop(APCG(cache_stack));
563         if (cache_entry->data.file.functions) {
564             for (i = 0; cache_entry->data.file.functions[i].function != NULL; i++) {
565                 zend_hash_del(EG(function_table),
566                     cache_entry->data.file.functions[i].name,
567                     cache_entry->data.file.functions[i].name_len+1);
568             }
569         }
570         if (cache_entry->data.file.classes) {
571             for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) {
572                 zend_hash_del(EG(class_table),
573                     cache_entry->data.file.classes[i].name,
574                     cache_entry->data.file.classes[i].name_len+1);
575             }
576         }
577         apc_cache_release(apc_cache, cache_entry);
578     }
579
580     apc_cache_destroy(apc_cache);
581     apc_cache_destroy(apc_user_cache);
582     apc_sma_cleanup();
583
584     APCG(initialized) = 0;
585     return 0;
586 }
587
588 /* }}} */
589
590 /* {{{ process init and shutdown */
591 int apc_process_init(int module_number TSRMLS_DC)
592 {
593     return 0;
594 }
595
596 int apc_process_shutdown(TSRMLS_D)
597 {
598     return 0;
599 }
600 /* }}} */
601
602 /* {{{ apc_deactivate */
603 static void apc_deactivate(TSRMLS_D)
604 {
605     /* The execution stack was unwound, which prevented us from decrementing
606      * the reference counts on active cache entries in `my_execute`.
607      */
608     while (apc_stack_size(APCG(cache_stack)) > 0) {
609         int i;
610         zend_class_entry* zce = NULL;
611         void ** centry = (void*)(&zce);
612 #ifdef ZEND_ENGINE_2
613         zend_class_entry** pzce = NULL;
614 #endif
615         
616         apc_cache_entry_t* cache_entry =
617             (apc_cache_entry_t*) apc_stack_pop(APCG(cache_stack));
618
619         if (cache_entry->data.file.classes) {
620             for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) {
621 #ifdef ZEND_ENGINE_2
622                 centry = (void**)&pzce; /* a triple indirection to get zend_class_entry*** */
623 #endif
624                 if(zend_hash_find(EG(class_table), 
625                     cache_entry->data.file.classes[i].name,
626                     cache_entry->data.file.classes[i].name_len+1,
627                     (void**)centry) == FAILURE)
628                 {
629                     /* double inclusion of conditional classes ends up failing 
630                      * this lookup the second time around.
631                      */
632                     continue;
633                 }
634
635 #ifdef ZEND_ENGINE_2
636                 zce = *pzce;
637 #endif
638                 zend_hash_del(EG(class_table),
639                     cache_entry->data.file.classes[i].name,
640                     cache_entry->data.file.classes[i].name_len+1);
641                 
642                 apc_free_class_entry_after_execution(zce);
643             }
644         }
645         apc_cache_release(apc_cache, cache_entry);
646     }
647
648 }
649 /* }}} */
650
651 /* {{{ request init and shutdown */
652
653 int apc_request_init(TSRMLS_D)
654 {
655     apc_stack_clear(APCG(cache_stack));
656     APCG(slam_rand) = -1;
657     APCG(copied_zvals) = NULL;
658
659 #ifdef APC_FILEHITS
660     ALLOC_INIT_ZVAL(APCG(filehits));
661     array_init(APCG(filehits));
662 #endif
663
664     return 0;
665 }
666
667 int apc_request_shutdown(TSRMLS_D)
668 {
669     apc_deactivate(TSRMLS_C);
670
671 #ifdef APC_FILEHITS
672     zval_ptr_dtor(&APCG(filehits));
673 #endif
674
675     return 0;
676 }
677
678 /* }}} */
679
680
681 /*
682  * Local variables:
683  * tab-width: 4
684  * c-basic-offset: 4
685  * End:
686  * vim600: expandtab sw=4 ts=4 sts=4 fdm=marker
687  * vim<600: expandtab sw=4 ts=4 sts=4
688  */