Annotation of embedaddon/php/ext/dba/libinifile/inifile.c, revision 1.1.1.1

1.1       misho       1: /*
                      2:    +----------------------------------------------------------------------+
                      3:    | PHP Version 5                                                        |
                      4:    +----------------------------------------------------------------------+
                      5:    | Copyright (c) 1997-2010 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:    | Author: Marcus Boerger <helly@php.net>                               |
                     16:    +----------------------------------------------------------------------+
                     17:  */
                     18: 
                     19: /* $Id: inifile.c 293036 2010-01-03 09:23:27Z sebastian $ */
                     20: 
                     21: #ifdef HAVE_CONFIG_H
                     22: #include "config.h"
                     23: #endif
                     24: 
                     25: #include "php.h"
                     26: #include "php_globals.h"
                     27: #include "safe_mode.h"
                     28: 
                     29: #include <stdlib.h>
                     30: #include <string.h>
                     31: #include <errno.h>
                     32: #if HAVE_UNISTD_H
                     33: #include <unistd.h>
                     34: #endif
                     35: 
                     36: #include "inifile.h"
                     37: 
                     38: /* ret = -1 means that database was opened for read-only
                     39:  * ret = 0  success
                     40:  * ret = 1  key already exists - nothing done
                     41:  */
                     42: 
                     43: /* {{{ inifile_version */
                     44: char *inifile_version() 
                     45: {
                     46:        return "1.0, $Revision: 293036 $";
                     47: }
                     48: /* }}} */ 
                     49: 
                     50: /* {{{ inifile_free_key */
                     51: void inifile_key_free(key_type *key)
                     52: {
                     53:        if (key->group) {
                     54:                efree(key->group);
                     55:        }
                     56:        if (key->name) {
                     57:                efree(key->name);
                     58:        }
                     59:        memset(key, 0, sizeof(key_type));
                     60: }
                     61: /* }}} */
                     62: 
                     63: /* {{{ inifile_free_val */
                     64: void inifile_val_free(val_type *val)
                     65: {
                     66:        if (val->value) {
                     67:                efree(val->value);
                     68:        }
                     69:        memset(val, 0, sizeof(val_type));
                     70: }
                     71: /* }}} */
                     72: 
                     73: /* {{{ inifile_free_val */
                     74: void inifile_line_free(line_type *ln)
                     75: {
                     76:        inifile_key_free(&ln->key);
                     77:        inifile_val_free(&ln->val);
                     78:        ln->pos = 0;
                     79: }
                     80: /* }}} */
                     81: 
                     82: /* {{{ inifile_alloc */
                     83: inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
                     84: {
                     85:        inifile *dba;
                     86: 
                     87:        if (!readonly) {
                     88:                if (!php_stream_truncate_supported(fp)) {
                     89:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
                     90:                        return NULL;
                     91:                }
                     92:        }
                     93:  
                     94:        dba = pemalloc(sizeof(inifile), persistent);
                     95:        memset(dba, 0, sizeof(inifile));
                     96:        dba->fp = fp;
                     97:        dba->readonly = readonly;
                     98:        return dba;
                     99: }
                    100: /* }}} */
                    101: 
                    102: /* {{{ inifile_free */
                    103: void inifile_free(inifile *dba, int persistent)
                    104: {
                    105:        if (dba) {
                    106:                inifile_line_free(&dba->curr);
                    107:                inifile_line_free(&dba->next);
                    108:                pefree(dba, persistent);
                    109:        }
                    110: }
                    111: /* }}} */
                    112: 
                    113: /* {{{ inifile_key_split */
                    114: key_type inifile_key_split(const char *group_name)
                    115: {
                    116:        key_type key;
                    117:        char *name;
                    118:        
                    119:        if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
                    120:                key.group = estrndup(group_name+1, name - (group_name + 1));
                    121:                key.name = estrdup(name+1);
                    122:        } else {
                    123:                key.group = estrdup("");
                    124:                key.name = estrdup(group_name);
                    125:        }
                    126:        return key;
                    127: }
                    128: /* }}} */
                    129: 
                    130: /* {{{ inifile_key_string */
                    131: char * inifile_key_string(const key_type *key)
                    132: {
                    133:        if (key->group && *key->group) {
                    134:                char *result;
                    135:                spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
                    136:                return result;
                    137:        } else if (key->name) {
                    138:                return estrdup(key->name);
                    139:        } else {
                    140:                return NULL;
                    141:        }
                    142: }
                    143: /* }}} */
                    144: 
                    145: /* {{{ etrim */
                    146: static char *etrim(const char *str)
                    147: {
                    148:        char *val;
                    149:        size_t l;
                    150:        
                    151:        if (!str) {
                    152:                return NULL;
                    153:        }
                    154:        val = (char*)str;
                    155:        while (*val && strchr(" \t\r\n", *val)) {
                    156:                val++;
                    157:        }
                    158:        l = strlen(val);
                    159:        while (l && (strchr(" \t\r\n", val[l-1]))) {
                    160:                l--;
                    161:        }
                    162:        return estrndup(val, l);
                    163: }
                    164: /* }}} */
                    165: 
                    166: /* {{{ inifile_findkey
                    167:  */
                    168: static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
                    169:        char *fline;
                    170:        char *pos;
                    171: 
                    172:        inifile_val_free(&ln->val);
                    173:        while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
                    174:                if (fline) {
                    175:                        if (fline[0] == '[') {
                    176:                                /* A value name cannot start with '['
                    177:                                 * So either we find a ']' or we found an error
                    178:                                 */
                    179:                                pos = strchr(fline+1, ']');
                    180:                                if (pos) {
                    181:                                        *pos = '\0';
                    182:                                        inifile_key_free(&ln->key);
                    183:                                        ln->key.group = etrim(fline+1);
                    184:                                        ln->key.name = estrdup("");
                    185:                                        ln->pos = php_stream_tell(dba->fp);
                    186:                                        efree(fline);
                    187:                                        return 1;
                    188:                                } else {
                    189:                                        efree(fline);
                    190:                                        continue;
                    191:                                }
                    192:                        } else {
                    193:                                pos = strchr(fline, '=');
                    194:                                if (pos) {
                    195:                                        *pos = '\0';
                    196:                                        /* keep group or make empty if not existent */
                    197:                                        if (!ln->key.group) {
                    198:                                                ln->key.group = estrdup("");
                    199:                                        }
                    200:                                        if (ln->key.name) {
                    201:                                                efree(ln->key.name);
                    202:                                        }
                    203:                                        ln->key.name = etrim(fline);
                    204:                                        ln->val.value = etrim(pos+1);
                    205:                                        ln->pos = php_stream_tell(dba->fp);
                    206:                                        efree(fline);
                    207:                                        return 1;
                    208:                                } else {
                    209:                                        /* simply ignore lines without '='
                    210:                                         * those should be comments
                    211:                                         */
                    212:                                         efree(fline);
                    213:                                         continue;
                    214:                                }
                    215:                        }
                    216:                }
                    217:        }
                    218:        inifile_line_free(ln);
                    219:        return 0;
                    220: }
                    221: /* }}} */
                    222: 
                    223: /* {{{ inifile_key_cmp */
                    224: /* 0 = EQUAL
                    225:  * 1 = GROUP-EQUAL,NAME-DIFFERENT
                    226:  * 2 = DIFFERENT
                    227:  */
                    228: static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
                    229: {
                    230:        assert(k1->group && k1->name && k2->group && k2->name);
                    231:        
                    232:        if (!strcasecmp(k1->group, k2->group)) {
                    233:                if (!strcasecmp(k1->name, k2->name)) {
                    234:                        return 0;
                    235:                } else {
                    236:                        return 1;
                    237:                }
                    238:        } else {
                    239:                return 2;
                    240:        }
                    241: }
                    242: /* }}} */
                    243: 
                    244: /* {{{ inifile_fetch
                    245:  */
                    246: val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
                    247:        line_type ln = {{NULL,NULL},{NULL}};
                    248:        val_type val;
                    249:        int res, grp_eq = 0;
                    250: 
                    251:        if (skip == -1 && dba->next.key.group && dba->next.key.name && !inifile_key_cmp(&dba->next.key, key TSRMLS_CC)) {
                    252:                /* we got position already from last fetch */
                    253:                php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
                    254:        } else {
                    255:                /* specific instance or not same key -> restart search */
                    256:                /* the slow way: restart and seacrch */
                    257:                php_stream_rewind(dba->fp);
                    258:                inifile_line_free(&dba->next);
                    259:        }
                    260:        if (skip == -1) {
                    261:                skip = 0;
                    262:        }
                    263:        while(inifile_read(dba, &ln TSRMLS_CC)) {
                    264:                if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
                    265:                        if (!skip) {
                    266:                                val.value = estrdup(ln.val.value ? ln.val.value : "");
                    267:                                /* allow faster access by updating key read into next */
                    268:                                inifile_line_free(&dba->next);
                    269:                                dba->next = ln;
                    270:                                dba->next.pos = php_stream_tell(dba->fp);
                    271:                                return val;
                    272:                        }
                    273:                        skip--;
                    274:                } else if (res == 1) {
                    275:                        grp_eq = 1;
                    276:                } else if (grp_eq) {
                    277:                        /* we are leaving group now: that means we cannot find the key */
                    278:                        break;
                    279:                }
                    280:        }
                    281:        inifile_line_free(&ln);
                    282:        dba->next.pos = php_stream_tell(dba->fp);
                    283:        return ln.val;
                    284: }
                    285: /* }}} */
                    286: 
                    287: /* {{{ inifile_firstkey
                    288:  */
                    289: int inifile_firstkey(inifile *dba TSRMLS_DC) {
                    290:        inifile_line_free(&dba->curr);
                    291:        dba->curr.pos = 0;
                    292:        return inifile_nextkey(dba TSRMLS_CC);
                    293: }
                    294: /* }}} */
                    295: 
                    296: /* {{{ inifile_nextkey
                    297:  */
                    298: int inifile_nextkey(inifile *dba TSRMLS_DC) {
                    299:        line_type ln = {{NULL,NULL},{NULL}};
                    300: 
                    301:        /*inifile_line_free(&dba->next); ??? */
                    302:        php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
                    303:        ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
                    304:        inifile_read(dba, &ln TSRMLS_CC);
                    305:        inifile_line_free(&dba->curr);
                    306:        dba->curr = ln;
                    307:        return ln.key.group || ln.key.name;
                    308: }      
                    309: /* }}} */
                    310: 
                    311: /* {{{ inifile_truncate
                    312:  */
                    313: static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
                    314: {
                    315:        int res;
                    316: 
                    317:        if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
                    318:                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
                    319:                return FAILURE;
                    320:        }
                    321:        php_stream_seek(dba->fp, size, SEEK_SET);
                    322:        return SUCCESS;
                    323: }
                    324: /* }}} */
                    325: 
                    326: /* {{{ inifile_find_group
                    327:  * if found pos_grp_start points to "[group_name]"
                    328:  */
                    329: static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC) 
                    330: {
                    331:        int ret = FAILURE;
                    332: 
                    333:        php_stream_flush(dba->fp);
                    334:        php_stream_seek(dba->fp, 0, SEEK_SET);
                    335:        inifile_line_free(&dba->curr);
                    336:        inifile_line_free(&dba->next);
                    337: 
                    338:        if (key->group && strlen(key->group)) {
                    339:                int res;
                    340:                line_type ln = {{NULL,NULL},{NULL}};
                    341: 
                    342:                res = 1;
                    343:                while(inifile_read(dba, &ln TSRMLS_CC)) {
                    344:                        if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
                    345:                                ret = SUCCESS;
                    346:                                break;
                    347:                        }
                    348:                        *pos_grp_start = php_stream_tell(dba->fp);
                    349:                }
                    350:                inifile_line_free(&ln);
                    351:        } else {
                    352:                *pos_grp_start = 0;
                    353:                ret = SUCCESS;
                    354:        }
                    355:        if (ret == FAILURE) {
                    356:                *pos_grp_start = php_stream_tell(dba->fp);
                    357:        }
                    358:        return ret;
                    359: }
                    360: /* }}} */
                    361: 
                    362: /* {{{ inifile_next_group
                    363:  * only valid after a call to inifile_find_group
                    364:  * if any next group is found pos_grp_start points to "[group_name]" or whitespace before that
                    365:  */
                    366: static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC) 
                    367: {
                    368:        int ret = FAILURE;
                    369:        line_type ln = {{NULL,NULL},{NULL}};
                    370: 
                    371:        *pos_grp_start = php_stream_tell(dba->fp);
                    372:        ln.key.group = estrdup(key->group);
                    373:        while(inifile_read(dba, &ln TSRMLS_CC)) {
                    374:                if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
                    375:                        ret = SUCCESS;
                    376:                        break;
                    377:                }
                    378:                *pos_grp_start = php_stream_tell(dba->fp);
                    379:        }
                    380:        inifile_line_free(&ln);
                    381:        return ret;
                    382: }
                    383: /* }}} */
                    384: 
                    385: /* {{{ inifile_copy_to
                    386:  */
                    387: static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
                    388: {
                    389:        php_stream *fp;
                    390:        
                    391:        if (pos_start == pos_end) {
                    392:                *ini_copy = NULL;
                    393:                return SUCCESS;
                    394:        }
                    395:        if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
                    396:                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
                    397:                *ini_copy = NULL;
                    398:                return FAILURE;
                    399:        }
                    400: 
                    401:        if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
                    402:                /* writes error */
                    403:                return FAILURE;
                    404:        }
                    405:        php_stream_seek(dba->fp, pos_start, SEEK_SET);
                    406:        if (!php_stream_copy_to_stream(dba->fp, fp, pos_end - pos_start)) {
                    407:                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
                    408:                return FAILURE;
                    409:        } 
                    410:        return SUCCESS;
                    411: }
                    412: /* }}} */
                    413: 
                    414: /* {{{ inifile_filter
                    415:  * copy from to dba while ignoring key name (group must equal)
                    416:  */
                    417: static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC) 
                    418: {
                    419:        size_t pos_start = 0, pos_next = 0, pos_curr;
                    420:        int ret = SUCCESS;
                    421:        line_type ln = {{NULL,NULL},{NULL}};
                    422: 
                    423:        php_stream_seek(from->fp, 0, SEEK_SET);
                    424:        php_stream_seek(dba->fp, 0, SEEK_END);
                    425:        while(inifile_read(from, &ln TSRMLS_CC)) {
                    426:                switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
                    427:                case 0:
                    428:                        pos_curr = php_stream_tell(from->fp);
                    429:                        if (pos_start != pos_next) {
                    430:                                php_stream_seek(from->fp, pos_start, SEEK_SET);
                    431:                                if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
                    432:                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
                    433:                                        ret = FAILURE;
                    434:                                }
                    435:                                php_stream_seek(from->fp, pos_curr, SEEK_SET);
                    436:                        }
                    437:                        pos_next = pos_start = pos_curr;
                    438:                        break;
                    439:                case 1:
                    440:                        pos_next = php_stream_tell(from->fp);
                    441:                        break;
                    442:                case 2:
                    443:                        /* the function is meant to process only entries from same group */
                    444:                        assert(0);
                    445:                        break;
                    446:                }
                    447:        }
                    448:        if (pos_start != pos_next) {
                    449:                php_stream_seek(from->fp, pos_start, SEEK_SET);
                    450:                if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
                    451:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
                    452:                        ret = FAILURE;
                    453:                }
                    454:        }
                    455:        inifile_line_free(&ln);
                    456:        return SUCCESS;
                    457: }
                    458: /* }}} */
                    459: 
                    460: /* {{{ inifile_delete_replace_append
                    461:  */
                    462: static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC) 
                    463: {
                    464:        size_t pos_grp_start, pos_grp_next;
                    465:        inifile *ini_tmp = NULL;
                    466:        php_stream *fp_tmp = NULL;
                    467:        int ret;
                    468: 
                    469:        /* 1) Search group start
                    470:         * 2) Search next group
                    471:         * 3) If not append: Copy group to ini_tmp
                    472:         * 4) Open temp_stream and copy remainder
                    473:         * 5) Truncate stream
                    474:         * 6) If not append AND key.name given: Filtered copy back from ini_tmp 
                    475:         *    to stream. Otherwise the user wanted to delete the group.
                    476:         * 7) Append value if given
                    477:         * 8) Append temporary stream
                    478:         */
                    479: 
                    480:        assert(!append || (key->name && value)); /* missuse */
                    481: 
                    482:        /* 1 - 3 */
                    483:        inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
                    484:        inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
                    485:        if (append) {
                    486:                ret = SUCCESS;
                    487:        } else {
                    488:                ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
                    489:        }
                    490: 
                    491:        /* 4 */
                    492:        if (ret == SUCCESS) {
                    493:                fp_tmp = php_stream_temp_create(0, 64 * 1024);
                    494:                if (!fp_tmp) {
                    495:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
                    496:                        ret = FAILURE;
                    497:                } else {
                    498:                        php_stream_seek(dba->fp, 0, SEEK_END);
                    499:                        if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
                    500:                                php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
                    501:                                if (!php_stream_copy_to_stream(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL)) {
                    502:                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
                    503:                                        ret = FAILURE;
                    504:                                }
                    505:                        }
                    506:                }
                    507:        }
                    508:        
                    509:        /* 5 */
                    510:        if (ret == SUCCESS) {
                    511:                if (!value || (key->name && strlen(key->name))) {
                    512:                        ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
                    513:                }
                    514:        }
                    515: 
                    516:        if (ret == SUCCESS) {
                    517:                if (key->name && strlen(key->name)) {
                    518:                        /* 6 */
                    519:                        if (!append && ini_tmp) {
                    520:                                ret = inifile_filter(dba, ini_tmp, key TSRMLS_CC);
                    521:                        }
                    522: 
                    523:                        /* 7 */
                    524:                        /* important: do not query ret==SUCCESS again: inifile_filter might fail but
                    525:                         * however next operation must be done.
                    526:                         */
                    527:                        if (value) {
                    528:                                if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
                    529:                                        php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
                    530:                                }
                    531:                                php_stream_printf(dba->fp TSRMLS_CC, "%s=%s\n", key->name, value->value ? value->value : "");
                    532:                        }
                    533:                }
                    534:                
                    535:                /* 8 */ 
                    536:                /* important: do not query ret==SUCCESS again: inifile_filter might fail but
                    537:                 * however next operation must be done.
                    538:                 */
                    539:                if (fp_tmp && php_stream_tell(fp_tmp)) {
                    540:                        php_stream_seek(fp_tmp, 0, SEEK_SET);
                    541:                        php_stream_seek(dba->fp, 0, SEEK_END);
                    542:                        if (!php_stream_copy_to_stream(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL)) {
                    543:                                php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
                    544:                                ret = FAILURE;
                    545:                        }
                    546:                }
                    547:        }
                    548: 
                    549:        if (ini_tmp) {
                    550:                php_stream_close(ini_tmp->fp);
                    551:                inifile_free(ini_tmp, 0);
                    552:        }
                    553:        if (fp_tmp) {
                    554:                php_stream_close(fp_tmp);
                    555:        }
                    556:        php_stream_flush(dba->fp);
                    557:        php_stream_seek(dba->fp, 0, SEEK_SET);
                    558: 
                    559:        return ret;
                    560: }
                    561: /* }}} */
                    562: 
                    563: /* {{{ inifile_delete
                    564:  */
                    565: int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC) 
                    566: {
                    567:        return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
                    568: }      
                    569: /* }}} */
                    570: 
                    571: /* {{{ inifile_relace
                    572:  */
                    573: int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC) 
                    574: {
                    575:        return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
                    576: }
                    577: /* }}} */
                    578: 
                    579: /* {{{ inifile_append
                    580:  */
                    581: int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC) 
                    582: {
                    583:        return inifile_delete_replace_append(dba, key, value, 1 TSRMLS_CC);
                    584: }
                    585: /* }}} */
                    586: 
                    587: /*
                    588:  * Local variables:
                    589:  * tab-width: 4
                    590:  * c-basic-offset: 4
                    591:  * End:
                    592:  * vim600: sw=4 ts=4 fdm=marker
                    593:  * vim<600: sw=4 ts=4
                    594:  */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>