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

vupdate.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 // Created on Mon Jul 14 13:58:43 PDT 1997 by heydon
00020 // Last modified on Tue Apr 26 02:07:36 EDT 2005 by ken@xorian.net   
00021 //      modified on Mon Nov 20 18:48:43 PST 2000 by mann   
00022 //      modified on Mon Jan 31 14:48:27 PST 2000 by heydon 
00023 //      modified on Fri Feb  6 17:17:48 PST 1998 by yuanyu 
00024 
00025 // vupdate(1) -- update the imports of a specified model
00026 
00027 
00028 #include <unistd.h>    // for getcwd(3), getpid(2)
00029 #include <stdio.h>     // for rename(2), sprintf(3)
00030 #include <ctype.h>     // for isdigit(3)
00031 #include <errno.h>
00032 
00033 #include <Basics.H>
00034 #include <sys/types.h> // for opendir(3), etc.
00035 #include <dirent.h>    // for opendir(3), etc.
00036 
00037 #include <FS.H>
00038 #include <Generics.H>
00039 #include <VestaConfig.H>
00040 #include <VestaSource.H>
00041 #include <UniqueId.H>
00042 #include <ParseImports.H>
00043 #include <ReposUI.H>
00044 
00045 using std::ostream;
00046 using std::ofstream;
00047 using std::ifstream;
00048 using std::cout;
00049 using std::cin;
00050 using std::cerr;
00051 using std::endl;
00052 
00053 // Possible values for the 'verbose' switch variable
00054 static const int Silent = 0;       // don't print anything
00055 static const int ChangesOnly = 1;  // only print models with updates (default)
00056 static const int Verbose = 2;      // print all models considered
00057 
00058 // Variables for the command-line switches
00059 static bool doWork = true;            // true iff -no-action switch NOT present
00060 static bool query = false;            // is -query switch present?
00061 static int verbose = ChangesOnly;     // is -silent or -verbose switch present?
00062 static bool update_local = false;     // is -update-local switch present?
00063 static bool update_co = false;       // is -update-checkout switch present?
00064 static bool advance_co = false;       // is -advance-checkout switch present?
00065 static bool onlyMine = false;         // is -only-mine switch present?
00066 static bool toCheckout = false;       // is -to-checkout switch present?
00067 static bool parse_errors_stop = false;
00068 static TextSeq limiters;              // arg(s) to -limit switch(es)
00069 static char *attrName = NULL;         // attribute name to match
00070 static TextSeq *attrVals = NULL;      // list of attribute values
00071 static bool attrMatchSense;           // true for ':', false for '^'
00072 
00073 static void Option(char *flag, char *meaning) throw ()
00074 {
00075     cerr << "  " << flag;
00076     int pad = max(0, 23 - strlen(flag));
00077     for (int i = 0; i < pad; i++) cerr << ' ';
00078     cerr << ' ' << meaning << endl;
00079 }
00080 
00081 static void Syntax(char *msg, char *arg = (char *)NULL) throw ()
00082 {
00083     cerr << "Error: " << msg;
00084     if (arg != (char *)NULL) cerr << ": '" << arg << "'";
00085     cerr << endl;
00086     cerr << "Syntax: vupdate [ options ] [ model ]" << endl;
00087     cerr << "Possible options are:" << endl;
00088     Option("-n or -no-action", "do not change any models");
00089     Option("-q or -query", "ask user about each update");
00090     Option("-s or -silent", "do not report updates");
00091     Option("-r or -update-local",
00092            "recurse on all models imported locally");
00093     Option("-c or -update-checkout",
00094            "recurse on all models imported from checkout sessions");
00095     Option("-u or -update-all", "set both -update-local and -update-checkout");
00096     Option("-a or -advance-checkout", "advance imported checkout sessions");
00097     Option("-v or -verbose", "report on all processed models");
00098     Option("-l or -limit substring",
00099            "only update imports containing 'substring'");
00100     Option("-L or -limit-checkout",
00101            "only update imports from checkout sessions");
00102     Option("-t or -to-checkout", "update to a more recent checkout session");
00103     Option("-m or -only-mine", "update a checkout import only if owned by me");
00104     Option("-A or -attr attr-spec",
00105            "only update to versions with given attributes");
00106     Option("-e or -parse-errors-stop",
00107            "stop if on import paths we can't understand (non-numeric arcs)");
00108     exit(1);
00109 }
00110 
00111 static void Indent(ostream &os, int n) throw ()
00112 {
00113     for (/*SKIP*/; n > 0; n--) os << ' ';
00114 }
00115 
00116 static void CopyFromTo(ifstream &ifs, ofstream &ofs, int loc = -1)
00117   throw (FS::Failure)
00118 /* Copy characters from "ifs" to "ofs" starting at the current character in
00119    "ifs" and up to (but not including) the character in "ifs" at position
00120    "loc". If "loc < 0", copy up to the end of "ifs". */
00121 {
00122     const int BuffSz = 1024;
00123     char buff[BuffSz];
00124     int toCopy = (loc >= 0) ? (loc - (int)FS::Posn(ifs)) : (-1);
00125     assert(loc < 0 || toCopy >= 0);
00126     try {
00127         while (loc < 0 || toCopy > 0) {
00128             int cnt = (loc < 0) ? BuffSz : min(BuffSz, toCopy);
00129             FS::Read(ifs, buff, cnt);
00130             FS::Write(ofs, buff, cnt);
00131             if (loc >= 0) toCopy -= cnt;
00132         }
00133     }
00134     catch (FS::EndOfFile) {
00135         // We should only encounter this exception if no definite stopping
00136         // point was provided.
00137         assert(loc < 0);
00138 
00139         // write the characters that were read
00140         FS::Write(ofs, buff, ifs.gcount());
00141     }
00142 }
00143 
00144 // name and VestaSource of repository's appendable root (/vesta)
00145 static const Text AppendableRootName = "/vesta/";
00146 static VestaSource *RepositoryRoot;
00147 
00148 // dummy class for initializing RepositoryRoot
00149 class VUpdateInit {
00150   public:
00151     VUpdateInit() throw () {
00152         RepositoryRoot = VestaSource::repositoryRoot();
00153     }
00154 };
00155 static VUpdateInit vupdateInit;
00156 
00157 static bool ImmutableDir(const Text &path) throw (SRPC::failure)
00158 /* Return "true" iff "path" is the absolute path of an immutable directory
00159    in the appendable root. */
00160 {
00161     if (path.FindText(AppendableRootName) != 0) return false;
00162     Text relPath(path.Sub(AppendableRootName.Length()));
00163     VestaSource *res = 0;
00164     VestaSource::errorCode err =
00165         RepositoryRoot->lookupPathname(relPath.cchars(), /*OUT*/ res);
00166     bool result = ((err == VestaSource::ok) &&
00167                    (res->type == VestaSource::immutableDirectory));
00168     if(res != 0)
00169       delete res;
00170     return result;
00171 }
00172 
00173 static void VersionDir(const Text &path, /*OUT*/ int &prefixLen,
00174   /*OUT*/ int &suffixStart, /*OUT*/ int &verNum,
00175   /*INOUT*/ bool &checkedIn, bool doStat)
00176   throw (ParseImports::Error, SRPC::failure)
00177 /* "path" is a (possibly relative) path of some model. Set "prefixLen" to
00178    the length of the prefix of "path" that is the name of the directory
00179    (ending with a '/') in which to search for the highest version of the
00180    package or checkout session of the model; "suffixStart" to the index
00181    of the first character of the suffix of "path" to be appended after
00182    the correct version (which, if non-empty, will start with '/'); and
00183    set "verNum" to the current version number. If "suffixStart" is set
00184    to "path.Length()", then the suffix is empty.
00185 
00186    Examples:
00187 
00188 |  path                          prefix                     suffix
00189 |  ----------------------------- ------------------------- --------------
00190 |  thread/3                      thread/                   <empty>
00191 |  thread/3/build.ves            thread/                   /build.ves
00192 |  thread/3/src/progs.ves        thread/                   /src/progs.ves
00193 |  thread/3.test/5               thread/3.test/            <empty>
00194 |  thread/3.test/5/build.ves     thread/3.test/            /build.ves
00195 |  thread/3.test/5/src/progs.ves thread/3.test/            /src/progs.ves
00196 |  thread/checkout/2/5/build.ves thread/checkout/2/        /build.ves
00197 |  thread/3.test/checkout/2/5    thread/3.test/checkout/2/ <empty>
00198 
00199    In the event that "path" names a checkout session, and if "checkedIn" is
00200    initially true or "doStat" is true and that checkout session has been
00201    checked in, then "prefixLen" will be set so the highest numbered version
00202    will be searched for on the main branch, and "checkedIn" will be set to
00203    "true". Otherwise, "checkedIn" will be unchanged. For example, if version
00204    2 of both the "thread" and "thread/3.test" packages have been checked in:
00205 
00206 |  path                          prefix                     suffix
00207 |  ----------------------------- ------------------------- --------------
00208 |  thread/checkout/2/5/build.ves thread/                   /build.ves
00209 |  thread/3.test/checkout/2/5    thread/3.test/            <empty>
00210 */
00211 {
00212     Text verTxt; // package sub-version number
00213 
00214     // first, see if "path" is a checkout version
00215     int len = path.Length();
00216     Text pattern("/checkout/");
00217     int ckout = path.FindText(pattern);
00218     if (ckout >= 0) {
00219         // skip first arc after "/checkout/"
00220         int ckoutEnd = ckout + pattern.Length();
00221         prefixLen = path.FindChar('/', ckoutEnd);
00222         if (prefixLen < 0) {
00223             Text msg("malformed checkout session name: '");
00224             msg += path; msg += "'";
00225             throw ParseImports::Error(msg);
00226         }
00227         Text ckoutVer(path.Sub(ckoutEnd, prefixLen - ckoutEnd));
00228         prefixLen++; // skip '/'
00229 
00230         // skip next arc for start of suffix
00231         // (and set 'verTxt' to name of next arc (package subversion))
00232         suffixStart = path.FindChar('/', prefixLen);
00233         if (suffixStart < 0) suffixStart = len;
00234         verTxt = path.Sub(prefixLen, suffixStart - prefixLen);
00235 
00236         // adjust "prefixLen" and "verTxt" if session has been checked in
00237         if (doStat) {
00238             Text dirName(path.Sub(0, ckout + 1) + ckoutVer);
00239             checkedIn = ImmutableDir(dirName);
00240         }
00241         if (checkedIn) {
00242             prefixLen = ckout + 1;
00243             verTxt = ckoutVer;
00244         }
00245     } else {
00246         // stop at first arc of "path" that consists entirely of digits
00247         prefixLen = 0;
00248         do {
00249             if (path[prefixLen] == '/') prefixLen++;
00250             int curr = prefixLen;
00251             while (curr < len && isdigit((int)(path[curr]))) curr++;
00252             if (curr >= len || path[curr] == '/') {
00253                 suffixStart = curr;
00254                 break;
00255             }
00256             prefixLen = path.FindChar('/', prefixLen);
00257         } while (prefixLen >= 0);
00258         if (prefixLen < 0 || prefixLen >= len) {
00259             Text msg("no numeric arc: '"); msg += path; msg += "'";
00260             throw ParseImports::Error(msg);
00261         }
00262         verTxt = path.Sub(prefixLen, suffixStart - prefixLen);
00263     }
00264     // convert "verTxt" to "verNum"
00265     if (sscanf(verTxt.chars(), "%d", &verNum) != 1) {
00266         // directory is not a number; give up...
00267         Text msg("version not numeric: '"); msg += verTxt; msg += "'";
00268         throw ParseImports::Error(msg);
00269     }
00270 } // end VersionDir
00271 
00272 static bool AllDigits(const char *str) throw ()
00273 /* Return "true" iff "str" is a string consisting of all digits. */
00274 {
00275     for (/*SKIP*/; *str != '\0'; str++) {
00276         if (!isdigit((int)(*str))) return false;
00277     }
00278     return true;
00279 }
00280 
00281 static bool AttribMatch(VestaSource *child) throw (SRPC::failure)
00282 /* Return true iff the attributes of 'child' match the '-attr' specification
00283    embodied by the global variables 'attrName', 'attrVals', and
00284    'attrMatchSense'. */
00285 {
00286   // does 'child' define attribute?
00287   const char *attrVal = child->getAttrib(attrName);
00288   if (attrVal != (char *)NULL) {
00289     delete [] attrVal;
00290     // were any attribute values specified?
00291     if (attrVals != (TextSeq *)NULL) {
00292       for (int i = 0; i < attrVals->size(); i++) {
00293         Text val = attrVals->get(i);
00294         if (child->inAttribs(attrName, val.cchars())) {
00295           return attrMatchSense;
00296         }
00297       }
00298       // there was no match on any value
00299       return !attrMatchSense;
00300     } else {
00301       // this is the '-attr attrName' case
00302       return true;
00303     }
00304   } else {
00305     // otherwise, there is a match iff this is the 'attr^vals' case
00306     return (attrVals != NULL && !attrMatchSense);
00307   }
00308 }
00309 
00310 // Structure type for 'ListCallback' closure argument
00311 typedef struct {
00312     int max;
00313     VestaSource *parent;
00314 } CallbackClosure;
00315 
00316 static bool ListCallback(void *closure, VestaSource::typeTag type,
00317   Arc arc, unsigned int index, Bit32 pseudoInode, ShortId filesid, bool master)
00318   throw (SRPC::failure)
00319 /* The 'closure' argument is actually a 'CallbackClosure *' that points
00320    to the largest version number seen so far. If 'type' is immutable
00321    directory and if 'arc' names a numeric directory whose value is larger
00322    than the current maximum, update the maximum value in 'closure'. */
00323 {
00324     CallbackClosure *cc = (CallbackClosure *)closure;
00325     if (type == VestaSource::immutableDirectory && AllDigits(arc)) {
00326         if (attrName != (char *)NULL) {
00327             // Fetch the 'VestaSource' object for 'index'
00328             VestaSource *child = 0;
00329             VestaSource::errorCode err =
00330               cc->parent->lookupIndex(index, /*OUT*/ child);
00331             bool no_good = (err != VestaSource::ok || !AttribMatch(child));
00332             if (child != NULL) delete child;
00333             if(no_good)
00334               return true;
00335         }
00336         int val;
00337         int scanRes = sscanf(arc, "%d", &val);
00338         assert(scanRes == 1);
00339         if (val > cc->max) cc->max = val;
00340     }
00341     return true;
00342 }
00343 
00344 static int HighestVersion(const Text &path)
00345   throw (ParseImports::Error, SRPC::failure)
00346 /* Return the numerical value of the name in the directory "dir" that
00347    consists of all digits, has the largest numerical value, and names
00348    a sub-directory (and hence, is not a ghost or stub). */
00349 {
00350     // verify that 'path' names an appendable directory, and get it
00351     if (path.FindText(AppendableRootName) != 0) {
00352         Text errMsg("imported path not in appendable root: ");
00353         errMsg += path;
00354         throw ParseImports::Error(errMsg);
00355     }
00356     Text relPath(path.Sub(AppendableRootName.Length()));
00357     VestaSource *dir = 0;
00358     VestaSource::errorCode err =
00359       RepositoryRoot->lookupPathname(relPath.cchars(), /*OUT*/ dir);
00360     if (err != VestaSource::ok) {
00361         Text errMsg("unable to open directory in import: ");
00362         errMsg += path;
00363         throw ParseImports::Error(errMsg);
00364     } else if (dir->type != VestaSource::appendableDirectory) {
00365         delete dir;
00366         Text errMsg("package import is not an appendable directory: ");
00367         errMsg += path;
00368         throw ParseImports::Error(errMsg);
00369     }
00370 
00371     assert(dir != 0);
00372 
00373     // iterate over the directory
00374     CallbackClosure cc;
00375     cc.max = -1;
00376     cc.parent = dir;
00377     err = dir->list(/*firstIndex=*/ 0, ListCallback, (void *)(&cc));
00378 
00379     // clean up
00380     delete dir;
00381     return cc.max;
00382 } // end HighestVersion
00383 
00384 static bool OthersCheckout(const Text &p_path)
00385 /* Return true iff the imported path 'p_path' is a checkout session
00386    belonging to someone else. */
00387 {
00388     // pattern to search for
00389     Text pattern("/checkout/");
00390 
00391     int ckout = p_path.FindText(pattern);
00392     int ckoutEnd = ckout + pattern.Length();
00393     int l_session_len;
00394     // Assume that this is either not a checkout session, or that it
00395     // must belong to us.
00396     bool result = false;
00397   
00398     if ((ckout >= 0) && // If we found "/checkout/"...
00399         // skip first arc after "/checkout/"
00400         ((l_session_len = p_path.FindChar('/', ckoutEnd)) >= 0))
00401     {
00402         Text l_session_path = p_path.Sub(0, l_session_len);
00403         // Look up the session
00404         VestaSource *l_session = NULL;
00405         try {
00406             l_session = ReposUI::filenameToVS(l_session_path);
00407         } catch (...) {
00408             // ReposUI::filenameToVS may throw an exception.  We need to
00409             // catch it if it does, but we really only care if we got
00410             // something.
00411         }
00412         // As long as we got it and it is a session...
00413         if ((l_session != NULL) && l_session->inAttribs("type", "session")) {
00414             // Get the global username
00415             Text l_uspec(AccessControl::self()->user());
00416 
00417             // Test if the session is checked out by someone who isn't
00418             // us.  (If it has no "checkout-by" attribute, assume it's
00419             // *not* ours.)
00420             char *l_checkout_user = l_session->getAttrib("checkout-by");
00421             if((l_checkout_user == 0) ||
00422                (strcmp(l_uspec.cchars(), l_checkout_user) != 0))
00423               result = true;
00424             if(l_checkout_user != 0)
00425               delete [] l_checkout_user;
00426         }
00427         if(l_session != 0)
00428           delete l_session;
00429     }
00430 
00431      return result;
00432 }
00433 
00434 static void Update(const Text &model, /*INOUT*/ TextSeq &localModels)
00435   throw (FS::DoesNotExist, FS::Failure, ParseImports::Error, 
00436          VestaConfig::failure, SRPC::failure);  // forward
00437 
00438 static void RecurseOnCheckout(const Text &p_path, const Text& suffix)
00439   throw (FS::DoesNotExist, FS::Failure, ParseImports::Error, 
00440          VestaConfig::failure, SRPC::failure)
00441 /* If p_path comes from a checkout session belonging to this user:
00442    If the -c flag was given, invoke vupdate recursively on the session.
00443    If the -a flag or -c flag was given, advance the session. */
00444 {
00445   if (!advance_co && !update_co) return;
00446 
00447   // pattern to search for
00448   Text pattern("/checkout/");
00449 
00450   int ckout = p_path.FindText(pattern);
00451   int ckoutEnd = ckout + pattern.Length();
00452   int l_session_len;
00453  
00454   // skip first arc after "/checkout/"
00455   if (ckout < 0 || (l_session_len = p_path.FindChar('/', ckoutEnd)) < 0) { 
00456     // Not a checkout import
00457     return;
00458   }
00459 
00460   Text l_session_path = p_path.Sub(0, l_session_len);
00461   // Look up the session
00462   VestaSource *l_session = NULL;
00463   try {
00464     l_session = ReposUI::filenameToVS(l_session_path);
00465   } catch (...) {
00466     // ReposUI::filenameToVS may throw an exception.  We need to
00467     // catch it if it does, but we really only care if we got
00468     // something.
00469   }
00470   if (l_session == NULL || !l_session->inAttribs("type", "session")) {
00471     // Not a session
00472     return;
00473   }
00474 
00475   // Get the global username
00476   Text l_uspec(AccessControl::self()->user());
00477   
00478   // Test if the session is checked out by someone who isn't us.  (If
00479   // it has no "checkout-by" attribute, assume it's *not* ours.)
00480   char *l_checkout_user = l_session->getAttrib("checkout-by");
00481   bool not_my_checkout = ((l_checkout_user == 0) ||
00482                           (strcmp(l_uspec.cchars(), l_checkout_user) != 0));
00483   if(l_checkout_user != 0)
00484     delete [] l_checkout_user;
00485   if(not_my_checkout)
00486     {
00487       delete l_session;
00488       return;
00489     }
00490 
00491   // Find working directory.
00492   char *l_workdir = l_session->getAttrib("work-dir");
00493   delete l_session;
00494   if (l_workdir == NULL) return;  // no workdir attribute
00495   // It's also possible that the session was non-exclusive, and the
00496   // working directory has since been deleted.  In this case, the
00497   // work-dir attribute exists, but the directory itself does not.
00498   // Return silently under this condition as well...
00499   if (!FS::IsDirectory(l_workdir))
00500     {
00501       if (verbose != Silent)
00502         cerr << "vupdate:  the working directory of checkout session "
00503              << p_path << " (" << l_workdir
00504              << ("), no longer exists!  "
00505                  "Skipping recursive updates for this import.")
00506              << endl;
00507       delete [] l_workdir;
00508       return;
00509     }
00510   
00511   // Construct model name in working directory
00512   Text w_path = Text(l_workdir) + (suffix.Empty() ? "/build.ves" : suffix);
00513 
00514   // OK to update it?
00515   if (update_co) {
00516     if (query) {
00517       cout << "  Update \"" << w_path << "\" (y/n)? ";
00518       cout.flush();
00519       const int BuffLen = 20; char buff[BuffLen];
00520       cin.getline(buff, BuffLen);
00521       if (buff[0] != 'y')
00522         {
00523           delete [] l_workdir;
00524           return;
00525         }
00526     }
00527 
00528     // Update the imported model
00529     TextSeq localModels(/*sizeHint=*/ 10);
00530     Update(w_path, /*INOUT*/ localModels);
00531     while (localModels.size() > 0) {
00532       // Update the imported model's local imports
00533       Text model = localModels.remlo();
00534       Update(model, /*INOUT*/ localModels);
00535     }
00536   }
00537 
00538   // Advance (by invoking a separate program).
00539   // If we get here, advance_co must be true.
00540   if (doWork) {
00541     Text cmd = Text("vadvance ") + l_workdir;
00542     system(cmd.cchars());
00543   }
00544   delete [] l_workdir;
00545 }
00546 
00547 static bool NewVersion(const Import *imp, /*OUT*/ Text &newPath,
00548   /*OUT*/ Text &toWrite)
00549   throw (FS::DoesNotExist, FS::Failure, ParseImports::Error, 
00550          VestaConfig::failure, SRPC::failure)
00551 /* Given the import "imp", return "true" iff the import needs to be
00552    updated. If "true" is returned, "newPath" is set to the complete
00553    path of the new import, and "toWrite" is set to the portion of
00554    "newPath" that should be written to the new model. If "false" is
00555    returned, the values of "newPath" and "toWrite" are undefined.
00556 
00557    In the event that "imp" refers to a model in a checkout session, and
00558    if the checkout session has since been checked in, "NewVersion" will
00559    return "true" and set "newPath" and "toWrite" so as to refer to the
00560    latest version along the checked-in branch. */
00561 {
00562     int prefixLen, suffixPos, verNum;
00563     bool checkedIn = false;
00564     char buff[20]; // for storing new version number
00565 
00566     // determine directory containing versions
00567     int pathLen = imp->path.Length();
00568     VersionDir(imp->path, /*OUT*/ prefixLen, /*OUT*/ suffixPos,
00569       /*OUT*/ verNum, /*INOUT*/ checkedIn, /*doStat=*/ true);
00570     Text verDir(imp->path.Sub(0, prefixLen));
00571     Text suffix(imp->path.Sub(suffixPos));
00572 
00573     // update and/or advance imported checkout session if needed
00574     if (!checkedIn) RecurseOnCheckout(imp->path, suffix);
00575 
00576     // determine the new version
00577     int newVerNum = HighestVersion(verDir);
00578 
00579     // check for version in model that is too large
00580     if (verNum > newVerNum) {
00581         // If the '-attr' switch was used, it's quite possible that there
00582         // was no match, or that the match was to a smaller version number
00583         // than the existing import. In that case, simply return 'false'
00584         // to indicate that the import should not be updated.
00585         if (attrName != (char *)NULL) return false;
00586 
00587         // Otherwise, the existing import names a non-existant version
00588         // number, so report an error.
00589         Text msg("version number exceeds largest existing version:\n  ");
00590         msg += imp->path;
00591         throw ParseImports::Error(msg);
00592     }
00593 
00594     // ----------------------------------------------------------------------
00595     // Determine whether it has been checked out again by looking for
00596     // a new version stub.  This will fail if the path we'd be
00597     // updating to is still in a checkout session (because there
00598     // aren't ever any version stubs in checkout sessions).
00599     bool newest_is_checkout = false;
00600     Text l_session_path;
00601     int l_session_ver_num;
00602     if (toCheckout) {
00603         int printRes = sprintf(buff, "%d", newVerNum + 1);
00604         assert(printRes > 0);
00605         Text newVerTxt(buff);
00606         // Build the path of the stub
00607         Text l_stub_path = verDir + newVerTxt;
00608         // Look up the stub, if it exists
00609         VestaSource *l_stub = 0;
00610         // ReposUI::filenameToVS may throw an exception.  We need to
00611         // catch it if it does, but we really only care if we got
00612         // something.
00613         try
00614           {
00615             l_stub = ReposUI::filenameToVS(l_stub_path);
00616           }
00617         catch (...) { }
00618         if (l_stub && l_stub->type == VestaSource::stub) {
00619           const char *l_session_dir = l_stub->getAttrib("session-dir");
00620           if(l_session_dir != NULL)
00621             {
00622               l_session_path = l_session_dir + Text('/');
00623               // If we don't care about who this checkout belongs to
00624               // or it belongs to us, we'll update to this. Also, we
00625               // have to make sure that the session path starts with
00626               // the right prefix, otherwise we won't be able to munge
00627               // the model text in place.
00628               if ((!onlyMine || !OthersCheckout(l_session_path)) &&
00629                   (l_session_path.Sub(0, prefixLen) == verDir))
00630                 {
00631                   newest_is_checkout = true;
00632                   // Update/advance imported checkout session if
00633                   // needed.
00634                   RecurseOnCheckout(l_session_path, suffix);
00635                   l_session_ver_num = HighestVersion(l_session_path);
00636                 }
00637               delete [] l_session_dir;
00638             }
00639         }
00640         if(l_stub != 0)
00641           delete l_stub;
00642     }
00643 
00644     // return if the highest version is already specified
00645     if (newVerNum == verNum && !checkedIn && !newest_is_checkout) {
00646         return false;
00647     }
00648     assert((verNum < newVerNum && !checkedIn)
00649            || (verNum <= newVerNum && checkedIn)
00650            || newest_is_checkout);
00651 
00652     Text newVerTxt;
00653     if (newest_is_checkout) {
00654         // format "l_session_ver_num" as a text
00655         int printRes = sprintf(buff, "%d", l_session_ver_num);
00656         assert(printRes > 0);
00657 
00658         // The new version is everything after the prefix in the
00659         // session path, plus a path separator and the session version
00660         // number.
00661         newVerTxt = l_session_path.Sub(prefixLen) + buff;
00662 
00663         // set "newPath"
00664         newPath = verDir + newVerTxt + imp->path.Sub(suffixPos);
00665     } else {
00666         // format "newVerNum" as a text
00667         int printRes = sprintf(buff, "%d", newVerNum); assert(printRes > 0);
00668         newVerTxt = buff;
00669 
00670         // set "newPath"
00671         newPath = verDir + newVerTxt + imp->path.Sub(suffixPos);
00672 
00673         // If the user said "-only-mine", and we would be updating a
00674         // checkout session reference belonging to another user,
00675         // report that we don't have an update to make.
00676         if (onlyMine && OthersCheckout(newPath)) return false;
00677     }
00678 
00679     // set "toWrite"
00680     VersionDir(imp->orig, /*OUT*/ prefixLen, /*OUT*/ suffixPos,
00681       /*OUT*/ verNum, /*INOUT*/ checkedIn, /*doStat=*/ false);
00682     toWrite = imp->orig.Sub(0, prefixLen) +newVerTxt+ imp->orig.Sub(suffixPos);
00683     return true;
00684 } // end NewVersion
00685 
00686 static void RenameTempFile(const Text &temp, const Text &orig)
00687   throw (FS::DoesNotExist, FS::Failure)
00688 {
00689     // first, try using rename(2)
00690     if (rename(temp.cchars(), orig.cchars()) != 0) {
00691         if (errno == EXDEV) {
00692             // in event of "cross-device link" error, copy instead
00693             ifstream ifs;
00694             ofstream ofs;
00695             FS::OpenReadOnly(temp, /*OUT*/ ifs);
00696             FS::OpenForWriting(orig, /*OUT*/ ofs);
00697             CopyFromTo(ifs, ofs);
00698             FS::Close(ifs);
00699             FS::Close(ofs);
00700 
00701             // delete temp file
00702             (void)unlink(temp.cchars());
00703         } else {
00704             throw FS::Failure("rename(2)", temp + " " + orig);
00705         }
00706     }
00707 } // RenameTempFile
00708 
00709 static Text TempSuffix()
00710 {
00711   char pidBuff[40];
00712   FP::Tag uid = UniqueId();
00713   int sres = sprintf(pidBuff, "-%016" FORMAT_LENGTH_INT_64 "x-%016" FORMAT_LENGTH_INT_64 "x~",
00714                      uid.Word0(), uid.Word1());
00715   assert(sres > 0);
00716   assert(sres < sizeof(pidBuff));
00717 
00718   return pidBuff;
00719 }
00720 
00721 /* Return the name of a temporary file, and open "ofs" on that file. This
00722    function guarantees that a unique filename is chosen across an entire
00723    Vesta site by using the "UniqueId" interface. */
00724 static Text OpenTempFile(/*OUT*/ ofstream &ofs) throw (FS::Failure)
00725 {
00726     char pidBuff[40];
00727     Text prefix = (VestaConfig::get_Text("UserInterface", "TempDir") +
00728                    "/vup");
00729     // Pick a unique temporary filename
00730     Text res = FS::TempFname(prefix, TempSuffix);
00731     // Open it.
00732     FS::OpenForWriting(res, /*OUT*/ ofs);
00733     // Return the filename.
00734     return res;
00735 }
00736 
00737 static bool LimiterMatch(const Text &p_path) throw ()
00738 /* Return true iff 'p_path' contains all of the strings in the command-line
00739    'limiters' as substrings. */
00740 {
00741     for (int i = 0; i < limiters.size(); i++) {
00742         if (p_path.FindText(limiters.get(i)) < 0) return false;
00743     }
00744     return true;
00745 }
00746 
00747 static void Update(const Text &model, /*INOUT*/ TextSeq &localModels)
00748   throw (FS::DoesNotExist, FS::Failure, ParseImports::Error, 
00749          VestaConfig::failure, SRPC::failure)
00750 /* Update the model named "model", which is expected to be an absolute path.
00751    If "doWork" is "true", then the model file is rewritten on disk. If "query"
00752    is true, then "doWork" is required to be true, and the program prompts the
00753    user on each query. If "verbose != Quiet", then messages are printed
00754    indicating the old/new versions of any updated imports. If a model does
00755    not have any updates, its name is printed only if "verbose == Verbose."
00756    As a side-effect (to support recursive updates), the names of any
00757    local models imported by "model" are appended to "localModels".
00758 
00759    Implementation: This function takes care of reading and parsing the
00760    input model, communicating update information to the user, and
00761    writing the revised model. The work of determining which non-local
00762    models require updating and the path to update to is handled by the
00763    NewVersion function above. */
00764 {
00765     ImportSeq imports(/*sizehint=*/ 10);
00766     ParseImports::P(model, /*INOUT*/ imports);
00767 
00768     // open input file and temporary output file
00769     ifstream ifs;
00770     ofstream ofs;
00771     Text newModel;
00772     if (doWork) {
00773         FS::OpenReadOnly(model, /*OUT*/ ifs);
00774         newModel = OpenTempFile(/*OUT*/ ofs);
00775     }
00776 
00777     try {
00778         // write new version
00779         bool modelPrinted = false;
00780         if (verbose == Verbose) {
00781             cout << model << endl;
00782             modelPrinted = true;
00783         }
00784         bool modelChanged = false; // monotonically increasing only
00785         while (imports.size() > 0) {
00786             // skip local imports
00787             Import imp(*(imports.getlo()));
00788             delete imports.remlo();
00789             if (imp.local) {
00790                 localModels.addhi(imp.path);
00791                 continue;
00792             }
00793 
00794             // skip imports preceeded by "noupdate" pragma
00795             if (imp.noUpdate) continue;
00796 
00797             // skip imports that don't match the limiting strings (if any)
00798             if (!LimiterMatch(imp.path)) continue;
00799 
00800             // copy part of input file if necessary
00801             if (doWork) {
00802                 CopyFromTo(ifs, ofs, imp.start);
00803                 FS::Seek(ifs, imp.end);
00804             }
00805 
00806             try
00807               {
00808                 // determine replacement for "imp.path"
00809                 Text newPath, pathToWrite;
00810                 if (NewVersion(&imp, /*OUT*/ newPath, /*OUT*/ pathToWrite)) {
00811                   // write report if necessary
00812                   if (verbose != Silent) {
00813                     if (!modelPrinted) {
00814                       cout << model << endl;
00815                       modelPrinted = true;
00816                     }
00817                     cout << "     " << imp.path << endl;
00818                     cout << "  -> " << newPath << endl;
00819                   }
00820 
00821                   // write new path
00822                   if (doWork) {
00823                     bool useNewVersion = true;
00824                     if (query) {
00825                       cout << "  Update this import (y/n)? "; cout.flush();
00826                       const int BuffLen = 20; char buff[BuffLen];
00827                       cin.getline(buff, BuffLen);
00828                       useNewVersion = (buff[0] == 'y');
00829                     }
00830                     ofs << (useNewVersion ? pathToWrite : imp.orig);
00831 
00832                     // note change was made
00833                     if (useNewVersion) modelChanged = true;
00834                   }
00835                   //if (verbose != Silent) cout << endl;
00836                 } else {
00837                   // copy original path
00838                   if (doWork) ofs << imp.orig;
00839                 }
00840               }
00841             // In the event of the kind of exception thrown by
00842             // VersionDir...
00843             catch (const ParseImports::Error &err)
00844               {
00845                 // If these should be fatal, re-throw the exception.
00846                 if(parse_errors_stop)
00847                   {
00848                     throw;
00849                   }
00850                 else
00851                   {
00852                     // Report the parsing problem
00853                     cerr << "vupdate: " << err << endl;
00854 
00855                     // copy original path
00856                     if (doWork) ofs << imp.orig;
00857                   }
00858               }
00859             // Any othe exceptions we just pass on
00860             catch(...)
00861               {
00862                 throw;
00863               }
00864         }
00865         if (doWork) {
00866             // copy the tail portion of the file
00867             if (modelChanged) CopyFromTo(ifs, ofs);
00868 
00869             // close files and rename or delete temp file
00870             FS::Close(ifs);
00871             FS::Close(ofs);
00872             if (modelChanged) {
00873                 RenameTempFile(newModel, model);
00874             } else {
00875                 // delete temp file
00876                 (void)unlink(newModel.cchars());
00877             }
00878         }
00879     }
00880     catch (...) {
00881         // in the event of a FS exception, delete the temp file
00882         (void)unlink(newModel.cchars());
00883         // Free any remaining Import objects.
00884         while (imports.size() > 0)
00885           delete imports.remlo();
00886         throw;
00887     }
00888 } // Update
00889 
00890 void CheckExclusive(/*INOUT*/ bool &exclusive) throw ()
00891 /* If "exclusive" is already "true", invoke "Syntax" with the correct error
00892    message. Otherwise, set "exclusive" to "true". */
00893 {
00894     if (exclusive) {
00895         Syntax("more than one mutually exclusive argument specified");
00896     }
00897     exclusive = true;
00898 }
00899 
00900 
00901 int main(int argc, char *argv[]) 
00902 {
00903     // parse command-line
00904     bool nqsExclusive = false, svExclusive = false;
00905     int arg;
00906     for (arg = 1; arg < argc && *argv[arg] == '-'; arg++) {
00907         char *argp = argv[arg];
00908         if (strncmp(argp, "--", 2) == 0) argp++;
00909         if (strcmp(argp, "-no-action") == 0 ||
00910             strcmp(argp, "-n") == 0) {
00911             CheckExclusive(/*INOUT*/ nqsExclusive);
00912             doWork = false;
00913         } else if (strcmp(argp, "-query") == 0 ||
00914                    strcmp(argp, "-q") == 0) {
00915             CheckExclusive(/*INOUT*/ nqsExclusive);
00916             query = true;
00917         } else if (strcmp(argp, "-silent") == 0 ||
00918                    strcmp(argp, "-s") == 0) {
00919             CheckExclusive(/*INOUT*/ nqsExclusive);
00920             CheckExclusive(/*INOUT*/ svExclusive);
00921             verbose = Silent;
00922         } else if (strcmp(argp, "-update-local") == 0 ||
00923                    strcmp(argp, "-r") == 0) {
00924             update_local = true;
00925         } else if (strcmp(argp, "-update-checkout") == 0 ||
00926                    strcmp(argp, "-c") == 0) {
00927             update_co = true;
00928             advance_co = true;
00929         } else if (strcmp(argp, "-update-all") == 0 ||
00930                    strcmp(argp, "-u") == 0) {
00931             update_local = true;
00932             update_co = true;
00933             advance_co = true;
00934         } else if (strcmp(argp, "-verbose") == 0 ||
00935                    strcmp(argp, "-v") == 0) {
00936             CheckExclusive(/*INOUT*/ svExclusive);
00937             verbose = Verbose;
00938         } else if (strcmp(argp, "-advance-checkout") == 0 ||
00939                    strcmp(argp, "-a") == 0) {
00940             advance_co = true;
00941         } else if (strcmp(argp, "-limit") == 0 ||
00942                    strcmp(argp, "-l") == 0) {
00943             // Move up one argument to get the substring argument
00944             arg++;
00945             // As long as we haven't run out of arguments...
00946             if (arg < argc) {
00947                 // Add this string to the list of limiters.
00948                 limiters.addhi(Text(argv[arg]));
00949             } else {
00950                 // Complain that the user didn't give us an argument
00951                 Syntax("-limit requires a string argument");
00952             }
00953         } else if (strcmp(argp, "-limit-checkout") == 0 ||
00954                    strcmp(argp, "-L") == 0) {
00955             // Add the string "/checkout/" to the list of limiters.
00956             // (Sort of a hack, but the vupdate documentation already
00957             // says it makes this assumption about checkout directories.)
00958             limiters.addhi("/checkout/");
00959         } else if (strcmp(argp, "-only-mine") == 0 ||
00960                    strcmp(argp, "-m") == 0) {
00961             onlyMine = true;
00962         } else if (strcmp(argp, "-to-checkout") == 0 ||
00963                    strcmp(argp, "-t") == 0) {
00964             toCheckout = true;
00965         } else if (strcmp(argp, "-attr") == 0 ||
00966                    strcmp(argp, "-A") == 0) {
00967             // Move up one argument to get the substring argument
00968             arg++;
00969             // As long as we haven't run out of arguments...
00970             if (arg < argc) {
00971                 // read attribute name
00972                 char *colon = index(argv[arg], ':');
00973                 char *circum = index(argv[arg], '^');
00974                 if (colon != (char *)NULL && circum != (char *)NULL) {
00975                     Syntax("illegal argument to '-attr' switch");
00976                 }
00977                 char *nextVal = (char *)NULL;
00978                 if (colon != (char *)NULL) {
00979                     *colon = '\0';
00980                     attrMatchSense = true;
00981                     nextVal = colon + 1;
00982                 } else if (circum != (char *)NULL) {
00983                     *circum = '\0';
00984                     attrMatchSense = false;
00985                     nextVal = circum + 1;
00986                 }
00987                 attrName = argv[arg];
00988 
00989                 // parse attribute values (if any)
00990                 if (nextVal != NULL) {
00991                     attrVals = NEW(TextSeq);
00992                     char *lastChar = nextVal + strlen(nextVal);
00993                     *lastChar = ','; // sentinel
00994                     while (nextVal <= lastChar) {
00995                         char *end = index(nextVal, ',');
00996                         *end++ = '\0'; // replace ',' with '\0'; advance
00997                         attrVals->addhi(Text(nextVal));
00998                         nextVal = end;
00999                     }
01000                 }
01001             } else {
01002                 // Complain that the user didn't give us an argument
01003                 Syntax("-attr requires an attr-spec argument");
01004             }
01005         } else if (strcmp(argp, "-parse-errors-stop") == 0 ||
01006                    strcmp(argp, "-e") == 0) {
01007             parse_errors_stop = true;
01008         } else {
01009             Syntax("illegal argument", argv[arg]);
01010         }
01011     }
01012     if (toCheckout && attrName != (char *)NULL) {
01013         Syntax("-to-checkout and -attr cannot both be specified");
01014     }
01015     if (arg < argc - 1) Syntax("too many arguments");
01016     Text model((arg < argc) ? argv[arg] : ".main.ves");
01017 
01018     // get the working directory
01019     char wd_buff[PATH_MAX+1];
01020     char *res = getcwd(wd_buff, PATH_MAX+1); assert(res != (char *)NULL);
01021     res = strcat(wd_buff, "/"); assert(res != (char *)NULL);
01022 
01023     // scan the model
01024     try {
01025         // update the specified model
01026         TextSeq localModels(/*sizeHint=*/ 10);
01027         Update(ParseImports::ResolvePath(model, Text(wd_buff)),
01028           /*INOUT*/ localModels);
01029 
01030         // if "-r" was specified, update imported local models also
01031         if (update_local) {
01032             while (localModels.size() > 0) {
01033                 model = localModels.remlo();
01034                 Update(model, /*INOUT*/ localModels);
01035             }
01036         }
01037     } catch (FS::DoesNotExist) {
01038         cerr << "vupdate: model file does not exist" << endl;
01039         exit(2);
01040     } catch (const FS::Failure &f) {
01041         cerr << "vupdate: " << f << endl;
01042         exit(2);
01043     } catch (const ParseImports::Error &err) {
01044         cerr << "vupdate: " << err << endl;
01045         exit(2);
01046     } catch (const VestaConfig::failure &f) {
01047         cerr << "vupdate: " << f.msg << endl;
01048         exit(2);
01049     } catch (const SRPC::failure &f) {
01050         cerr << "vupdate: error contacting Vesta repository: " << f.msg <<endl;
01051         exit(2);
01052     }
01053 
01054     // print warning message if "-n" specified
01055     if (!doWork && verbose) {
01056         cout << "WARNING: At your request, nothing was updated!" << endl;
01057     }
01058     exit(0);
01059 }

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