Annotation of embedaddon/php/ext/pdo/pdo_sql_parser.re, revision 1.1

1.1     ! misho       1: /*
        !             2:   +----------------------------------------------------------------------+
        !             3:   | PHP Version 5                                                        |
        !             4:   +----------------------------------------------------------------------+
        !             5:   | Copyright (c) 1997-2012 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: George Schlossnagle <george@omniti.com>                      |
        !            16:   +----------------------------------------------------------------------+
        !            17: */
        !            18: 
        !            19: /* $Id: pdo_sql_parser.re 321634 2012-01-01 13:15:04Z felipe $ */
        !            20: 
        !            21: #include "php.h"
        !            22: #include "php_pdo_driver.h"
        !            23: #include "php_pdo_int.h"
        !            24: 
        !            25: #define PDO_PARSER_TEXT 1
        !            26: #define PDO_PARSER_BIND 2
        !            27: #define PDO_PARSER_BIND_POS 3
        !            28: #define PDO_PARSER_EOI 4
        !            29: 
        !            30: #define RET(i) {s->cur = cursor; return i; }
        !            31: #define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
        !            32: 
        !            33: #define YYCTYPE         unsigned char
        !            34: #define YYCURSOR        cursor
        !            35: #define YYLIMIT         cursor
        !            36: #define YYMARKER        s->ptr
        !            37: #define YYFILL(n)
        !            38: 
        !            39: typedef struct Scanner {
        !            40:        char    *ptr, *cur, *tok;
        !            41: } Scanner;
        !            42: 
        !            43: static int scan(Scanner *s) 
        !            44: {
        !            45:        char *cursor = s->cur;
        !            46: 
        !            47:        s->tok = cursor;
        !            48:        /*!re2c
        !            49:        BINDCHR         = [:][a-zA-Z0-9_]+;
        !            50:        QUESTION        = [?];
        !            51:        COMMENTS        = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*);
        !            52:        SPECIALS        = [:?"'];
        !            53:        MULTICHAR       = [:?];
        !            54:        EOF                     = [\000];
        !            55:        ANYNOEOF        = [\001-\377];
        !            56:        */
        !            57: 
        !            58:        /*!re2c
        !            59:                (["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); }
        !            60:                (['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); }
        !            61:                MULTICHAR{2,}                                                   { RET(PDO_PARSER_TEXT); }
        !            62:                BINDCHR                                                                 { RET(PDO_PARSER_BIND); }
        !            63:                QUESTION                                                                { RET(PDO_PARSER_BIND_POS); }
        !            64:                SPECIALS                                                                { SKIP_ONE(PDO_PARSER_TEXT); }
        !            65:                COMMENTS                                                                { RET(PDO_PARSER_TEXT); }
        !            66:                (ANYNOEOF\SPECIALS)+                                    { RET(PDO_PARSER_TEXT); }
        !            67:                EOF                                                                             { RET(PDO_PARSER_EOI); }
        !            68:        */      
        !            69: }
        !            70: 
        !            71: struct placeholder {
        !            72:        char *pos;
        !            73:        int len;
        !            74:        int bindno;
        !            75:        int qlen;               /* quoted length of value */
        !            76:        char *quoted;   /* quoted value */
        !            77:        int freeq;
        !            78:        struct placeholder *next;
        !            79: };
        !            80: 
        !            81: PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, 
        !            82:        char **outquery, int *outquery_len TSRMLS_DC)
        !            83: {
        !            84:        Scanner s;
        !            85:        char *ptr, *newbuffer;
        !            86:        int t;
        !            87:        int bindno = 0;
        !            88:        int ret = 0;
        !            89:        int newbuffer_len;
        !            90:        HashTable *params;
        !            91:        struct pdo_bound_param_data *param;
        !            92:        int query_type = PDO_PLACEHOLDER_NONE;
        !            93:        struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
        !            94: 
        !            95:        ptr = *outquery;
        !            96:        s.cur = inquery;
        !            97: 
        !            98:        /* phase 1: look for args */
        !            99:        while((t = scan(&s)) != PDO_PARSER_EOI) {
        !           100:                if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
        !           101:                        if (t == PDO_PARSER_BIND) {
        !           102:                                int len = s.cur - s.tok;
        !           103:                                if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
        !           104:                                        continue;
        !           105:                                }
        !           106:                                query_type |= PDO_PLACEHOLDER_NAMED;
        !           107:                        } else {
        !           108:                                query_type |= PDO_PLACEHOLDER_POSITIONAL;
        !           109:                        }
        !           110: 
        !           111:                        plc = emalloc(sizeof(*plc));
        !           112:                        memset(plc, 0, sizeof(*plc));
        !           113:                        plc->next = NULL;
        !           114:                        plc->pos = s.tok;
        !           115:                        plc->len = s.cur - s.tok;
        !           116:                        plc->bindno = bindno++;
        !           117: 
        !           118:                        if (placetail) {
        !           119:                                placetail->next = plc;
        !           120:                        } else {
        !           121:                                placeholders = plc;
        !           122:                        }
        !           123:                        placetail = plc;
        !           124:                }
        !           125:        }
        !           126: 
        !           127:        if (bindno == 0) {
        !           128:                /* nothing to do; good! */
        !           129:                return 0;
        !           130:        }
        !           131: 
        !           132:        /* did the query make sense to me? */
        !           133:        if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
        !           134:                /* they mixed both types; punt */
        !           135:                pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters" TSRMLS_CC);
        !           136:                ret = -1;
        !           137:                goto clean_up;
        !           138:        }
        !           139: 
        !           140:        if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
        !           141:                /* query matches native syntax */
        !           142:                ret = 0;
        !           143:                goto clean_up;
        !           144:        }
        !           145: 
        !           146:        if (stmt->named_rewrite_template) {
        !           147:                /* magic/hack.
        !           148:                 * We we pretend that the query was positional even if
        !           149:                 * it was named so that we fall into the
        !           150:                 * named rewrite case below.  Not too pretty,
        !           151:                 * but it works. */
        !           152:                query_type = PDO_PLACEHOLDER_POSITIONAL;
        !           153:        }
        !           154:        
        !           155:        params = stmt->bound_params;
        !           156:        
        !           157:        /* Do we have placeholders but no bound params */
        !           158:        if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
        !           159:                pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound" TSRMLS_CC);
        !           160:                ret = -1;
        !           161:                goto clean_up;
        !           162:        }
        !           163: 
        !           164:        if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
        !           165:                /* extra bit of validation for instances when same params are bound more then once */
        !           166:                if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
        !           167:                        int ok = 1;
        !           168:                        for (plc = placeholders; plc; plc = plc->next) {
        !           169:                                if (zend_hash_find(params, plc->pos, plc->len, (void**) &param) == FAILURE) {
        !           170:                                        ok = 0;
        !           171:                                        break;
        !           172:                                }
        !           173:                        }
        !           174:                        if (ok) {
        !           175:                                goto safe;
        !           176:                        }
        !           177:                }
        !           178:                pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens" TSRMLS_CC);
        !           179:                ret = -1;
        !           180:                goto clean_up;
        !           181:        }
        !           182: safe:
        !           183:        /* what are we going to do ? */
        !           184:        if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
        !           185:                /* query generation */
        !           186: 
        !           187:                newbuffer_len = inquery_len;
        !           188: 
        !           189:                /* let's quote all the values */        
        !           190:                for (plc = placeholders; plc; plc = plc->next) {
        !           191:                        if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
        !           192:                                ret = zend_hash_index_find(params, plc->bindno, (void**) &param);
        !           193:                        } else {
        !           194:                                ret = zend_hash_find(params, plc->pos, plc->len, (void**) &param);
        !           195:                        }
        !           196:                        if (ret == FAILURE) {
        !           197:                                /* parameter was not defined */
        !           198:                                ret = -1;
        !           199:                                pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
        !           200:                                goto clean_up;
        !           201:                        }
        !           202:                        if (stmt->dbh->methods->quoter) {
        !           203:                                if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(param->parameter) == IS_RESOURCE) {
        !           204:                                        php_stream *stm;
        !           205: 
        !           206:                                        php_stream_from_zval_no_verify(stm, &param->parameter);
        !           207:                                        if (stm) {
        !           208:                                                size_t len;
        !           209:                                                char *buf = NULL;
        !           210:                                        
        !           211:                                                len = php_stream_copy_to_mem(stm, &buf, PHP_STREAM_COPY_ALL, 0);
        !           212:                                                if (!stmt->dbh->methods->quoter(stmt->dbh, buf, len, &plc->quoted, &plc->qlen,
        !           213:                                                                param->param_type TSRMLS_CC)) {
        !           214:                                                        /* bork */
        !           215:                                                        ret = -1;
        !           216:                                                        strncpy(stmt->error_code, stmt->dbh->error_code, 6);
        !           217:                                                        if (buf) {
        !           218:                                                                efree(buf);
        !           219:                                                        }
        !           220:                                                        goto clean_up;
        !           221:                                                }
        !           222:                                                if (buf) {
        !           223:                                                        efree(buf);
        !           224:                                                }
        !           225:                                        } else {
        !           226:                                                pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource" TSRMLS_CC);
        !           227:                                                ret = -1;
        !           228:                                                goto clean_up;
        !           229:                                        }
        !           230:                                        plc->freeq = 1;
        !           231:                                } else {
        !           232:                                        switch (Z_TYPE_P(param->parameter)) {
        !           233:                                                case IS_NULL:
        !           234:                                                        plc->quoted = "NULL";
        !           235:                                                        plc->qlen = sizeof("NULL")-1;
        !           236:                                                        plc->freeq = 0;
        !           237:                                                        break;
        !           238: 
        !           239:                                                case IS_BOOL:
        !           240:                                                        convert_to_long(param->parameter);
        !           241: 
        !           242:                                                case IS_LONG:
        !           243:                                                case IS_DOUBLE:
        !           244:                                                        convert_to_string(param->parameter);
        !           245:                                                        plc->qlen = Z_STRLEN_P(param->parameter);
        !           246:                                                        plc->quoted = Z_STRVAL_P(param->parameter);
        !           247:                                                        plc->freeq = 0;
        !           248:                                                        break;
        !           249: 
        !           250:                                                default:
        !           251:                                                        convert_to_string(param->parameter);
        !           252:                                                        if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
        !           253:                                                                        Z_STRLEN_P(param->parameter), &plc->quoted, &plc->qlen,
        !           254:                                                                        param->param_type TSRMLS_CC)) {
        !           255:                                                                /* bork */
        !           256:                                                                ret = -1;
        !           257:                                                                strncpy(stmt->error_code, stmt->dbh->error_code, 6);
        !           258:                                                                goto clean_up;
        !           259:                                                        }
        !           260:                                                        plc->freeq = 1;
        !           261:                                        }
        !           262:                                }
        !           263:                        } else {
        !           264:                                plc->quoted = Z_STRVAL_P(param->parameter);
        !           265:                                plc->qlen = Z_STRLEN_P(param->parameter);
        !           266:                        }
        !           267:                        newbuffer_len += plc->qlen;
        !           268:                }
        !           269: 
        !           270: rewrite:
        !           271:                /* allocate output buffer */
        !           272:                newbuffer = emalloc(newbuffer_len + 1);
        !           273:                *outquery = newbuffer;
        !           274: 
        !           275:                /* and build the query */
        !           276:                plc = placeholders;
        !           277:                ptr = inquery;
        !           278: 
        !           279:                do {
        !           280:                        t = plc->pos - ptr;
        !           281:                        if (t) {
        !           282:                                memcpy(newbuffer, ptr, t);
        !           283:                                newbuffer += t;
        !           284:                        }
        !           285:                        memcpy(newbuffer, plc->quoted, plc->qlen);
        !           286:                        newbuffer += plc->qlen;
        !           287:                        ptr = plc->pos + plc->len;
        !           288: 
        !           289:                        plc = plc->next;
        !           290:                } while (plc);
        !           291: 
        !           292:                t = (inquery + inquery_len) - ptr;
        !           293:                if (t) {
        !           294:                        memcpy(newbuffer, ptr, t);
        !           295:                        newbuffer += t;
        !           296:                }
        !           297:                *newbuffer = '\0';
        !           298:                *outquery_len = newbuffer - *outquery;
        !           299: 
        !           300:                ret = 1;
        !           301:                goto clean_up;
        !           302: 
        !           303:        } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
        !           304:                /* rewrite ? to :pdoX */
        !           305:                char *name, *idxbuf;
        !           306:                const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
        !           307:                int bind_no = 1;
        !           308:                
        !           309:                newbuffer_len = inquery_len;
        !           310: 
        !           311:                if (stmt->bound_param_map == NULL) {
        !           312:                        ALLOC_HASHTABLE(stmt->bound_param_map);
        !           313:                        zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
        !           314:                }
        !           315: 
        !           316:                for (plc = placeholders; plc; plc = plc->next) {
        !           317:                        int skip_map = 0;
        !           318:                        char *p;
        !           319:                        name = estrndup(plc->pos, plc->len);
        !           320: 
        !           321:                        /* check if bound parameter is already available */
        !           322:                        if (!strcmp(name, "?") || zend_hash_find(stmt->bound_param_map, name, plc->len + 1, (void**) &p) == FAILURE) {
        !           323:                                spprintf(&idxbuf, 0, tmpl, bind_no++);
        !           324:                        } else {
        !           325:                                idxbuf = estrdup(p);
        !           326:                                skip_map = 1;
        !           327:                        }
        !           328: 
        !           329:                        plc->quoted = idxbuf;
        !           330:                        plc->qlen = strlen(plc->quoted);
        !           331:                        plc->freeq = 1;
        !           332:                        newbuffer_len += plc->qlen;
        !           333: 
        !           334:                        if (!skip_map && stmt->named_rewrite_template) {
        !           335:                                /* create a mapping */
        !           336:                                zend_hash_update(stmt->bound_param_map, name, plc->len + 1, idxbuf, plc->qlen + 1, NULL);
        !           337:                        }
        !           338: 
        !           339:                        /* map number to name */
        !           340:                        zend_hash_index_update(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1, NULL);
        !           341:                        
        !           342:                        efree(name);
        !           343:                }
        !           344:                                
        !           345:                goto rewrite;
        !           346: 
        !           347:        } else {
        !           348:                /* rewrite :name to ? */
        !           349:                
        !           350:                newbuffer_len = inquery_len;
        !           351:        
        !           352:                if (stmt->bound_param_map == NULL) {
        !           353:                        ALLOC_HASHTABLE(stmt->bound_param_map);
        !           354:                        zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
        !           355:                }
        !           356:                
        !           357:                for (plc = placeholders; plc; plc = plc->next) {
        !           358:                        char *name;
        !           359:                        
        !           360:                        name = estrndup(plc->pos, plc->len);
        !           361:                        zend_hash_index_update(stmt->bound_param_map, plc->bindno, name, plc->len + 1, NULL);
        !           362:                        efree(name);
        !           363:                        plc->quoted = "?";
        !           364:                        plc->qlen = 1;
        !           365:                }
        !           366: 
        !           367:                goto rewrite;
        !           368:        }
        !           369: 
        !           370: clean_up:
        !           371: 
        !           372:        while (placeholders) {
        !           373:                plc = placeholders;
        !           374:                placeholders = plc->next;
        !           375: 
        !           376:                if (plc->freeq) {
        !           377:                        efree(plc->quoted);
        !           378:                }
        !           379: 
        !           380:                efree(plc);
        !           381:        }
        !           382: 
        !           383:        return ret;
        !           384: }
        !           385: 
        !           386: #if 0
        !           387: int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery, 
        !           388:                int *outquery_len TSRMLS_DC)
        !           389: {
        !           390:        Scanner s;
        !           391:        char *ptr;
        !           392:        int t;
        !           393:        int bindno = 0;
        !           394:        int newbuffer_len;
        !           395:        int padding;
        !           396:        HashTable *params = stmt->bound_params;
        !           397:        struct pdo_bound_param_data *param;
        !           398:        /* allocate buffer for query with expanded binds, ptr is our writing pointer */
        !           399:        newbuffer_len = inquery_len;
        !           400: 
        !           401:        /* calculate the possible padding factor due to quoting */
        !           402:        if(stmt->dbh->max_escaped_char_length) {
        !           403:                padding = stmt->dbh->max_escaped_char_length;
        !           404:        } else {
        !           405:                padding = 3;
        !           406:        }
        !           407:        if(params) {
        !           408:                zend_hash_internal_pointer_reset(params);
        !           409:                while (SUCCESS == zend_hash_get_current_data(params, (void**)&param)) {
        !           410:                        if(param->parameter) {
        !           411:                                convert_to_string(param->parameter);
        !           412:                                /* accomodate a string that needs to be fully quoted
        !           413:                    bind placeholders are at least 2 characters, so
        !           414:                    the accomodate their own "'s
        !           415:                 */
        !           416:                                newbuffer_len += padding * Z_STRLEN_P(param->parameter);
        !           417:                        }
        !           418:                        zend_hash_move_forward(params);
        !           419:                }
        !           420:        }
        !           421:        *outquery = (char *) emalloc(newbuffer_len + 1);
        !           422:        *outquery_len = 0;
        !           423: 
        !           424:        ptr = *outquery;
        !           425:        s.cur = inquery;
        !           426:        while((t = scan(&s)) != PDO_PARSER_EOI) {
        !           427:                if(t == PDO_PARSER_TEXT) {
        !           428:                        memcpy(ptr, s.tok, s.cur - s.tok);
        !           429:                        ptr += (s.cur - s.tok);
        !           430:                        *outquery_len += (s.cur - s.tok);
        !           431:                }
        !           432:                else if(t == PDO_PARSER_BIND) {
        !           433:                        if(!params) { 
        !           434:                                /* error */
        !           435:                                efree(*outquery);
        !           436:                                *outquery = NULL;
        !           437:                                return (int) (s.cur - inquery);
        !           438:                        }
        !           439:                        /* lookup bind first via hash and then index */
        !           440:                        /* stupid keys need to be null-terminated, even though we know their length */
        !           441:                        if((SUCCESS == zend_hash_find(params, s.tok, s.cur-s.tok,(void **)&param))  
        !           442:                            ||
        !           443:                           (SUCCESS == zend_hash_index_find(params, bindno, (void **)&param))) 
        !           444:                        {
        !           445:                                char *quotedstr;
        !           446:                                int quotedstrlen;
        !           447:                                /* restore the in-string key, doesn't need null-termination here */
        !           448:                                /* currently everything is a string here */
        !           449:                                
        !           450:                                /* quote the bind value if necessary */
        !           451:                                if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter), 
        !           452:                                        Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
        !           453:                                {
        !           454:                                        memcpy(ptr, quotedstr, quotedstrlen);
        !           455:                                        ptr += quotedstrlen;
        !           456:                                        *outquery_len += quotedstrlen;
        !           457:                                        efree(quotedstr);
        !           458:                                } else {
        !           459:                                        memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
        !           460:                                        ptr += Z_STRLEN_P(param->parameter);
        !           461:                                        *outquery_len += (Z_STRLEN_P(param->parameter));
        !           462:                                }
        !           463:                        }
        !           464:                        else {
        !           465:                                /* error and cleanup */
        !           466:                                efree(*outquery);
        !           467:                                *outquery = NULL;
        !           468:                                return (int) (s.cur - inquery);
        !           469:                        }
        !           470:                        bindno++;
        !           471:                }
        !           472:                else if(t == PDO_PARSER_BIND_POS) {
        !           473:                        if(!params) { 
        !           474:                                /* error */
        !           475:                                efree(*outquery);
        !           476:                                *outquery = NULL;
        !           477:                                return (int) (s.cur - inquery);
        !           478:                        }
        !           479:                        /* lookup bind by index */
        !           480:                        if(SUCCESS == zend_hash_index_find(params, bindno, (void **)&param)) 
        !           481:                        {
        !           482:                                char *quotedstr;
        !           483:                                int quotedstrlen;
        !           484:                                /* currently everything is a string here */
        !           485:                                
        !           486:                                /* quote the bind value if necessary */
        !           487:                                if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter), 
        !           488:                                        Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
        !           489:                                {
        !           490:                                        memcpy(ptr, quotedstr, quotedstrlen);
        !           491:                                        ptr += quotedstrlen;
        !           492:                                        *outquery_len += quotedstrlen;
        !           493:                                        efree(quotedstr);
        !           494:                                } else {
        !           495:                                        memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
        !           496:                                        ptr += Z_STRLEN_P(param->parameter);
        !           497:                                        *outquery_len += (Z_STRLEN_P(param->parameter));
        !           498:                                }
        !           499:                        }
        !           500:                        else {
        !           501:                                /* error and cleanup */
        !           502:                                efree(*outquery);
        !           503:                                *outquery = NULL;
        !           504:                                return (int) (s.cur - inquery);
        !           505:                        }
        !           506:                        bindno++;
        !           507:                }
        !           508:        }       
        !           509:        *ptr = '\0';
        !           510:        return 0;
        !           511: }
        !           512: #endif
        !           513: 
        !           514: /*
        !           515:  * Local variables:
        !           516:  * tab-width: 4
        !           517:  * c-basic-offset: 4
        !           518:  * End:
        !           519:  * vim600: noet sw=4 ts=4 fdm=marker ft=c
        !           520:  * vim<600: noet sw=4 ts=4
        !           521:  */

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