Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make escapeSystemdPath implement the correct systemd escaping algorithm #177273

Merged
merged 5 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions lib/ascii-table.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{ " " = 32;
"!" = 33;
"\"" = 34;
"#" = 35;
"$" = 36;
"%" = 37;
"&" = 38;
"'" = 39;
"(" = 40;
")" = 41;
"*" = 42;
"+" = 43;
"," = 44;
"-" = 45;
"." = 46;
"/" = 47;
"0" = 48;
"1" = 49;
"2" = 50;
"3" = 51;
"4" = 52;
"5" = 53;
"6" = 54;
"7" = 55;
"8" = 56;
"9" = 57;
":" = 58;
";" = 59;
"<" = 60;
"=" = 61;
">" = 62;
"?" = 63;
"@" = 64;
"A" = 65;
"B" = 66;
"C" = 67;
"D" = 68;
"E" = 69;
"F" = 70;
"G" = 71;
"H" = 72;
"I" = 73;
"J" = 74;
"K" = 75;
"L" = 76;
"M" = 77;
"N" = 78;
"O" = 79;
"P" = 80;
"Q" = 81;
"R" = 82;
"S" = 83;
"T" = 84;
"U" = 85;
"V" = 86;
"W" = 87;
"X" = 88;
"Y" = 89;
"Z" = 90;
"[" = 91;
"\\" = 92;
"]" = 93;
"^" = 94;
"_" = 95;
"`" = 96;
"a" = 97;
"b" = 98;
"c" = 99;
"d" = 100;
"e" = 101;
"f" = 102;
"g" = 103;
"h" = 104;
"i" = 105;
"j" = 106;
"k" = 107;
"l" = 108;
"m" = 109;
"n" = 110;
"o" = 111;
"p" = 112;
"q" = 113;
"r" = 114;
"s" = 115;
"t" = 116;
"u" = 117;
"v" = 118;
"w" = 119;
"x" = 120;
"y" = 121;
"z" = 122;
"{" = 123;
"|" = 124;
"}" = 125;
"~" = 126;
}
38 changes: 38 additions & 0 deletions lib/strings.nix
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ rec {
*/
makeBinPath = makeSearchPathOutput "bin" "bin";

/* Normalize path, removing extranous /s

Type: normalizePath :: string -> string

Example:
normalizePath "/a//b///c/"
=> "/a/b/c/"
*/
normalizePath = s: (builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x+y) "" (splitString "" s));

/* Depending on the boolean `cond', return either the given string
or the empty string. Useful to concatenate against a bigger string.

Expand Down Expand Up @@ -294,6 +304,21 @@ rec {
map f (stringToCharacters s)
);

/* Convert char to ascii value, must be in printable range

Type: charToInt :: string -> int

Example:
charToInt "A"
=> 65
charToInt "("
=> 40

*/
charToInt = let
table = import ./ascii-table.nix;
in c: builtins.getAttr c table;

/* Escape occurrence of the elements of `list` in `string` by
prefixing it with a backslash.

Expand All @@ -305,6 +330,19 @@ rec {
*/
escape = list: replaceChars list (map (c: "\\${c}") list);

/* Escape occurence of the element of `list` in `string` by
converting to its ASCII value and prefixing it with \\x.
Only works for printable ascii characters.

Type: escapeC = [string] -> string -> string

Example:
escapeC [" "] "foo bar"
=> "foo\\x20bar"

*/
escapeC = list: replaceChars list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);

/* Quote string to be used safely within the Bourne shell.

Type: escapeShellArg :: string -> string
Expand Down
15 changes: 15 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,21 @@ runTests {
expected = true;
};

testNormalizePath = {
expr = strings.normalizePath "//a/b//c////d/";
expected = "/a/b/c/d/";
};

testCharToInt = {
expr = strings.charToInt "A";
expected = 65;
};

testEscapeC = {
expr = strings.escapeC [ " " ] "Hello World";
expected = "Hello\\x20World";
};

# LISTS

testFilter = {
Expand Down
18 changes: 13 additions & 5 deletions nixos/lib/utils.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ rec {
|| hasPrefix a'.mountPoint b'.mountPoint
|| any (hasPrefix a'.mountPoint) b'.depends;

# Escape a path according to the systemd rules, e.g. /dev/xyzzy
# becomes dev-xyzzy. FIXME: slow.
escapeSystemdPath = s:
replaceChars ["/" "-" " "] ["-" "\\x2d" "\\x20"]
(removePrefix "/" s);
# Escape a path according to the systemd rules. FIXME: slow
# The rules are described in systemd.unit(5) as follows:
# The escaping algorithm operates as follows: given a string, any "/" character is replaced by "-", and all other characters which are not ASCII alphanumerics, ":", "_" or "." are replaced by C-style "\x2d" escapes. In addition, "." is replaced with such a C-style escape when it would appear as the first character in the escaped string.
# When the input qualifies as absolute file system path, this algorithm is extended slightly: the path to the root directory "/" is encoded as single dash "-". In addition, any leading, trailing or duplicate "/" characters are removed from the string before transformation. Example: /foo//bar/baz/ becomes "foo-bar-baz".
escapeSystemdPath = s: let
replacePrefix = p: r: s: (if (hasPrefix p s) then r + (removePrefix p s) else s);
trim = s: removeSuffix "/" (removePrefix "/" s);
normalizedPath = strings.normalizePath s;
in
replaceChars ["/"] ["-"]
(replacePrefix "." (strings.escapeC ["."] ".")
dali99 marked this conversation as resolved.
Show resolved Hide resolved
(strings.escapeC (stringToCharacters " !\"#$%&'()*+,;<=>=@[\\]^`{|}~-")
(if normalizedPath == "/" then normalizedPath else trim normalizedPath)));

# Quotes an argument for use in Exec* service lines.
# systemd accepts "-quoted strings with escape sequences, toJSON produces
Expand Down