1/*
2   +----------------------------------------------------------------------+
3   | Zend Engine                                                          |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2014 Zend Technologies Ltd. (http://www.zend.com) |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt.                                |
11   | If you did not receive a copy of the Zend license and are unable to  |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@zend.com so we can mail you a copy immediately.              |
14   +----------------------------------------------------------------------+
15   | Authors: Marcus Boerger <helly@php.net>                              |
16   +----------------------------------------------------------------------+
17*/
18
19/* $Id$ */
20
21#include "zend.h"
22#include "zend_API.h"
23#include "zend_interfaces.h"
24#include "zend_exceptions.h"
25
26ZEND_API zend_class_entry *zend_ce_traversable;
27ZEND_API zend_class_entry *zend_ce_aggregate;
28ZEND_API zend_class_entry *zend_ce_iterator;
29ZEND_API zend_class_entry *zend_ce_arrayaccess;
30ZEND_API zend_class_entry *zend_ce_serializable;
31
32/* {{{ zend_call_method
33 Only returns the returned zval if retval_ptr != NULL */
34ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC)
35{
36    int result;
37    zend_fcall_info fci;
38    zval z_fname;
39    zval *retval;
40    HashTable *function_table;
41
42    zval **params[2];
43
44    params[0] = &arg1;
45    params[1] = &arg2;
46
47    fci.size = sizeof(fci);
48    /*fci.function_table = NULL; will be read form zend_class_entry of object if needed */
49    fci.object_ptr = object_pp ? *object_pp : NULL;
50    fci.function_name = &z_fname;
51    fci.retval_ptr_ptr = retval_ptr_ptr ? retval_ptr_ptr : &retval;
52    fci.param_count = param_count;
53    fci.params = params;
54    fci.no_separation = 1;
55    fci.symbol_table = NULL;
56
57    if (!fn_proxy && !obj_ce) {
58        /* no interest in caching and no information already present that is
59         * needed later inside zend_call_function. */
60        ZVAL_STRINGL(&z_fname, function_name, function_name_len, 0);
61        fci.function_table = !object_pp ? EG(function_table) : NULL;
62        result = zend_call_function(&fci, NULL TSRMLS_CC);
63    } else {
64        zend_fcall_info_cache fcic;
65
66        fcic.initialized = 1;
67        if (!obj_ce) {
68            obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL;
69        }
70        if (obj_ce) {
71            function_table = &obj_ce->function_table;
72        } else {
73            function_table = EG(function_table);
74        }
75        if (!fn_proxy || !*fn_proxy) {
76            if (zend_hash_find(function_table, function_name, function_name_len+1, (void **) &fcic.function_handler) == FAILURE) {
77                /* error at c-level */
78                zend_error(E_CORE_ERROR, "Couldn't find implementation for method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name);
79            }
80            if (fn_proxy) {
81                *fn_proxy = fcic.function_handler;
82            }
83        } else {
84            fcic.function_handler = *fn_proxy;
85        }
86        fcic.calling_scope = obj_ce;
87        if (object_pp) {
88            fcic.called_scope = Z_OBJCE_PP(object_pp);
89        } else if (obj_ce &&
90                   !(EG(called_scope) &&
91                     instanceof_function(EG(called_scope), obj_ce TSRMLS_CC))) {
92            fcic.called_scope = obj_ce;
93        } else {
94            fcic.called_scope = EG(called_scope);
95        }
96        fcic.object_ptr = object_pp ? *object_pp : NULL;
97        result = zend_call_function(&fci, &fcic TSRMLS_CC);
98    }
99    if (result == FAILURE) {
100        /* error at c-level */
101        if (!obj_ce) {
102            obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL;
103        }
104        if (!EG(exception)) {
105            zend_error(E_CORE_ERROR, "Couldn't execute method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name);
106        }
107    }
108    if (!retval_ptr_ptr) {
109        if (retval) {
110            zval_ptr_dtor(&retval);
111        }
112        return NULL;
113    }
114    return *retval_ptr_ptr;
115}
116/* }}} */
117
118/* iterator interface, c-level functions used by engine */
119
120/* {{{ zend_user_it_new_iterator */
121ZEND_API zval *zend_user_it_new_iterator(zend_class_entry *ce, zval *object TSRMLS_DC)
122{
123    zval *retval;
124
125    return zend_call_method_with_0_params(&object, ce, &ce->iterator_funcs.zf_new_iterator, "getiterator", &retval);
126
127}
128/* }}} */
129
130/* {{{ zend_user_it_invalidate_current */
131ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter TSRMLS_DC)
132{
133    zend_user_iterator *iter = (zend_user_iterator*)_iter;
134
135    if (iter->value) {
136        zval_ptr_dtor(&iter->value);
137        iter->value = NULL;
138    }
139}
140/* }}} */
141
142/* {{{ zend_user_it_dtor */
143static void zend_user_it_dtor(zend_object_iterator *_iter TSRMLS_DC)
144{
145    zend_user_iterator *iter = (zend_user_iterator*)_iter;
146    zval *object = (zval*)iter->it.data;
147
148    zend_user_it_invalidate_current(_iter TSRMLS_CC);
149    zval_ptr_dtor(&object);
150    efree(iter);
151}
152/* }}} */
153
154/* {{{ zend_user_it_valid */
155ZEND_API int zend_user_it_valid(zend_object_iterator *_iter TSRMLS_DC)
156{
157    if (_iter) {
158        zend_user_iterator *iter = (zend_user_iterator*)_iter;
159        zval *object = (zval*)iter->it.data;
160        zval *more;
161        int result;
162
163        zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_valid, "valid", &more);
164        if (more) {
165            result = i_zend_is_true(more);
166            zval_ptr_dtor(&more);
167            return result ? SUCCESS : FAILURE;
168        }
169    }
170    return FAILURE;
171}
172/* }}} */
173
174/* {{{ zend_user_it_get_current_data */
175ZEND_API void zend_user_it_get_current_data(zend_object_iterator *_iter, zval ***data TSRMLS_DC)
176{
177    zend_user_iterator *iter = (zend_user_iterator*)_iter;
178    zval *object = (zval*)iter->it.data;
179
180    if (!iter->value) {
181        zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_current, "current", &iter->value);
182    }
183    *data = &iter->value;
184}
185/* }}} */
186
187/* {{{ zend_user_it_get_current_key_default */
188#if 0
189static int zend_user_it_get_current_key_default(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC)
190{
191    *int_key = _iter->index;
192    return HASH_KEY_IS_LONG;
193}
194#endif
195/* }}} */
196
197/* {{{ zend_user_it_get_current_key */
198ZEND_API int zend_user_it_get_current_key(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC)
199{
200    zend_user_iterator *iter = (zend_user_iterator*)_iter;
201    zval *object = (zval*)iter->it.data;
202    zval *retval;
203
204    zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_key, "key", &retval);
205
206    if (!retval) {
207        *int_key = 0;
208        if (!EG(exception))
209        {
210            zend_error(E_WARNING, "Nothing returned from %s::key()", iter->ce->name);
211        }
212        return HASH_KEY_IS_LONG;
213    }
214    switch (Z_TYPE_P(retval)) {
215        default:
216            zend_error(E_WARNING, "Illegal type returned from %s::key()", iter->ce->name);
217        case IS_NULL:
218            *int_key = 0;
219            zval_ptr_dtor(&retval);
220            return HASH_KEY_IS_LONG;
221
222        case IS_STRING:
223            *str_key = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
224            *str_key_len = Z_STRLEN_P(retval)+1;
225            zval_ptr_dtor(&retval);
226            return HASH_KEY_IS_STRING;
227
228        case IS_DOUBLE:
229            *int_key = (long)Z_DVAL_P(retval);
230            zval_ptr_dtor(&retval);
231            return HASH_KEY_IS_LONG;
232
233        case IS_RESOURCE:
234        case IS_BOOL:
235        case IS_LONG:
236            *int_key = (long)Z_LVAL_P(retval);
237            zval_ptr_dtor(&retval);
238            return HASH_KEY_IS_LONG;
239    }
240}
241/* }}} */
242
243/* {{{ zend_user_it_move_forward */
244ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC)
245{
246    zend_user_iterator *iter = (zend_user_iterator*)_iter;
247    zval *object = (zval*)iter->it.data;
248
249    zend_user_it_invalidate_current(_iter TSRMLS_CC);
250    zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_next, "next", NULL);
251}
252/* }}} */
253
254/* {{{ zend_user_it_rewind */
255ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter TSRMLS_DC)
256{
257    zend_user_iterator *iter = (zend_user_iterator*)_iter;
258    zval *object = (zval*)iter->it.data;
259
260    zend_user_it_invalidate_current(_iter TSRMLS_CC);
261    zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_rewind, "rewind", NULL);
262}
263/* }}} */
264
265zend_object_iterator_funcs zend_interface_iterator_funcs_iterator = {
266    zend_user_it_dtor,
267    zend_user_it_valid,
268    zend_user_it_get_current_data,
269    zend_user_it_get_current_key,
270    zend_user_it_move_forward,
271    zend_user_it_rewind,
272    zend_user_it_invalidate_current
273};
274
275/* {{{ zend_user_it_get_iterator */
276static zend_object_iterator *zend_user_it_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC)
277{
278    zend_user_iterator *iterator;
279
280    if (by_ref) {
281        zend_error(E_ERROR, "An iterator cannot be used with foreach by reference");
282    }
283
284    iterator = emalloc(sizeof(zend_user_iterator));
285
286    Z_ADDREF_P(object);
287    iterator->it.data = (void*)object;
288    iterator->it.funcs = ce->iterator_funcs.funcs;
289    iterator->ce = Z_OBJCE_P(object);
290    iterator->value = NULL;
291    return (zend_object_iterator*)iterator;
292}
293/* }}} */
294
295/* {{{ zend_user_it_get_new_iterator */
296ZEND_API zend_object_iterator *zend_user_it_get_new_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC)
297{
298    zval *iterator = zend_user_it_new_iterator(ce, object TSRMLS_CC);
299    zend_object_iterator *new_iterator;
300
301    zend_class_entry *ce_it = iterator && Z_TYPE_P(iterator) == IS_OBJECT ? Z_OBJCE_P(iterator) : NULL;
302
303    if (!ce_it || !ce_it->get_iterator || (ce_it->get_iterator == zend_user_it_get_new_iterator && iterator == object)) {
304        if (!EG(exception)) {
305            zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Objects returned by %s::getIterator() must be traversable or implement interface Iterator", ce ? ce->name : Z_OBJCE_P(object)->name);
306        }
307        if (iterator) {
308            zval_ptr_dtor(&iterator);
309        }
310        return NULL;
311    }
312
313    new_iterator = ce_it->get_iterator(ce_it, iterator, by_ref TSRMLS_CC);
314    zval_ptr_dtor(&iterator);
315    return new_iterator;
316}
317/* }}} */
318
319/* {{{ zend_implement_traversable */
320static int zend_implement_traversable(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC)
321{
322    /* check that class_type is traversable at c-level or implements at least one of 'aggregate' and 'Iterator' */
323    zend_uint i;
324
325    if (class_type->get_iterator || (class_type->parent && class_type->parent->get_iterator)) {
326        return SUCCESS;
327    }
328    for (i = 0; i < class_type->num_interfaces; i++) {
329        if (class_type->interfaces[i] == zend_ce_aggregate || class_type->interfaces[i] == zend_ce_iterator) {
330            return SUCCESS;
331        }
332    }
333    zend_error(E_CORE_ERROR, "Class %s must implement interface %s as part of either %s or %s",
334        class_type->name,
335        zend_ce_traversable->name,
336        zend_ce_iterator->name,
337        zend_ce_aggregate->name);
338    return FAILURE;
339}
340/* }}} */
341
342/* {{{ zend_implement_aggregate */
343static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC)
344{
345    int i, t = -1;
346
347    if (class_type->get_iterator) {
348        if (class_type->type == ZEND_INTERNAL_CLASS) {
349            /* inheritance ensures the class has necessary userland methods */
350            return SUCCESS;
351        } else if (class_type->get_iterator != zend_user_it_get_new_iterator) {
352            /* c-level get_iterator cannot be changed (exception being only Traversable is implmented) */
353            if (class_type->num_interfaces) {
354                for (i = 0; i < class_type->num_interfaces; i++) {
355                    if (class_type->interfaces[i] == zend_ce_iterator) {
356                        zend_error(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
357                                    class_type->name,
358                                    interface->name,
359                                    zend_ce_iterator->name);
360                        return FAILURE;
361                    }
362                    if (class_type->interfaces[i] == zend_ce_traversable) {
363                        t = i;
364                    }
365                }
366            }
367            if (t == -1) {
368                return FAILURE;
369            }
370        }
371    }
372    class_type->iterator_funcs.zf_new_iterator = NULL;
373    class_type->get_iterator = zend_user_it_get_new_iterator;
374    return SUCCESS;
375}
376/* }}} */
377
378/* {{{ zend_implement_iterator */
379static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC)
380{
381    if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_iterator) {
382        if (class_type->type == ZEND_INTERNAL_CLASS) {
383            /* inheritance ensures the class has the necessary userland methods */
384            return SUCCESS;
385        } else {
386            /* c-level get_iterator cannot be changed */
387            if (class_type->get_iterator == zend_user_it_get_new_iterator) {
388                zend_error(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
389                            class_type->name,
390                            interface->name,
391                            zend_ce_aggregate->name);
392            }
393            return FAILURE;
394        }
395    }
396    class_type->get_iterator = zend_user_it_get_iterator;
397    class_type->iterator_funcs.zf_valid = NULL;
398    class_type->iterator_funcs.zf_current = NULL;
399    class_type->iterator_funcs.zf_key = NULL;
400    class_type->iterator_funcs.zf_next = NULL;
401    class_type->iterator_funcs.zf_rewind = NULL;
402    if (!class_type->iterator_funcs.funcs) {
403        class_type->iterator_funcs.funcs = &zend_interface_iterator_funcs_iterator;
404    }
405    return SUCCESS;
406}
407/* }}} */
408
409/* {{{ zend_implement_arrayaccess */
410static int zend_implement_arrayaccess(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC)
411{
412#if 0
413    /* get ht from ce */
414    if (ht->read_dimension != zend_std_read_dimension
415    ||  ht->write_dimension != zend_std_write_dimension
416    ||  ht->has_dimension != zend_std_has_dimension
417    ||  ht->unset_dimension != zend_std_unset_dimension) {
418        return FAILURE;
419    }
420#endif
421    return SUCCESS;
422}
423/* }}}*/
424
425/* {{{ zend_user_serialize */
426ZEND_API int zend_user_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC)
427{
428    zend_class_entry * ce = Z_OBJCE_P(object);
429    zval *retval;
430    int result;
431
432    zend_call_method_with_0_params(&object, ce, &ce->serialize_func, "serialize", &retval);
433
434
435    if (!retval || EG(exception)) {
436        result = FAILURE;
437    } else {
438        switch(Z_TYPE_P(retval)) {
439        case IS_NULL:
440            /* we could also make this '*buf_len = 0' but this allows to skip variables */
441            zval_ptr_dtor(&retval);
442            return FAILURE;
443        case IS_STRING:
444            *buffer = (unsigned char*)estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
445            *buf_len = Z_STRLEN_P(retval);
446            result = SUCCESS;
447            break;
448        default: /* failure */
449            result = FAILURE;
450            break;
451        }
452        zval_ptr_dtor(&retval);
453    }
454
455    if (result == FAILURE && !EG(exception)) {
456        zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s::serialize() must return a string or NULL", ce->name);
457    }
458    return result;
459}
460/* }}} */
461
462/* {{{ zend_user_unserialize */
463ZEND_API int zend_user_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC)
464{
465    zval * zdata;
466
467    object_init_ex(*object, ce);
468
469    MAKE_STD_ZVAL(zdata);
470    ZVAL_STRINGL(zdata, (char*)buf, buf_len, 1);
471
472    zend_call_method_with_1_params(object, ce, &ce->unserialize_func, "unserialize", NULL, zdata);
473
474    zval_ptr_dtor(&zdata);
475
476    if (EG(exception)) {
477        return FAILURE;
478    } else {
479        return SUCCESS;
480    }
481}
482/* }}} */
483
484ZEND_API int zend_class_serialize_deny(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC) /* {{{ */
485{
486    zend_class_entry *ce = Z_OBJCE_P(object);
487    zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Serialization of '%s' is not allowed", ce->name);
488    return FAILURE;
489}
490/* }}} */
491
492ZEND_API int zend_class_unserialize_deny(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */
493{
494    zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Unserialization of '%s' is not allowed", ce->name);
495    return FAILURE;
496}
497/* }}} */
498
499/* {{{ zend_implement_serializable */
500static int zend_implement_serializable(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC)
501{
502    if (class_type->parent
503        && (class_type->parent->serialize || class_type->parent->unserialize)
504        && !instanceof_function_ex(class_type->parent, zend_ce_serializable, 1 TSRMLS_CC)) {
505        return FAILURE;
506    }
507    if (!class_type->serialize) {
508        class_type->serialize = zend_user_serialize;
509    }
510    if (!class_type->unserialize) {
511        class_type->unserialize = zend_user_unserialize;
512    }
513    return SUCCESS;
514}
515/* }}}*/
516
517/* {{{ function tables */
518const zend_function_entry zend_funcs_aggregate[] = {
519    ZEND_ABSTRACT_ME(iterator, getIterator, NULL)
520    {NULL, NULL, NULL}
521};
522
523const zend_function_entry zend_funcs_iterator[] = {
524    ZEND_ABSTRACT_ME(iterator, current,  NULL)
525    ZEND_ABSTRACT_ME(iterator, next,     NULL)
526    ZEND_ABSTRACT_ME(iterator, key,      NULL)
527    ZEND_ABSTRACT_ME(iterator, valid,    NULL)
528    ZEND_ABSTRACT_ME(iterator, rewind,   NULL)
529    {NULL, NULL, NULL}
530};
531
532const zend_function_entry *zend_funcs_traversable    = NULL;
533
534ZEND_BEGIN_ARG_INFO_EX(arginfo_arrayaccess_offset, 0, 0, 1)
535    ZEND_ARG_INFO(0, offset)
536ZEND_END_ARG_INFO()
537
538ZEND_BEGIN_ARG_INFO_EX(arginfo_arrayaccess_offset_get, 0, 0, 1) /* actually this should be return by ref but atm cannot be */
539    ZEND_ARG_INFO(0, offset)
540ZEND_END_ARG_INFO()
541
542ZEND_BEGIN_ARG_INFO_EX(arginfo_arrayaccess_offset_value, 0, 0, 2)
543    ZEND_ARG_INFO(0, offset)
544    ZEND_ARG_INFO(0, value)
545ZEND_END_ARG_INFO()
546
547const zend_function_entry zend_funcs_arrayaccess[] = {
548    ZEND_ABSTRACT_ME(arrayaccess, offsetExists, arginfo_arrayaccess_offset)
549    ZEND_ABSTRACT_ME(arrayaccess, offsetGet,    arginfo_arrayaccess_offset_get)
550    ZEND_ABSTRACT_ME(arrayaccess, offsetSet,    arginfo_arrayaccess_offset_value)
551    ZEND_ABSTRACT_ME(arrayaccess, offsetUnset,  arginfo_arrayaccess_offset)
552    {NULL, NULL, NULL}
553};
554
555ZEND_BEGIN_ARG_INFO(arginfo_serializable_serialize, 0)
556    ZEND_ARG_INFO(0, serialized)
557ZEND_END_ARG_INFO()
558
559const zend_function_entry zend_funcs_serializable[] = {
560    ZEND_ABSTRACT_ME(serializable, serialize,   NULL)
561    ZEND_FENTRY(unserialize, NULL, arginfo_serializable_serialize, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT|ZEND_ACC_CTOR)
562    {NULL, NULL, NULL}
563};
564/* }}} */
565
566#define REGISTER_ITERATOR_INTERFACE(class_name, class_name_str) \
567    {\
568        zend_class_entry ce;\
569        INIT_CLASS_ENTRY(ce, # class_name_str, zend_funcs_ ## class_name) \
570        zend_ce_ ## class_name = zend_register_internal_interface(&ce TSRMLS_CC);\
571        zend_ce_ ## class_name->interface_gets_implemented = zend_implement_ ## class_name;\
572    }
573
574#define REGISTER_ITERATOR_IMPLEMENT(class_name, interface_name) \
575    zend_class_implements(zend_ce_ ## class_name TSRMLS_CC, 1, zend_ce_ ## interface_name)
576
577/* {{{ zend_register_interfaces */
578ZEND_API void zend_register_interfaces(TSRMLS_D)
579{
580    REGISTER_ITERATOR_INTERFACE(traversable, Traversable);
581
582    REGISTER_ITERATOR_INTERFACE(aggregate, IteratorAggregate);
583    REGISTER_ITERATOR_IMPLEMENT(aggregate, traversable);
584
585    REGISTER_ITERATOR_INTERFACE(iterator, Iterator);
586    REGISTER_ITERATOR_IMPLEMENT(iterator, traversable);
587
588    REGISTER_ITERATOR_INTERFACE(arrayaccess, ArrayAccess);
589
590    REGISTER_ITERATOR_INTERFACE(serializable, Serializable)
591}
592/* }}} */
593
594/*
595 * Local variables:
596 * tab-width: 4
597 * c-basic-offset: 4
598 * indent-tabs-mode: t
599 * End:
600 */
601