Main Page | Modules | Namespace List | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages | Examples

apr_brigade.c

Go to the documentation of this file.
00001 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 #include "apr.h"
00018 #include "apr_lib.h"
00019 #include "apr_strings.h"
00020 #include "apr_pools.h"
00021 #include "apr_tables.h"
00022 #include "apr_buckets.h"
00023 #include "apr_errno.h"
00024 #define APR_WANT_MEMFUNC
00025 #define APR_WANT_STRFUNC
00026 #include "apr_want.h"
00027 
00028 #if APR_HAVE_SYS_UIO_H
00029 #include <sys/uio.h>
00030 #endif
00031 
00032 static apr_status_t brigade_cleanup(void *data) 
00033 {
00034     return apr_brigade_cleanup(data);
00035 }
00036 
00037 APU_DECLARE(apr_status_t) apr_brigade_cleanup(void *data)
00038 {
00039     apr_bucket_brigade *b = data;
00040     apr_bucket *e;
00041 
00042     /*
00043      * Bah! We can't use APR_RING_FOREACH here because this bucket has
00044      * gone away when we dig inside it to get the next one.
00045      */
00046     while (!APR_BRIGADE_EMPTY(b)) {
00047         e = APR_BRIGADE_FIRST(b);
00048         apr_bucket_delete(e);
00049     }
00050     /*
00051      * We don't need to free(bb) because it's allocated from a pool.
00052      */
00053     return APR_SUCCESS;
00054 }
00055 
00056 APU_DECLARE(apr_status_t) apr_brigade_destroy(apr_bucket_brigade *b)
00057 {
00058     apr_pool_cleanup_kill(b->p, b, brigade_cleanup);
00059     return apr_brigade_cleanup(b);
00060 }
00061 
00062 APU_DECLARE(apr_bucket_brigade *) apr_brigade_create(apr_pool_t *p,
00063                                                      apr_bucket_alloc_t *list)
00064 {
00065     apr_bucket_brigade *b;
00066 
00067     b = apr_palloc(p, sizeof(*b));
00068     b->p = p;
00069     b->bucket_alloc = list;
00070 
00071     APR_RING_INIT(&b->list, apr_bucket, link);
00072 
00073     apr_pool_cleanup_register(b->p, b, brigade_cleanup, apr_pool_cleanup_null);
00074     return b;
00075 }
00076 
00077 APU_DECLARE(apr_bucket_brigade *) apr_brigade_split(apr_bucket_brigade *b,
00078                                                     apr_bucket *e)
00079 {
00080     apr_bucket_brigade *a;
00081     apr_bucket *f;
00082 
00083     a = apr_brigade_create(b->p, b->bucket_alloc);
00084     /* Return an empty brigade if there is nothing left in 
00085      * the first brigade to split off 
00086      */
00087     if (e != APR_BRIGADE_SENTINEL(b)) {
00088         f = APR_RING_LAST(&b->list);
00089         APR_RING_UNSPLICE(e, f, link);
00090         APR_RING_SPLICE_HEAD(&a->list, e, f, apr_bucket, link);
00091     }
00092 
00093     APR_BRIGADE_CHECK_CONSISTENCY(a);
00094     APR_BRIGADE_CHECK_CONSISTENCY(b);
00095 
00096     return a;
00097 }
00098 
00099 APU_DECLARE(apr_status_t) apr_brigade_partition(apr_bucket_brigade *b,
00100                                                 apr_off_t point,
00101                                                 apr_bucket **after_point)
00102 {
00103     apr_bucket *e;
00104     const char *s;
00105     apr_size_t len;
00106     apr_status_t rv;
00107 
00108     if (point < 0) {
00109         /* this could cause weird (not necessarily SEGV) things to happen */
00110         return APR_EINVAL;
00111     }
00112     if (point == 0) {
00113         *after_point = APR_BRIGADE_FIRST(b);
00114         return APR_SUCCESS;
00115     }
00116 
00117     APR_BRIGADE_CHECK_CONSISTENCY(b);
00118 
00119     APR_BRIGADE_FOREACH(e, b) {
00120         if ((e->length == (apr_size_t)(-1)) && (point > (apr_size_t)(-1))) {
00121             /* XXX: point is too far out to simply split this bucket,
00122              * we must fix this bucket's size and keep going... */
00123             rv = apr_bucket_read(e, &s, &len, APR_BLOCK_READ);
00124             if (rv != APR_SUCCESS) {
00125                 *after_point = e;
00126                 return rv;
00127             }
00128         }
00129         if ((point < e->length) || (e->length == (apr_size_t)(-1))) {
00130             /* We already checked e->length -1 above, so we now
00131              * trust e->length < MAX_APR_SIZE_T.
00132              * First try to split the bucket natively... */
00133             if ((rv = apr_bucket_split(e, (apr_size_t)point)) 
00134                     != APR_ENOTIMPL) {
00135                 *after_point = APR_BUCKET_NEXT(e);
00136                 return rv;
00137             }
00138 
00139             /* if the bucket cannot be split, we must read from it,
00140              * changing its type to one that can be split */
00141             rv = apr_bucket_read(e, &s, &len, APR_BLOCK_READ);
00142             if (rv != APR_SUCCESS) {
00143                 *after_point = e;
00144                 return rv;
00145             }
00146 
00147             /* this assumes that len == e->length, which is okay because e
00148              * might have been morphed by the apr_bucket_read() above, but
00149              * if it was, the length would have been adjusted appropriately */
00150             if (point < e->length) {
00151                 rv = apr_bucket_split(e, (apr_size_t)point);
00152                 *after_point = APR_BUCKET_NEXT(e);
00153                 return rv;
00154             }
00155         }
00156         if (point == e->length) {
00157             *after_point = APR_BUCKET_NEXT(e);
00158             return APR_SUCCESS;
00159         }
00160         point -= e->length;
00161     }
00162     *after_point = APR_BRIGADE_SENTINEL(b); 
00163     return APR_INCOMPLETE;
00164 }
00165 
00166 APU_DECLARE(apr_status_t) apr_brigade_length(apr_bucket_brigade *bb,
00167                                              int read_all, apr_off_t *length)
00168 {
00169     apr_off_t total = 0;
00170     apr_bucket *bkt;
00171 
00172     APR_BRIGADE_FOREACH(bkt, bb) {
00173         if (bkt->length == (apr_size_t)(-1)) {
00174             const char *ignore;
00175             apr_size_t len;
00176             apr_status_t status;
00177 
00178             if (!read_all) {
00179                 *length = -1;
00180                 return APR_SUCCESS;
00181             }
00182 
00183             if ((status = apr_bucket_read(bkt, &ignore, &len,
00184                                           APR_BLOCK_READ)) != APR_SUCCESS) {
00185                 return status;
00186             }
00187         }
00188 
00189         total += bkt->length;
00190     }
00191 
00192     *length = total;
00193     return APR_SUCCESS;
00194 }
00195 
00196 APU_DECLARE(apr_status_t) apr_brigade_flatten(apr_bucket_brigade *bb,
00197                                               char *c, apr_size_t *len)
00198 {
00199     apr_size_t actual = 0;
00200     apr_bucket *b;
00201  
00202     APR_BRIGADE_FOREACH(b, bb) {
00203         const char *str;
00204         apr_size_t str_len;
00205         apr_status_t status;
00206 
00207         status = apr_bucket_read(b, &str, &str_len, APR_BLOCK_READ);
00208         if (status != APR_SUCCESS) {
00209             return status;
00210         }
00211 
00212         /* If we would overflow. */
00213         if (str_len + actual > *len) {
00214             str_len = *len - actual;
00215         }
00216 
00217         /* XXX: It appears that overflow of the final bucket
00218          * is DISCARDED without any warning to the caller.
00219          *
00220          * No, we only copy the data up to their requested size.  -- jre
00221          */
00222         memcpy(c, str, str_len);
00223 
00224         c += str_len;
00225         actual += str_len;
00226 
00227         /* This could probably be actual == *len, but be safe from stray
00228          * photons. */
00229         if (actual >= *len) {
00230             break;
00231         }
00232     }
00233 
00234     *len = actual;
00235     return APR_SUCCESS;
00236 }
00237 
00238 APU_DECLARE(apr_status_t) apr_brigade_pflatten(apr_bucket_brigade *bb,
00239                                                char **c,
00240                                                apr_size_t *len,
00241                                                apr_pool_t *pool)
00242 {
00243     apr_off_t actual;
00244     apr_size_t total;
00245     apr_status_t rv;
00246 
00247     apr_brigade_length(bb, 1, &actual);
00248     
00249     /* XXX: This is dangerous beyond belief.  At least in the
00250      * apr_brigade_flatten case, the user explicitly stated their
00251      * buffer length - so we don't up and palloc 4GB for a single
00252      * file bucket.  This API must grow a useful max boundry,
00253      * either compiled-in or preset via the *len value.
00254      *
00255      * Shouldn't both fn's grow an additional return value for 
00256      * the case that the brigade couldn't be flattened into the
00257      * provided or allocated buffer (such as APR_EMOREDATA?)
00258      * Not a failure, simply an advisory result.
00259      */
00260     total = (apr_size_t)actual;
00261 
00262     *c = apr_palloc(pool, total);
00263     
00264     rv = apr_brigade_flatten(bb, *c, &total);
00265 
00266     if (rv != APR_SUCCESS) {
00267         return rv;
00268     }
00269 
00270     *len = total;
00271     return APR_SUCCESS;
00272 }
00273 
00274 APU_DECLARE(apr_status_t) apr_brigade_split_line(apr_bucket_brigade *bbOut,
00275                                                  apr_bucket_brigade *bbIn,
00276                                                  apr_read_type_e block,
00277                                                  apr_off_t maxbytes)
00278 {
00279     apr_off_t readbytes = 0;
00280 
00281     while (!APR_BRIGADE_EMPTY(bbIn)) {
00282         const char *pos;
00283         const char *str;
00284         apr_size_t len;
00285         apr_status_t rv;
00286         apr_bucket *e;
00287 
00288         e = APR_BRIGADE_FIRST(bbIn);
00289         rv = apr_bucket_read(e, &str, &len, block);
00290 
00291         if (rv != APR_SUCCESS) {
00292             return rv;
00293         }
00294 
00295         pos = memchr(str, APR_ASCII_LF, len);
00296         /* We found a match. */
00297         if (pos != NULL) {
00298             apr_bucket_split(e, pos - str + 1);
00299             APR_BUCKET_REMOVE(e);
00300             APR_BRIGADE_INSERT_TAIL(bbOut, e);
00301             return APR_SUCCESS;
00302         }
00303         APR_BUCKET_REMOVE(e);
00304         APR_BRIGADE_INSERT_TAIL(bbOut, e);
00305         readbytes += len;
00306         /* We didn't find an APR_ASCII_LF within the maximum line length. */
00307         if (readbytes >= maxbytes) {
00308             break;
00309         }
00310     }
00311 
00312     return APR_SUCCESS;
00313 }
00314 
00315 
00316 APU_DECLARE(apr_status_t) apr_brigade_to_iovec(apr_bucket_brigade *b, 
00317                                                struct iovec *vec, int *nvec)
00318 {
00319     int left = *nvec;
00320     apr_bucket *e;
00321     struct iovec *orig;
00322     apr_size_t iov_len;
00323     apr_status_t rv;
00324 
00325     orig = vec;
00326     APR_BRIGADE_FOREACH(e, b) {
00327         if (left-- == 0)
00328             break;
00329 
00330         rv = apr_bucket_read(e, (const char **)&vec->iov_base, &iov_len,
00331                              APR_NONBLOCK_READ);
00332         if (rv != APR_SUCCESS)
00333             return rv;
00334         vec->iov_len = iov_len; /* set indirectly in case size differs */
00335         ++vec;
00336     }
00337 
00338     *nvec = vec - orig;
00339     return APR_SUCCESS;
00340 }
00341 
00342 APU_DECLARE(apr_status_t) apr_brigade_vputstrs(apr_bucket_brigade *b, 
00343                                                apr_brigade_flush flush,
00344                                                void *ctx,
00345                                                va_list va)
00346 {
00347     for (;;) {
00348         const char *str = va_arg(va, const char *);
00349         apr_status_t rv;
00350 
00351         if (str == NULL)
00352             break;
00353 
00354         rv = apr_brigade_write(b, flush, ctx, str, strlen(str));
00355         if (rv != APR_SUCCESS)
00356             return rv;
00357     }
00358 
00359     return APR_SUCCESS;
00360 }
00361 
00362 APU_DECLARE(apr_status_t) apr_brigade_putc(apr_bucket_brigade *b,
00363                                            apr_brigade_flush flush, void *ctx,
00364                                            const char c)
00365 {
00366     return apr_brigade_write(b, flush, ctx, &c, 1);
00367 }
00368 
00369 APU_DECLARE(apr_status_t) apr_brigade_write(apr_bucket_brigade *b,
00370                                             apr_brigade_flush flush,
00371                                             void *ctx, 
00372                                             const char *str, apr_size_t nbyte)
00373 {
00374     apr_bucket *e = APR_BRIGADE_LAST(b);
00375     apr_size_t remaining = APR_BUCKET_BUFF_SIZE;
00376     char *buf = NULL;
00377 
00378     if (!APR_BRIGADE_EMPTY(b) && APR_BUCKET_IS_HEAP(e)) {
00379         apr_bucket_heap *h = e->data;
00380 
00381         /* HEAP bucket start offsets are always in-memory, safe to cast */
00382         remaining = h->alloc_len - (e->length + (apr_size_t)e->start);
00383         buf = h->base + e->start + e->length;
00384     }
00385 
00386     if (nbyte > remaining) {
00387         /* either a buffer bucket exists but is full, 
00388          * or no buffer bucket exists and the data is too big
00389          * to buffer.  In either case, we should flush.  */
00390         if (flush) {
00391             e = apr_bucket_transient_create(str, nbyte, b->bucket_alloc);
00392             APR_BRIGADE_INSERT_TAIL(b, e);
00393             return flush(b, ctx);
00394         }
00395         else {
00396             e = apr_bucket_heap_create(str, nbyte, NULL, b->bucket_alloc);
00397             APR_BRIGADE_INSERT_TAIL(b, e);
00398             return APR_SUCCESS;
00399         }
00400     }
00401     else if (!buf) {
00402         /* we don't have a buffer, but the data is small enough
00403          * that we don't mind making a new buffer */
00404         buf = apr_bucket_alloc(APR_BUCKET_BUFF_SIZE, b->bucket_alloc);
00405         e = apr_bucket_heap_create(buf, APR_BUCKET_BUFF_SIZE,
00406                                    apr_bucket_free, b->bucket_alloc);
00407         APR_BRIGADE_INSERT_TAIL(b, e);
00408         e->length = 0;   /* We are writing into the brigade, and
00409                           * allocating more memory than we need.  This
00410                           * ensures that the bucket thinks it is empty just
00411                           * after we create it.  We'll fix the length
00412                           * once we put data in it below.
00413                           */
00414     }
00415 
00416     /* there is a sufficiently big buffer bucket available now */
00417     memcpy(buf, str, nbyte);
00418     e->length += nbyte;
00419 
00420     return APR_SUCCESS;
00421 }
00422 
00423 APU_DECLARE(apr_status_t) apr_brigade_writev(apr_bucket_brigade *b,
00424                                              apr_brigade_flush flush,
00425                                              void *ctx,
00426                                              const struct iovec *vec,
00427                                              apr_size_t nvec)
00428 {
00429     apr_bucket *e;
00430     apr_size_t total_len;
00431     apr_size_t i;
00432     char *buf;
00433 
00434     /* Compute the total length of the data to be written.
00435      */
00436     total_len = 0;
00437     for (i = 0; i < nvec; i++) {
00438        total_len += vec[i].iov_len;
00439     }
00440 
00441     /* If the data to be written is very large, try to convert
00442      * the iovec to transient buckets rather than copying.
00443      */
00444     if (total_len > APR_BUCKET_BUFF_SIZE) {
00445         if (flush) {
00446             for (i = 0; i < nvec; i++) {
00447                 e = apr_bucket_transient_create(vec[i].iov_base,
00448                                                 vec[i].iov_len,
00449                                                 b->bucket_alloc);
00450                 APR_BRIGADE_INSERT_TAIL(b, e);
00451             }
00452             return flush(b, ctx);
00453         }
00454         else {
00455             for (i = 0; i < nvec; i++) {
00456                 e = apr_bucket_heap_create((const char *) vec[i].iov_base,
00457                                            vec[i].iov_len, NULL,
00458                                            b->bucket_alloc);
00459                 APR_BRIGADE_INSERT_TAIL(b, e);
00460             }
00461             return APR_SUCCESS;
00462         }
00463     }
00464 
00465     i = 0;
00466 
00467     /* If there is a heap bucket at the end of the brigade
00468      * already, copy into the existing bucket.
00469      */
00470     e = APR_BRIGADE_LAST(b);
00471     if (!APR_BRIGADE_EMPTY(b) && APR_BUCKET_IS_HEAP(e)) {
00472         apr_bucket_heap *h = e->data;
00473         apr_size_t remaining = h->alloc_len -
00474             (e->length + (apr_size_t)e->start);
00475         buf = h->base + e->start + e->length;
00476 
00477         if (remaining >= total_len) {
00478             /* Simple case: all the data will fit in the
00479              * existing heap bucket
00480              */
00481             for (; i < nvec; i++) {
00482                 apr_size_t len = vec[i].iov_len;
00483                 memcpy(buf, (const void *) vec[i].iov_base, len);
00484                 buf += len;
00485             }
00486             e->length += total_len;
00487             return APR_SUCCESS;
00488         }
00489         else {
00490             /* More complicated case: not all of the data
00491              * will fit in the existing heap bucket.  The
00492              * total data size is <= APR_BUCKET_BUFF_SIZE,
00493              * so we'll need only one additional bucket.
00494              */
00495             const char *start_buf = buf;
00496             for (; i < nvec; i++) {
00497                 apr_size_t len = vec[i].iov_len;
00498                 if (len > remaining) {
00499                     break;
00500                 }
00501                 memcpy(buf, (const void *) vec[i].iov_base, len);
00502                 buf += len;
00503                 remaining -= len;
00504             }
00505             e->length += (buf - start_buf);
00506             total_len -= (buf - start_buf);
00507 
00508             if (flush) {
00509                 apr_status_t rv = flush(b, ctx);
00510                 if (rv != APR_SUCCESS) {
00511                     return rv;
00512                 }
00513             }
00514 
00515             /* Now fall through into the case below to
00516              * allocate another heap bucket and copy the
00517              * rest of the array.  (Note that i is not
00518              * reset to zero here; it holds the index
00519              * of the first vector element to be
00520              * written to the new bucket.)
00521              */
00522         }
00523     }
00524 
00525     /* Allocate a new heap bucket, and copy the data into it.
00526      * The checks above ensure that the amount of data to be
00527      * written here is no larger than APR_BUCKET_BUFF_SIZE.
00528      */
00529     buf = apr_bucket_alloc(APR_BUCKET_BUFF_SIZE, b->bucket_alloc);
00530     e = apr_bucket_heap_create(buf, APR_BUCKET_BUFF_SIZE,
00531                                apr_bucket_free, b->bucket_alloc);
00532     for (; i < nvec; i++) {
00533         apr_size_t len = vec[i].iov_len;
00534         memcpy(buf, (const void *) vec[i].iov_base, len);
00535         buf += len;
00536     }
00537     e->length = total_len;
00538     APR_BRIGADE_INSERT_TAIL(b, e);
00539 
00540     return APR_SUCCESS;
00541 }
00542 
00543 APU_DECLARE(apr_status_t) apr_brigade_puts(apr_bucket_brigade *bb,
00544                                            apr_brigade_flush flush, void *ctx,
00545                                            const char *str)
00546 {
00547     apr_size_t len = strlen(str);
00548     apr_bucket *bkt = APR_BRIGADE_LAST(bb);
00549     if (!APR_BRIGADE_EMPTY(bb) && APR_BUCKET_IS_HEAP(bkt)) {
00550         /* If there is enough space available in a heap bucket
00551          * at the end of the brigade, copy the string directly
00552          * into the heap bucket
00553          */
00554         apr_bucket_heap *h = bkt->data;
00555         apr_size_t bytes_avail = h->alloc_len - bkt->length;
00556 
00557         if (bytes_avail >= len) {
00558             char *buf = h->base + bkt->start + bkt->length;
00559             memcpy(buf, str, len);
00560             bkt->length += len;
00561             return APR_SUCCESS;
00562         }
00563     }
00564 
00565     /* If the string could not be copied into an existing heap
00566      * bucket, delegate the work to apr_brigade_write(), which
00567      * knows how to grow the brigade
00568      */
00569     return apr_brigade_write(bb, flush, ctx, str, len);
00570 }
00571 
00572 APU_DECLARE_NONSTD(apr_status_t) apr_brigade_putstrs(apr_bucket_brigade *b, 
00573                                                      apr_brigade_flush flush,
00574                                                      void *ctx, ...)
00575 {
00576     va_list va;
00577     apr_status_t rv;
00578 
00579     va_start(va, ctx);
00580     rv = apr_brigade_vputstrs(b, flush, ctx, va);
00581     va_end(va);
00582     return rv;
00583 }
00584 
00585 APU_DECLARE_NONSTD(apr_status_t) apr_brigade_printf(apr_bucket_brigade *b, 
00586                                                     apr_brigade_flush flush,
00587                                                     void *ctx, 
00588                                                     const char *fmt, ...)
00589 {
00590     va_list ap;
00591     apr_status_t rv;
00592 
00593     va_start(ap, fmt);
00594     rv = apr_brigade_vprintf(b, flush, ctx, fmt, ap);
00595     va_end(ap);
00596     return rv;
00597 }
00598 
00599 struct brigade_vprintf_data_t {
00600     apr_vformatter_buff_t vbuff;
00601 
00602     apr_bucket_brigade *b;  /* associated brigade */
00603     apr_brigade_flush *flusher; /* flushing function */
00604     void *ctx;
00605 
00606     char *cbuff; /* buffer to flush from */
00607 };
00608 
00609 static apr_status_t brigade_flush(apr_vformatter_buff_t *buff)
00610 {
00611     /* callback function passed to ap_vformatter to be
00612      * called when vformatter needs to buff and
00613      * buff.curpos > buff.endpos
00614      */
00615 
00616     /* "downcast," have really passed a brigade_vprintf_data_t* */
00617     struct brigade_vprintf_data_t *vd = (struct brigade_vprintf_data_t*)buff;
00618     apr_status_t res = APR_SUCCESS;
00619 
00620     res = apr_brigade_write(vd->b, *vd->flusher, vd->ctx, vd->cbuff,
00621                           APR_BUCKET_BUFF_SIZE);
00622 
00623     if(res != APR_SUCCESS) {
00624       return -1;
00625     }
00626 
00627     vd->vbuff.curpos = vd->cbuff;
00628     vd->vbuff.endpos = vd->cbuff + APR_BUCKET_BUFF_SIZE;
00629 
00630     return res;
00631 }
00632 
00633 APU_DECLARE(apr_status_t) apr_brigade_vprintf(apr_bucket_brigade *b,
00634                                               apr_brigade_flush flush,
00635                                               void *ctx,
00636                                               const char *fmt, va_list va)
00637 {
00638     /* the cast, in order of appearance */
00639     struct brigade_vprintf_data_t vd;
00640     char buf[APR_BUCKET_BUFF_SIZE];
00641     apr_size_t written;
00642 
00643     vd.vbuff.curpos = buf;
00644     vd.vbuff.endpos = buf + APR_BUCKET_BUFF_SIZE;
00645     vd.b = b;
00646     vd.flusher = &flush;
00647     vd.ctx = ctx;
00648     vd.cbuff = buf;
00649 
00650     written = apr_vformatter(brigade_flush, &vd.vbuff, fmt, va);
00651 
00652     if (written == -1) {
00653       return -1;
00654     }
00655 
00656     /* tack on null terminator to remaining string */
00657     *(vd.vbuff.curpos) = '\0';
00658 
00659     /* write out what remains in the buffer */
00660     return apr_brigade_write(b, flush, ctx, buf, vd.vbuff.curpos - buf);
00661 }
00662