Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does a nix path type make it into the nix store and when not?

Tags:

nix

nixos

I have noticed in the past that in nix, a ./myfile.txt path type seems to

  • sometimes evaluate to /home/myuser/mydir/myfile.txt, and
  • sometimes to /nix/store/55j24v9qwdarikv7kd3lc0pvxdr9r2y8-myfile.txt.

I would like to understand exactly when which case happens.

This is especially important for files that contains any form of secret information, as all files in /nix/store are world-readable by all users on the system.

(When using nixops, there's a special "keys" feature for this purpose, see section Managing keys in the manual, but I think it's still important when and how this path-to-store-path copying happens in nix itself.)

like image 724
nh2 Avatar asked May 08 '17 14:05

nh2


People also ask

What is Nix path?

The NIX_PATH variable is the search path used by nix when using the angular brackets syntax.

What is Nix store?

Nix stores all packages into a common place called the Nix store, usually located at /nix/store . Each package is stored in a unique subdirectory in the store, and each package has its own tree structure. For example, a SimGrid package might be stored in /nix/store/l5rah62vpsr3ap63xmk197y0s1l6g2zx-simgrid-3.22.

What is a derivation in Nix?

Derivations are the building blocks of a Nix system, from a file system view point. The Nix language is used to describe such derivations.

What is Nix ENV?

The command nix-env is used to manipulate Nix user environments. User environments are sets of software packages available to a user at some point in time. In other words, they are a synthesised view of the programs available in the Nix store.


1 Answers

User clever on the #nixos IRC channel explained:

When it happens

The expansion into /nix/store/... happens when you use a path inside ${} string interpolation, for example mystring = "cat ${./myfile.txt}.

It does not happen when you use the toString function, e.g. toString ./myfile.txt will not give you a path pointing into /nix/store.

For example:

toString ./notes.txt == "/home/clever/apps/nixos-installer/installer-gui/notes.txt"
"${./notes.txt}"     == "/nix/store/55j24v9qwdarikv7kd3lc0pvxdr9r2y8-notes.txt"

How it happens

The 55j24v9qwdarikv7kd3lc0pvxdr9r2y8 hash part is taken from the contents of the file referenced by the ./path, so that it changes when the file changes and things that depend on it can rebuild accordingly.

The copying of files into /nix/store happens at the time of nix-instantiate; the evaluation of nix expressions is still purely functional (no copying around happens at evaluation time), but instantiation ("building") is not.

To make this possible, every string in nix has a "context" that tracks what the string depends on (in practice a list of .drv paths behind it).

For example, the string "/nix/store/rkvwvi007k7w8lp4cc0n10yhlz5xjfmk-hello-2.10" from the GNU hello package has some invisible state, that says it depends on the hello derivation. And if that string winds up as the input to stdenv.mkDerivation, the newly made derivation will "magically" depend on the hello package being built.

This works even if you mess with the string via builtins.substring. See this code of nix for how the context of the longer string is extracted in line 1653, and used as the context for the substring in line 1657.

You can get rid of a string's dependency context using builtins.unsafeDiscardStringContext.

Where it happens in the nix code

${} interpolation uses coerceToString, which has a bool copyToStore argument that defaults to true:

/* String coercion.  Converts strings, paths and derivations to a
   string.  If `coerceMore' is set, also converts nulls, integers,
   booleans and lists to a string.  If `copyToStore' is set,
   referenced paths are copied to the Nix store as a side effect. */
string coerceToString(const Pos & pos, Value & v, PathSet & context,
                      bool coerceMore = false, bool copyToStore = true);

It is implemented here, and the check for the interpolated thing being a ./path, and the copying to /nix/store, is happening just below:

if (v.type == tPath) {
    Path path(canonPath(v.path));
    return copyToStore ? copyPathToStore(context, path) : path;
}

toString is implemented with prim_toString, and it passes false for the copyToStore argument:

/* Convert the argument to a string.  Paths are *not* copied to the
   store, so `toString /foo/bar' yields `"/foo/bar"', not
   `"/nix/store/whatever..."'. */
static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
    PathSet context;
    string s = state.coerceToString(pos, *args[0], context, true, false);
    mkString(v, s, context);
}
like image 177
nh2 Avatar answered Oct 04 '22 17:10

nh2