diff --git a/corepkgs/buildenv/builder.pl.in b/corepkgs/buildenv/builder.pl.in index b5f4f0c7b..e2258783f 100755 --- a/corepkgs/buildenv/builder.pl.in +++ b/corepkgs/buildenv/builder.pl.in @@ -99,10 +99,15 @@ sub createLinks { print "STATELINK $srcFile to $dstFile \n"; my $new_dstFile; - if($pkgStateIdentifier eq "__EMTPY__") - { $new_dstFile = $dstFile; } - else - { $new_dstFile = "$dstFile-$pkgStateIdentifier"; } + my $new_stateIdentifier; + if($pkgStateIdentifier eq "__EMTPY__"){ + $new_dstFile = $dstFile; + $new_stateIdentifier = ""; + } + else{ + $new_dstFile = "$dstFile-$pkgStateIdentifier"; + $new_stateIdentifier = $pkgStateIdentifier; + } if (-l $new_dstFile) { if (!$ignoreCollisions) { @@ -111,9 +116,22 @@ sub createLinks { } } + # if only my GF were this dirty ... + # TODO ... sysopen (DSTFILEHANDLE, $new_dstFile, O_RDWR|O_EXCL|O_CREAT, 0755); printf DSTFILEHANDLE "#! @shell@ \n"; - printf DSTFILEHANDLE "/nixstate/nix/bin/nix-state --run --identifier=$pkgStateIdentifier \$* $srcFile \n"; + printf DSTFILEHANDLE "args2=( ) \n"; + printf DSTFILEHANDLE "for arg in \"\$@\" \n"; + printf DSTFILEHANDLE "do \n"; + printf DSTFILEHANDLE " if [ \"\$arg\" = \"--help\" ]; then \n"; + printf DSTFILEHANDLE " args2=( \"\${args2[@]}\" \"\\--help\" ) \n"; + printf DSTFILEHANDLE " elif [ \"\$arg\" = \"--version\" ]; then \n"; + printf DSTFILEHANDLE " args2=( \"\${args2[@]}\" \"\\--version\" ) \n"; + printf DSTFILEHANDLE " else \n"; + printf DSTFILEHANDLE " args2=( \"\${args2[@]}\" \"\$arg\" ) \n"; + printf DSTFILEHANDLE " fi \n"; + printf DSTFILEHANDLE "done \n"; + printf DSTFILEHANDLE "/nixstate/nix/bin/nix-state --run --identifier=$new_stateIdentifier $srcFile \"\${args2[@]}\" \n"; #TODO !!!!!!!!!! fix hard link close (DSTFILEHANDLE); } } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index f33f90ce2..8049f5f75 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -88,10 +88,9 @@ static void initAndRun(int argc, char * * argv) /* Setup Nix paths. */ nixStore = canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR))); nixStoreState = canonPath(getEnv("NIX_STORE_STATE_DIR", NIX_STORE_STATE_DIR)); //store state dir usually /nix/state - nixStoreStateRepos = canonPath(getEnv("NIX_STORE_STATE_REPOS_DIR", NIX_STORE_STATE_REPOS_DIR)); //store state dir usually /nix/state nixDataDir = canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR)); nixLogDir = canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR)); - nixStateDir = canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)); //nix global state dir + nixStateDir = canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)); //nix global state dir nixDBPath = getEnv("NIX_DB_DIR", nixStateDir + "/db"); nixSVNPath = getEnv("NIX_SVN_BIN_DIR", NIX_SVN_BIN_DIR); nixConfDir = canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR)); diff --git a/src/libstore/db.cc b/src/libstore/db.cc index 9a280c3b0..6ec74019c 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -658,7 +658,10 @@ void Database::setStateRevisions(const Transaction & txn, TableId revisions_tabl //save the date and comments Strings metadata; metadata.push_back(int2String(ts)); - if(statePath == rootStatePath) + + //get all paths that point to the same state (using shareing) and check if one of them equals the rootStatePath + PathSet sharedWith = getSharedWithPathSetRecTxn(txn, statePath); + if(statePath == rootStatePath || sharedWith.find(rootStatePath) != sharedWith.end()) metadata.push_back(comment); else metadata.push_back("Part of the snashot closure for " + rootStatePath); diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 2e65df36d..a39d6e0cc 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -10,7 +10,6 @@ namespace nix { string nixStore = "/UNINIT"; string nixStoreState = "/UNINIT"; -string nixStoreStateRepos = "/UNINIT"; string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; string nixStateDir = "/UNINIT"; diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 75cfe83ce..bb4c4019b 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -21,9 +21,6 @@ extern string nixLogDir; /* nixStoreState is the directory where the state dirs of the components are stored. */ extern string nixStoreState; -/* nixStoreState is the directory where the repositorys of the state dirs of the components are stored. */ -extern string nixStoreStateRepos; - /* nixStateDir is the directory where state is stored. */ extern string nixStateDir; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index fdfed2004..37303a951 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -1776,6 +1776,48 @@ void setStateStateReferencesTxn(const Transaction & txn, const Path & statePath, nixDB.setStateReferences(txn, dbStateStateReferences, dbStateRevisions, statePath, references, revision, timestamp); } +//Lookups which statePaths directy share (point to) statePath +PathSet getSharedWithPathSetTxn(const Transaction & txn, const Path & statePath) +{ + PathSet statePaths; + + //enumtable + Strings keys; + nixDB.enumTable(txn, dbSharedState, keys); + + for (Strings::iterator i = keys.begin(); i != keys.end(); ++i){ + Path shared_with; + nixDB.queryString(txn, dbSharedState, *i, shared_with); + if (shared_with == statePath) + statePaths.insert(*i); + } + + return statePaths; +} + +PathSet getSharedWithPathSetRecTxn_private(const Transaction & txn, const Path & statePath, PathSet & statePaths) +{ + //Get all paths pointing to statePath + PathSet newStatePaths = getSharedWithPathSetTxn(txn, statePath); + + //go into recursion to see if there are more paths indirectly pointing to statePath + for (PathSet::iterator i = newStatePaths.begin(); i != newStatePaths.end(); ++i) + //Only recurse on the really new statePaths to prevent infinite recursion + if(statePaths.find(*i) == statePaths.end()) + statePaths = pathSets_union(statePaths, getSharedWithPathSetRecTxn_private(txn, *i, statePaths)); + + return statePaths; +} + +//Note: Also returns statePath in the PathSet +PathSet getSharedWithPathSetRecTxn(const Transaction & txn, const Path & statePath) +{ + //To no shared state path + Path statePath_ns = toNonSharedPathTxn(txn, statePath); + PathSet empty; + return getSharedWithPathSetRecTxn_private(txn, statePath_ns, empty); +} + /* Upgrade from schema 1 (Nix <= 0.7) to schema 2 (Nix >= 0.8). */ static void upgradeStore07() diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 3447c6a1a..ef1b7d770 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -242,6 +242,7 @@ bool querySolidStateReferencesTxn(const Transaction & txn, const Path & statePat void setSharedStateTxn(const Transaction & txn, const Path & fromExisting, const Path & toNew); PathSet toNonSharedPathSetTxn(const Transaction & txn, const PathSet & statePaths); Path toNonSharedPathTxn(const Transaction & txn, const Path & statePath); +PathSet getSharedWithPathSetRecTxn(const Transaction & txn, const Path & statePath); } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index bcedffd12..86d526afa 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -557,7 +557,7 @@ static void installDerivations(Globals & globals, toBeShared[oldStatePath] = newStatePath; } else{ //If not equal, then we do not replace, so we push back (just like the else branch) - printMsg(lvlError, format("Installing new state-component component '%1%' with identifier '%2%'") % drvName.name % oldStateIdentifier); + printMsg(lvlError, format("Installing new state-component component '%1%' with identifier '%2%'") % drvName.name % newStateIdentifier); allElems.push_back(*i); } } diff --git a/src/nix-state/help.txt b/src/nix-state/help.txt index f818366ab..a217ec83c 100644 --- a/src/nix-state/help.txt +++ b/src/nix-state/help.txt @@ -4,8 +4,6 @@ Usage: nix-state [OPTIONS...] [ARGUMENTS...] Operations: - --realise / -r: ensure path validity; if a derivation, ensure that validity of the outputs - --version: output version information --help: display help diff --git a/src/nix-state/help.txt.hh b/src/nix-state/help.txt.hh index 5671798ca..f1bfd717b 100644 --- a/src/nix-state/help.txt.hh +++ b/src/nix-state/help.txt.hh @@ -1 +1 @@ -static unsigned char helpText[] = {0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x6e, 0x69, 0x78, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x5b, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x2e, 0x2e, 0x2e, 0x5d, 0x20, 0x5b, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x53, 0x2e, 0x2e, 0x2e, 0x5d, 0x0a, 0x0a, 0x60, 0x6e, 0x69, 0x78, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x27, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x70, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4e, 0x69, 0x78, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x0a, 0x0a, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x20, 0x2f, 0x20, 0x2d, 0x72, 0x3a, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x3b, 0x20, 0x69, 0x66, 0x20, 0x61, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x0a, 0x0a, 0x20, 0x20, 0x2d, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x2d, 0x2d, 0x68, 0x65, 0x6c, 0x70, 0x3a, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x0a, }; +static unsigned char helpText[] = {0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x6e, 0x69, 0x78, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x5b, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x2e, 0x2e, 0x2e, 0x5d, 0x20, 0x5b, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x53, 0x2e, 0x2e, 0x2e, 0x5d, 0x0a, 0x0a, 0x60, 0x6e, 0x69, 0x78, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x27, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x70, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4e, 0x69, 0x78, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x0a, 0x0a, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x2d, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x2d, 0x2d, 0x68, 0x65, 0x6c, 0x70, 0x3a, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x0a, }; diff --git a/src/nix-state/nix-state.cc b/src/nix-state/nix-state.cc index 23f8deaaf..d164c1450 100644 --- a/src/nix-state/nix-state.cc +++ b/src/nix-state/nix-state.cc @@ -13,6 +13,7 @@ #include "derivations.hh" #include "references.hh" #include "store-state.hh" +#include "config.h" using namespace nix; using std::cin; @@ -46,8 +47,7 @@ void printHelp() cout << string((char *) helpText, sizeof helpText); } - -Derivation getDerivation(const string & fullPath, const string & program_args, string state_identifier, Path & componentPath, Path & statePath, +Derivation getDerivation(const string & fullPath, const Strings & program_args, string state_identifier, Path & componentPath, Path & statePath, string & binary, string & derivationPath, bool isStateComponent, bool getDerivers, PathSet & derivers) //optional { @@ -94,20 +94,26 @@ Derivation getDerivation(const string & fullPath, const string & program_args, s //Wrapper Derivation getDerivation_andCheckArgs_(Strings opFlags, Strings opArgs, Path & componentPath, Path & statePath, - string & binary, string & derivationPath, bool isStateComponent, string & program_args, + string & binary, string & derivationPath, bool isStateComponent, Strings & program_args, bool getDerivers, PathSet & derivers) //optional { if (!opFlags.empty()) throw UsageError("unknown flag"); - if ( opArgs.size() != 1 && opArgs.size() != 2 ) - throw UsageError("only one or two arguments allowed component path and program arguments (counts as one) "); + if ( opArgs.size() < 1) + throw UsageError("you must specify at least the component path (optional are the program arguments wrapped like this \"$@\")"); - string fullPath = opArgs.front(); if(opArgs.size() > 1){ + opArgs.pop_front(); - program_args = opArgs.front(); - //Strings progam_args_strings = tokenizeString(program_args, " "); + for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i){ + string arg = *i; + if(arg == "\\--help" || arg == "\\--version") + arg = arg.substr(1, arg.length()); + + //printMsg(lvlError, format("Args: %1%") % arg); + program_args.push_back(arg); + } } return getDerivation(fullPath, program_args, stateIdentifier, componentPath, statePath, binary, derivationPath, isStateComponent, getDerivers, derivers); @@ -115,7 +121,7 @@ Derivation getDerivation_andCheckArgs_(Strings opFlags, Strings opArgs, Path & c //Wrapper Derivation getDerivation_andCheckArgs(Strings opFlags, Strings opArgs, Path & componentPath, Path & statePath, - string & binary, string & derivationPath, bool & isStateComponent, string & program_args) + string & binary, string & derivationPath, bool & isStateComponent, Strings & program_args) { PathSet empty; return getDerivation_andCheckArgs_(opFlags, opArgs, componentPath, statePath, binary, derivationPath, isStateComponent, program_args, false, empty); @@ -130,7 +136,7 @@ static void opShowDerivations(Strings opFlags, Strings opArgs) PathSet derivers; string derivationPath; bool isStateComponent; - string program_args; + Strings program_args; Derivation drv = getDerivation_andCheckArgs_(opFlags, opArgs, componentPath, statePath, binary, derivationPath, isStateComponent, program_args, true, derivers); if(!isStateComponent) @@ -149,7 +155,7 @@ static void opShowStatePath(Strings opFlags, Strings opArgs) string binary; string derivationPath; bool isStateComponent; - string program_args; + Strings program_args; Derivation drv = getDerivation_andCheckArgs(opFlags, opArgs, componentPath, statePath, binary, derivationPath, isStateComponent, program_args); if(!isStateComponent) @@ -187,10 +193,17 @@ static void queryAvailableStateRevisions(Strings opFlags, Strings opArgs) string binary; string derivationPath; bool isStateComponent; - string program_args; + Strings program_args; Derivation drv = getDerivation_andCheckArgs(opFlags, opArgs, componentPath, statePath, binary, derivationPath, isStateComponent, program_args); } + //Unshare if neccacary + Path nonSharedStatePath = toNonSharedPathTxn(noTxn, statePath); + if(nonSharedStatePath != statePath){ + printMsg(lvlError, format("The statePath is shared with this path %1%") % nonSharedStatePath); + statePath = nonSharedStatePath; + } + RevisionInfos revisions; bool notEmpty = store->queryAvailableStateRevisions(statePath, revisions); @@ -222,8 +235,6 @@ static void queryAvailableStateRevisions(Strings opFlags, Strings opArgs) human_date.erase(human_date.find("\n",0),1); //remove newline string comment = revisions[rev].comment; - - if(trim(comment) != "") printMsg(lvlError, format("Rev. %1% @ %2% (%3%) -- %4%") % rev_s % human_date % ts % comment); else @@ -238,7 +249,7 @@ static void revertToRevision(Strings opFlags, Strings opArgs) string binary; string derivationPath; bool isStateComponent; - string program_args; + Strings program_args; Derivation drv = getDerivation_andCheckArgs(opFlags, opArgs, componentPath, statePath, binary, derivationPath, isStateComponent, program_args); bool recursive = revert_recursively; @@ -462,10 +473,10 @@ static void opRunComponent(Strings opFlags, Strings opArgs) string root_binary; string root_derivationPath; bool root_isStateComponent; - string root_program_args; + Strings root_program_args; Derivation root_drv = getDerivation_andCheckArgs(opFlags, opArgs, root_componentPath, root_statePath, root_binary, root_derivationPath, root_isStateComponent, root_program_args); - printMsg(lvlError, format("compp: '%1%'\nstatep: '%2%'\nbinary: '%3%'\ndrv:'%4%'") % root_componentPath % root_statePath % root_binary % root_derivationPath); + //printMsg(lvlError, format("compp: '%1%'\nstatep: '%2%'\nbinary: '%3%'\ndrv:'%4%'") % root_componentPath % root_statePath % root_binary % root_derivationPath); //TODO //Check for locks ... ? or put locks on the neseccary state components @@ -485,8 +496,20 @@ static void opRunComponent(Strings opFlags, Strings opArgs) if(!only_commit){ if( ! FileExist(root_componentPath + root_binary) ) throw Error(format("You must specify the full binary path: '%1%'") % (root_componentPath + root_binary)); - - executeShellCommand(root_componentPath + root_binary + " " + root_program_args); + + string root_args = ""; + for (Strings::iterator i = root_program_args.begin(); i != root_program_args.end(); ++i){ + + if(*i == "--help") + printMsg(lvlInfo, format("%1%\n------------------------------------------------------------") % helpText); + if(*i == "--version") + printMsg(lvlInfo, format("%1% (Nix) %2%\n------------------------------------------------------------") % programId % NIX_VERSION ); + + root_args += " \"" + *i + "\""; + } + + //printMsg(lvlError, format("Command: '%1%'") % (root_componentPath + root_binary + root_args)); + executeShellCommand(root_componentPath + root_binary + root_args); } //******************* Scan for new references if neccecary @@ -659,9 +682,19 @@ void run(Strings args) printMsg(lvlError, format("NOW: '%1%'") % IsDirectory("/nix/store/65c7p6c8j0vy6b8fjgq84zziiavswqha-hellohardcodedstateworld-1.0/bin/hello") ); printMsg(lvlError, format("NOW: '%1%'") % FileExist("/nix/store/65c7p6c8j0vy6b8fjgq8") ); printMsg(lvlError, format("NOW: '%1%'") % IsDirectory("/nix/store/65c7p6c8j0vy6b8fjg") ); + + store = openStore(); + // /nix/state/g8vby0bjfrs85qpf1jfajrcrmlawn442-hellohardcodedstateworld-1.0- + // /nix/state/6l93ff3bn1mk61jbdd34diafmb4aq7c6-hellohardcodedstateworld-1.0- + // /nix/state/x8k4xiv8m4zmx26gmb0pyymmd6671fyy-hellohardcodedstateworld-1.0- + + PathSet p = getSharedWithPathSetRecTxn(noTxn, "/nix/state/6l93ff3bn1mk61jbdd34diafmb4aq7c6-hellohardcodedstateworld-1.0-"); + for (PathSet::iterator j = p.begin(); j != p.end(); ++j) + printMsg(lvlError, format("P: '%1%'") % *j ); return; - */ + + // */ @@ -733,7 +766,8 @@ void run(Strings args) else opArgs.push_back(arg); - if (oldOp && oldOp != op) + //in the startscript u can have --run, but could do showrevisions + if (oldOp && oldOp != op && oldOp != opRunComponent) throw UsageError("only one operation may be specified"); }