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

ReposUI.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 // ReposUI.C
00021 //
00022 
00023 #include <Basics.H>
00024 #include <Text.H>
00025 #include <VestaSource.H>
00026 #include <VDirSurrogate.H>
00027 #include <sys/mount.h>
00028 #include <dirent.h>
00029 #include <fnmatch.h>
00030 #include "ReposUI.H"
00031 #include <VestaConfig.H>
00032 #include <Units.H>
00033 
00034 using std::cin;
00035 using std::cout;
00036 using std::endl;
00037 using std::flush;
00038 using std::ofstream;
00039 using std::ifstream;
00040 
00041 static void
00042 throw_errno(const Text& msg, int saved_errno)
00043 {
00044   Text etxt = Basics::errno_Text(saved_errno);
00045   throw ReposUI::failure(msg + ": " + etxt, saved_errno);
00046 }
00047 
00048 static void
00049 throw_errno(const Text& msg)
00050 {
00051   throw_errno(msg, errno);
00052 }
00053 
00054 // Return true if name has "/", ".", or ".." as first component
00055 bool
00056 ReposUI::qualified(const Text& name) throw ()
00057 {
00058     return (name[0] == '/') ||
00059       (name[0] == '.' && (name[1] == '\0' || name[1] == '/')) ||
00060       (name[0] == '.' && name[1] == '.' && (name[2]=='\0' || name[2]=='/'));
00061 }
00062 
00063 // If name is not qualified, prepend supplied root.
00064 Text
00065 ReposUI::qualify(const Text& name, const Text& root) throw ()
00066 {
00067     if (qualified(name)) {
00068         return name;
00069     } else if (name == "") {
00070         return root;
00071     } else {
00072         return root + "/" + name;
00073     }
00074 }
00075 
00076 // Split name into parent name and final arc.
00077 // name must not be empty or end with "/"
00078 void
00079 ReposUI::split(const Text& name, Text& par, Text& arc, const Text& errtext)
00080   throw (ReposUI::failure)
00081 {
00082     int lastslash = name.FindCharR('/');
00083     if (lastslash == -1) {
00084         arc = name;
00085         par = "";
00086     } else {
00087         arc = name.Sub(lastslash + 1);
00088         par = name.Sub(0, lastslash);
00089         if (par == "") par = "/";
00090     }
00091     if (arc.Length() == 0) {
00092         throw ReposUI::failure(errtext +
00093                                " must not have empty final arc");
00094     }
00095     if (arc == "." || arc == "..") {
00096         throw ReposUI::failure(errtext +
00097                                " must not have . or .. as final arc");
00098     }
00099 }
00100 
00101 // If the given absolute name has prefix as its initial arc(s), return
00102 // true and put the offset the offset to the remainder of the name in tail;
00103 // else return false.
00104 int
00105 stripPrefix(Text name, Text prefix, /*OUT*/int& tail)
00106 {
00107   int plen = prefix.Length();
00108   if (prefix == name.Sub(0, plen)) {
00109     if (name.Length() == plen) {
00110       tail = plen;
00111       return true;
00112     }
00113     if (name[plen] == '/') {
00114       tail = plen + 1;
00115       return true;
00116     }
00117   }
00118   return false;
00119 }
00120 
00121 static Text
00122 precanonicalize(const Text& name, /*OUT*/ReposUI::RootLoc& rootloc)
00123      throw (VestaConfig::failure, ReposUI::failure)
00124 {
00125   // 1) If the name does not begin with "/", prepend the current
00126   // directory name (inserting a "/" separator if needed).  The
00127   // current directory name is obtained by calling getcwd().
00128   Text aname;
00129   try {
00130     aname = FS::GetAbsolutePath(name);
00131   }
00132   catch (FS::Failure f) {
00133     throw_errno(f.get_op() + " failed", f.get_errno());
00134   }
00135 
00136   // 2) Textually remove ".", "..", and empty components
00137   Text cname = FS::RemoveSpecialArcs(aname);
00138  
00139   // 3) If the first component of the name is (now) "/vesta" or
00140   // [UserInterface]AppendableRootName, skip this component and 
00141   // interpret the rest relative to the appendable root;
00142   // or similarly for "/vesta-work", [UserInterface]MutableRootName,
00143   // and the mutable root.  Otherwise, leave the name unchanged
00144   // and flag an error.
00145   cname = ReposUI::stripRoot(cname, /*OUT*/rootloc);
00146   return cname;
00147 }
00148 
00149 Text
00150 ReposUI::canonicalize(const Text& name)
00151      throw (VestaConfig::failure, ReposUI::failure)
00152 {
00153   ReposUI::RootLoc rootloc;
00154   Text cname = precanonicalize(name, /*OUT*/rootloc);
00155   Text root;
00156   if (rootloc == ReposUI::VESTA) {
00157     root = "/vesta";
00158   } else if (rootloc == ReposUI::VESTAWORK) {
00159     root = "/vesta-work";
00160   } else {
00161     throw ReposUI::failure(name + " is not in the Vesta repository");
00162   }
00163   return root + (cname.Empty() ? "" : "/") + cname;
00164 }
00165 
00166 Text
00167 ReposUI::canonicalize(const Text& name, const Text& qual)
00168      throw (VestaConfig::failure, ReposUI::failure)
00169 {
00170   ReposUI::RootLoc rootloc;
00171   Text cname = precanonicalize(qualify(name, qual), /*OUT*/rootloc);
00172   Text root;
00173   if (rootloc == ReposUI::VESTA) {
00174     root = "/vesta";
00175   } else if (rootloc == ReposUI::VESTAWORK) {
00176     root = "/vesta-work";
00177   } else {
00178     throw ReposUI::failure(name + " is not in the Vesta repository");
00179   }
00180   return root + (cname.Empty() ? "" : "/") + cname;
00181 }
00182 
00183 Text ReposUI::stripRoot(const Text& name, /*OUT*/RootLoc& rootloc) 
00184   throw (VestaConfig::failure)
00185 {
00186   int tail = 0;
00187   if (stripPrefix(name, "/vesta", /*OUT*/tail) ||
00188       stripPrefix(name, VestaConfig::get_Text("UserInterface",
00189                                               "AppendableRootName"),
00190                   /*OUT*/tail)) {    
00191     rootloc = ReposUI::VESTA;
00192   } 
00193   else if (stripPrefix(name, "/vesta-work", /*OUT*/tail) ||
00194            stripPrefix(name, VestaConfig::get_Text("UserInterface",
00195                                                    "MutableRootName"),
00196                        /*OUT*/tail)) {
00197     rootloc = ReposUI::VESTAWORK;
00198   } 
00199   else {
00200     rootloc = ReposUI::NOWHERE;
00201   }
00202   return name.Sub(tail);
00203 }
00204 
00205 
00206 Text ReposUI::stripSpecificRoot(const Text& name, RootLoc which_root, 
00207                                 bool require_canonical)
00208   throw (ReposUI::failure, VestaConfig::failure)
00209 {
00210   int tail = 0;
00211   bool success = false;
00212   switch(which_root) {
00213   case ReposUI::VESTA:
00214     success = stripPrefix(name, "/vesta", /*OUT*/tail);
00215     if(!success && !require_canonical) {
00216       success = stripPrefix(name,
00217                             VestaConfig::get_Text("UserInterface",
00218                                                   "AppendableRootName"),
00219                             /*OUT*/tail);
00220     }
00221     if(!success)
00222       throw ReposUI::failure(name +
00223                      " is not in the appendable portion of the Vesta repository");
00224     break;
00225   case ReposUI::VESTAWORK:
00226     success = stripPrefix(name, "/vesta-work", /*OUT*/tail);
00227     if(!success && !require_canonical) {
00228       success = stripPrefix(name,
00229                             VestaConfig::get_Text("UserInterface",
00230                                                   "MutableRootName"),
00231                             /*OUT*/tail);
00232     }
00233     if(!success)
00234       throw ReposUI::failure(name +
00235                      " is not in the mutable portion of the Vesta repository");
00236     break;
00237   default: 
00238     throw ReposUI::failure("stripSpecificRoot: Invalid parameter");
00239   }
00240   return name.Sub(tail);
00241 }
00242 
00243 VestaSource* 
00244 ReposUI::lookupCreatePath(Text root_name, 
00245                           Text pathname, Text hints)
00246   throw(ReposUI::failure, VestaConfig::failure, SRPC::failure)
00247 {
00248   int len = pathname.Length();
00249   VestaSource* vs_next;
00250   Text current_path = root_name;
00251 
00252   VestaSource* vs_current = ReposUI::filenameToMasterVS(root_name, hints);
00253 
00254   while(!pathname.Empty()) {
00255     int separator = pathname.FindChar('/');
00256     // find out the directory name that should be looked up in current vs 
00257     // and be built in a case it is not there.  
00258     Text dir_name;
00259     if(separator > 0 && separator < len) {
00260       dir_name = pathname.Sub(0, separator);
00261       pathname = pathname.Sub(separator + 1);
00262     }
00263     else if(separator < 0) {
00264       dir_name = pathname;
00265       pathname = "";
00266     }
00267     else {
00268       assert(separator == 0);
00269       pathname = pathname.Sub(separator + 1);
00270       continue;
00271     }
00272     Arc arc = dir_name.cchars();
00273     // look up the directory name
00274     VestaSource::errorCode lookup_err = vs_current->lookup(arc, vs_next);
00275     if(lookup_err != VestaSource::ok) {
00276       if(lookup_err == VestaSource::notFound) {
00277         // insert the directory  
00278         VestaSource::errorCode create_err = 
00279           vs_current->insertAppendableDirectory(arc, /*master*/true,
00280                                                 /*who=*/NULL,
00281                                                 /*chk=*/VestaSource::dontReplace,
00282                                                 /*newvs=*/&vs_next);
00283         if(create_err != VestaSource::ok) {
00284           throw ReposUI::failure(current_path + "/" + arc + ": " + 
00285                                  ReposUI::errorCodeText(create_err), 
00286                                  -1, create_err);
00287         }
00288       }
00289       else {
00290         throw ReposUI::failure(current_path + "/" + arc + ": " + 
00291                                ReposUI::errorCodeText(lookup_err), 
00292                                -1, lookup_err);
00293       }
00294     }
00295     else {
00296       // vs_next has to be a master for performing next step lookup,
00297       // creation a new directory (in a master object) if needed, 
00298       // or as a returning result. 
00299       if(!vs_next->master) {
00300         delete vs_next;
00301         try {
00302           vs_next = ReposUI::filenameToMasterVS(current_path, hints);
00303         } catch(...) {
00304           // Don't leak vs_current if we can't find the master copy of
00305           // this directory.
00306           delete vs_current;
00307           throw;
00308         }
00309       }
00310     }
00311     // free memory allocated by current VS pointer and set it to point 
00312     // to the new VS
00313     delete vs_current;
00314     vs_current = vs_next;
00315     current_path = current_path + "/" + arc;  
00316   }
00317  
00318   return vs_current;
00319 }
00320 
00321 
00322 VestaSource*
00323 ReposUI::filenameToVS(Text name, Text host, Text port)
00324   throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00325 {
00326   ReposUI::RootLoc rootloc;
00327   Text cname = precanonicalize(name, /*OUT*/rootloc);
00328   VestaSource* root = NULL;
00329   bool needFree = false;
00330 
00331   try {
00332     if (rootloc == ReposUI::VESTA) {
00333       if (host.Empty()) {
00334         root = VestaSource::repositoryRoot();
00335       } else {
00336         root = VDirSurrogate::repositoryRoot(host, port);
00337         needFree = true;
00338       }
00339     } else if (rootloc == ReposUI::VESTAWORK) {
00340       if (host.Empty()) {
00341         root = VestaSource::mutableRoot();
00342       } else {
00343         root = VDirSurrogate::mutableRoot(host, port);
00344         needFree = true;
00345       }
00346     } else {
00347       throw ReposUI::failure(name + " is not in the Vesta repository");
00348     }
00349 
00350     VestaSource *vs;
00351     VestaSource::errorCode err =
00352       root->lookupPathname(cname.cchars(), vs);
00353     if (needFree) delete root;
00354     if (err != VestaSource::ok) {
00355       throw ReposUI::failure(name + ": " + ReposUI::errorCodeText(err),
00356                              -1, err);
00357     }
00358     return vs;
00359   } catch (SRPC::failure f) {
00360     if (needFree && root) delete root;
00361     throw ReposUI::failure(name + ": SRPC failure; " + f.msg);
00362   }
00363 }
00364 
00365 VestaSource*
00366 ReposUI::filenameToVS(Text name)
00367   throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00368 {
00369   return filenameToVS(name, "", "");
00370 }
00371 
00372 VestaSource*
00373 ReposUI::filenameToVS(Text name, Text hostport)
00374   throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00375 {
00376   Text host, port;
00377   if (hostport.Empty()) {
00378     host = port = "";
00379   } else {
00380     int colon = hostport.FindChar(':');
00381     if (colon == -1) {
00382       host = hostport;
00383       port = VestaConfig::get_Text("Repository", "VestaSourceSRPC_port");
00384     } else {
00385       host = hostport.Sub(0, colon);
00386       port = hostport.Sub(colon+1);
00387     }
00388   }
00389   return filenameToVS(name, host, port);
00390 }
00391 
00392 
00393 
00394 bool getMasterHintsCallback(void* closure, const char* value) 
00395 {
00396   if(closure != 0) {
00397     Text* hints = (Text*) closure;
00398     *hints = *hints + " " + Text(value);
00399   }
00400   return true;
00401 }
00402 
00403 
00404 // Common routine for filenameToMasterVS and filenameToRealVS.
00405 // Algorithm outline:
00406 // 1) Canonicalize name
00407 // 2) repos = local repository
00408 // 3) Look up name in repos
00409 // 4) If found and result matches criterion, return it
00410 // 5) Traverse from this name upward until root and add all 
00411 //    master-repository values to hints 
00412 // 6) take the next hint from "hints" that has not already been used.
00413 // 7) If none found, fail.
00414 // 8) repos = hint
00415 // 9) Go to 3.
00416 
00417 static VestaSource*
00418 filenameToSpecialVS(Text name, Text hints, bool needMaster)
00419      throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00420 {
00421   Table<Text, Text>::Default tried;
00422   Text reposhost, reposport, defaultport;
00423   enum { NOTHING, NAME, COPY } found = NOTHING;
00424   
00425   // 1) Canonicalize name
00426   ReposUI::RootLoc rootloc;
00427   Text cname = precanonicalize(name, /*OUT*/rootloc);
00428   if (rootloc != ReposUI::VESTA) {
00429     throw ReposUI::failure(name + " is not under /vesta");
00430   }
00431 
00432   // 2) repos = local repository
00433   reposhost = VDirSurrogate::defaultHost();
00434   reposport = defaultport = VDirSurrogate::defaultPort();
00435  
00436 
00437   for (;;) {
00438     // 3) Look up name in repos
00439     tried.Put(reposhost + ":" + reposport, "");
00440 
00441     VestaSource* vs = NULL;
00442     VestaSource* root = NULL;
00443     VestaSource::errorCode err = VestaSource::rpcFailure;
00444 
00445     try {
00446       root = VDirSurrogate::repositoryRoot(reposhost, reposport);
00447       err = root->lookupPathname(cname.cchars(), vs);
00448     } catch (SRPC::failure f) {
00449       // fall through to try another hint
00450       tried.Put(reposhost + ":" + reposport, f.msg);
00451     }
00452     
00453     // 4) If found and result matches criterion, return it
00454     if (err == VestaSource::ok) {
00455       if (vs->master) {
00456               delete root;
00457               return vs;
00458       }
00459       tried.Put(reposhost + ":" + reposport, "Not master");
00460       if (vs->type != VestaSource::stub &&
00461           vs->type != VestaSource::ghost) {
00462         if (!needMaster) {
00463                 delete root;
00464                 return vs;
00465         }
00466         found = COPY;
00467       } else {
00468         if (found < NAME) found = NAME;
00469       }
00470     }
00471     else {
00472       Text reason;
00473       bool inTable = tried.Get(reposhost + ":" + reposport, reason);
00474       if(!inTable || (inTable && reason == ""))
00475         tried.Put(reposhost + ":" + reposport, ReposUI::errorCodeText(err));
00476     }
00477     
00478     // 5) Traverse from this name upward until root and add all 
00479     //    master-repository values to hints
00480     Text head = cname;
00481     reposhost = reposport = "";
00482     if (root) {
00483       for (;;) {
00484         if (vs) {
00485           vs->getAttrib("master-repository", 
00486                         getMasterHintsCallback, (void*) &hints);
00487           delete vs;
00488           vs = NULL;
00489         }
00490         int slash = head.FindCharR('/');
00491         if (slash == -1) break;
00492         head = head.Sub(0, slash);
00493         err = root->lookupPathname(head.cchars(), vs);
00494       }
00495       delete root;
00496     }
00497 
00498     // 6) take the next hint from "hints" that has not already been used.
00499     for (;;) {
00500       int i = 0;
00501       while (hints[i] == ',' || hints[i] == ' ') i++;
00502       if (hints[i] == '\000') break;
00503       int j = i;
00504       while (hints[j] && hints[j] != ',' && hints[j] != ' ') j++;
00505       Text mht = hints.Sub(i, j - i);
00506       hints = hints.Sub(j);
00507       Text dummy;
00508       if (!tried.Get(mht, dummy)) {
00509         // Found a new hint
00510         int colon = mht.FindCharR(':');
00511         if(colon > 0) {
00512           reposhost = mht.Sub(0, colon);
00513           reposport = mht.Sub(colon+1);
00514         }
00515         else {
00516           reposhost = mht;
00517           reposport = defaultport;
00518         }
00519         break;
00520       }
00521     }
00522 
00523     // 7) If none found, fail.
00524     if (reposhost.Empty()) {
00525       Text msg;
00526       switch (found) {
00527       case NOTHING:
00528         msg = "can't find ";
00529         break;
00530       case NAME:
00531         msg = "can't find a replica of ";
00532         break;
00533       case COPY:
00534         msg = "can't find master replica of ";
00535         break;
00536       }
00537       msg = msg + name + " (tried:";
00538       Table<Text, Text>::Iterator iter(&tried);
00539       Text repname;
00540       Text repreason;
00541       while (iter.Next(repname, repreason)) {
00542         msg = msg + " " + repname;
00543         if(needMaster && repreason != "") {
00544           msg = msg + " [" + repreason  + "]";
00545         }
00546       }
00547       throw ReposUI::failure(msg + ")");
00548     }
00549     // 8) repos = hint
00550     // 9) Go to 3.
00551   }
00552 }
00553 
00554 VestaSource*
00555 ReposUI::filenameToMasterVS(Text name, Text hints)
00556      throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00557 {
00558   return filenameToSpecialVS(name, hints, true);
00559 }
00560 
00561 VestaSource*
00562 ReposUI::filenameToRealVS(Text name, Text hints)
00563      throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00564 {
00565   return filenameToSpecialVS(name, hints, false);
00566 }
00567 
00568 Text
00569 ReposUI::vsToFilename(VestaSource* vs)
00570   throw (ReposUI::failure, VestaConfig::failure, SRPC::failure)
00571 {
00572     Text ret = "";
00573     LongId longid_par;
00574     unsigned int index;
00575     VestaSource* vs_cur = vs;
00576     VestaSource* vs_par;
00577     VestaSource* vs_junk;
00578     VestaSource::errorCode err;
00579     char arcbuf[MAX_ARC_LEN+1];
00580     for (;;) {
00581         if (vs_cur->longid == RootLongId) {
00582             ret = "/vesta/" + ret;
00583             break;
00584         } else if (vs_cur->longid == MutableRootLongId) {
00585             ret = "/vesta-work/" + ret;
00586             break;
00587         }
00588         longid_par = vs_cur->longid.getParent(&index);
00589         if (longid_par == NullLongId) {
00590             throw ReposUI::failure("cannot find filename for given longid");
00591         }
00592         vs_par = VDirSurrogate::
00593 	  LongIdLookup(longid_par, vs_cur->host(), vs_cur->port());
00594         assert(vs_par != NULL);
00595         err = vs_par->lookupIndex(index, vs_junk, arcbuf);
00596         assert(err == VestaSource::ok);
00597         delete vs_junk;
00598         ret = Text(arcbuf) + "/" + ret;
00599         if (vs_cur != vs) delete vs_cur;
00600         vs_cur = vs_par;
00601     }
00602     // Strip bogus final "/" from return value
00603     ret = ret.Sub(0, ret.Length() - 1);
00604     if (vs_cur != vs) delete vs_cur;
00605     return ret;           
00606 }
00607 
00608 Text 
00609 ReposUI::getMasterHintDir(VestaSource* vs, const Text& cname)
00610       throw (ReposUI::failure, SRPC::failure)
00611 {
00612   if(!vs->master)
00613     throw ReposUI::failure("Not master", -1, VestaSource::notMaster);
00614 
00615   VestaSource* vs_cur = vs;
00616   VestaSource* vs_parent = NULL; 
00617   Text hint_dir = cname;
00618   while(vs_cur) {
00619     // get parent
00620     vs_parent = vs_cur->getParent();
00621     if(vs_parent) {
00622       // stop if parent is non master
00623       if(!vs_parent->master) 
00624         break;
00625      
00626       if(vs_cur != vs)
00627         delete vs_cur;
00628       vs_cur = vs_parent;
00629 
00630       int slash = hint_dir.FindCharR('/');
00631       assert(slash != -1);
00632       hint_dir = hint_dir.Sub(0, slash);
00633     }
00634     else // it is root
00635       break;
00636   }
00637   
00638   if(vs_parent)
00639     delete vs_parent;
00640   if(vs_cur && vs_cur != vs)
00641     delete vs_cur;
00642     
00643   return hint_dir;
00644 }
00645 
00646 struct verclosure {
00647   long high;
00648   VestaSource* parent;
00649   Text hints;
00650   Text path;
00651 };
00652 
00653 static bool
00654 vercallback(void* closure, VestaSource::typeTag type, Arc arc,
00655             unsigned int index, Bit32 pseudoInode, ShortId filesid,bool master)
00656 {
00657     verclosure* cl = (verclosure*) closure;
00658     char* endptr;
00659     long val = strtol(arc, &endptr, 10);
00660     if (*endptr == '\0' && val > cl->high) { 
00661       if(type != VestaSource::stub) {
00662         cl->high = val;
00663       } else if(!master) {
00664         try {
00665           if(cl->path == "")
00666              cl->path = ReposUI::vsToFilename(cl->parent);
00667           Text name = cl->path + "/" + arc;
00668           VestaSource* vs_real = ReposUI::filenameToRealVS(name, cl->hints);
00669           if(vs_real->type != VestaSource::stub)
00670             cl->high = val;
00671           delete vs_real;
00672         } catch(ReposUI::failure) {
00673           // Ignore
00674         }
00675       }
00676     }
00677     return true;
00678 }
00679 
00680 long
00681 ReposUI::highver(VestaSource* vs, Text hints, Text path) 
00682   throw (ReposUI::failure, SRPC::failure)
00683 {
00684     // Return the highest version number in directory vs.  That is,
00685     // considering the set of arcs in the directory that are composed
00686     // entirely of decimal digits, interpret each as a decimal number
00687     // and return the value of the largest that is not a stub.
00688     // Return -1 if the set is empty.
00689     verclosure cl;
00690     cl.high = -1;
00691     cl.parent = vs;
00692     cl.hints = hints;
00693     cl.path = path;
00694    
00695     VestaSource::errorCode err = vs->list(0, vercallback, &cl);
00696     if (err != VestaSource::ok) {
00697         throw ReposUI::failure("error listing directory of versions: " +
00698                                ReposUI::errorCodeText(err), -1, err);
00699     }
00700     return cl.high;
00701 }
00702 
00703 Text
00704 ReposUI::uniquify(VestaSource* vs, const Text& prefix)
00705      throw (ReposUI::failure, SRPC::failure)
00706 {
00707   char buf[64];
00708   Text result;
00709   unsigned int suffix = 1;
00710   while(1)
00711     {
00712       // Generate a candidate name
00713       sprintf(buf, "%d", suffix);
00714       result = prefix + "." + buf;
00715       // See if it already exists
00716       VestaSource *kid = 0;
00717       VestaSource::errorCode err = vs->lookup(result.cchars(), /*OUT*/ kid);
00718       // If this name doesn't exist, we're done.
00719       if(err == VestaSource::notFound)
00720         {
00721           break;
00722         }
00723       // If this name does exist, free the object just created.
00724       else if(err == VestaSource::ok)
00725         {
00726           assert(kid != 0);
00727           delete kid;
00728         }
00729       // Any other error is fatal.
00730       else
00731         {
00732           throw ReposUI::failure("error on lookup for generating unique name: " +
00733                                  ReposUI::errorCodeText(err), -1, err);
00734         }
00735       // Increment the suffix for our next attempt.
00736       suffix++;
00737     }
00738 
00739   return result;
00740 }
00741 
00742 struct changed_closure {
00743     bool changed;
00744     VestaSource* vs;
00745     time_t since;
00746 };
00747 
00748 static bool
00749 changed_callback(void* closure, VestaSource::typeTag type,
00750                  Arc arc, unsigned int index, Bit32 pseudoInode,
00751                  ShortId filesid, bool master)
00752 {
00753     changed_closure* cl = (changed_closure*) closure;
00754     assert(!cl->changed);
00755     switch (type) {
00756       case VestaSource::mutableFile:
00757         cl->changed = true;
00758         break;
00759       case VestaSource::mutableDirectory:
00760         {
00761             VestaSource* vs;
00762             VestaSource::errorCode err = cl->vs->lookupIndex(index, vs);
00763             if (err != VestaSource::ok) {
00764                 throw ReposUI::failure("error on lookupIndex: " +
00765                                        ReposUI::errorCodeText(err), -1 , err);
00766             }
00767             cl->changed = ReposUI::changed(vs, cl->since);
00768         }
00769         break;
00770       default:
00771         break;
00772     }
00773     return !cl->changed; // stop if a change found
00774 }
00775 
00776 bool
00777 ReposUI::changed(VestaSource* vs, time_t since)
00778   throw (ReposUI::failure, SRPC::failure)
00779 {
00780     // Return true if the tree rooted at the given mutable working
00781     // directory has changed since the last vcheckout or vadvance.  The
00782     // time of the vadvance must be provided as an argument, but the
00783     // checking is not based entirely on time.  The tree is deemed to have
00784     // changed if (1) the modified time of any directory in the tree is
00785     // greater than the given time, or (2) any directory in the tree
00786     // contains a mutable file (i.e., a file for which no copy-on-write
00787     // has been performed since the last vcheckout or vadvance).
00788 
00789     if (vs->timestamp() > since) return true;
00790     changed_closure cl;
00791     cl.changed = false;
00792     cl.vs = vs;
00793     cl.since = since;
00794     VestaSource::errorCode err = vs->list(0, changed_callback, &cl);
00795     if (err != VestaSource::ok) {
00796         throw ReposUI::failure("error listing directory: " +
00797                                ReposUI::errorCodeText(err), -1 , err);
00798     }
00799     return cl.changed;
00800 }
00801 
00802 struct cleanup_closure {
00803     VestaSource* vs;
00804     Sequence<Text>* pattern;
00805     size_t maxsize;
00806     Text prefix;
00807 };
00808 
00809 static bool
00810 cleanup_callback(void* closure, VestaSource::typeTag type,
00811                  Arc arc, unsigned int index, Bit32 pseudoInode,
00812                  ShortId filesid, bool master)
00813 {
00814   cleanup_closure* cl = (cleanup_closure*) closure;
00815   VestaSource* vs;
00816   VestaSource::errorCode err = cl->vs->lookupIndex(index, vs);
00817   if (err != VestaSource::ok) {
00818     throw ReposUI::failure("error on lookupIndex: " +
00819                            ReposUI::errorCodeText(err), -1 , err);
00820   }
00821 
00822   switch (type) {
00823   case VestaSource::mutableFile:
00824   case VestaSource::immutableFile: {
00825     int i;
00826     for (i=0; i<cl->pattern->size(); i++) {
00827       if (fnmatch(cl->pattern->get(i).cchars(), arc, 0) == 0) {
00828         err = cl->vs->reallyDelete(arc);
00829         if (err != VestaSource::ok) {
00830           throw ReposUI::failure("error deleting " + cl->prefix + arc + ": " +
00831                                  ReposUI::errorCodeText(err), -1 , err);
00832         }
00833         return true;
00834       }
00835     }
00836     if (type == VestaSource::mutableFile && vs->size() > cl->maxsize) {
00837       throw(ReposUI::failure(cl->prefix + arc + " is " +
00838                              Basics::FormatUnitVal(vs->size()) + 
00839                              " which is over the max file size safety limit (" +
00840                              Basics::FormatUnitVal(cl->maxsize) +  ")"));
00841     }
00842     break; }
00843 
00844   case VestaSource::mutableDirectory: {
00845     cleanup_closure cl2 = *cl;
00846     cl2.vs = vs;
00847     cl2.prefix = cl->prefix + arc + "/";
00848     err = vs->list(0, cleanup_callback, &cl2);
00849     if (err != VestaSource::ok) {
00850       throw ReposUI::failure("error listing directory " + cl->prefix +
00851                              ReposUI::errorCodeText(err), -1 , err);
00852     }
00853     break; }
00854 
00855   default:
00856     break;
00857   }
00858   return true;
00859 }
00860 
00861 void
00862 ReposUI::cleanup(VestaSource* vs, const Text& pattern,
00863                  unsigned long maxsize, const Text& prefix)
00864   throw (ReposUI::failure, SRPC::failure)
00865 {
00866   cleanup_closure cl;
00867   cl.vs = vs;
00868   int i = 0;
00869   cl.pattern = NEW(Sequence<Text>);
00870   for (;;) {
00871     while (pattern[i] && pattern[i] == ' ') i++;
00872     if (!pattern[i]) break;
00873     int j = i;
00874     while (pattern[j] && pattern[j] != ' ') j++;
00875     cl.pattern->addhi(pattern.Sub(i, j-i));
00876     i = j;
00877   }
00878   cl.maxsize = maxsize;
00879   cl.prefix = prefix;
00880   int len = prefix.Length();
00881   if (len == 0 || prefix[len - 1] != '/') {
00882     cl.prefix = cl.prefix + "/";
00883   }
00884   VestaSource::errorCode err = vs->list(0, cleanup_callback, &cl);
00885   if (err != VestaSource::ok) {
00886     throw ReposUI::failure("error listing directory " + prefix +
00887                            ReposUI::errorCodeText(err), -1 , err);
00888   }
00889 }
00890 
00891 const Text
00892 ReposUI::errorCodeText(VestaSource::errorCode err) throw (ReposUI::failure)
00893 {
00894     switch (err) {
00895       case VestaSource::ok:
00896         return "No error";
00897       case VestaSource::notFound:
00898         return "Not found";
00899       case VestaSource::noPermission:
00900         return "No permission";
00901       case VestaSource::nameInUse:
00902         return "Name in use";
00903       case VestaSource::inappropriateOp:
00904         return "Operation not available on given source type";
00905       case VestaSource::nameTooLong:
00906         return "Name too long";
00907       case VestaSource::rpcFailure:
00908         return "Remote call from repository to another server failed";
00909       case VestaSource::notADirectory:
00910         return "Not a directory";
00911       case VestaSource::isADirectory:
00912         return "Is a directory";
00913       case VestaSource::invalidArgs:
00914         return "Invalid argument";
00915       case VestaSource::outOfSpace:
00916         return "Out of disk space";
00917       case VestaSource::notMaster:
00918         return "Not master replica";
00919       case VestaSource::longIdOverflow:
00920         return "LongId overflow (directories nested too deeply)";
00921       default: {
00922           const char *str = VestaSource::errorCodeString(err);
00923           if (str) {
00924               return Text("VestaSource::") + str;
00925           } else {
00926               throw ReposUI::failure("unknown VestaSource error code",
00927                                      -1, err);
00928           }
00929       }
00930     }
00931     //return "oops"; // not reached
00932 }
00933 
00934 static Text promptMessage(bool interactive,
00935                           const Text &description) throw (ReposUI::failure)
00936 {
00937   Text message;
00938   // Read message from standard input
00939   char buf[1024];
00940   int i = 0;
00941   int c;
00942   bool start_of_line = true;
00943   bool dot_starts_line = false;
00944   bool broke_with_dot = false;
00945   if (interactive) {
00946     cout << "Enter " << description
00947          << ", terminated with ^D or . on a line by itself\n"
00948          << ": " << flush;
00949   }
00950   while ((c = cin.get()) != EOF) {
00951     if (c == '\000') {
00952       throw ReposUI::failure(description + " contains a NUL character", 
00953                              -1, VestaSource::invalidArgs);
00954     }
00955     if (c == '.' && start_of_line == true) {
00956       dot_starts_line = true;
00957     }
00958     else if(c != '\n') {
00959       dot_starts_line = false;
00960     }
00961     start_of_line = false;
00962     buf[i++] = c;
00963     if (interactive && c == '\n') {
00964       if ( dot_starts_line == true ) {
00965         broke_with_dot = true;
00966         break;
00967       }
00968       cout << ": " << flush;
00969       start_of_line = true;
00970     }
00971     if (i == sizeof(buf) - 1) {
00972       buf[i] = '\000';
00973       message = message + buf;
00974       i = 0;
00975     }
00976   }
00977 
00978   buf[i] = '\000';
00979   message = message + buf;
00980   // now remove the "\n.\n" if that's how the message was ended.
00981   if(broke_with_dot) message = message.Sub(0, message.Length()-3);
00982   if (interactive) cout << endl;
00983 
00984   return message;
00985 }
00986 
00987 Text ReposUI::getMessage(const Text &description,
00988                          const Text &in_description,
00989                          const char* in_message) 
00990   throw (ReposUI::failure, FS::Failure)
00991 {
00992   Text message;
00993   char* editor = NULL;
00994   bool interactive = isatty(0);
00995   editor = getenv("EDITOR");
00996   if(!editor)
00997     {
00998       if(interactive && in_message)
00999         cout << in_description << ":" << endl
01000              << in_message << endl;
01001       message = promptMessage(interactive, description);
01002     }
01003   else
01004     {
01005       char temp_name[15] = "/tmp/msgXXXXXX";
01006       int temp_fd = mkstemp(temp_name);
01007       if(temp_fd == -1)
01008         throw ReposUI::failure("error creating a temp file for editing "+
01009                                description);
01010       // Close the file descriptor mkstemp opened, looping on EINTR
01011       {
01012         int close_res;
01013         do
01014           close_res = ::close(temp_fd);
01015         while((close_res != 0) && (errno == EINTR));
01016       }
01017       Text fname = (Text)temp_name;
01018       // copy the input message to the file
01019       Text default_msg = ("<enter " + description + " here>");
01020       ofstream ofs;
01021       FS::OpenForWriting(fname, ofs);
01022       if(in_message)
01023         FS::Write(ofs, (char *) in_message, strlen(in_message));
01024       else
01025         FS::Write(ofs, default_msg.chars(), default_msg.Length());
01026       FS::Close(ofs);
01027       // run editor
01028       cout << "Running " << editor << " to edit " << description << endl;
01029       Text cmd = (Text)editor + " " + fname;
01030       if(system(cmd.chars()) != 0) {
01031         throw ReposUI::failure("error running editor " + (Text)editor);
01032       }
01033       // read the message from the file
01034       ifstream ifs;
01035       FS::OpenReadOnly(fname, ifs);
01036       char buff[1024];
01037       try {
01038         while(!FS::AtEOF(ifs)) {
01039           memset(buff,0, 1024);
01040           FS::Read(ifs, buff, 1023);
01041           message = message + (Text)buff;
01042         }
01043       }
01044       catch(FS::EndOfFile) {
01045         message = message + (Text)buff;
01046       }
01047       FS::Close(ifs);
01048       FS::Delete(fname);
01049 
01050       // Convert the default message to an empty message.
01051       if(message == default_msg)
01052         message = "";
01053       else
01054         {
01055           // Convert an all whitespace message to an empty message.
01056           int i = 0;
01057           while((i < message.Length()) && isspace(message[i]))
01058             i++;
01059           if(i == message.Length())
01060             message = "";
01061         }
01062 
01063       // If the user entered an empty message or left the input message
01064       // unedited, ask them for confirmation.  (This is the user's only
01065       // chance to abort when EDITOR is set.)
01066       if(message.Empty() ||
01067          (in_message && message == in_message))
01068         {
01069           if(message.Empty())
01070             cout << "Empty " << description;
01071           else
01072             cout << in_description << " unedited for " << description;
01073           cout << ".  Continue (y/n)? " << flush;
01074 
01075           char buff[20];
01076           cin.getline(buff, sizeof(buff));
01077           if((buff[0] != 'y') && (buff[0] != 'Y'))
01078             {
01079               throw ReposUI::failure("user aborted after editing " +
01080                                      description);
01081             }
01082         }
01083     }
01084   return message;
01085 }
01086 
01087 Text ReposUI::prevVersion(VestaSource* vs, Text hints)
01088   throw (failure, SRPC::failure)
01089 {
01090         VestaSource* parent = NULL;
01091         VestaSource* version = vs;
01092         VestaSource *cvs;
01093 
01094         //Hints for finding content in other repositories
01095         if(hints == "" && VestaConfig::is_set("UserInterface", "DefaultHints")) {
01096                 hints = VestaConfig::get_Text("UserInterface", "DefaultHints");
01097         }
01098 
01099         //Follow the content attribute back as far as it goes.
01100         while(1) {
01101                 char *content = version->getAttrib("content");
01102                 if(!content) break;
01103 
01104                 //Lookup the path given in the attribute
01105                 try {
01106                         cvs = filenameToRealVS(content, hints);
01107                 } catch(...) {
01108                         cvs = NULL;
01109                 }
01110 
01111                 // follow the content attribute
01112                 if(cvs) {
01113                         assert(version != cvs);
01114                         if(version != vs) delete version;
01115                         version = cvs;
01116                         delete [] content;
01117                         continue;
01118                 }
01119 
01120                 // We couldn't find a replica of the content, so we'll look for
01121                 // one of its parent
01122                 Text parent_t, arc;
01123                 split(content, parent_t, arc, "malformed content attribute");
01124                 try {
01125                         parent = filenameToRealVS(parent_t, hints);
01126                 } catch(...) {
01127                         //Ignore
01128                 }
01129                 delete [] content;
01130                 break;
01131         }
01132         
01133         //Presumably, we're now looking at a version in a session directory.
01134         //The real old-version is on the session directory itself.
01135         if(parent == NULL || !parent->inAttribs("type", "session")) {
01136                 parent = version->getParent();
01137         }
01138         char *ov = NULL;
01139         Text r("");
01140         if(parent->inAttribs("type", "session")) {
01141                 ov = parent->getAttrib("old-version");
01142         }
01143         if(ov) {
01144                 r = ov;
01145         } else if(ov = version->getAttrib("old-version")) {
01146                 //If version has an old-version attribute, use it.
01147                 r = ov;
01148         } else if(parent->inAttribs("type", "package") || parent->inAttribs("type", "branch")) {
01149                 //If parent is a package or branch (maybe we're at version 0
01150                 //of a branch which wouldn't have an old-verions attribute),
01151                 //use the parent's old-version
01152                 ov = parent->getAttrib("old-version");
01153                 if(ov) r = ov;
01154         } else {
01155                 // Come up with something creative to put in here.
01156         }
01157 
01158         //And now we've done everything we can, clean up and return whatever we found.
01159         delete parent;
01160         if(ov) delete [] ov;
01161         if(version != vs) delete version;
01162         return r;
01163 }

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