Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/util/process_wrapper/BUILD.bazel b/util/process_wrapper/BUILD.bazel
new file mode 100644
index 0000000..c26d9a6
--- /dev/null
+++ b/util/process_wrapper/BUILD.bazel
@@ -0,0 +1,45 @@
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//rust:defs.bzl", "rust_binary", "rust_test")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:transitions.bzl", "without_process_wrapper")
+
+alias(
+    name = "process_wrapper",
+    actual = select({
+        # This will never get used, it's only here to break the circular dependency to allow building process_wrapper
+        ":use_fake_process_wrapper": ":process_wrapper_fake",
+        "//conditions:default": ":process_wrapper_impl",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+cc_binary(
+    name = "process_wrapper_fake",
+    srcs = ["fake.cc"],
+)
+
+config_setting(
+    name = "use_fake_process_wrapper",
+    flag_values = {
+        "//rust/settings:use_process_wrapper": "False",
+    },
+)
+
+# Changing the name of this rule requires a corresponding
+# change in //rust/private/rustc.bzl:925
+without_process_wrapper(
+    name = "process_wrapper_impl",
+    target = ":process_wrapper_bin",
+    visibility = ["//visibility:public"],
+)
+
+rust_binary(
+    name = "process_wrapper_bin",
+    srcs = glob(["*.rs"]),
+)
+
+rust_test(
+    name = "process_wrapper_test",
+    crate = ":process_wrapper_bin",
+)
diff --git a/util/process_wrapper/fake.cc b/util/process_wrapper/fake.cc
new file mode 100644
index 0000000..a0b6869
--- /dev/null
+++ b/util/process_wrapper/fake.cc
@@ -0,0 +1,4 @@
+int main() {
+    // Noop on purpose.
+    return 0;
+}
\ No newline at end of file
diff --git a/util/process_wrapper/flags.rs b/util/process_wrapper/flags.rs
new file mode 100644
index 0000000..d3d6fe5
--- /dev/null
+++ b/util/process_wrapper/flags.rs
@@ -0,0 +1,276 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::collections::{BTreeMap, HashSet};
+use std::error::Error;
+use std::fmt;
+use std::fmt::Write;
+use std::iter::Peekable;
+use std::mem::take;
+
+#[derive(Debug, Clone)]
+pub(crate) enum FlagParseError {
+    UnknownFlag(String),
+    ValueMissing(String),
+    ProvidedMultipleTimes(String),
+    ProgramNameMissing,
+}
+
+impl fmt::Display for FlagParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::UnknownFlag(ref flag) => write!(f, "unknown flag \"{}\"", flag),
+            Self::ValueMissing(ref flag) => write!(f, "flag \"{}\" missing parameter(s)", flag),
+            Self::ProvidedMultipleTimes(ref flag) => {
+                write!(f, "flag \"{}\" can only appear once", flag)
+            }
+            Self::ProgramNameMissing => {
+                write!(f, "program name (argv[0]) missing")
+            }
+        }
+    }
+}
+impl Error for FlagParseError {}
+
+struct FlagDef<'a, T> {
+    name: String,
+    help: String,
+    output_storage: &'a mut Option<T>,
+}
+
+impl<'a, T> fmt::Display for FlagDef<'a, T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}\t{}", self.name, self.help)
+    }
+}
+
+impl<'a, T> fmt::Debug for FlagDef<'a, T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("FlagDef")
+            .field("name", &self.name)
+            .field("help", &self.help)
+            .finish()
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct Flags<'a> {
+    single: BTreeMap<String, FlagDef<'a, String>>,
+    repeated: BTreeMap<String, FlagDef<'a, Vec<String>>>,
+}
+
+#[derive(Debug)]
+pub(crate) enum ParseOutcome {
+    Help(String),
+    Parsed(Vec<String>),
+}
+
+impl<'a> Flags<'a> {
+    pub(crate) fn new() -> Flags<'a> {
+        Flags {
+            single: BTreeMap::new(),
+            repeated: BTreeMap::new(),
+        }
+    }
+
+    pub(crate) fn define_flag(
+        &mut self,
+        name: impl Into<String>,
+        help: impl Into<String>,
+        output_storage: &'a mut Option<String>,
+    ) {
+        let name = name.into();
+        if self.repeated.contains_key(&name) {
+            panic!("argument \"{}\" already defined as repeated flag", name)
+        }
+        self.single.insert(
+            name.clone(),
+            FlagDef::<'a, String> {
+                name,
+                help: help.into(),
+                output_storage,
+            },
+        );
+    }
+
+    pub(crate) fn define_repeated_flag(
+        &mut self,
+        name: impl Into<String>,
+        help: impl Into<String>,
+        output_storage: &'a mut Option<Vec<String>>,
+    ) {
+        let name = name.into();
+        if self.single.contains_key(&name) {
+            panic!("argument \"{}\" already defined as flag", name)
+        }
+        self.repeated.insert(
+            name.clone(),
+            FlagDef::<'a, Vec<String>> {
+                name,
+                help: help.into(),
+                output_storage,
+            },
+        );
+    }
+
+    fn help(&self, program_name: String) -> String {
+        let single = self.single.values().map(|fd| fd.to_string());
+        let repeated = self.repeated.values().map(|fd| fd.to_string());
+        let mut all: Vec<String> = single.chain(repeated).collect();
+        all.sort();
+
+        let mut help_text = String::new();
+        writeln!(
+            &mut help_text,
+            "Help for {}: [options] -- [extra arguments]",
+            program_name
+        )
+        .unwrap();
+        for line in all {
+            writeln!(&mut help_text, "\t{}", line).unwrap();
+        }
+        help_text
+    }
+
+    pub(crate) fn parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError> {
+        let mut argv_iter = argv.into_iter().peekable();
+        let program_name = argv_iter.next().ok_or(FlagParseError::ProgramNameMissing)?;
+
+        // To check if a non-repeated flag has been set already.
+        let mut seen_single_flags = HashSet::<String>::new();
+
+        while let Some(flag) = argv_iter.next() {
+            if flag == "--help" {
+                return Ok(ParseOutcome::Help(self.help(program_name)));
+            }
+            if !flag.starts_with("--") {
+                return Err(FlagParseError::UnknownFlag(flag));
+            }
+            let mut args = consume_args(&flag, &mut argv_iter);
+            if flag == "--" {
+                return Ok(ParseOutcome::Parsed(args));
+            }
+            if args.is_empty() {
+                return Err(FlagParseError::ValueMissing(flag.clone()));
+            }
+            if let Some(flag_def) = self.single.get_mut(&flag) {
+                if args.len() > 1 || seen_single_flags.contains(&flag) {
+                    return Err(FlagParseError::ProvidedMultipleTimes(flag.clone()));
+                }
+                let arg = args.first_mut().unwrap();
+                seen_single_flags.insert(flag);
+                *flag_def.output_storage = Some(take(arg));
+                continue;
+            }
+            if let Some(flag_def) = self.repeated.get_mut(&flag) {
+                flag_def
+                    .output_storage
+                    .get_or_insert_with(Vec::new)
+                    .append(&mut args);
+                continue;
+            }
+            return Err(FlagParseError::UnknownFlag(flag));
+        }
+        Ok(ParseOutcome::Parsed(vec![]))
+    }
+}
+
+fn consume_args<I: Iterator<Item = String>>(
+    flag: &str,
+    argv_iter: &mut Peekable<I>,
+) -> Vec<String> {
+    if flag == "--" {
+        // If we have found --, the rest of the iterator is just returned as-is.
+        argv_iter.collect()
+    } else {
+        let mut args = vec![];
+        while let Some(arg) = argv_iter.next_if(|s| !s.starts_with("--")) {
+            args.push(arg);
+        }
+        args
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn args(args: &[&str]) -> Vec<String> {
+        ["foo"].iter().chain(args).map(|&s| s.to_owned()).collect()
+    }
+
+    #[test]
+    fn test_flag_help() {
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--help"])).unwrap();
+        if let ParseOutcome::Help(h) = result {
+            assert!(h.contains("Help for foo"));
+            assert!(h.contains("--bar\tbar help"));
+        } else {
+            panic!("expected that --help would invoke help, instead parsed arguments")
+        }
+    }
+
+    #[test]
+    fn test_flag_single_repeated() {
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "bb"]));
+        if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+            assert_eq!(f, "--bar");
+        } else {
+            panic!("expected error, got {:?}", result)
+        }
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"]));
+        if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+            assert_eq!(f, "--bar");
+        } else {
+            panic!("expected error, got {:?}", result)
+        }
+    }
+
+    #[test]
+    fn test_repeated_flags() {
+        // Test case 1) --bar something something_else should work as a repeated flag.
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_repeated_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "bb"])).unwrap();
+        assert!(matches!(result, ParseOutcome::Parsed(_)));
+        assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+        // Test case 2) --bar something --bar something_else should also work as a repeated flag.
+        bar = None;
+        let mut parser = Flags::new();
+        parser.define_repeated_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"])).unwrap();
+        assert!(matches!(result, ParseOutcome::Parsed(_)));
+        assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+    }
+
+    #[test]
+    fn test_extra_args() {
+        let parser = Flags::new();
+        let result = parser.parse(args(&["--", "bb"])).unwrap();
+        if let ParseOutcome::Parsed(got) = result {
+            assert_eq!(got, vec!["bb".to_owned()])
+        } else {
+            panic!("expected correct parsing, got {:?}", result)
+        }
+    }
+}
diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs
new file mode 100644
index 0000000..41140a3
--- /dev/null
+++ b/util/process_wrapper/main.rs
@@ -0,0 +1,79 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod flags;
+mod options;
+mod util;
+
+use std::fs::{copy, OpenOptions};
+use std::process::{exit, Command, Stdio};
+
+use crate::options::options;
+
+fn main() {
+    let opts = match options() {
+        Err(err) => panic!("process wrapper error: {}", err),
+        Ok(v) => v,
+    };
+    let stdout = if let Some(stdout_file) = opts.stdout_file {
+        OpenOptions::new()
+            .create(true)
+            .truncate(true)
+            .write(true)
+            .open(stdout_file)
+            .expect("process wrapper error: unable to open stdout file")
+            .into()
+    } else {
+        Stdio::inherit()
+    };
+    let stderr = if let Some(stderr_file) = opts.stderr_file {
+        OpenOptions::new()
+            .create(true)
+            .truncate(true)
+            .write(true)
+            .open(stderr_file)
+            .expect("process wrapper error: unable to open stderr file")
+            .into()
+    } else {
+        Stdio::inherit()
+    };
+    let status = Command::new(opts.executable)
+        .args(opts.child_arguments)
+        .env_clear()
+        .envs(opts.child_environment)
+        .stdout(stdout)
+        .stderr(stderr)
+        .status()
+        .expect("process wrapper error: failed to spawn child process");
+
+    if status.success() {
+        if let Some(tf) = opts.touch_file {
+            OpenOptions::new()
+                .create(true)
+                .write(true)
+                .open(tf)
+                .expect("process wrapper error: failed to create touch file");
+        }
+        if let Some((copy_source, copy_dest)) = opts.copy_output {
+            copy(&copy_source, &copy_dest).unwrap_or_else(|_| {
+                panic!(
+                    "process wrapper error: failed to copy {} into {}",
+                    copy_source, copy_dest
+                )
+            });
+        }
+    }
+
+    exit(status.code().unwrap())
+}
diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs
new file mode 100644
index 0000000..24bba9f
--- /dev/null
+++ b/util/process_wrapper/options.rs
@@ -0,0 +1,226 @@
+use std::collections::HashMap;
+use std::env;
+use std::fmt;
+use std::process::exit;
+
+use crate::flags::{FlagParseError, Flags, ParseOutcome};
+use crate::util::*;
+
+#[derive(Debug)]
+pub(crate) enum OptionError {
+    FlagError(FlagParseError),
+    Generic(String),
+}
+
+impl fmt::Display for OptionError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::FlagError(e) => write!(f, "error parsing flags: {}", e),
+            Self::Generic(s) => write!(f, "{}", s),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct Options {
+    // Contains the path to the child executable
+    pub(crate) executable: String,
+    // Contains arguments for the child process fetched from files.
+    pub(crate) child_arguments: Vec<String>,
+    // Contains environment variables for the child process fetched from files.
+    pub(crate) child_environment: HashMap<String, String>,
+    // If set, create the specified file after the child process successfully
+    // terminated its execution.
+    pub(crate) touch_file: Option<String>,
+    // If set to (source, dest) copies the source file to dest.
+    pub(crate) copy_output: Option<(String, String)>,
+    // If set, redirects the child process stdout to this file.
+    pub(crate) stdout_file: Option<String>,
+    // If set, redirects the child process stderr to this file.
+    pub(crate) stderr_file: Option<String>,
+}
+
+pub(crate) fn options() -> Result<Options, OptionError> {
+    // Process argument list until -- is encountered.
+    // Everything after is sent to the child process.
+    let mut subst_mapping_raw = None;
+    let mut volatile_status_file_raw = None;
+    let mut env_file_raw = None;
+    let mut arg_file_raw = None;
+    let mut touch_file = None;
+    let mut copy_output_raw = None;
+    let mut stdout_file = None;
+    let mut stderr_file = None;
+    let mut flags = Flags::new();
+    flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
+    flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
+    flags.define_repeated_flag(
+        "--env-file",
+        "File(s) containing environment variables to pass to the child process.",
+        &mut env_file_raw,
+    );
+    flags.define_repeated_flag(
+        "--arg-file",
+        "File(s) containing command line arguments to pass to the child process.",
+        &mut arg_file_raw,
+    );
+    flags.define_flag(
+        "--touch-file",
+        "Create this file after the child process runs successfully.",
+        &mut touch_file,
+    );
+    flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
+    flags.define_flag(
+        "--stdout-file",
+        "Redirect subprocess stdout in this file.",
+        &mut stdout_file,
+    );
+    flags.define_flag(
+        "--stderr-file",
+        "Redirect subprocess stderr in this file.",
+        &mut stderr_file,
+    );
+
+    let mut child_args = match flags
+        .parse(env::args().collect())
+        .map_err(OptionError::FlagError)?
+    {
+        ParseOutcome::Help(help) => {
+            eprintln!("{}", help);
+            exit(0);
+        }
+        ParseOutcome::Parsed(p) => p,
+    };
+    let current_dir = std::env::current_dir()
+        .map_err(|e| OptionError::Generic(format!("failed to get current directory: {}", e)))?
+        .to_str()
+        .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
+        .to_owned();
+    let subst_mappings = subst_mapping_raw
+        .unwrap_or_default()
+        .into_iter()
+        .map(|arg| {
+            let (key, val) = arg.split_once('=').ok_or_else(|| {
+                OptionError::Generic(format!("empty key for substitution '{}'", arg))
+            })?;
+            let v = if val == "${pwd}" {
+                current_dir.as_str()
+            } else {
+                val
+            }
+            .to_owned();
+            Ok((key.to_owned(), v))
+        })
+        .collect::<Result<Vec<(String, String)>, OptionError>>()?;
+    let stamp_mappings =
+        volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
+
+    let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
+    let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
+    // Process --copy-output
+    let copy_output = copy_output_raw
+        .map(|co| {
+            if co.len() != 2 {
+                return Err(OptionError::Generic(format!(
+                    "\"--copy-output\" needs exactly 2 parameters, {} provided",
+                    co.len()
+                )));
+            }
+            let copy_source = &co[0];
+            let copy_dest = &co[1];
+            if copy_source == copy_dest {
+                return Err(OptionError::Generic(format!(
+                    "\"--copy-output\" source ({}) and dest ({}) need to be different.",
+                    copy_source, copy_dest
+                )));
+            }
+            Ok((copy_source.to_owned(), copy_dest.to_owned()))
+        })
+        .transpose()?;
+
+    // Prepare the environment variables, unifying those read from files with the ones
+    // of the current process.
+    let vars = environment_block(environment_file_block, &stamp_mappings, &subst_mappings);
+    // Append all the arguments fetched from files to those provided via command line.
+    child_args.append(&mut file_arguments);
+    let child_args = prepare_args(child_args, &subst_mappings);
+    // Split the executable path from the rest of the arguments.
+    let (exec_path, args) = child_args.split_first().ok_or_else(|| {
+        OptionError::Generic(
+            "at least one argument after -- is required (the child process path)".to_owned(),
+        )
+    })?;
+
+    Ok(Options {
+        executable: exec_path.to_owned(),
+        child_arguments: args.to_vec(),
+        child_environment: vars,
+        touch_file,
+        copy_output,
+        stdout_file,
+        stderr_file,
+    })
+}
+
+fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
+    let mut args = vec![];
+    for path in paths.into_iter() {
+        let mut lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+        args.append(&mut lines);
+    }
+    Ok(args)
+}
+
+fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
+    let mut env_vars = HashMap::new();
+    for path in paths.into_iter() {
+        let lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+        for line in lines.into_iter() {
+            let (k, v) = line
+                .split_once('=')
+                .ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
+            env_vars.insert(k.to_owned(), v.to_owned());
+        }
+    }
+    Ok(env_vars)
+}
+
+fn prepare_args(mut args: Vec<String>, subst_mappings: &[(String, String)]) -> Vec<String> {
+    for (f, replace_with) in subst_mappings {
+        for arg in args.iter_mut() {
+            let from = format!("${{{}}}", f);
+            let new = arg.replace(from.as_str(), replace_with);
+            *arg = new;
+        }
+    }
+    args
+}
+
+fn environment_block(
+    environment_file_block: HashMap<String, String>,
+    stamp_mappings: &[(String, String)],
+    subst_mappings: &[(String, String)],
+) -> HashMap<String, String> {
+    // Taking all environment variables from the current process
+    // and sending them down to the child process
+    let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
+    // Have the last values added take precedence over the first.
+    // This is simpler than needing to track duplicates and explicitly override
+    // them.
+    environment_variables.extend(environment_file_block.into_iter());
+    for (f, replace_with) in stamp_mappings {
+        for value in environment_variables.values_mut() {
+            let from = format!("{{{}}}", f);
+            let new = value.replace(from.as_str(), replace_with);
+            *value = new;
+        }
+    }
+    for (f, replace_with) in subst_mappings {
+        for value in environment_variables.values_mut() {
+            let from = format!("${{{}}}", f);
+            let new = value.replace(from.as_str(), replace_with);
+            *value = new;
+        }
+    }
+    environment_variables
+}
diff --git a/util/process_wrapper/util.rs b/util/process_wrapper/util.rs
new file mode 100644
index 0000000..4b3d6bb
--- /dev/null
+++ b/util/process_wrapper/util.rs
@@ -0,0 +1,103 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fs::File;
+use std::io::{BufRead, BufReader, Read};
+
+pub(crate) fn read_file_to_array(path: String) -> Result<Vec<String>, String> {
+    let file = File::open(path).map_err(|e| e.to_string())?;
+    read_to_array(file)
+}
+
+pub(crate) fn read_stamp_status_to_array(path: String) -> Result<Vec<(String, String)>, String> {
+    let file = File::open(path).map_err(|e| e.to_string())?;
+    stamp_status_to_array(file)
+}
+
+fn read_to_array(reader: impl Read) -> Result<Vec<String>, String> {
+    let reader = BufReader::new(reader);
+    let mut ret = vec![];
+    let mut escaped_line = String::new();
+    for l in reader.lines() {
+        let line = l.map_err(|e| e.to_string())?;
+        if line.is_empty() {
+            continue;
+        }
+        // a \ at the end of a line allows us to escape the new line break,
+        // \\ yields a single \, so \\\ translates to a single \ and a new line
+        // escape
+        let end_backslash_count = line.chars().rev().take_while(|&c| c == '\\').count();
+        // a 0 or even number of backslashes do not lead to a new line escape
+        let escape = end_backslash_count % 2 == 1;
+        //  remove backslashes and add back two for every one
+        let l = line.trim_end_matches('\\');
+        escaped_line.push_str(l);
+        for _ in 0..end_backslash_count / 2 {
+            escaped_line.push('\\');
+        }
+        if escape {
+            // we add a newline as we expect a line after this
+            escaped_line.push('\n');
+        } else {
+            ret.push(escaped_line);
+            escaped_line = String::new();
+        }
+    }
+    Ok(ret)
+}
+
+fn stamp_status_to_array(reader: impl Read) -> Result<Vec<(String, String)>, String> {
+    let escaped_lines = read_to_array(reader)?;
+    escaped_lines
+        .into_iter()
+        .map(|l| {
+            let (s1, s2) = l
+                .split_once(' ')
+                .ok_or_else(|| format!("wrong workspace status file format for \"{}\"", l))?;
+            Ok((s1.to_owned(), s2.to_owned()))
+        })
+        .collect()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_read_to_array() {
+        let input = r#"some escaped \\\
+string
+with other lines"#
+            .to_owned();
+        let expected = vec![
+            r#"some escaped \
+string"#,
+            "with other lines",
+        ];
+        let got = read_to_array(input.as_bytes()).unwrap();
+        assert_eq!(expected, got);
+    }
+
+    #[test]
+    fn test_stamp_status_to_array() {
+        let lines = "aaa bbb\\\nvvv\nccc ddd\neee fff";
+        let got = stamp_status_to_array(lines.as_bytes()).unwrap();
+        let expected = vec![
+            ("aaa".to_owned(), "bbb\nvvv".to_owned()),
+            ("ccc".to_owned(), "ddd".to_owned()),
+            ("eee".to_owned(), "fff".to_owned()),
+        ];
+        assert_eq!(expected, got);
+    }
+}