1 #include "attr-path.hh"
2 #include "common-eval-args.hh"
3 #include "derivations.hh"
4 #include "eval.hh"
5 #include "get-drvs.hh"
6 #include "globals.hh"
7 #include "names.hh"
8 #include "profiles.hh"
9 #include "shared.hh"
10 #include "store-api.hh"
11 #include "user-env.hh"
12 #include "util.hh"
13 #include "json.hh"
14 #include "value-to-json.hh"
15 #include "xml-writer.hh"
16 #include "legacy.hh"
17 
18 #include <cerrno>
19 #include <ctime>
20 #include <algorithm>
21 #include <iostream>
22 #include <sstream>
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 
28 
29 using namespace nix;
30 using std::cout;
31 
32 
33 typedef enum {
34     srcNixExprDrvs,
35     srcNixExprs,
36     srcStorePaths,
37     srcProfile,
38     srcAttrPath,
39     srcUnknown
40 } InstallSourceType;
41 
42 
43 struct InstallSourceInfo
44 {
45     InstallSourceType type;
46     Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
47     Path profile; /* for srcProfile */
48     string systemFilter; /* for srcNixExprDrvs */
49     Bindings * autoArgs;
50 };
51 
52 
53 struct Globals
54 {
55     InstallSourceInfo instSource;
56     Path profile;
57     std::shared_ptr<EvalState> state;
58     bool dryRun;
59     bool preserveInstalled;
60     bool removeAll;
61     string forceName;
62     bool prebuiltOnly;
63 };
64 
65 
66 typedef void (* Operation) (Globals & globals,
67     Strings opFlags, Strings opArgs);
68 
69 
needArg(Strings::iterator & i,Strings & args,const string & arg)70 static string needArg(Strings::iterator & i,
71     Strings & args, const string & arg)
72 {
73     if (i == args.end()) throw UsageError(
74         format("'%1%' requires an argument") % arg);
75     return *i++;
76 }
77 
78 
parseInstallSourceOptions(Globals & globals,Strings::iterator & i,Strings & args,const string & arg)79 static bool parseInstallSourceOptions(Globals & globals,
80     Strings::iterator & i, Strings & args, const string & arg)
81 {
82     if (arg == "--from-expression" || arg == "-E")
83         globals.instSource.type = srcNixExprs;
84     else if (arg == "--from-profile") {
85         globals.instSource.type = srcProfile;
86         globals.instSource.profile = needArg(i, args, arg);
87     }
88     else if (arg == "--attr" || arg == "-A")
89         globals.instSource.type = srcAttrPath;
90     else return false;
91     return true;
92 }
93 
94 
isNixExpr(const Path & path,struct stat & st)95 static bool isNixExpr(const Path & path, struct stat & st)
96 {
97     return S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && pathExists(path + "/default.nix"));
98 }
99 
100 
getAllExprs(EvalState & state,const Path & path,StringSet & attrs,Value & v)101 static void getAllExprs(EvalState & state,
102     const Path & path, StringSet & attrs, Value & v)
103 {
104     StringSet namesSorted;
105     for (auto & i : readDirectory(path)) namesSorted.insert(i.name);
106 
107     for (auto & i : namesSorted) {
108         /* Ignore the manifest.nix used by profiles.  This is
109            necessary to prevent it from showing up in channels (which
110            are implemented using profiles). */
111         if (i == "manifest.nix") continue;
112 
113         Path path2 = path + "/" + i;
114 
115         struct stat st;
116         if (stat(path2.c_str(), &st) == -1)
117             continue; // ignore dangling symlinks in ~/.nix-defexpr
118 
119         if (isNixExpr(path2, st) && (!S_ISREG(st.st_mode) || hasSuffix(path2, ".nix"))) {
120             /* Strip off the `.nix' filename suffix (if applicable),
121                otherwise the attribute cannot be selected with the
122                `-A' option.  Useful if you want to stick a Nix
123                expression directly in ~/.nix-defexpr. */
124             string attrName = i;
125             if (hasSuffix(attrName, ".nix"))
126                 attrName = string(attrName, 0, attrName.size() - 4);
127             if (attrs.find(attrName) != attrs.end()) {
128                 printError(format("warning: name collision in input Nix expressions, skipping '%1%'") % path2);
129                 continue;
130             }
131             attrs.insert(attrName);
132             /* Load the expression on demand. */
133             Value & vFun = state.getBuiltin("import");
134             Value & vArg(*state.allocValue());
135             mkString(vArg, path2);
136             if (v.attrs->size() == v.attrs->capacity())
137                 throw Error(format("too many Nix expressions in directory '%1%'") % path);
138             mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
139         }
140         else if (S_ISDIR(st.st_mode))
141             /* `path2' is a directory (with no default.nix in it);
142                recurse into it. */
143             getAllExprs(state, path2, attrs, v);
144     }
145 }
146 
147 
loadSourceExpr(EvalState & state,const Path & path,Value & v)148 static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
149 {
150     struct stat st;
151     if (stat(path.c_str(), &st) == -1)
152         throw SysError(format("getting information about '%1%'") % path);
153 
154     if (isNixExpr(path, st))
155         state.evalFile(path, v);
156 
157     /* The path is a directory.  Put the Nix expressions in the
158        directory in a set, with the file name of each expression as
159        the attribute name.  Recurse into subdirectories (but keep the
160        set flat, not nested, to make it easier for a user to have a
161        ~/.nix-defexpr directory that includes some system-wide
162        directory). */
163     else if (S_ISDIR(st.st_mode)) {
164         state.mkAttrs(v, 1024);
165         state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0);
166         StringSet attrs;
167         getAllExprs(state, path, attrs, v);
168         v.attrs->sort();
169     }
170 
171     else throw Error("path '%s' is not a directory or a Nix expression", path);
172 }
173 
174 
loadDerivations(EvalState & state,Path nixExprPath,string systemFilter,Bindings & autoArgs,const string & pathPrefix,DrvInfos & elems)175 static void loadDerivations(EvalState & state, Path nixExprPath,
176     string systemFilter, Bindings & autoArgs,
177     const string & pathPrefix, DrvInfos & elems)
178 {
179     Value vRoot;
180     loadSourceExpr(state, nixExprPath, vRoot);
181 
182     Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
183 
184     getDerivations(state, v, pathPrefix, autoArgs, elems, true);
185 
186     /* Filter out all derivations not applicable to the current
187        system. */
188     for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
189         j = i; j++;
190         if (systemFilter != "*" && i->querySystem() != systemFilter)
191             elems.erase(i);
192     }
193 }
194 
195 
getPriority(EvalState & state,DrvInfo & drv)196 static long getPriority(EvalState & state, DrvInfo & drv)
197 {
198     return drv.queryMetaInt("priority", 0);
199 }
200 
201 
comparePriorities(EvalState & state,DrvInfo & drv1,DrvInfo & drv2)202 static long comparePriorities(EvalState & state, DrvInfo & drv1, DrvInfo & drv2)
203 {
204     return getPriority(state, drv2) - getPriority(state, drv1);
205 }
206 
207 
208 // FIXME: this function is rather slow since it checks a single path
209 // at a time.
isPrebuilt(EvalState & state,DrvInfo & elem)210 static bool isPrebuilt(EvalState & state, DrvInfo & elem)
211 {
212     Path path = elem.queryOutPath();
213     if (state.store->isValidPath(path)) return true;
214     PathSet ps = state.store->querySubstitutablePaths({path});
215     return ps.find(path) != ps.end();
216 }
217 
218 
checkSelectorUse(DrvNames & selectors)219 static void checkSelectorUse(DrvNames & selectors)
220 {
221     /* Check that all selectors have been used. */
222     for (auto & i : selectors)
223         if (i.hits == 0 && i.fullName != "*")
224             throw Error(format("selector '%1%' matches no derivations") % i.fullName);
225 }
226 
227 
filterBySelector(EvalState & state,const DrvInfos & allElems,const Strings & args,bool newestOnly)228 static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
229     const Strings & args, bool newestOnly)
230 {
231     DrvNames selectors = drvNamesFromArgs(args);
232     if (selectors.empty())
233         selectors.push_back(DrvName("*"));
234 
235     DrvInfos elems;
236     set<unsigned int> done;
237 
238     for (auto & i : selectors) {
239         typedef list<std::pair<DrvInfo, unsigned int> > Matches;
240         Matches matches;
241         unsigned int n = 0;
242         for (DrvInfos::const_iterator j = allElems.begin();
243              j != allElems.end(); ++j, ++n)
244         {
245             DrvName drvName(j->queryName());
246             if (i.matches(drvName)) {
247                 i.hits++;
248                 matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
249             }
250         }
251 
252         /* If `newestOnly', if a selector matches multiple derivations
253            with the same name, pick the one matching the current
254            system.  If there are still multiple derivations, pick the
255            one with the highest priority.  If there are still multiple
256            derivations, pick the one with the highest version.
257            Finally, if there are still multiple derivations,
258            arbitrarily pick the first one. */
259         if (newestOnly) {
260 
261             /* Map from package names to derivations. */
262             typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
263             Newest newest;
264             StringSet multiple;
265 
266             for (auto & j : matches) {
267                 DrvName drvName(j.first.queryName());
268                 long d = 1;
269 
270                 Newest::iterator k = newest.find(drvName.name);
271 
272                 if (k != newest.end()) {
273                     d = j.first.querySystem() == k->second.first.querySystem() ? 0 :
274                         j.first.querySystem() == settings.thisSystem ? 1 :
275                         k->second.first.querySystem() == settings.thisSystem ? -1 : 0;
276                     if (d == 0)
277                         d = comparePriorities(state, j.first, k->second.first);
278                     if (d == 0)
279                         d = compareVersions(drvName.version, DrvName(k->second.first.queryName()).version);
280                 }
281 
282                 if (d > 0) {
283                     newest.erase(drvName.name);
284                     newest.insert(Newest::value_type(drvName.name, j));
285                     multiple.erase(j.first.queryName());
286                 } else if (d == 0) {
287                     multiple.insert(j.first.queryName());
288                 }
289             }
290 
291             matches.clear();
292             for (auto & j : newest) {
293                 if (multiple.find(j.second.first.queryName()) != multiple.end())
294                     printInfo(
295                         "warning: there are multiple derivations named '%1%'; using the first one",
296                         j.second.first.queryName());
297                 matches.push_back(j.second);
298             }
299         }
300 
301         /* Insert only those elements in the final list that we
302            haven't inserted before. */
303         for (auto & j : matches)
304             if (done.find(j.second) == done.end()) {
305                 done.insert(j.second);
306                 elems.push_back(j.first);
307             }
308     }
309 
310     checkSelectorUse(selectors);
311 
312     return elems;
313 }
314 
315 
isPath(const string & s)316 static bool isPath(const string & s)
317 {
318     return s.find('/') != string::npos;
319 }
320 
321 
queryInstSources(EvalState & state,InstallSourceInfo & instSource,const Strings & args,DrvInfos & elems,bool newestOnly)322 static void queryInstSources(EvalState & state,
323     InstallSourceInfo & instSource, const Strings & args,
324     DrvInfos & elems, bool newestOnly)
325 {
326     InstallSourceType type = instSource.type;
327     if (type == srcUnknown && args.size() > 0 && isPath(args.front()))
328         type = srcStorePaths;
329 
330     switch (type) {
331 
332         /* Get the available user environment elements from the
333            derivations specified in a Nix expression, including only
334            those with names matching any of the names in `args'. */
335         case srcUnknown:
336         case srcNixExprDrvs: {
337 
338             /* Load the derivations from the (default or specified)
339                Nix expression. */
340             DrvInfos allElems;
341             loadDerivations(state, instSource.nixExprPath,
342                 instSource.systemFilter, *instSource.autoArgs, "", allElems);
343 
344             elems = filterBySelector(state, allElems, args, newestOnly);
345 
346             break;
347         }
348 
349         /* Get the available user environment elements from the Nix
350            expressions specified on the command line; these should be
351            functions that take the default Nix expression file as
352            argument, e.g., if the file is `./foo.nix', then the
353            argument `x: x.bar' is equivalent to `(x: x.bar)
354            (import ./foo.nix)' = `(import ./foo.nix).bar'. */
355         case srcNixExprs: {
356 
357             Value vArg;
358             loadSourceExpr(state, instSource.nixExprPath, vArg);
359 
360             for (auto & i : args) {
361                 Expr * eFun = state.parseExprFromString(i, absPath("."));
362                 Value vFun, vTmp;
363                 state.eval(eFun, vFun);
364                 mkApp(vTmp, vFun, vArg);
365                 getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
366             }
367 
368             break;
369         }
370 
371         /* The available user environment elements are specified as a
372            list of store paths (which may or may not be
373            derivations). */
374         case srcStorePaths: {
375 
376             for (auto & i : args) {
377                 Path path = state.store->followLinksToStorePath(i);
378 
379                 string name = baseNameOf(path);
380                 string::size_type dash = name.find('-');
381                 if (dash != string::npos)
382                     name = string(name, dash + 1);
383 
384                 DrvInfo elem(state, "", nullptr);
385                 elem.setName(name);
386 
387                 if (isDerivation(path)) {
388                     elem.setDrvPath(path);
389                     elem.setOutPath(state.store->derivationFromPath(path).findOutput("out"));
390                     if (name.size() >= drvExtension.size() &&
391                         string(name, name.size() - drvExtension.size()) == drvExtension)
392                         name = string(name, 0, name.size() - drvExtension.size());
393                 }
394                 else elem.setOutPath(path);
395 
396                 elems.push_back(elem);
397             }
398 
399             break;
400         }
401 
402         /* Get the available user environment elements from another
403            user environment.  These are then filtered as in the
404            `srcNixExprDrvs' case. */
405         case srcProfile: {
406             elems = filterBySelector(state,
407                 queryInstalled(state, instSource.profile),
408                 args, newestOnly);
409             break;
410         }
411 
412         case srcAttrPath: {
413             Value vRoot;
414             loadSourceExpr(state, instSource.nixExprPath, vRoot);
415             for (auto & i : args) {
416                 Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot));
417                 getDerivations(state, v, "", *instSource.autoArgs, elems, true);
418             }
419             break;
420         }
421     }
422 }
423 
424 
printMissing(EvalState & state,DrvInfos & elems)425 static void printMissing(EvalState & state, DrvInfos & elems)
426 {
427     PathSet targets;
428     for (auto & i : elems) {
429         Path drvPath = i.queryDrvPath();
430         if (drvPath != "")
431             targets.insert(drvPath);
432         else
433             targets.insert(i.queryOutPath());
434     }
435 
436     printMissing(state.store, targets);
437 }
438 
439 
keep(DrvInfo & drv)440 static bool keep(DrvInfo & drv)
441 {
442     return drv.queryMetaBool("keep", false);
443 }
444 
445 
installDerivations(Globals & globals,const Strings & args,const Path & profile)446 static void installDerivations(Globals & globals,
447     const Strings & args, const Path & profile)
448 {
449     debug(format("installing derivations"));
450 
451     /* Get the set of user environment elements to be installed. */
452     DrvInfos newElems, newElemsTmp;
453     queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true);
454 
455     /* If --prebuilt-only is given, filter out source-only packages. */
456     for (auto & i : newElemsTmp)
457         if (!globals.prebuiltOnly || isPrebuilt(*globals.state, i))
458             newElems.push_back(i);
459 
460     StringSet newNames;
461     for (auto & i : newElems) {
462         /* `forceName' is a hack to get package names right in some
463            one-click installs, namely those where the name used in the
464            path is not the one we want (e.g., `java-front' versus
465            `java-front-0.9pre15899'). */
466         if (globals.forceName != "")
467             i.setName(globals.forceName);
468         newNames.insert(DrvName(i.queryName()).name);
469     }
470 
471 
472     while (true) {
473         string lockToken = optimisticLockProfile(profile);
474 
475         DrvInfos allElems(newElems);
476 
477         /* Add in the already installed derivations, unless they have
478            the same name as a to-be-installed element. */
479         if (!globals.removeAll) {
480             DrvInfos installedElems = queryInstalled(*globals.state, profile);
481 
482             for (auto & i : installedElems) {
483                 DrvName drvName(i.queryName());
484                 if (!globals.preserveInstalled &&
485                     newNames.find(drvName.name) != newNames.end() &&
486                     !keep(i))
487                     printInfo("replacing old '%s'", i.queryName());
488                 else
489                     allElems.push_back(i);
490             }
491 
492             for (auto & i : newElems)
493                 printInfo("installing '%s'", i.queryName());
494         }
495 
496         printMissing(*globals.state, newElems);
497 
498         if (globals.dryRun) return;
499 
500         if (createUserEnv(*globals.state, allElems,
501                 profile, settings.envKeepDerivations, lockToken)) break;
502     }
503 }
504 
505 
opInstall(Globals & globals,Strings opFlags,Strings opArgs)506 static void opInstall(Globals & globals, Strings opFlags, Strings opArgs)
507 {
508     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
509         string arg = *i++;
510         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
511         else if (arg == "--preserve-installed" || arg == "-P")
512             globals.preserveInstalled = true;
513         else if (arg == "--remove-all" || arg == "-r")
514             globals.removeAll = true;
515         else throw UsageError(format("unknown flag '%1%'") % arg);
516     }
517 
518     installDerivations(globals, opArgs, globals.profile);
519 }
520 
521 
522 typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType;
523 
524 
upgradeDerivations(Globals & globals,const Strings & args,UpgradeType upgradeType)525 static void upgradeDerivations(Globals & globals,
526     const Strings & args, UpgradeType upgradeType)
527 {
528     debug(format("upgrading derivations"));
529 
530     /* Upgrade works as follows: we take all currently installed
531        derivations, and for any derivation matching any selector, look
532        for a derivation in the input Nix expression that has the same
533        name and a higher version number. */
534 
535     while (true) {
536         string lockToken = optimisticLockProfile(globals.profile);
537 
538         DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
539 
540         /* Fetch all derivations from the input file. */
541         DrvInfos availElems;
542         queryInstSources(*globals.state, globals.instSource, args, availElems, false);
543 
544         /* Go through all installed derivations. */
545         DrvInfos newElems;
546         for (auto & i : installedElems) {
547             DrvName drvName(i.queryName());
548 
549             try {
550 
551                 if (keep(i)) {
552                     newElems.push_back(i);
553                     continue;
554                 }
555 
556                 /* Find the derivation in the input Nix expression
557                    with the same name that satisfies the version
558                    constraints specified by upgradeType.  If there are
559                    multiple matches, take the one with the highest
560                    priority.  If there are still multiple matches,
561                    take the one with the highest version.
562                    Do not upgrade if it would decrease the priority. */
563                 DrvInfos::iterator bestElem = availElems.end();
564                 string bestVersion;
565                 for (auto j = availElems.begin(); j != availElems.end(); ++j) {
566                     if (comparePriorities(*globals.state, i, *j) > 0)
567                         continue;
568                     DrvName newName(j->queryName());
569                     if (newName.name == drvName.name) {
570                         int d = compareVersions(drvName.version, newName.version);
571                         if ((upgradeType == utLt && d < 0) ||
572                             (upgradeType == utLeq && d <= 0) ||
573                             (upgradeType == utEq && d == 0) ||
574                             upgradeType == utAlways)
575                         {
576                             long d2 = -1;
577                             if (bestElem != availElems.end()) {
578                                 d2 = comparePriorities(*globals.state, *bestElem, *j);
579                                 if (d2 == 0) d2 = compareVersions(bestVersion, newName.version);
580                             }
581                             if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) {
582                                 bestElem = j;
583                                 bestVersion = newName.version;
584                             }
585                         }
586                     }
587                 }
588 
589                 if (bestElem != availElems.end() &&
590                     i.queryOutPath() !=
591                     bestElem->queryOutPath())
592                 {
593                     const char * action = compareVersions(drvName.version, bestVersion) <= 0
594                         ? "upgrading" : "downgrading";
595                     printInfo("%1% '%2%' to '%3%'",
596                         action, i.queryName(), bestElem->queryName());
597                     newElems.push_back(*bestElem);
598                 } else newElems.push_back(i);
599 
600             } catch (Error & e) {
601                 e.addPrefix(fmt("while trying to find an upgrade for '%s':\n", i.queryName()));
602                 throw;
603             }
604         }
605 
606         printMissing(*globals.state, newElems);
607 
608         if (globals.dryRun) return;
609 
610         if (createUserEnv(*globals.state, newElems,
611                 globals.profile, settings.envKeepDerivations, lockToken)) break;
612     }
613 }
614 
615 
opUpgrade(Globals & globals,Strings opFlags,Strings opArgs)616 static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
617 {
618     UpgradeType upgradeType = utLt;
619     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
620         string arg = *i++;
621         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
622         else if (arg == "--lt") upgradeType = utLt;
623         else if (arg == "--leq") upgradeType = utLeq;
624         else if (arg == "--eq") upgradeType = utEq;
625         else if (arg == "--always") upgradeType = utAlways;
626         else throw UsageError(format("unknown flag '%1%'") % arg);
627     }
628 
629     upgradeDerivations(globals, opArgs, upgradeType);
630 }
631 
632 
setMetaFlag(EvalState & state,DrvInfo & drv,const string & name,const string & value)633 static void setMetaFlag(EvalState & state, DrvInfo & drv,
634     const string & name, const string & value)
635 {
636     Value * v = state.allocValue();
637     mkString(*v, value.c_str());
638     drv.setMeta(name, v);
639 }
640 
641 
opSetFlag(Globals & globals,Strings opFlags,Strings opArgs)642 static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
643 {
644     if (opFlags.size() > 0)
645         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
646     if (opArgs.size() < 2)
647         throw UsageError("not enough arguments to '--set-flag'");
648 
649     Strings::iterator arg = opArgs.begin();
650     string flagName = *arg++;
651     string flagValue = *arg++;
652     DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
653 
654     while (true) {
655         string lockToken = optimisticLockProfile(globals.profile);
656 
657         DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
658 
659         /* Update all matching derivations. */
660         for (auto & i : installedElems) {
661             DrvName drvName(i.queryName());
662             for (auto & j : selectors)
663                 if (j.matches(drvName)) {
664                     printInfo("setting flag on '%1%'", i.queryName());
665                     j.hits++;
666                     setMetaFlag(*globals.state, i, flagName, flagValue);
667                     break;
668                 }
669         }
670 
671         checkSelectorUse(selectors);
672 
673         /* Write the new user environment. */
674         if (createUserEnv(*globals.state, installedElems,
675                 globals.profile, settings.envKeepDerivations, lockToken)) break;
676     }
677 }
678 
679 
opSet(Globals & globals,Strings opFlags,Strings opArgs)680 static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
681 {
682     auto store2 = globals.state->store.dynamic_pointer_cast<LocalFSStore>();
683     if (!store2) throw Error("--set is not supported for this Nix store");
684 
685     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
686         string arg = *i++;
687         if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
688         else throw UsageError(format("unknown flag '%1%'") % arg);
689     }
690 
691     DrvInfos elems;
692     queryInstSources(*globals.state, globals.instSource, opArgs, elems, true);
693 
694     if (elems.size() != 1)
695         throw Error("--set requires exactly one derivation");
696 
697     DrvInfo & drv(elems.front());
698 
699     if (globals.forceName != "")
700         drv.setName(globals.forceName);
701 
702     if (drv.queryDrvPath() != "") {
703         PathSet paths = {drv.queryDrvPath()};
704         printMissing(globals.state->store, paths);
705         if (globals.dryRun) return;
706         globals.state->store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal);
707     }
708     else {
709         printMissing(globals.state->store, {drv.queryOutPath()});
710         if (globals.dryRun) return;
711         globals.state->store->ensurePath(drv.queryOutPath());
712     }
713 
714     debug(format("switching to new user environment"));
715     Path generation = createGeneration(ref<LocalFSStore>(store2), globals.profile, drv.queryOutPath());
716     switchLink(globals.profile, generation);
717 }
718 
719 
uninstallDerivations(Globals & globals,Strings & selectors,Path & profile)720 static void uninstallDerivations(Globals & globals, Strings & selectors,
721     Path & profile)
722 {
723     while (true) {
724         string lockToken = optimisticLockProfile(profile);
725 
726         DrvInfos installedElems = queryInstalled(*globals.state, profile);
727         DrvInfos newElems;
728 
729         for (auto & i : installedElems) {
730             DrvName drvName(i.queryName());
731             bool found = false;
732             for (auto & j : selectors)
733                 /* !!! the repeated calls to followLinksToStorePath()
734                    are expensive, should pre-compute them. */
735                 if ((isPath(j) && i.queryOutPath() == globals.state->store->followLinksToStorePath(j))
736                     || DrvName(j).matches(drvName))
737                 {
738                     printInfo("uninstalling '%s'", i.queryName());
739                     found = true;
740                     break;
741                 }
742             if (!found) newElems.push_back(i);
743         }
744 
745         if (globals.dryRun) return;
746 
747         if (createUserEnv(*globals.state, newElems,
748                 profile, settings.envKeepDerivations, lockToken)) break;
749     }
750 }
751 
752 
opUninstall(Globals & globals,Strings opFlags,Strings opArgs)753 static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
754 {
755     if (opFlags.size() > 0)
756         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
757     uninstallDerivations(globals, opArgs, globals.profile);
758 }
759 
760 
cmpChars(char a,char b)761 static bool cmpChars(char a, char b)
762 {
763     return toupper(a) < toupper(b);
764 }
765 
766 
cmpElemByName(const DrvInfo & a,const DrvInfo & b)767 static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
768 {
769     auto a_name = a.queryName();
770     auto b_name = b.queryName();
771     return lexicographical_compare(
772         a_name.begin(), a_name.end(),
773         b_name.begin(), b_name.end(), cmpChars);
774 }
775 
776 
777 typedef list<Strings> Table;
778 
779 
printTable(Table & table)780 void printTable(Table & table)
781 {
782     auto nrColumns = table.size() > 0 ? table.front().size() : 0;
783 
784     vector<size_t> widths;
785     widths.resize(nrColumns);
786 
787     for (auto & i : table) {
788         assert(i.size() == nrColumns);
789         Strings::iterator j;
790         size_t column;
791         for (j = i.begin(), column = 0; j != i.end(); ++j, ++column)
792             if (j->size() > widths[column]) widths[column] = j->size();
793     }
794 
795     for (auto & i : table) {
796         Strings::iterator j;
797         size_t column;
798         for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
799             string s = *j;
800             replace(s.begin(), s.end(), '\n', ' ');
801             cout << s;
802             if (column < nrColumns - 1)
803                 cout << string(widths[column] - s.size() + 2, ' ');
804         }
805         cout << std::endl;
806     }
807 }
808 
809 
810 /* This function compares the version of an element against the
811    versions in the given set of elements.  `cvLess' means that only
812    lower versions are in the set, `cvEqual' means that at most an
813    equal version is in the set, and `cvGreater' means that there is at
814    least one element with a higher version in the set.  `cvUnavail'
815    means that there are no elements with the same name in the set. */
816 
817 typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
818 
compareVersionAgainstSet(const DrvInfo & elem,const DrvInfos & elems,string & version)819 static VersionDiff compareVersionAgainstSet(
820     const DrvInfo & elem, const DrvInfos & elems, string & version)
821 {
822     DrvName name(elem.queryName());
823 
824     VersionDiff diff = cvUnavail;
825     version = "?";
826 
827     for (auto & i : elems) {
828         DrvName name2(i.queryName());
829         if (name.name == name2.name) {
830             int d = compareVersions(name.version, name2.version);
831             if (d < 0) {
832                 diff = cvGreater;
833                 version = name2.version;
834             }
835             else if (diff != cvGreater && d == 0) {
836                 diff = cvEqual;
837                 version = name2.version;
838             }
839             else if (diff != cvGreater && diff != cvEqual && d > 0) {
840                 diff = cvLess;
841                 if (version == "" || compareVersions(version, name2.version) < 0)
842                     version = name2.version;
843             }
844         }
845     }
846 
847     return diff;
848 }
849 
850 
queryJSON(Globals & globals,vector<DrvInfo> & elems)851 static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
852 {
853     JSONObject topObj(cout, true);
854     for (auto & i : elems) {
855         JSONObject pkgObj = topObj.object(i.attrPath);
856 
857         auto drvName = DrvName(i.queryName());
858         pkgObj.attr("name", drvName.fullName);
859         pkgObj.attr("pname", drvName.name);
860         pkgObj.attr("version", drvName.version);
861         pkgObj.attr("system", i.querySystem());
862 
863         JSONObject metaObj = pkgObj.object("meta");
864         StringSet metaNames = i.queryMetaNames();
865         for (auto & j : metaNames) {
866             auto placeholder = metaObj.placeholder(j);
867             Value * v = i.queryMeta(j);
868             if (!v) {
869                 printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
870                 placeholder.write(nullptr);
871             } else {
872                 PathSet context;
873                 printValueAsJSON(*globals.state, true, *v, placeholder, context);
874             }
875         }
876     }
877 }
878 
879 
opQuery(Globals & globals,Strings opFlags,Strings opArgs)880 static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
881 {
882     Strings remaining;
883     string attrPath;
884 
885     bool printStatus = false;
886     bool printName = true;
887     bool printAttrPath = false;
888     bool printSystem = false;
889     bool printDrvPath = false;
890     bool printOutPath = false;
891     bool printDescription = false;
892     bool printMeta = false;
893     bool compareVersions = false;
894     bool xmlOutput = false;
895     bool jsonOutput = false;
896 
897     enum { sInstalled, sAvailable } source = sInstalled;
898 
899     settings.readOnlyMode = true; /* makes evaluation a bit faster */
900 
901     for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
902         string arg = *i++;
903         if (arg == "--status" || arg == "-s") printStatus = true;
904         else if (arg == "--no-name") printName = false;
905         else if (arg == "--system") printSystem = true;
906         else if (arg == "--description") printDescription = true;
907         else if (arg == "--compare-versions" || arg == "-c") compareVersions = true;
908         else if (arg == "--drv-path") printDrvPath = true;
909         else if (arg == "--out-path") printOutPath = true;
910         else if (arg == "--meta") printMeta = true;
911         else if (arg == "--installed") source = sInstalled;
912         else if (arg == "--available" || arg == "-a") source = sAvailable;
913         else if (arg == "--xml") xmlOutput = true;
914         else if (arg == "--json") jsonOutput = true;
915         else if (arg == "--attr-path" || arg == "-P") printAttrPath = true;
916         else if (arg == "--attr" || arg == "-A")
917             attrPath = needArg(i, opFlags, arg);
918         else
919             throw UsageError(format("unknown flag '%1%'") % arg);
920     }
921 
922 
923     /* Obtain derivation information from the specified source. */
924     DrvInfos availElems, installedElems;
925 
926     if (source == sInstalled || compareVersions || printStatus)
927         installedElems = queryInstalled(*globals.state, globals.profile);
928 
929     if (source == sAvailable || compareVersions)
930         loadDerivations(*globals.state, globals.instSource.nixExprPath,
931             globals.instSource.systemFilter, *globals.instSource.autoArgs,
932             attrPath, availElems);
933 
934     DrvInfos elems_ = filterBySelector(*globals.state,
935         source == sInstalled ? installedElems : availElems,
936         opArgs, false);
937 
938     DrvInfos & otherElems(source == sInstalled ? availElems : installedElems);
939 
940 
941     /* Sort them by name. */
942     /* !!! */
943     vector<DrvInfo> elems;
944     for (auto & i : elems_) elems.push_back(i);
945     sort(elems.begin(), elems.end(), cmpElemByName);
946 
947 
948     /* We only need to know the installed paths when we are querying
949        the status of the derivation. */
950     PathSet installed; /* installed paths */
951 
952     if (printStatus) {
953         for (auto & i : installedElems)
954             installed.insert(i.queryOutPath());
955     }
956 
957 
958     /* Query which paths have substitutes. */
959     PathSet validPaths, substitutablePaths;
960     if (printStatus || globals.prebuiltOnly) {
961         PathSet paths;
962         for (auto & i : elems)
963             try {
964                 paths.insert(i.queryOutPath());
965             } catch (AssertionError & e) {
966                 printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
967                 i.setFailed();
968             }
969         validPaths = globals.state->store->queryValidPaths(paths);
970         substitutablePaths = globals.state->store->querySubstitutablePaths(paths);
971     }
972 
973 
974     /* Print the desired columns, or XML output. */
975     if (jsonOutput) {
976         queryJSON(globals, elems);
977         return;
978     }
979 
980     bool tty = isatty(STDOUT_FILENO);
981     RunPager pager;
982 
983     Table table;
984     std::ostringstream dummy;
985     XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
986     XMLOpenElement xmlRoot(xml, "items");
987 
988     for (auto & i : elems) {
989         try {
990             if (i.hasFailed()) continue;
991 
992             //Activity act(*logger, lvlDebug, format("outputting query result '%1%'") % i.attrPath);
993 
994             if (globals.prebuiltOnly &&
995                 validPaths.find(i.queryOutPath()) == validPaths.end() &&
996                 substitutablePaths.find(i.queryOutPath()) == substitutablePaths.end())
997                 continue;
998 
999             /* For table output. */
1000             Strings columns;
1001 
1002             /* For XML output. */
1003             XMLAttrs attrs;
1004 
1005             if (printStatus) {
1006                 Path outPath = i.queryOutPath();
1007                 bool hasSubs = substitutablePaths.find(outPath) != substitutablePaths.end();
1008                 bool isInstalled = installed.find(outPath) != installed.end();
1009                 bool isValid = validPaths.find(outPath) != validPaths.end();
1010                 if (xmlOutput) {
1011                     attrs["installed"] = isInstalled ? "1" : "0";
1012                     attrs["valid"] = isValid ? "1" : "0";
1013                     attrs["substitutable"] = hasSubs ? "1" : "0";
1014                 } else
1015                     columns.push_back(
1016                         (string) (isInstalled ? "I" : "-")
1017                         + (isValid ? "P" : "-")
1018                         + (hasSubs ? "S" : "-"));
1019             }
1020 
1021             if (xmlOutput)
1022                 attrs["attrPath"] = i.attrPath;
1023             else if (printAttrPath)
1024                 columns.push_back(i.attrPath);
1025 
1026             if (xmlOutput) {
1027                 auto drvName = DrvName(i.queryName());
1028                 attrs["name"] = drvName.fullName;
1029                 attrs["pname"] = drvName.name;
1030                 attrs["version"] = drvName.version;
1031             } else if (printName) {
1032                 columns.push_back(i.queryName());
1033             }
1034 
1035             if (compareVersions) {
1036                 /* Compare this element against the versions of the
1037                    same named packages in either the set of available
1038                    elements, or the set of installed elements.  !!!
1039                    This is O(N * M), should be O(N * lg M). */
1040                 string version;
1041                 VersionDiff diff = compareVersionAgainstSet(i, otherElems, version);
1042 
1043                 char ch;
1044                 switch (diff) {
1045                     case cvLess: ch = '>'; break;
1046                     case cvEqual: ch = '='; break;
1047                     case cvGreater: ch = '<'; break;
1048                     case cvUnavail: ch = '-'; break;
1049                     default: abort();
1050                 }
1051 
1052                 if (xmlOutput) {
1053                     if (diff != cvUnavail) {
1054                         attrs["versionDiff"] = ch;
1055                         attrs["maxComparedVersion"] = version;
1056                     }
1057                 } else {
1058                     string column = (string) "" + ch + " " + version;
1059                     if (diff == cvGreater && tty)
1060                         column = ANSI_RED + column + ANSI_NORMAL;
1061                     columns.push_back(column);
1062                 }
1063             }
1064 
1065             if (xmlOutput) {
1066                 if (i.querySystem() != "") attrs["system"] = i.querySystem();
1067             }
1068             else if (printSystem)
1069                 columns.push_back(i.querySystem());
1070 
1071             if (printDrvPath) {
1072                 string drvPath = i.queryDrvPath();
1073                 if (xmlOutput) {
1074                     if (drvPath != "") attrs["drvPath"] = drvPath;
1075                 } else
1076                     columns.push_back(drvPath == "" ? "-" : drvPath);
1077             }
1078 
1079             if (printOutPath && !xmlOutput) {
1080                 DrvInfo::Outputs outputs = i.queryOutputs();
1081                 string s;
1082                 for (auto & j : outputs) {
1083                     if (!s.empty()) s += ';';
1084                     if (j.first != "out") { s += j.first; s += "="; }
1085                     s += j.second;
1086                 }
1087                 columns.push_back(s);
1088             }
1089 
1090             if (printDescription) {
1091                 string descr = i.queryMetaString("description");
1092                 if (xmlOutput) {
1093                     if (descr != "") attrs["description"] = descr;
1094                 } else
1095                     columns.push_back(descr);
1096             }
1097 
1098             if (xmlOutput) {
1099                 if (printOutPath || printMeta) {
1100                     XMLOpenElement item(xml, "item", attrs);
1101                     if (printOutPath) {
1102                         DrvInfo::Outputs outputs = i.queryOutputs();
1103                         for (auto & j : outputs) {
1104                             XMLAttrs attrs2;
1105                             attrs2["name"] = j.first;
1106                             attrs2["path"] = j.second;
1107                             xml.writeEmptyElement("output", attrs2);
1108                         }
1109                     }
1110                     if (printMeta) {
1111                         StringSet metaNames = i.queryMetaNames();
1112                         for (auto & j : metaNames) {
1113                             XMLAttrs attrs2;
1114                             attrs2["name"] = j;
1115                             Value * v = i.queryMeta(j);
1116                             if (!v)
1117                                 printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
1118                             else {
1119                                 if (v->type == tString) {
1120                                     attrs2["type"] = "string";
1121                                     attrs2["value"] = v->string.s;
1122                                     xml.writeEmptyElement("meta", attrs2);
1123                                 } else if (v->type == tInt) {
1124                                     attrs2["type"] = "int";
1125                                     attrs2["value"] = (format("%1%") % v->integer).str();
1126                                     xml.writeEmptyElement("meta", attrs2);
1127                                 } else if (v->type == tFloat) {
1128                                     attrs2["type"] = "float";
1129                                     attrs2["value"] = (format("%1%") % v->fpoint).str();
1130                                     xml.writeEmptyElement("meta", attrs2);
1131                                 } else if (v->type == tBool) {
1132                                     attrs2["type"] = "bool";
1133                                     attrs2["value"] = v->boolean ? "true" : "false";
1134                                     xml.writeEmptyElement("meta", attrs2);
1135                                 } else if (v->isList()) {
1136                                     attrs2["type"] = "strings";
1137                                     XMLOpenElement m(xml, "meta", attrs2);
1138                                     for (unsigned int j = 0; j < v->listSize(); ++j) {
1139                                         if (v->listElems()[j]->type != tString) continue;
1140                                         XMLAttrs attrs3;
1141                                         attrs3["value"] = v->listElems()[j]->string.s;
1142                                         xml.writeEmptyElement("string", attrs3);
1143                                     }
1144                               } else if (v->type == tAttrs) {
1145                                   attrs2["type"] = "strings";
1146                                   XMLOpenElement m(xml, "meta", attrs2);
1147                                   Bindings & attrs = *v->attrs;
1148                                   for (auto &i : attrs) {
1149                                       Attr & a(*attrs.find(i.name));
1150                                       if(a.value->type != tString) continue;
1151                                       XMLAttrs attrs3;
1152                                       attrs3["type"] = i.name;
1153                                       attrs3["value"] = a.value->string.s;
1154                                       xml.writeEmptyElement("string", attrs3);
1155                                 }
1156                               }
1157                             }
1158                         }
1159                     }
1160                 } else
1161                     xml.writeEmptyElement("item", attrs);
1162             } else
1163                 table.push_back(columns);
1164 
1165             cout.flush();
1166 
1167         } catch (AssertionError & e) {
1168             printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
1169         } catch (Error & e) {
1170             e.addPrefix(fmt("while querying the derivation named '%1%':\n", i.queryName()));
1171             throw;
1172         }
1173     }
1174 
1175     if (!xmlOutput) printTable(table);
1176 }
1177 
1178 
opSwitchProfile(Globals & globals,Strings opFlags,Strings opArgs)1179 static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
1180 {
1181     if (opFlags.size() > 0)
1182         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1183     if (opArgs.size() != 1)
1184         throw UsageError(format("exactly one argument expected"));
1185 
1186     Path profile = absPath(opArgs.front());
1187     Path profileLink = getHome() + "/.nix-profile";
1188 
1189     switchLink(profileLink, profile);
1190 }
1191 
1192 
1193 static const int prevGen = -2;
1194 
1195 
switchGeneration(Globals & globals,int dstGen)1196 static void switchGeneration(Globals & globals, int dstGen)
1197 {
1198     PathLocks lock;
1199     lockProfile(lock, globals.profile);
1200 
1201     int curGen;
1202     Generations gens = findGenerations(globals.profile, curGen);
1203 
1204     Generation dst;
1205     for (auto & i : gens)
1206         if ((dstGen == prevGen && i.number < curGen) ||
1207             (dstGen >= 0 && i.number == dstGen))
1208             dst = i;
1209 
1210     if (!dst) {
1211         if (dstGen == prevGen)
1212             throw Error(format("no generation older than the current (%1%) exists")
1213                 % curGen);
1214         else
1215             throw Error(format("generation %1% does not exist") % dstGen);
1216     }
1217 
1218     printInfo(format("switching from generation %1% to %2%")
1219         % curGen % dst.number);
1220 
1221     if (globals.dryRun) return;
1222 
1223     switchLink(globals.profile, dst.path);
1224 }
1225 
1226 
opSwitchGeneration(Globals & globals,Strings opFlags,Strings opArgs)1227 static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
1228 {
1229     if (opFlags.size() > 0)
1230         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1231     if (opArgs.size() != 1)
1232         throw UsageError(format("exactly one argument expected"));
1233 
1234     int dstGen;
1235     if (!string2Int(opArgs.front(), dstGen))
1236         throw UsageError(format("expected a generation number"));
1237 
1238     switchGeneration(globals, dstGen);
1239 }
1240 
1241 
opRollback(Globals & globals,Strings opFlags,Strings opArgs)1242 static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
1243 {
1244     if (opFlags.size() > 0)
1245         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1246     if (opArgs.size() != 0)
1247         throw UsageError(format("no arguments expected"));
1248 
1249     switchGeneration(globals, prevGen);
1250 }
1251 
1252 
opListGenerations(Globals & globals,Strings opFlags,Strings opArgs)1253 static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
1254 {
1255     if (opFlags.size() > 0)
1256         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1257     if (opArgs.size() != 0)
1258         throw UsageError(format("no arguments expected"));
1259 
1260     PathLocks lock;
1261     lockProfile(lock, globals.profile);
1262 
1263     int curGen;
1264     Generations gens = findGenerations(globals.profile, curGen);
1265 
1266     RunPager pager;
1267 
1268     for (auto & i : gens) {
1269         tm t;
1270         if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
1271         cout << format("%|4|   %|4|-%|02|-%|02| %|02|:%|02|:%|02|   %||\n")
1272             % i.number
1273             % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday
1274             % t.tm_hour % t.tm_min % t.tm_sec
1275             % (i.number == curGen ? "(current)" : "");
1276     }
1277 }
1278 
1279 
opDeleteGenerations(Globals & globals,Strings opFlags,Strings opArgs)1280 static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
1281 {
1282     if (opFlags.size() > 0)
1283         throw UsageError(format("unknown flag '%1%'") % opFlags.front());
1284 
1285     if (opArgs.size() == 1 && opArgs.front() == "old") {
1286         deleteOldGenerations(globals.profile, globals.dryRun);
1287     } else if (opArgs.size() == 1 && opArgs.front().find('d') != string::npos) {
1288         deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
1289     } else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) {
1290         if(opArgs.front().size() < 2)
1291             throw Error(format("invalid number of generations ‘%1%’") % opArgs.front());
1292         string str_max = string(opArgs.front(), 1, opArgs.front().size());
1293         int max;
1294         if (!string2Int(str_max, max) || max == 0)
1295             throw Error(format("invalid number of generations to keep ‘%1%’") % opArgs.front());
1296         deleteGenerationsGreaterThan(globals.profile, max, globals.dryRun);
1297     } else {
1298         std::set<unsigned int> gens;
1299         for (auto & i : opArgs) {
1300             unsigned int n;
1301             if (!string2Int(i, n))
1302                 throw UsageError(format("invalid generation number '%1%'") % i);
1303             gens.insert(n);
1304         }
1305         deleteGenerations(globals.profile, gens, globals.dryRun);
1306     }
1307 }
1308 
1309 
opVersion(Globals & globals,Strings opFlags,Strings opArgs)1310 static void opVersion(Globals & globals, Strings opFlags, Strings opArgs)
1311 {
1312     printVersion("nix-env");
1313 }
1314 
1315 
_main(int argc,char ** argv)1316 static int _main(int argc, char * * argv)
1317 {
1318     {
1319         Strings opFlags, opArgs;
1320         Operation op = 0;
1321         RepairFlag repair = NoRepair;
1322         string file;
1323 
1324         Globals globals;
1325 
1326         globals.instSource.type = srcUnknown;
1327         globals.instSource.nixExprPath = getHome() + "/.nix-defexpr";
1328         globals.instSource.systemFilter = "*";
1329 
1330         if (!pathExists(globals.instSource.nixExprPath)) {
1331             try {
1332                 createDirs(globals.instSource.nixExprPath);
1333                 replaceSymlink(
1334                     fmt("%s/profiles/per-user/%s/channels", settings.nixStateDir, getUserName()),
1335                     globals.instSource.nixExprPath + "/channels");
1336                 if (getuid() != 0)
1337                     replaceSymlink(
1338                         fmt("%s/profiles/per-user/root/channels", settings.nixStateDir),
1339                         globals.instSource.nixExprPath + "/channels_root");
1340             } catch (Error &) { }
1341         }
1342 
1343         globals.dryRun = false;
1344         globals.preserveInstalled = false;
1345         globals.removeAll = false;
1346         globals.prebuiltOnly = false;
1347 
1348         struct MyArgs : LegacyArgs, MixEvalArgs
1349         {
1350             using LegacyArgs::LegacyArgs;
1351         };
1352 
1353         MyArgs myArgs(baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
1354             Operation oldOp = op;
1355 
1356             if (*arg == "--help")
1357                 showManPage("nix-env");
1358             else if (*arg == "--version")
1359                 op = opVersion;
1360             else if (*arg == "--install" || *arg == "-i")
1361                 op = opInstall;
1362             else if (*arg == "--force-name") // undocumented flag for nix-install-package
1363                 globals.forceName = getArg(*arg, arg, end);
1364             else if (*arg == "--uninstall" || *arg == "-e")
1365                 op = opUninstall;
1366             else if (*arg == "--upgrade" || *arg == "-u")
1367                 op = opUpgrade;
1368             else if (*arg == "--set-flag")
1369                 op = opSetFlag;
1370             else if (*arg == "--set")
1371                 op = opSet;
1372             else if (*arg == "--query" || *arg == "-q")
1373                 op = opQuery;
1374             else if (*arg == "--profile" || *arg == "-p")
1375                 globals.profile = absPath(getArg(*arg, arg, end));
1376             else if (*arg == "--file" || *arg == "-f")
1377                 file = getArg(*arg, arg, end);
1378             else if (*arg == "--switch-profile" || *arg == "-S")
1379                 op = opSwitchProfile;
1380             else if (*arg == "--switch-generation" || *arg == "-G")
1381                 op = opSwitchGeneration;
1382             else if (*arg == "--rollback")
1383                 op = opRollback;
1384             else if (*arg == "--list-generations")
1385                 op = opListGenerations;
1386             else if (*arg == "--delete-generations")
1387                 op = opDeleteGenerations;
1388             else if (*arg == "--dry-run") {
1389                 printInfo("(dry run; not doing anything)");
1390                 globals.dryRun = true;
1391             }
1392             else if (*arg == "--system-filter")
1393                 globals.instSource.systemFilter = getArg(*arg, arg, end);
1394             else if (*arg == "--prebuilt-only" || *arg == "-b")
1395                 globals.prebuiltOnly = true;
1396             else if (*arg == "--repair")
1397                 repair = Repair;
1398             else if (*arg != "" && arg->at(0) == '-') {
1399                 opFlags.push_back(*arg);
1400                 /* FIXME: hacky */
1401                 if (*arg == "--from-profile" ||
1402                     (op == opQuery && (*arg == "--attr" || *arg == "-A")))
1403                     opFlags.push_back(getArg(*arg, arg, end));
1404             }
1405             else
1406                 opArgs.push_back(*arg);
1407 
1408             if (oldOp && oldOp != op)
1409                 throw UsageError("only one operation may be specified");
1410 
1411             return true;
1412         });
1413 
1414         myArgs.parseCmdline(argvToStrings(argc, argv));
1415 
1416         initPlugins();
1417 
1418         if (!op) throw UsageError("no operation specified");
1419 
1420         auto store = openStore();
1421 
1422         globals.state = std::shared_ptr<EvalState>(new EvalState(myArgs.searchPath, store));
1423         globals.state->repair = repair;
1424 
1425         if (file != "")
1426             globals.instSource.nixExprPath = lookupFileArg(*globals.state, file);
1427 
1428         globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);
1429 
1430         if (globals.profile == "")
1431             globals.profile = getEnv("NIX_PROFILE", "");
1432 
1433         if (globals.profile == "") {
1434             Path profileLink = getHome() + "/.nix-profile";
1435             try {
1436                 if (!pathExists(profileLink)) {
1437                     replaceSymlink(
1438                         getuid() == 0
1439                         ? settings.nixStateDir + "/profiles/default"
1440                         : fmt("%s/profiles/per-user/%s/profile", settings.nixStateDir, getUserName()),
1441                         profileLink);
1442                 }
1443                 globals.profile = absPath(readLink(profileLink), dirOf(profileLink));
1444             } catch (Error &) {
1445                 globals.profile = profileLink;
1446             }
1447         }
1448 
1449         op(globals, opFlags, opArgs);
1450 
1451         globals.state->printStats();
1452 
1453         return 0;
1454     }
1455 }
1456 
1457 static RegisterLegacyCommand s1("nix-env", _main);
1458