Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

VestaAttribs.C

Go to the documentation of this file.
00001 // Copyright (C) 2001, Compaq Computer Corporation
00002 // 
00003 // This file is part of Vesta.
00004 // 
00005 // Vesta is free software; you can redistribute it and/or
00006 // modify it under the terms of the GNU Lesser General Public
00007 // License as published by the Free Software Foundation; either
00008 // version 2.1 of the License, or (at your option) any later version.
00009 // 
00010 // Vesta is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013 // Lesser General Public License for more details.
00014 // 
00015 // You should have received a copy of the GNU Lesser General Public
00016 // License along with Vesta; if not, write to the Free Software
00017 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 
00019 //
00020 // VestaAttribs.C
00021 //
00022 // Mutable attributes for Vesta sources.
00023 //
00024 
00025 #include "VestaAttribs.H"
00026 #include "VestaAttribsRep.H"
00027 #include "CharsKey.H"
00028 #include "VestaLog.H"
00029 #include "VRConcurrency.H"
00030 #include "VLogHelp.H"
00031 #include "logging.H"
00032 
00033 // Invariants.  See comments in VestaSource.H for context.  The
00034 // invariants use the notation w1 > w2 to mean that w1 sorts later in
00035 // the history than w2.  Note: we write all operations here as if they
00036 // had a value argument.  The value argument to clear has no effect on
00037 // the attributes as viewed from the function level, and clients can
00038 // provide only "" as the value for this argument; however, the
00039 // implementation can generate non-empty strings as values via the
00040 // set/clear replacement mentioned in invariant (3) below.
00041 //
00042 // 1) If K contains w1 = (o1, n, v1, t1) with o1 = clear or o1 = set,
00043 //    then K does not contain a w2 = (o2, n, v2, t2) with w1 > w2
00044 //    for any o2, v2, t2.
00045 //
00046 // 2) If K contains w1 = (o1, n, v, t1) with o1 = remove or o1 = add,
00047 //    then K does not contain any w2 = (o2, n, v, t2) with o2 != clear
00048 //    and w1 > w2.
00049 //
00050 // 3) K is a subset of H, except that if H contains wh = (set, n, v, t),
00051 //    K might instead contain wk = (clear, n, v, t).
00052 //
00053 // 4) K and H are equivalent.
00054 
00055 
00056 typedef Table<CharsKey, bool>::Default CharsTable;
00057 
00058 VestaAttribsRep*
00059 VestaAttribsRep::create(VestaSource::attribOp op, const char* name,
00060                         const char* value, time_t timestamp) throw ()
00061 {
00062     int nameLen = strlen(name);
00063     int valueLen = strlen(value);
00064     VestaAttribsRep* ar = (VestaAttribsRep*)
00065       VMemPool::allocate(VMemPool::vAttrib,
00066                          VATTR_MINSIZE + nameLen + valueLen);
00067     ar->setOp(op);
00068     ar->setNext(0);
00069     Bit32 tt = (Bit32) timestamp;
00070     memcpy(&ar->rep[VATTR_TIMESTAMP], &tt, 4);
00071     strcpy((char*) &ar->rep[VATTR_NAME], name);
00072     strcpy((char*) &ar->rep[VATTR_NAME + nameLen + 1], value);
00073     return ar;
00074 }
00075 
00076 
00077 Bit32
00078 VestaAttribs::firstAttrib() throw ()
00079 {
00080     if (attribs == NULL) return 0;
00081     Bit32 firstsp;
00082     memcpy(&firstsp, attribs, sizeof(Bit32));
00083     return firstsp;
00084 }
00085 
00086 void
00087 VestaAttribs::setFirstAttrib(Bit32 newval) throw ()
00088 {
00089     Bit32 firstsp;
00090     memcpy(attribs, &newval, sizeof(Bit32));
00091 }
00092 
00093 bool
00094 VestaAttribs::inAttribs(const char* name, const char* value)
00095   throw (SRPC::failure /*can't really happen*/)
00096 {
00097     // Examine history in reverse order
00098     VestaAttribsRep* cur =
00099       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00100     while (cur) {
00101         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00102         if (strcmp(cur->name(), name) == 0) {
00103             switch (cur->op()) {
00104               case opSet:
00105                 if (strcmp(cur->value(), value) == 0) {
00106                     // This value is in the set
00107                     return true;
00108                 } else {
00109                     // There can be no more values in the set
00110                     return false;
00111                 }
00112               case opClear:
00113                 // There can be no more values in the set
00114                 return false;
00115               case opAdd:
00116                 if (strcmp(cur->value(), value) == 0) {
00117                     // This value is in the set
00118                     return true;
00119                 }
00120                 break;
00121               case opRemove:
00122                 if (strcmp(cur->value(), value) == 0) {
00123                     // This value cannot be in the set
00124                     return false;
00125                 }
00126                 break;
00127             }
00128         }
00129         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00130     }
00131     // Value was not found in the set
00132     return false;
00133 }
00134 
00135 const char*
00136 VestaAttribs::getAttribConst(const char* name) throw ()
00137 {
00138     VestaAttribsRep* cur =
00139       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00140     while (cur) {
00141         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00142         if (strcmp(cur->name(), name) == 0) {
00143             switch (cur->op()) {
00144               case opSet:
00145                 // There is exactly one value; return it.  By (1) this 
00146                 // opSet would not be here if the value had been
00147                 // deleted by a later clear or set (and moreover we
00148                 // would have exited the loop already if there had
00149                 // been one), and by (2) this opSet would not be here
00150                 // if the value had been deleted by a remove.
00151                 return cur->value();
00152               case opClear:
00153                 // There are no values; return NULL.
00154                 return NULL;
00155               case opAdd:
00156                 // This value is in the set.  By (1) this opAdd would
00157                 // not be here if the value had been deleted by a
00158                 // clear or set (and moreover we would have exited the
00159                 // loop already if there had been one), and by (2)
00160                 // this opAdd would not be here if the value had been
00161                 // deleted by a remove.
00162                 return cur->value();
00163               case opRemove:
00164                 // There might be some values; keep looking.
00165                 break;
00166             }
00167         }
00168         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00169     }
00170     // No values were found.
00171     return NULL;
00172 }
00173 
00174 char*
00175 VestaAttribs::getAttrib(const char* name)
00176   throw (SRPC::failure /*can't really happen*/)
00177 {
00178     const char* value = getAttribConst(name);
00179     if (value == NULL) return NULL;
00180     return strdup(value);
00181 }
00182 
00183 void
00184 VestaAttribs::getAttrib(const char* name,
00185                        VestaAttribs::valueCallback cb, void* cl)
00186 {
00187     VestaAttribsRep* cur =
00188       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00189     bool cont = true;
00190     while (cur && cont) {
00191         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00192         if (strcmp(cur->name(), name) == 0) {
00193             switch (cur->op()) {
00194               case opSet:
00195                 // There is exactly one more value; return it and stop.
00196                 // By (1) this opSet would not be here if the value
00197                 // had been deleted by a later clear or set (and
00198                 // moreover we would have exited the loop already if
00199                 // there had been one), and by (2) this opSet would
00200                 // not be here if the value had been deleted by a
00201                 // remove. 
00202                 cont = cb(cl, cur->value());
00203                 return;
00204               case opClear:
00205                 // There are no more values; stop.
00206                 return;
00207               case opAdd:
00208                 // This value is in the set.  By (1) this opAdd would
00209                 // not be here if the value had been deleted by a
00210                 // clear or set (and moreover we would have exited the
00211                 // loop already if there had been one), and by (2)
00212                 // this opAdd would not be here if the value had been
00213                 // deleted by a remove.
00214                 cont = cb(cl, cur->value());
00215                 break;
00216               case opRemove:
00217                 // There might be some more values; keep looking.
00218                 break;
00219             }
00220         }
00221         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00222     }
00223 }
00224 
00225 void
00226 VestaAttribs::listAttribs(VestaAttribs::valueCallback cb, void* cl)
00227 {
00228     CharsTable listed;
00229     CharsKey k;
00230     bool v;
00231     VestaAttribsRep* cur =
00232       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00233     bool cont = true;
00234     while (cur && cont) {
00235         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00236         switch (cur->op()) {
00237           case opSet:
00238           case opAdd:
00239             // There is something bound to this name.  By (1) and (2)
00240             // this op would not be here if the value it set or added
00241             // had been deleted.
00242             k.s = cur->name();
00243             if (!listed.Get(k, v)) {
00244                 cont = cb(cl, k.s);
00245                 listed.Put(k, true);
00246             }
00247             break;
00248           case opClear:
00249           case opRemove:
00250             // No useful information.
00251             break;
00252         }
00253         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00254     }
00255 }
00256 
00257 void
00258 VestaAttribs::getAttribHistory(VestaAttribs::historyCallback cb, void* cl)
00259 {
00260     VestaAttribsRep* cur =
00261       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00262     bool cont = true;
00263     while (cur && cont) {
00264         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00265         attribOp curop = cur->op();
00266         cont = cb(cl, curop, cur->name(), cur->value(), cur->timestamp());
00267         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00268     }
00269 }
00270 
00271 static int
00272 compareWrite(VestaAttribs::attribOp o1,
00273              const char* n1, const char* v1, time_t t1, 
00274              VestaAttribs::attribOp o2,
00275              const char* n2, const char* v2, time_t t2)
00276 {
00277     if (t1 > t2) return 1;
00278     if (t1 < t2) return -1;
00279     int nc = strcmp(n1, n2);
00280     if (nc != 0) return nc;
00281     nc = strcmp(v1, v2);
00282     if (nc != 0) return nc;
00283     // Compare o1, o2 last, so that different ops with same arguments
00284     //  are adjacent in order.
00285     if (((int) o1) > ((int) o2)) return 1;
00286     if (((int) o1) < ((int) o2)) return -1;
00287     return 0;
00288 }    
00289 
00290 // Come up with a timestamp for a new attribute history record such
00291 // that it won't be shadowes by an existing record.
00292 static time_t new_timestamp(VestaAttribsRep* first,
00293                             VestaAttribs::attribOp op,
00294                             const char* name, const char* value)
00295 {
00296   time_t result = time(NULL);
00297   if(first == 0) return result;
00298   if(compareWrite(op, name, value, result,
00299                   first->op(), first->name(),
00300                   first->value(), first->timestamp()) < 1)
00301     {
00302       // This would sort after the first history record, so we take
00303       // that timestamp and add one.
00304       result = first->timestamp() + 1;
00305     }
00306   return result;
00307 }
00308 
00309 //
00310 // This version of the method does the real work, but does not
00311 // do access checking, setuid/getuid magic, or logging
00312 //
00313 VestaSource::errorCode
00314 VestaAttribs::writeAttrib(VestaAttribs::attribOp op, const char* name,
00315                           const char* value, time_t &timestamp) throw ()
00316 {
00317     if (!hasAttribs()) return VestaSource::invalidArgs;
00318 
00319     VestaAttribsRep* prev = NULL;
00320     VestaAttribsRep* cur =
00321       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00322 
00323     if (timestamp == 0) {
00324       timestamp = new_timestamp(cur, op, name, value);
00325     }
00326 
00327     while (cur != NULL) {
00328         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00329         int cw = compareWrite(op, name, value, timestamp,
00330                               cur->op(), cur->name(),
00331                               cur->value(), cur->timestamp());
00332         if (cw == 1) {
00333             break;
00334         } else if (cw == 0) {
00335             // Exact duplicate write; has no effect
00336             return VestaSource::nameInUse;
00337         }
00338         // Preserve invariant
00339         if (strcmp(name, cur->name()) == 0) {
00340             switch (cur->op()) {
00341               case opSet:
00342               case opClear:
00343                 // Invariant says this op should be discarded
00344                 return VestaSource::ok;
00345               case opAdd:
00346               case opRemove:
00347                 if (strcmp(value, cur->value()) == 0) {
00348                     switch (op) {
00349                       case opAdd:
00350                       case opRemove:
00351                         // Invariant says this op should be discarded
00352                         return VestaSource::ok;
00353                       case opSet:
00354                         // Change the new opSet to a opClear.  This
00355                         // cannot cause us to put the operation in the
00356                         // wrong order in the list, because the change
00357                         // makes the operation compare (epsilon)
00358                         // smaller, so at worst its place could be
00359                         // further down the list than we have looked yet. 
00360                         op = opClear;
00361                         break;
00362                       case opClear:
00363                         break;
00364                     }
00365                 }
00366                 break;
00367             }
00368         }
00369         prev = cur;
00370         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00371     }
00372 
00373     // Insert into chain.
00374     //   prev is the predecessor, if any.
00375     //   cur is the successor, if any.
00376     VestaAttribsRep* wr =
00377       VestaAttribsRep::create(op, name, value, timestamp);
00378     if (prev == NULL) {
00379         wr->setNext(firstAttrib());
00380         setFirstAttrib(VMemPool::shortenPointer(wr));
00381     } else {
00382         wr->setNext(prev->next());
00383         prev->setNext(VMemPool::shortenPointer(wr));
00384     }
00385 
00386     // Restore invariant
00387     prev = wr;
00388     if (op == opSet || op == opClear) {
00389         while (cur != NULL) {
00390             assert(VMemPool::type(cur) == VMemPool::vAttrib);
00391             if (strcmp(name, cur->name()) == 0) {
00392                 // Remove cur
00393                 prev->setNext(cur->next());
00394                 attribOp curop = cur->op();
00395                 VMemPool::free(cur, VATTR_MINSIZE + strlen(cur->name())
00396                                + strlen(cur->value()),
00397                                VMemPool::vAttrib);
00398                 cur = prev;
00399                 
00400                 switch (curop) {
00401                   case opAdd:
00402                   case opRemove:
00403                     break;
00404                   case opSet:
00405                   case opClear:
00406                     return VestaSource::ok;
00407                 }
00408             }
00409             prev = cur;
00410             cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00411         }
00412     } else {
00413         while (cur != NULL) {
00414             assert(VMemPool::type(cur) == VMemPool::vAttrib);
00415             if (strcmp(name, cur->name()) == 0) {
00416                 switch (cur->op()) {
00417                   case opAdd:
00418                   case opRemove:
00419                     if (strcmp(cur->value(), value) == 0) {
00420                         // Remove cur
00421                         prev->setNext(cur->next());
00422                         VMemPool::free(cur, VATTR_MINSIZE + strlen(cur->name())
00423                                        + strlen(cur->value()),
00424                                        VMemPool::vAttrib);
00425                         // Can't be any more ops on this (name, value)
00426                         return VestaSource::ok;
00427                     }
00428                     break;
00429                   case opSet:
00430                     if (strcmp(cur->value(), value) == 0) {
00431 
00432                         // Change the opSet to a opClear.  This can't
00433                         // change the ordering of the operation with
00434                         // respect to the rest of the history,
00435                         // because the old and new operations sort
00436                         // immediately adjacent to each other, and it
00437                         // can't create a duplicate operation in the
00438                         // history, because if there is an opSet for a
00439                         // name, the invariants guarantee there can't
00440                         // be an opClear for the same name.
00441 
00442                         cur->setOp(opClear);
00443                     }
00444                     // There can't be any more ops on this name
00445                     return VestaSource::ok;
00446                   case opClear:
00447                     // There can't be any more ops on this name
00448                     return VestaSource::ok;
00449                 }
00450             }
00451             prev = cur;
00452             cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00453         }
00454     }
00455     return VestaSource::ok;
00456 }
00457 
00458 // Return true if writing this attrib tuple would change K,
00459 // but do not actually write it.  Mostly code cribbed from writeAttrib.
00460 bool
00461 VestaAttribs::wouldWriteAttrib(VestaAttribs::attribOp op, const char* name,
00462                          const char* value, time_t &timestamp) throw ()
00463 {
00464     if (!hasAttribs()) return false;
00465 
00466     VestaAttribsRep* prev = NULL;
00467     VestaAttribsRep* cur =
00468       (VestaAttribsRep*) VMemPool::lengthenPointer(firstAttrib());
00469 
00470     if (timestamp == 0) {
00471         timestamp = new_timestamp(cur, op, name, value);
00472     }
00473 
00474     while (cur != NULL) {
00475         assert(VMemPool::type(cur) == VMemPool::vAttrib);
00476         int cw = compareWrite(op, name, value, timestamp,
00477                               cur->op(), cur->name(),
00478                               cur->value(), cur->timestamp());
00479         if (cw == 1) {
00480             break;
00481         } else if (cw == 0) {
00482             // Exact duplicate write; has no effect
00483             return false;
00484         }
00485         // Preserve invariant
00486         if (strcmp(name, cur->name()) == 0) {
00487             switch (cur->op()) {
00488               case opSet:
00489               case opClear:
00490                 // Invariant says this op should be discarded
00491                 return false;
00492               case opAdd:
00493               case opRemove:
00494                 if (strcmp(value, cur->value()) == 0) {
00495                     switch (op) {
00496                       case opAdd:
00497                       case opRemove:
00498                         // Invariant says this op should be discarded
00499                         return false;
00500                       case opSet:
00501                         // Change the new opSet to a opClear.  This
00502                         // cannot cause us to put the operation in the
00503                         // wrong order in the list, because the change
00504                         // makes the operation compare (epsilon)
00505                         // smaller, so at worst its place could be
00506                         // further down the list than we have looked yet. 
00507                         op = opClear;
00508                         break;
00509                       case opClear:
00510                         break;
00511                     }
00512                 }
00513                 break;
00514             }
00515         }
00516         prev = cur;
00517         cur = (VestaAttribsRep*) VMemPool::lengthenPointer(cur->next());
00518     }
00519     return true;
00520 }
00521 
00522 Bit32
00523 VestaAttribs::copyAttribs(Bit32 from) throw ()
00524 {
00525     if (from == 0) return 0;
00526     VestaAttribsRep* fromp =
00527       (VestaAttribsRep*) VMemPool::lengthenPointer(from);
00528     assert(VMemPool::type(fromp) == VMemPool::vAttrib);
00529     VestaAttribsRep* first =
00530       VestaAttribsRep::create(fromp->op(), fromp->name(),
00531                               fromp->value(), fromp->timestamp());
00532     VestaAttribsRep* prev = first;
00533     VestaAttribsRep* cur;
00534 
00535     from = fromp->next();
00536     while (from != 0) {
00537         fromp = (VestaAttribsRep*) VMemPool::lengthenPointer(from);
00538         assert(VMemPool::type(fromp) == VMemPool::vAttrib);
00539         cur = VestaAttribsRep::create(fromp->op(), fromp->name(),
00540                                       fromp->value(), fromp->timestamp());
00541         prev->setNext(VMemPool::shortenPointer(cur));
00542         prev = cur;
00543         from = fromp->next();
00544     }
00545     return VMemPool::shortenPointer(first);
00546 }
00547 
00548 
00549 void
00550 VestaAttribsRep::mark() throw ()
00551 {
00552     VestaAttribsRep* ar = this;
00553     while (ar != NULL) {
00554         assert(VMemPool::type(ar) == VMemPool::vAttrib);
00555         ar->setVisited(true);
00556         ar = (VestaAttribsRep*) VMemPool::lengthenPointer(ar->next());
00557     }
00558 }
00559 
00560 
00561 void
00562 VestaAttribsRep::markCallback(void* closure, VMemPool::typeCode type)
00563   throw ()
00564 {
00565     // Most attribs get marked when the directory entry they are in
00566     // is marked, but the attribs for the root directories
00567     // are not in a directory entry.
00568     VestaAttribsRep* ar;
00569     ar = (VestaAttribsRep*)
00570       VMemPool::lengthenPointer(VestaSource::repositoryRoot()->firstAttrib());
00571     if (ar) ar->mark();
00572     ar = (VestaAttribsRep*)
00573       VMemPool::lengthenPointer(VestaSource::mutableRoot()->firstAttrib());
00574     if (ar) ar->mark();
00575     ar = (VestaAttribsRep*)
00576       VMemPool::lengthenPointer(VestaSource::volatileRoot()->firstAttrib());
00577     if (ar) ar->mark();
00578 }
00579 
00580 
00581 bool
00582 VestaAttribsRep::sweepCallback(void* closure, VMemPool::typeCode type,
00583                                void* addr, Bit32& size) throw ()
00584 {
00585     VestaAttribsRep* ar = (VestaAttribsRep*) addr;
00586     int namelen = strlen(ar->name());
00587     size = VATTR_MINSIZE + namelen + strlen(ar->value(namelen));
00588     bool ret = ar->visited();
00589     ar->setVisited(false);
00590     return ret;
00591 }
00592 
00593 void
00594 VestaAttribsRep::rebuildCallback(void* closure, VMemPool::typeCode type,
00595                                  void* addr, Bit32& size) throw ()
00596 {
00597     // Just return the size; nothing else to do
00598     VestaAttribsRep* ar = (VestaAttribsRep*) addr;
00599     int namelen = strlen(ar->name());
00600     size = VATTR_MINSIZE + namelen + strlen(ar->value(namelen));
00601 }
00602 
00603 Bit32
00604 VestaAttribsRep::checkpoint(Bit32& nextSP, std::fstream& ckpt) throw ()
00605 {
00606     if (visited()) return next(); // reused field
00607     // Do iteratively to avoid deep recursion
00608     VestaAttribsRep *ar = this, *ar_next;
00609     while (ar != NULL) {
00610         assert(VMemPool::type(ar) == VMemPool::vAttrib);
00611         int namelen = strlen(ar->name());
00612         int size = VATTR_MINSIZE + namelen + strlen(ar->value(namelen));
00613         int pad = ((-size) & VMemPool::alignmentMask);
00614         Bit32 newSP = nextSP;
00615         nextSP += size + pad;
00616         Bit32 oldNextSP = ar->next();
00617         if (oldNextSP != 0)
00618           {
00619             ar_next =
00620               (VestaAttribsRep*) VMemPool::lengthenPointer(oldNextSP);
00621             // We now need to set ar's next pointer to the short
00622             // pointer of the next enry in the checkpoint.
00623             if(ar_next->visited())
00624               {
00625                 // The next VestaAttribsRep has already been
00626                 // checkpointed, so its next pointer is its new short
00627                 // pointer.
00628                 Bit32 next_new_SP = ar_next->next();
00629                 ar->setNext(next_new_SP);
00630                 Repos::dprintf(DBG_ALWAYS,
00631                                "WARNING: multiply referenced attribute chain; "
00632                                "post-checkpoint short pointer = 0x%x\n",
00633                                next_new_SP);
00634                 // Since the next entry has already been checkpointed,
00635                 // this will be the last loop iteration.
00636                 ar_next = 0;
00637               }
00638             else
00639               // We'll be checkpointing the next VestaAttribsRep right
00640               // after this one, so we know the next short pointer.
00641               ar->setNext(nextSP);
00642           }
00643         else
00644           ar_next = 0;
00645 
00646         ckpt.write((char *) ar->rep, size);
00647         while (pad--) {
00648           ckpt.put(VMemPool::freeByte);
00649         }
00650         ar->setVisited(true);
00651         ar->setNext(newSP);  // reuse (smash) this field
00652         ar = ar_next;
00653     }
00654     return next();
00655 }

Generated on Mon May 8 00:48:50 2006 for Vesta by  doxygen 1.4.2