From a08741ffbdf1adfb8c4acc790120faed8251ee79 Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Sat, 11 Jun 2022 11:18:54 +0200 Subject: [PATCH 1/5] lib.strings: Add function to convert character to number --- lib/ascii-table.nix | 96 +++++++++++++++++++++++++++++++++++++++++++++ lib/strings.nix | 15 +++++++ 2 files changed, 111 insertions(+) create mode 100644 lib/ascii-table.nix diff --git a/lib/ascii-table.nix b/lib/ascii-table.nix new file mode 100644 index 0000000000000..c564e12bcc6ff --- /dev/null +++ b/lib/ascii-table.nix @@ -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; +} diff --git a/lib/strings.nix b/lib/strings.nix index 295d98900e994..54f89d1be2581 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -294,6 +294,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. From 4c420ee4854d8b0a9d0d224588f0cd033f6b4381 Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Sat, 11 Jun 2022 11:18:54 +0200 Subject: [PATCH 2/5] lib.strings: Add function to do C-style escaping --- lib/strings.nix | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/strings.nix b/lib/strings.nix index 54f89d1be2581..b12dac54f4ff3 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -320,6 +320,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 From 4284ac9dfb118097eb9e2d5f27e11208f2e715e4 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Sun, 12 Jun 2022 14:09:42 +0200 Subject: [PATCH 3/5] lib.strings: Add normalizePath dedupes extranous /'s in filepaths Co-authored-by: Daniel Olsen --- lib/strings.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/strings.nix b/lib/strings.nix index b12dac54f4ff3..be217cb064697 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -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. From 3251123a779636d6883032f158ff99ebe98c0da4 Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Sat, 11 Jun 2022 11:18:54 +0200 Subject: [PATCH 4/5] nixos/lib.escapeSystemdPath: Implement the correct algorithm for escaping names in systemd units Co-authored-by: ajs124 --- nixos/lib/utils.nix | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix index f646f70323e35..9eefa80d1c8b7 100644 --- a/nixos/lib/utils.nix +++ b/nixos/lib/utils.nix @@ -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 ["."] ".") + (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 From 23c1754fff74ee55ca2fa7188130805a7793c9c0 Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Thu, 23 Jun 2022 22:46:23 +0200 Subject: [PATCH 5/5] lib/tests/misc: Add tests for charToInt, escapeC, and normalizePath --- lib/tests/misc.nix | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 74020bc7c8e5d..8e0cf1f45bb60 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -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 = {