├── .gitmodules ├── test ├── image4 │ ├── baz │ └── dir-to-install │ │ └── qux │ │ └── bar ├── image1 │ ├── bin │ │ ├── program │ │ ├── program2 │ │ └── bad-bin │ ├── dir-to-install │ │ └── foo │ ├── something-to-install │ ├── dir-to-not-install │ │ └── foo │ └── something-to-not-install ├── image2 │ ├── dir-to-install │ │ └── bar │ ├── something-to-install │ └── bin │ │ └── oldprogram ├── image3 │ └── bin │ │ └── cargo ├── image5 │ └── dir-to-install │ │ └── foo ├── image-docdir1 │ └── share │ │ └── doc │ │ └── rust │ │ ├── README │ │ └── rustdocs.txt └── image-docdir2 │ └── share │ └── doc │ └── cargo │ ├── README │ └── cargodocs.txt ├── rust-installer-version ├── .gitignore ├── triagebot.toml ├── src ├── lib.rs ├── main.rs ├── scripter.rs ├── tarballer.rs ├── util.rs ├── combiner.rs ├── generator.rs ├── compression.rs └── remove_dir_all.rs ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── gen-installer.sh ├── make-tarballs.sh ├── combine-installers.sh ├── gen-install-script.sh ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE ├── install-template.sh └── test.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image4/baz: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rust-installer-version: -------------------------------------------------------------------------------- 1 | 3 -------------------------------------------------------------------------------- /test/image1/bin/program: -------------------------------------------------------------------------------- 1 | #!/bin/sh -------------------------------------------------------------------------------- /test/image1/bin/program2: -------------------------------------------------------------------------------- 1 | #!/bin/sh -------------------------------------------------------------------------------- /test/image1/dir-to-install/foo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image1/something-to-install: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image2/dir-to-install/bar: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image2/something-to-install: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image3/bin/cargo: -------------------------------------------------------------------------------- 1 | #!/bin/sh -------------------------------------------------------------------------------- /test/image5/dir-to-install/foo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image1/bin/bad-bin: -------------------------------------------------------------------------------- 1 | #!/bin/bogus -------------------------------------------------------------------------------- /test/image1/dir-to-not-install/foo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image1/something-to-not-install: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image2/bin/oldprogram: -------------------------------------------------------------------------------- 1 | #!/bin/sh -------------------------------------------------------------------------------- /test/image4/dir-to-install/qux/bar: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/image-docdir1/share/doc/rust/README: -------------------------------------------------------------------------------- 1 | rust 2 | -------------------------------------------------------------------------------- /test/image-docdir2/share/doc/cargo/README: -------------------------------------------------------------------------------- 1 | cargo 2 | -------------------------------------------------------------------------------- /test/image-docdir1/share/doc/rust/rustdocs.txt: -------------------------------------------------------------------------------- 1 | rust 2 | -------------------------------------------------------------------------------- /test/image-docdir2/share/doc/cargo/cargodocs.txt: -------------------------------------------------------------------------------- 1 | cargo 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | tmp 3 | target/ 4 | **/*.rs.bk 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /triagebot.toml: -------------------------------------------------------------------------------- 1 | [assign] 2 | 3 | [assign.owners] 4 | "*" = ["@Mark-Simulacrum"] 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod util; 3 | 4 | mod combiner; 5 | mod compression; 6 | mod generator; 7 | mod scripter; 8 | mod tarballer; 9 | 10 | pub use crate::combiner::Combiner; 11 | pub use crate::generator::Generator; 12 | pub use crate::scripter::Scripter; 13 | pub use crate::tarballer::Tarballer; 14 | 15 | /// The installer version, output only to be used by combine-installers.sh. 16 | /// (should match `SOURCE_DIRECTORY/rust_installer_version`) 17 | pub const RUST_INSTALLER_VERSION: u32 = 3; 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CI 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | env: 11 | LZMA_API_STATIC: 1 12 | steps: 13 | - name: Checkout the source code 14 | uses: actions/checkout@v2 15 | 16 | - name: Install Rust stable 17 | run: rustup toolchain update stable && rustup default stable 18 | 19 | - name: Build the tool 20 | run: cargo build 21 | 22 | - name: Execute the test suite 23 | run: ./test.sh 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["The Rust Project Developers"] 3 | name = "installer" 4 | version = "0.0.0" 5 | edition = "2018" 6 | 7 | [[bin]] 8 | doc = false 9 | name = "rust-installer" 10 | path = "src/main.rs" 11 | 12 | [dependencies] 13 | anyhow = "1.0.19" 14 | flate2 = "1.0.1" 15 | rayon = "1.0" 16 | tar = "0.4.13" 17 | walkdir = "2" 18 | xz2 = "0.1.4" 19 | num_cpus = "1" 20 | remove_dir_all = "0.5" 21 | 22 | [dependencies.clap] 23 | features = ["derive"] 24 | version = "3.1" 25 | 26 | [target."cfg(windows)".dependencies] 27 | lazy_static = "1" 28 | winapi = { version = "0.3", features = ["errhandlingapi", "handleapi", "ioapiset", "winerror", "winioctl", "winnt"] } 29 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use clap::{self, Parser}; 3 | 4 | #[derive(Parser)] 5 | struct CommandLine { 6 | #[clap(subcommand)] 7 | command: Subcommand, 8 | } 9 | 10 | #[derive(clap::Subcommand)] 11 | enum Subcommand { 12 | Generate(installer::Generator), 13 | Combine(installer::Combiner), 14 | Script(installer::Scripter), 15 | Tarball(installer::Tarballer), 16 | } 17 | 18 | fn main() -> Result<()> { 19 | let command_line = CommandLine::parse(); 20 | match command_line.command { 21 | Subcommand::Combine(combiner) => combiner.run().context("failed to combine installers")?, 22 | Subcommand::Generate(generator) => generator.run().context("failed to generate installer")?, 23 | Subcommand::Script(scripter) => scripter.run().context("failed to generate installation script")?, 24 | Subcommand::Tarball(tarballer) => tarballer.run().context("failed to generate tarballs")?, 25 | } 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /gen-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014 The Rust Project Developers. See the COPYRIGHT 3 | # file at the top-level directory of this distribution and at 4 | # http://rust-lang.org/COPYRIGHT. 5 | # 6 | # Licensed under the Apache License, Version 2.0 or the MIT license 8 | # , at your 9 | # option. This file may not be copied, modified, or distributed 10 | # except according to those terms. 11 | 12 | set -ue 13 | 14 | # Prints the absolute path of a directory to stdout 15 | abs_path() { 16 | local path="$1" 17 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 18 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 19 | # for good measure. 20 | (unset CDPATH && cd "$path" > /dev/null && pwd) 21 | } 22 | 23 | src_dir="$(abs_path $(dirname "$0"))" 24 | cargo run --manifest-path="$src_dir/Cargo.toml" -- generate "$@" 25 | -------------------------------------------------------------------------------- /make-tarballs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2014 The Rust Project Developers. See the COPYRIGHT 3 | # file at the top-level directory of this distribution and at 4 | # http://rust-lang.org/COPYRIGHT. 5 | # 6 | # Licensed under the Apache License, Version 2.0 or the MIT license 8 | # , at your 9 | # option. This file may not be copied, modified, or distributed 10 | # except according to those terms. 11 | 12 | set -ue 13 | 14 | # Prints the absolute path of a directory to stdout 15 | abs_path() { 16 | local path="$1" 17 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 18 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 19 | # for good measure. 20 | (unset CDPATH && cd "$path" > /dev/null && pwd) 21 | } 22 | 23 | src_dir="$(abs_path $(dirname "$0"))" 24 | cargo run --manifest-path="$src_dir/Cargo.toml" -- tarball "$@" 25 | -------------------------------------------------------------------------------- /combine-installers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014 The Rust Project Developers. See the COPYRIGHT 3 | # file at the top-level directory of this distribution and at 4 | # http://rust-lang.org/COPYRIGHT. 5 | # 6 | # Licensed under the Apache License, Version 2.0 or the MIT license 8 | # , at your 9 | # option. This file may not be copied, modified, or distributed 10 | # except according to those terms. 11 | 12 | set -ue 13 | 14 | # Prints the absolute path of a directory to stdout 15 | abs_path() { 16 | local path="$1" 17 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 18 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 19 | # for good measure. 20 | (unset CDPATH && cd "$path" > /dev/null && pwd) 21 | } 22 | 23 | src_dir="$(abs_path $(dirname "$0"))" 24 | cargo run --manifest-path="$src_dir/Cargo.toml" -- combine "$@" 25 | -------------------------------------------------------------------------------- /gen-install-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014 The Rust Project Developers. See the COPYRIGHT 3 | # file at the top-level directory of this distribution and at 4 | # http://rust-lang.org/COPYRIGHT. 5 | # 6 | # Licensed under the Apache License, Version 2.0 or the MIT license 8 | # , at your 9 | # option. This file may not be copied, modified, or distributed 10 | # except according to those terms. 11 | 12 | set -ue 13 | 14 | # Prints the absolute path of a directory to stdout 15 | abs_path() { 16 | local path="$1" 17 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 18 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 19 | # for good measure. 20 | (unset CDPATH && cd "$path" > /dev/null && pwd) 21 | } 22 | 23 | src_dir="$(abs_path $(dirname "$0"))" 24 | cargo run --manifest-path="$src_dir/Cargo.toml" -- script "$@" 25 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/scripter.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use anyhow::{Context, Result}; 3 | use std::io::Write; 4 | 5 | const TEMPLATE: &'static str = include_str!("../install-template.sh"); 6 | 7 | actor! { 8 | #[derive(Debug)] 9 | pub struct Scripter { 10 | /// The name of the product, for display 11 | #[clap(value_name = "NAME")] 12 | product_name: String = "Product", 13 | 14 | /// The directory under lib/ where the manifest lives 15 | #[clap(value_name = "DIR")] 16 | rel_manifest_dir: String = "manifestlib", 17 | 18 | /// The string to print after successful installation 19 | #[clap(value_name = "MESSAGE")] 20 | success_message: String = "Installed.", 21 | 22 | /// Places to look for legacy manifests to uninstall 23 | #[clap(value_name = "DIRS")] 24 | legacy_manifest_dirs: String = "", 25 | 26 | /// The name of the output script 27 | #[clap(value_name = "FILE")] 28 | output_script: String = "install.sh", 29 | } 30 | } 31 | 32 | impl Scripter { 33 | /// Generates the actual installer script 34 | pub fn run(self) -> Result<()> { 35 | // Replace dashes in the success message with spaces (our arg handling botches spaces) 36 | // TODO: still needed? Kept for compatibility for now. 37 | let product_name = self.product_name.replace('-', " "); 38 | 39 | // Replace dashes in the success message with spaces (our arg handling botches spaces) 40 | // TODO: still needed? Kept for compatibility for now. 41 | let success_message = self.success_message.replace('-', " "); 42 | 43 | let script = TEMPLATE 44 | .replace("%%TEMPLATE_PRODUCT_NAME%%", &sh_quote(&product_name)) 45 | .replace("%%TEMPLATE_REL_MANIFEST_DIR%%", &self.rel_manifest_dir) 46 | .replace("%%TEMPLATE_SUCCESS_MESSAGE%%", &sh_quote(&success_message)) 47 | .replace( 48 | "%%TEMPLATE_LEGACY_MANIFEST_DIRS%%", 49 | &sh_quote(&self.legacy_manifest_dirs), 50 | ) 51 | .replace( 52 | "%%TEMPLATE_RUST_INSTALLER_VERSION%%", 53 | &sh_quote(&crate::RUST_INSTALLER_VERSION), 54 | ); 55 | 56 | create_new_executable(&self.output_script)? 57 | .write_all(script.as_ref()) 58 | .with_context(|| format!("failed to write output script '{}'", self.output_script))?; 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | fn sh_quote(s: &T) -> String { 65 | // We'll single-quote the whole thing, so first replace single-quotes with 66 | // '"'"' (leave quoting, double-quote one `'`, re-enter single-quoting) 67 | format!("'{}'", s.to_string().replace('\'', r#"'"'"'"#)) 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moved to rust-lang/rust 2 | 3 | This repository now lives inline to rust-lang/rust (https://github.com/rust-lang/rust/tree/master/src/tools/rust-installer) and has been archived here. All future changes should be filed against rust-lang/rust, existing issues/PRs will not be monitored. 4 | 5 | [![Build Status](https://travis-ci.org/rust-lang/rust-installer.svg?branch=master)](https://travis-ci.org/rust-lang/rust-installer) 6 | 7 | A generator for the install.sh script commonly used to install Rust in 8 | Unix environments. It is used By Rust, Cargo, and is intended to be 9 | used by a future combined installer of Rust + Cargo. 10 | 11 | # Usage 12 | 13 | ``` 14 | ./gen-installer.sh --product-name=Rust \ 15 | --rel-manifest-dir=rustlib \ 16 | --success-message=Rust-is-ready-to-roll. \ 17 | --image-dir=./install-image \ 18 | --work-dir=./temp \ 19 | --output-dir=./dist \ 20 | --non-installed-overlay=./overlay \ 21 | --package-name=rustc-nightly-i686-apple-darwin \ 22 | --component-name=rustc \ 23 | --legacy-manifest-dirs=rustlib \ 24 | --bulk-dirs=share/doc 25 | ``` 26 | 27 | Or, to just generate the script. 28 | 29 | ``` 30 | ./gen-install-script.sh --product-name=Rust \ 31 | --rel-manifest-dir=rustlib \ 32 | --success-message=Rust-is-ready-to-roll. \ 33 | --output-script=install.sh \ 34 | --legacy-manifest-dirs=rustlib 35 | ``` 36 | 37 | *Note: the dashes in `success-message` are converted to spaces. The 38 | script's argument handling is broken with spaces.* 39 | 40 | To combine installers. 41 | 42 | ``` 43 | ./combine-installers.sh --product-name=Rust \ 44 | --rel-manifest-dir=rustlib \ 45 | --success-message=Rust-is-ready-to-roll. \ 46 | --work-dir=./temp \ 47 | --output-dir=./dist \ 48 | --non-installed-overlay=./overlay \ 49 | --package-name=rustc-nightly-i686-apple-darwin \ 50 | --legacy-manifest-dirs=rustlib \ 51 | --input-tarballs=./rustc.tar.gz,cargo.tar.gz 52 | ``` 53 | 54 | # Future work 55 | 56 | * Make install.sh not have to be customized, pull it's data from a 57 | config file. 58 | * Be more resiliant to installation failures, particularly if the disk 59 | is full. 60 | * Pre-install and post-uninstall scripts. 61 | * Allow components to depend on or contradict other components. 62 | * Sanity check that expected destination dirs (bin, lib, share exist)? 63 | * Add --docdir flag. Is there a standard name for this? 64 | * Remove empty directories on uninstall. 65 | * Detect mismatches in --prefix, --mandir, etc. in follow-on 66 | installs/uninstalls. 67 | * Fix argument handling for spaces. 68 | * Add --bindir. 69 | 70 | # License 71 | 72 | This software is distributed under the terms of both the MIT license 73 | and/or the Apache License (Version 2.0), at your option. 74 | 75 | See [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details. 76 | -------------------------------------------------------------------------------- /src/tarballer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use std::fs::{read_link, symlink_metadata}; 3 | use std::io::{empty, BufWriter, Write}; 4 | use std::path::Path; 5 | use tar::{Builder, Header}; 6 | use walkdir::WalkDir; 7 | 8 | use crate::{ 9 | compression::{CombinedEncoder, CompressionFormats}, 10 | util::*, 11 | }; 12 | 13 | actor! { 14 | #[derive(Debug)] 15 | pub struct Tarballer { 16 | /// The input folder to be compressed. 17 | #[clap(value_name = "NAME")] 18 | input: String = "package", 19 | 20 | /// The prefix of the tarballs. 21 | #[clap(value_name = "PATH")] 22 | output: String = "./dist", 23 | 24 | /// The folder in which the input is to be found. 25 | #[clap(value_name = "DIR")] 26 | work_dir: String = "./workdir", 27 | 28 | /// The formats used to compress the tarball. 29 | #[clap(value_name = "FORMAT", default_value_t)] 30 | compression_formats: CompressionFormats, 31 | } 32 | } 33 | 34 | impl Tarballer { 35 | /// Generates the actual tarballs 36 | pub fn run(self) -> Result<()> { 37 | let tarball_name = self.output.clone() + ".tar"; 38 | let encoder = CombinedEncoder::new( 39 | self.compression_formats 40 | .iter() 41 | .map(|f| f.encode(&tarball_name)) 42 | .collect::>>()?, 43 | ); 44 | 45 | // Sort files by their suffix, to group files with the same name from 46 | // different locations (likely identical) and files with the same 47 | // extension (likely containing similar data). 48 | let (dirs, mut files) = get_recursive_paths(&self.work_dir, &self.input) 49 | .context("failed to collect file paths")?; 50 | files.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev())); 51 | 52 | // Write the tar into both encoded files. We write all directories 53 | // first, so files may be directly created. (See rust-lang/rustup.rs#1092.) 54 | let buf = BufWriter::with_capacity(1024 * 1024, encoder); 55 | let mut builder = Builder::new(buf); 56 | 57 | let pool = rayon::ThreadPoolBuilder::new() 58 | .num_threads(2) 59 | .build() 60 | .unwrap(); 61 | pool.install(move || { 62 | for path in dirs { 63 | let src = Path::new(&self.work_dir).join(&path); 64 | builder 65 | .append_dir(&path, &src) 66 | .with_context(|| format!("failed to tar dir '{}'", src.display()))?; 67 | } 68 | for path in files { 69 | let src = Path::new(&self.work_dir).join(&path); 70 | append_path(&mut builder, &src, &path) 71 | .with_context(|| format!("failed to tar file '{}'", src.display()))?; 72 | } 73 | builder 74 | .into_inner() 75 | .context("failed to finish writing .tar stream")? 76 | .into_inner() 77 | .ok() 78 | .unwrap() 79 | .finish()?; 80 | 81 | Ok(()) 82 | }) 83 | } 84 | } 85 | 86 | fn append_path(builder: &mut Builder, src: &Path, path: &String) -> Result<()> { 87 | let stat = symlink_metadata(src)?; 88 | let mut header = Header::new_gnu(); 89 | header.set_metadata(&stat); 90 | if stat.file_type().is_symlink() { 91 | let link = read_link(src)?; 92 | header.set_link_name(&link)?; 93 | builder.append_data(&mut header, path, &mut empty())?; 94 | } else { 95 | if cfg!(windows) { 96 | // Windows doesn't really have a mode, so `tar` never marks files executable. 97 | // Use an extension whitelist to update files that usually should be so. 98 | const EXECUTABLES: [&'static str; 4] = ["exe", "dll", "py", "sh"]; 99 | if let Some(ext) = src.extension().and_then(|s| s.to_str()) { 100 | if EXECUTABLES.contains(&ext) { 101 | let mode = header.mode()?; 102 | header.set_mode(mode | 0o111); 103 | } 104 | } 105 | } 106 | let file = open_file(src)?; 107 | builder.append_data(&mut header, path, &file)?; 108 | } 109 | Ok(()) 110 | } 111 | 112 | /// Returns all `(directories, files)` under the source path. 113 | fn get_recursive_paths(root: P, name: Q) -> Result<(Vec, Vec)> 114 | where 115 | P: AsRef, 116 | Q: AsRef, 117 | { 118 | let root = root.as_ref(); 119 | let name = name.as_ref(); 120 | 121 | if !name.is_relative() && !name.starts_with(root) { 122 | bail!( 123 | "input '{}' is not in work dir '{}'", 124 | name.display(), 125 | root.display() 126 | ); 127 | } 128 | 129 | let mut dirs = vec![]; 130 | let mut files = vec![]; 131 | for entry in WalkDir::new(root.join(name)) { 132 | let entry = entry?; 133 | let path = entry.path().strip_prefix(root)?; 134 | let path = path_to_str(&path)?; 135 | 136 | if entry.file_type().is_dir() { 137 | dirs.push(path.to_owned()); 138 | } else { 139 | files.push(path.to_owned()); 140 | } 141 | } 142 | Ok((dirs, files)) 143 | } 144 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{format_err, Context, Result}; 2 | use std::fs; 3 | use std::path::Path; 4 | use walkdir::WalkDir; 5 | 6 | // Needed to set the script mode to executable. 7 | #[cfg(unix)] 8 | use std::os::unix::fs::OpenOptionsExt; 9 | // FIXME: what about Windows? Are default ACLs executable? 10 | 11 | #[cfg(unix)] 12 | use std::os::unix::fs::symlink as symlink_file; 13 | #[cfg(windows)] 14 | use std::os::windows::fs::symlink_file; 15 | 16 | /// Converts a `&Path` to a UTF-8 `&str`. 17 | pub fn path_to_str(path: &Path) -> Result<&str> { 18 | path.to_str() 19 | .ok_or_else(|| format_err!("path is not valid UTF-8 '{}'", path.display())) 20 | } 21 | 22 | /// Wraps `fs::copy` with a nicer error message. 23 | pub fn copy, Q: AsRef>(from: P, to: Q) -> Result { 24 | if fs::symlink_metadata(&from)?.file_type().is_symlink() { 25 | let link = fs::read_link(&from)?; 26 | symlink_file(link, &to)?; 27 | Ok(0) 28 | } else { 29 | let amt = fs::copy(&from, &to).with_context(|| { 30 | format!( 31 | "failed to copy '{}' to '{}'", 32 | from.as_ref().display(), 33 | to.as_ref().display() 34 | ) 35 | })?; 36 | Ok(amt) 37 | } 38 | } 39 | 40 | /// Wraps `fs::create_dir` with a nicer error message. 41 | pub fn create_dir>(path: P) -> Result<()> { 42 | fs::create_dir(&path) 43 | .with_context(|| format!("failed to create dir '{}'", path.as_ref().display()))?; 44 | Ok(()) 45 | } 46 | 47 | /// Wraps `fs::create_dir_all` with a nicer error message. 48 | pub fn create_dir_all>(path: P) -> Result<()> { 49 | fs::create_dir_all(&path) 50 | .with_context(|| format!("failed to create dir '{}'", path.as_ref().display()))?; 51 | Ok(()) 52 | } 53 | 54 | /// Wraps `fs::OpenOptions::create_new().open()` as executable, with a nicer error message. 55 | pub fn create_new_executable>(path: P) -> Result { 56 | let mut options = fs::OpenOptions::new(); 57 | options.write(true).create_new(true); 58 | #[cfg(unix)] 59 | options.mode(0o755); 60 | let file = options 61 | .open(&path) 62 | .with_context(|| format!("failed to create file '{}'", path.as_ref().display()))?; 63 | Ok(file) 64 | } 65 | 66 | /// Wraps `fs::OpenOptions::create_new().open()`, with a nicer error message. 67 | pub fn create_new_file>(path: P) -> Result { 68 | let file = fs::OpenOptions::new() 69 | .write(true) 70 | .create_new(true) 71 | .open(&path) 72 | .with_context(|| format!("failed to create file '{}'", path.as_ref().display()))?; 73 | Ok(file) 74 | } 75 | 76 | /// Wraps `fs::File::open()` with a nicer error message. 77 | pub fn open_file>(path: P) -> Result { 78 | let file = fs::File::open(&path) 79 | .with_context(|| format!("failed to open file '{}'", path.as_ref().display()))?; 80 | Ok(file) 81 | } 82 | 83 | /// Wraps `remove_dir_all` with a nicer error message. 84 | pub fn remove_dir_all>(path: P) -> Result<()> { 85 | remove_dir_all::remove_dir_all(path.as_ref()) 86 | .with_context(|| format!("failed to remove dir '{}'", path.as_ref().display()))?; 87 | Ok(()) 88 | } 89 | 90 | /// Wrap `fs::remove_file` with a nicer error message 91 | pub fn remove_file>(path: P) -> Result<()> { 92 | fs::remove_file(path.as_ref()) 93 | .with_context(|| format!("failed to remove file '{}'", path.as_ref().display()))?; 94 | Ok(()) 95 | } 96 | 97 | /// Copies the `src` directory recursively to `dst`. Both are assumed to exist 98 | /// when this function is called. 99 | pub fn copy_recursive(src: &Path, dst: &Path) -> Result<()> { 100 | copy_with_callback(src, dst, |_, _| Ok(())) 101 | } 102 | 103 | /// Copies the `src` directory recursively to `dst`. Both are assumed to exist 104 | /// when this function is called. Invokes a callback for each path visited. 105 | pub fn copy_with_callback(src: &Path, dst: &Path, mut callback: F) -> Result<()> 106 | where 107 | F: FnMut(&Path, fs::FileType) -> Result<()>, 108 | { 109 | for entry in WalkDir::new(src).min_depth(1) { 110 | let entry = entry?; 111 | let file_type = entry.file_type(); 112 | let path = entry.path().strip_prefix(src)?; 113 | let dst = dst.join(path); 114 | 115 | if file_type.is_dir() { 116 | create_dir(&dst)?; 117 | } else { 118 | copy(entry.path(), dst)?; 119 | } 120 | callback(&path, file_type)?; 121 | } 122 | Ok(()) 123 | } 124 | 125 | macro_rules! actor_field_default { 126 | () => { Default::default() }; 127 | (= $expr:expr) => { $expr.into() } 128 | } 129 | 130 | /// Creates an "actor" with default values, setters for all fields, and Clap parser support. 131 | macro_rules! actor { 132 | ($( #[ $attr:meta ] )+ pub struct $name:ident { 133 | $( $( #[ $field_attr:meta ] )+ $field:ident : $type:ty $(= $default:tt)*, )* 134 | }) => { 135 | $( #[ $attr ] )+ 136 | #[derive(clap::Args)] 137 | pub struct $name { 138 | $( $( #[ $field_attr ] )+ #[clap(long, $(default_value = $default)*)] $field : $type, )* 139 | } 140 | 141 | impl Default for $name { 142 | fn default() -> $name { 143 | $name { 144 | $($field : actor_field_default!($(= $default)*), )* 145 | } 146 | } 147 | } 148 | 149 | impl $name { 150 | $(pub fn $field(&mut self, value: $type) -> &mut Self { 151 | self.$field = value; 152 | self 153 | })* 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/combiner.rs: -------------------------------------------------------------------------------- 1 | use super::Scripter; 2 | use super::Tarballer; 3 | use crate::{ 4 | compression::{CompressionFormat, CompressionFormats}, 5 | util::*, 6 | }; 7 | use anyhow::{bail, Context, Result}; 8 | use std::io::{Read, Write}; 9 | use std::path::Path; 10 | use tar::Archive; 11 | 12 | actor! { 13 | #[derive(Debug)] 14 | pub struct Combiner { 15 | /// The name of the product, for display. 16 | #[clap(value_name = "NAME")] 17 | product_name: String = "Product", 18 | 19 | /// The name of the package tarball. 20 | #[clap(value_name = "NAME")] 21 | package_name: String = "package", 22 | 23 | /// The directory under lib/ where the manifest lives. 24 | #[clap(value_name = "DIR")] 25 | rel_manifest_dir: String = "packagelib", 26 | 27 | /// The string to print after successful installation. 28 | #[clap(value_name = "MESSAGE")] 29 | success_message: String = "Installed.", 30 | 31 | /// Places to look for legacy manifests to uninstall. 32 | #[clap(value_name = "DIRS")] 33 | legacy_manifest_dirs: String = "", 34 | 35 | /// Installers to combine. 36 | #[clap(value_name = "FILE,FILE")] 37 | input_tarballs: String = "", 38 | 39 | /// Directory containing files that should not be installed. 40 | #[clap(value_name = "DIR")] 41 | non_installed_overlay: String = "", 42 | 43 | /// The directory to do temporary work. 44 | #[clap(value_name = "DIR")] 45 | work_dir: String = "./workdir", 46 | 47 | /// The location to put the final image and tarball. 48 | #[clap(value_name = "DIR")] 49 | output_dir: String = "./dist", 50 | 51 | /// The formats used to compress the tarball 52 | #[clap(value_name = "FORMAT", default_value_t)] 53 | compression_formats: CompressionFormats, 54 | } 55 | } 56 | 57 | impl Combiner { 58 | /// Combines the installer tarballs. 59 | pub fn run(self) -> Result<()> { 60 | create_dir_all(&self.work_dir)?; 61 | 62 | let package_dir = Path::new(&self.work_dir).join(&self.package_name); 63 | if package_dir.exists() { 64 | remove_dir_all(&package_dir)?; 65 | } 66 | create_dir_all(&package_dir)?; 67 | 68 | // Merge each installer into the work directory of the new installer. 69 | let components = create_new_file(package_dir.join("components"))?; 70 | for input_tarball in self 71 | .input_tarballs 72 | .split(',') 73 | .map(str::trim) 74 | .filter(|s| !s.is_empty()) 75 | { 76 | // Extract the input tarballs 77 | let compression = 78 | CompressionFormat::detect_from_path(input_tarball).ok_or_else(|| { 79 | anyhow::anyhow!("couldn't figure out the format of {}", input_tarball) 80 | })?; 81 | Archive::new(compression.decode(input_tarball)?) 82 | .unpack(&self.work_dir) 83 | .with_context(|| { 84 | format!( 85 | "unable to extract '{}' into '{}'", 86 | &input_tarball, self.work_dir 87 | ) 88 | })?; 89 | 90 | let pkg_name = 91 | input_tarball.trim_end_matches(&format!(".tar.{}", compression.extension())); 92 | let pkg_name = Path::new(pkg_name).file_name().unwrap(); 93 | let pkg_dir = Path::new(&self.work_dir).join(&pkg_name); 94 | 95 | // Verify the version number. 96 | let mut version = String::new(); 97 | open_file(pkg_dir.join("rust-installer-version")) 98 | .and_then(|mut file| Ok(file.read_to_string(&mut version)?)) 99 | .with_context(|| format!("failed to read version in '{}'", input_tarball))?; 100 | if version.trim().parse() != Ok(crate::RUST_INSTALLER_VERSION) { 101 | bail!("incorrect installer version in {}", input_tarball); 102 | } 103 | 104 | // Copy components to the new combined installer. 105 | let mut pkg_components = String::new(); 106 | open_file(pkg_dir.join("components")) 107 | .and_then(|mut file| Ok(file.read_to_string(&mut pkg_components)?)) 108 | .with_context(|| format!("failed to read components in '{}'", input_tarball))?; 109 | for component in pkg_components.split_whitespace() { 110 | // All we need to do is copy the component directory. We could 111 | // move it, but rustbuild wants to reuse the unpacked package 112 | // dir for OS-specific installers on macOS and Windows. 113 | let component_dir = package_dir.join(&component); 114 | create_dir(&component_dir)?; 115 | copy_recursive(&pkg_dir.join(&component), &component_dir)?; 116 | 117 | // Merge the component name. 118 | writeln!(&components, "{}", component).context("failed to write new components")?; 119 | } 120 | } 121 | drop(components); 122 | 123 | // Write the installer version. 124 | let version = package_dir.join("rust-installer-version"); 125 | writeln!( 126 | create_new_file(version)?, 127 | "{}", 128 | crate::RUST_INSTALLER_VERSION 129 | ) 130 | .context("failed to write new installer version")?; 131 | 132 | // Copy the overlay. 133 | if !self.non_installed_overlay.is_empty() { 134 | copy_recursive(self.non_installed_overlay.as_ref(), &package_dir)?; 135 | } 136 | 137 | // Generate the install script. 138 | let output_script = package_dir.join("install.sh"); 139 | let mut scripter = Scripter::default(); 140 | scripter 141 | .product_name(self.product_name) 142 | .rel_manifest_dir(self.rel_manifest_dir) 143 | .success_message(self.success_message) 144 | .legacy_manifest_dirs(self.legacy_manifest_dirs) 145 | .output_script(path_to_str(&output_script)?.into()); 146 | scripter.run()?; 147 | 148 | // Make the tarballs. 149 | create_dir_all(&self.output_dir)?; 150 | let output = Path::new(&self.output_dir).join(&self.package_name); 151 | let mut tarballer = Tarballer::default(); 152 | tarballer 153 | .work_dir(self.work_dir) 154 | .input(self.package_name) 155 | .output(path_to_str(&output)?.into()) 156 | .compression_formats(self.compression_formats.clone()); 157 | tarballer.run()?; 158 | 159 | Ok(()) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/generator.rs: -------------------------------------------------------------------------------- 1 | use super::Scripter; 2 | use super::Tarballer; 3 | use crate::compression::CompressionFormats; 4 | use crate::util::*; 5 | use anyhow::{bail, format_err, Context, Result}; 6 | use std::collections::BTreeSet; 7 | use std::io::Write; 8 | use std::path::Path; 9 | 10 | actor! { 11 | #[derive(Debug)] 12 | pub struct Generator { 13 | /// The name of the product, for display 14 | #[clap(value_name = "NAME")] 15 | product_name: String = "Product", 16 | 17 | /// The name of the component, distinct from other installed components 18 | #[clap(value_name = "NAME")] 19 | component_name: String = "component", 20 | 21 | /// The name of the package, tarball 22 | #[clap(value_name = "NAME")] 23 | package_name: String = "package", 24 | 25 | /// The directory under lib/ where the manifest lives 26 | #[clap(value_name = "DIR")] 27 | rel_manifest_dir: String = "packagelib", 28 | 29 | /// The string to print after successful installation 30 | #[clap(value_name = "MESSAGE")] 31 | success_message: String = "Installed.", 32 | 33 | /// Places to look for legacy manifests to uninstall 34 | #[clap(value_name = "DIRS")] 35 | legacy_manifest_dirs: String = "", 36 | 37 | /// Directory containing files that should not be installed 38 | #[clap(value_name = "DIR")] 39 | non_installed_overlay: String = "", 40 | 41 | /// Path prefixes of directories that should be installed/uninstalled in bulk 42 | #[clap(value_name = "DIRS")] 43 | bulk_dirs: String = "", 44 | 45 | /// The directory containing the installation medium 46 | #[clap(value_name = "DIR")] 47 | image_dir: String = "./install_image", 48 | 49 | /// The directory to do temporary work 50 | #[clap(value_name = "DIR")] 51 | work_dir: String = "./workdir", 52 | 53 | /// The location to put the final image and tarball 54 | #[clap(value_name = "DIR")] 55 | output_dir: String = "./dist", 56 | 57 | /// The formats used to compress the tarball 58 | #[clap(value_name = "FORMAT", default_value_t)] 59 | compression_formats: CompressionFormats, 60 | } 61 | } 62 | 63 | impl Generator { 64 | /// Generates the actual installer tarball 65 | pub fn run(self) -> Result<()> { 66 | create_dir_all(&self.work_dir)?; 67 | 68 | let package_dir = Path::new(&self.work_dir).join(&self.package_name); 69 | if package_dir.exists() { 70 | remove_dir_all(&package_dir)?; 71 | } 72 | 73 | // Copy the image and write the manifest 74 | let component_dir = package_dir.join(&self.component_name); 75 | create_dir_all(&component_dir)?; 76 | copy_and_manifest(self.image_dir.as_ref(), &component_dir, &self.bulk_dirs)?; 77 | 78 | // Write the component name 79 | let components = package_dir.join("components"); 80 | writeln!(create_new_file(components)?, "{}", self.component_name) 81 | .context("failed to write the component file")?; 82 | 83 | // Write the installer version (only used by combine-installers.sh) 84 | let version = package_dir.join("rust-installer-version"); 85 | writeln!( 86 | create_new_file(version)?, 87 | "{}", 88 | crate::RUST_INSTALLER_VERSION 89 | ) 90 | .context("failed to write new installer version")?; 91 | 92 | // Copy the overlay 93 | if !self.non_installed_overlay.is_empty() { 94 | copy_recursive(self.non_installed_overlay.as_ref(), &package_dir)?; 95 | } 96 | 97 | // Generate the install script 98 | let output_script = package_dir.join("install.sh"); 99 | let mut scripter = Scripter::default(); 100 | scripter 101 | .product_name(self.product_name) 102 | .rel_manifest_dir(self.rel_manifest_dir) 103 | .success_message(self.success_message) 104 | .legacy_manifest_dirs(self.legacy_manifest_dirs) 105 | .output_script(path_to_str(&output_script)?.into()); 106 | scripter.run()?; 107 | 108 | // Make the tarballs 109 | create_dir_all(&self.output_dir)?; 110 | let output = Path::new(&self.output_dir).join(&self.package_name); 111 | let mut tarballer = Tarballer::default(); 112 | tarballer 113 | .work_dir(self.work_dir) 114 | .input(self.package_name) 115 | .output(path_to_str(&output)?.into()) 116 | .compression_formats(self.compression_formats.clone()); 117 | tarballer.run()?; 118 | 119 | Ok(()) 120 | } 121 | } 122 | 123 | /// Copies the `src` directory recursively to `dst`, writing `manifest.in` too. 124 | fn copy_and_manifest(src: &Path, dst: &Path, bulk_dirs: &str) -> Result<()> { 125 | let mut manifest = create_new_file(dst.join("manifest.in"))?; 126 | let bulk_dirs: Vec<_> = bulk_dirs 127 | .split(',') 128 | .filter(|s| !s.is_empty()) 129 | .map(Path::new) 130 | .collect(); 131 | 132 | let mut paths = BTreeSet::new(); 133 | copy_with_callback(src, dst, |path, file_type| { 134 | // We need paths to be compatible with both Unix and Windows. 135 | if path 136 | .components() 137 | .filter_map(|c| c.as_os_str().to_str()) 138 | .any(|s| s.contains('\\')) 139 | { 140 | bail!( 141 | "rust-installer doesn't support '\\' in path components: {:?}", 142 | path 143 | ); 144 | } 145 | 146 | // Normalize to Unix-style path separators. 147 | let normalized_string; 148 | let mut string = path.to_str().ok_or_else(|| { 149 | format_err!( 150 | "rust-installer doesn't support non-Unicode paths: {:?}", 151 | path 152 | ) 153 | })?; 154 | if string.contains('\\') { 155 | normalized_string = string.replace('\\', "/"); 156 | string = &normalized_string; 157 | } 158 | 159 | if file_type.is_dir() { 160 | // Only manifest directories that are explicitly bulk. 161 | if bulk_dirs.contains(&path) { 162 | paths.insert(format!("dir:{}\n", string)); 163 | } 164 | } else { 165 | // Only manifest files that aren't under bulk directories. 166 | if !bulk_dirs.iter().any(|d| path.starts_with(d)) { 167 | paths.insert(format!("file:{}\n", string)); 168 | } 169 | } 170 | Ok(()) 171 | })?; 172 | 173 | for path in paths { 174 | manifest.write_all(path.as_bytes())?; 175 | } 176 | 177 | Ok(()) 178 | } 179 | -------------------------------------------------------------------------------- /src/compression.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error}; 2 | use flate2::{read::GzDecoder, write::GzEncoder}; 3 | use rayon::prelude::*; 4 | use std::{convert::TryFrom, fmt, io::Read, io::Write, path::Path, str::FromStr}; 5 | use xz2::{read::XzDecoder, write::XzEncoder}; 6 | 7 | #[derive(Debug, Copy, Clone)] 8 | pub enum CompressionFormat { 9 | Gz, 10 | Xz, 11 | } 12 | 13 | impl CompressionFormat { 14 | pub(crate) fn detect_from_path(path: impl AsRef) -> Option { 15 | match path.as_ref().extension().and_then(|e| e.to_str()) { 16 | Some("gz") => Some(CompressionFormat::Gz), 17 | Some("xz") => Some(CompressionFormat::Xz), 18 | _ => None, 19 | } 20 | } 21 | 22 | pub(crate) fn extension(&self) -> &'static str { 23 | match self { 24 | CompressionFormat::Gz => "gz", 25 | CompressionFormat::Xz => "xz", 26 | } 27 | } 28 | 29 | pub(crate) fn encode(&self, path: impl AsRef) -> Result, Error> { 30 | let mut os = path.as_ref().as_os_str().to_os_string(); 31 | os.push(format!(".{}", self.extension())); 32 | let path = Path::new(&os); 33 | 34 | if path.exists() { 35 | crate::util::remove_file(path)?; 36 | } 37 | let file = crate::util::create_new_file(path)?; 38 | 39 | Ok(match self { 40 | CompressionFormat::Gz => Box::new(GzEncoder::new(file, flate2::Compression::best())), 41 | CompressionFormat::Xz => { 42 | let mut filters = xz2::stream::Filters::new(); 43 | // the preset is overridden by the other options so it doesn't matter 44 | let mut lzma_ops = xz2::stream::LzmaOptions::new_preset(9).unwrap(); 45 | // This sets the overall dictionary size, which is also how much memory (baseline) 46 | // is needed for decompression. 47 | lzma_ops.dict_size(64 * 1024 * 1024); 48 | // Use the best match finder for compression ratio. 49 | lzma_ops.match_finder(xz2::stream::MatchFinder::BinaryTree4); 50 | lzma_ops.mode(xz2::stream::Mode::Normal); 51 | // Set nice len to the maximum for best compression ratio 52 | lzma_ops.nice_len(273); 53 | // Set depth to a reasonable value, 0 means auto, 1000 is somwhat high but gives 54 | // good results. 55 | lzma_ops.depth(1000); 56 | // 2 is the default and does well for most files 57 | lzma_ops.position_bits(2); 58 | // 0 is the default and does well for most files 59 | lzma_ops.literal_position_bits(0); 60 | // 3 is the default and does well for most files 61 | lzma_ops.literal_context_bits(3); 62 | 63 | filters.lzma2(&lzma_ops); 64 | let compressor = XzEncoder::new_stream( 65 | std::io::BufWriter::new(file), 66 | xz2::stream::MtStreamBuilder::new() 67 | .threads(1) 68 | .filters(filters) 69 | .encoder() 70 | .unwrap(), 71 | ); 72 | Box::new(compressor) 73 | } 74 | }) 75 | } 76 | 77 | pub(crate) fn decode(&self, path: impl AsRef) -> Result, Error> { 78 | let file = crate::util::open_file(path.as_ref())?; 79 | Ok(match self { 80 | CompressionFormat::Gz => Box::new(GzDecoder::new(file)), 81 | CompressionFormat::Xz => Box::new(XzDecoder::new(file)), 82 | }) 83 | } 84 | } 85 | 86 | /// This struct wraps Vec in order to parse the value from the command line. 87 | #[derive(Debug, Clone)] 88 | pub struct CompressionFormats(Vec); 89 | 90 | impl TryFrom<&'_ str> for CompressionFormats { 91 | type Error = Error; 92 | 93 | fn try_from(value: &str) -> Result { 94 | let mut parsed = Vec::new(); 95 | for format in value.split(',') { 96 | match format.trim() { 97 | "gz" => parsed.push(CompressionFormat::Gz), 98 | "xz" => parsed.push(CompressionFormat::Xz), 99 | other => anyhow::bail!("unknown compression format: {}", other), 100 | } 101 | } 102 | Ok(CompressionFormats(parsed)) 103 | } 104 | } 105 | 106 | impl FromStr for CompressionFormats { 107 | type Err = Error; 108 | 109 | fn from_str(value: &str) -> Result { 110 | Self::try_from(value) 111 | } 112 | } 113 | 114 | impl fmt::Display for CompressionFormats { 115 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 116 | for (i, format) in self.iter().enumerate() { 117 | if i != 0 { 118 | write!(f, ",")?; 119 | } 120 | fmt::Display::fmt( 121 | match format { 122 | CompressionFormat::Xz => "xz", 123 | CompressionFormat::Gz => "gz", 124 | }, 125 | f, 126 | )?; 127 | } 128 | Ok(()) 129 | } 130 | } 131 | 132 | impl Default for CompressionFormats { 133 | fn default() -> Self { 134 | Self(vec![CompressionFormat::Gz, CompressionFormat::Xz]) 135 | } 136 | } 137 | 138 | impl CompressionFormats { 139 | pub(crate) fn iter(&self) -> impl Iterator + '_ { 140 | self.0.iter().map(|i| *i) 141 | } 142 | } 143 | 144 | pub(crate) trait Encoder: Send + Write { 145 | fn finish(self: Box) -> Result<(), Error>; 146 | } 147 | 148 | impl Encoder for GzEncoder { 149 | fn finish(self: Box) -> Result<(), Error> { 150 | GzEncoder::finish(*self).context("failed to finish .gz file")?; 151 | Ok(()) 152 | } 153 | } 154 | 155 | impl Encoder for XzEncoder { 156 | fn finish(self: Box) -> Result<(), Error> { 157 | XzEncoder::finish(*self).context("failed to finish .xz file")?; 158 | Ok(()) 159 | } 160 | } 161 | 162 | pub(crate) struct CombinedEncoder { 163 | encoders: Vec>, 164 | } 165 | 166 | impl CombinedEncoder { 167 | pub(crate) fn new(encoders: Vec>) -> Box { 168 | Box::new(Self { encoders }) 169 | } 170 | } 171 | 172 | impl Write for CombinedEncoder { 173 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 174 | self.write_all(buf)?; 175 | Ok(buf.len()) 176 | } 177 | 178 | fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { 179 | self.encoders 180 | .par_iter_mut() 181 | .map(|w| w.write_all(buf)) 182 | .collect::>>()?; 183 | Ok(()) 184 | } 185 | 186 | fn flush(&mut self) -> std::io::Result<()> { 187 | self.encoders 188 | .par_iter_mut() 189 | .map(|w| w.flush()) 190 | .collect::>>()?; 191 | Ok(()) 192 | } 193 | } 194 | 195 | impl Encoder for CombinedEncoder { 196 | fn finish(self: Box) -> Result<(), Error> { 197 | self.encoders 198 | .into_par_iter() 199 | .map(|e| e.finish()) 200 | .collect::, Error>>()?; 201 | Ok(()) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /install-template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014 The Rust Project Developers. See the COPYRIGHT 3 | # file at the top-level directory of this distribution and at 4 | # http://rust-lang.org/COPYRIGHT. 5 | # 6 | # Licensed under the Apache License, Version 2.0 or the MIT license 8 | # , at your 9 | # option. This file may not be copied, modified, or distributed 10 | # except according to those terms. 11 | 12 | # No undefined variables 13 | set -u 14 | 15 | init_logging() { 16 | local _abs_libdir="$1" 17 | local _logfile="$_abs_libdir/$TEMPLATE_REL_MANIFEST_DIR/install.log" 18 | rm -f "$_logfile" 19 | need_ok "failed to remove old installation log" 20 | touch "$_logfile" 21 | need_ok "failed to create installation log" 22 | LOGFILE="$_logfile" 23 | } 24 | 25 | log_line() { 26 | local _line="$1" 27 | 28 | if [ -n "${LOGFILE-}" -a -e "${LOGFILE-}" ]; then 29 | echo "$_line" >> "$LOGFILE" 30 | # Ignore errors, which may happen e.g. after the manifest dir is deleted 31 | fi 32 | } 33 | 34 | msg() { 35 | local _line="install: ${1-}" 36 | echo "$_line" 37 | log_line "$_line" 38 | } 39 | 40 | verbose_msg() { 41 | if [ -n "${CFG_VERBOSE-}" ]; then 42 | msg "${1-}" 43 | else 44 | log_line "install: ${1-}" 45 | fi 46 | } 47 | 48 | step_msg() { 49 | msg 50 | msg "$1" 51 | msg 52 | } 53 | 54 | verbose_step_msg() { 55 | if [ -n "${CFG_VERBOSE-}" ]; then 56 | msg 57 | msg "$1" 58 | msg 59 | else 60 | log_line "" 61 | log_line "install: $1" 62 | log_line "" 63 | fi 64 | } 65 | 66 | warn() { 67 | local _line="install: WARNING: $1" 68 | echo "$_line" >&2 69 | log_line "$_line" 70 | } 71 | 72 | err() { 73 | local _line="install: error: $1" 74 | echo "$_line" >&2 75 | log_line "$_line" 76 | exit 1 77 | } 78 | 79 | # A non-user error that is likely to result in a corrupted install 80 | critical_err() { 81 | local _line="install: error: $1. see logs at '${LOGFILE-}'" 82 | echo "$_line" >&2 83 | log_line "$_line" 84 | exit 1 85 | } 86 | 87 | need_ok() { 88 | if [ $? -ne 0 ] 89 | then 90 | err "$1" 91 | fi 92 | } 93 | 94 | critical_need_ok() { 95 | if [ $? -ne 0 ] 96 | then 97 | critical_err "$1" 98 | fi 99 | } 100 | 101 | want_ok() { 102 | if [ $? -ne 0 ]; then 103 | warn "$1" 104 | fi 105 | } 106 | 107 | assert_nz() { 108 | if [ -z "$1" ]; then err "assert_nz $2"; fi 109 | } 110 | 111 | need_cmd() { 112 | if command -v $1 >/dev/null 2>&1 113 | then verbose_msg "found $1" 114 | else err "need $1" 115 | fi 116 | } 117 | 118 | run() { 119 | local _line="\$ $*" 120 | "$@" 121 | local _retval=$? 122 | log_line "$_line" 123 | return $_retval 124 | } 125 | 126 | write_to_file() { 127 | local _msg="$1" 128 | local _file="$2" 129 | local _line="$ echo \"$_msg\" > \"$_file\"" 130 | echo "$_msg" > "$_file" 131 | local _retval=$? 132 | log_line "$_line" 133 | return $_retval 134 | } 135 | 136 | append_to_file() { 137 | local _msg="$1" 138 | local _file="$2" 139 | local _line="$ echo \"$_msg\" >> \"$_file\"" 140 | echo "$_msg" >> "$_file" 141 | local _retval=$? 142 | log_line "$_line" 143 | return $_retval 144 | } 145 | 146 | make_dir_recursive() { 147 | local _dir="$1" 148 | local _line="$ umask 022 && mkdir -p \"$_dir\"" 149 | umask 022 && mkdir -p "$_dir" 150 | local _retval=$? 151 | log_line "$_line" 152 | return $_retval 153 | } 154 | 155 | putvar() { 156 | local t 157 | local tlen 158 | eval t=\$$1 159 | eval tlen=\${#$1} 160 | } 161 | 162 | valopt() { 163 | VAL_OPTIONS="$VAL_OPTIONS $1" 164 | 165 | local op=$1 166 | local default=$2 167 | shift 168 | shift 169 | local doc="$*" 170 | if [ $HELP -eq 0 ] 171 | then 172 | local uop=$(echo $op | tr 'a-z-' 'A-Z_') 173 | local v="CFG_${uop}" 174 | eval $v="$default" 175 | for arg in $CFG_ARGS 176 | do 177 | if echo "$arg" | grep -q -- "--$op=" 178 | then 179 | local val=$(echo "$arg" | cut -f2 -d=) 180 | eval $v=$val 181 | fi 182 | done 183 | putvar $v 184 | else 185 | if [ -z "$default" ] 186 | then 187 | default="" 188 | fi 189 | op="${op}=[${default}]" 190 | printf " --%-30s %s\n" "$op" "$doc" 191 | fi 192 | } 193 | 194 | opt() { 195 | BOOL_OPTIONS="$BOOL_OPTIONS $1" 196 | 197 | local op=$1 198 | local default=$2 199 | shift 200 | shift 201 | local doc="$*" 202 | local flag="" 203 | 204 | if [ $default -eq 0 ] 205 | then 206 | flag="enable" 207 | else 208 | flag="disable" 209 | doc="don't $doc" 210 | fi 211 | 212 | if [ $HELP -eq 0 ] 213 | then 214 | for arg in $CFG_ARGS 215 | do 216 | if [ "$arg" = "--${flag}-${op}" ] 217 | then 218 | op=$(echo $op | tr 'a-z-' 'A-Z_') 219 | flag=$(echo $flag | tr 'a-z' 'A-Z') 220 | local v="CFG_${flag}_${op}" 221 | eval $v=1 222 | putvar $v 223 | fi 224 | done 225 | else 226 | if [ ! -z "${META-}" ] 227 | then 228 | op="$op=<$META>" 229 | fi 230 | printf " --%-30s %s\n" "$flag-$op" "$doc" 231 | fi 232 | } 233 | 234 | flag() { 235 | BOOL_OPTIONS="$BOOL_OPTIONS $1" 236 | 237 | local op=$1 238 | shift 239 | local doc="$*" 240 | 241 | if [ $HELP -eq 0 ] 242 | then 243 | for arg in $CFG_ARGS 244 | do 245 | if [ "$arg" = "--${op}" ] 246 | then 247 | op=$(echo $op | tr 'a-z-' 'A-Z_') 248 | local v="CFG_${op}" 249 | eval $v=1 250 | putvar $v 251 | fi 252 | done 253 | else 254 | if [ ! -z "${META-}" ] 255 | then 256 | op="$op=<$META>" 257 | fi 258 | printf " --%-30s %s\n" "$op" "$doc" 259 | fi 260 | } 261 | 262 | validate_opt () { 263 | for arg in $CFG_ARGS 264 | do 265 | local is_arg_valid=0 266 | for option in $BOOL_OPTIONS 267 | do 268 | if test --disable-$option = $arg 269 | then 270 | is_arg_valid=1 271 | fi 272 | if test --enable-$option = $arg 273 | then 274 | is_arg_valid=1 275 | fi 276 | if test --$option = $arg 277 | then 278 | is_arg_valid=1 279 | fi 280 | done 281 | for option in $VAL_OPTIONS 282 | do 283 | if echo "$arg" | grep -q -- "--$option=" 284 | then 285 | is_arg_valid=1 286 | fi 287 | done 288 | if [ "$arg" = "--help" ] 289 | then 290 | echo 291 | echo "No more help available for Configure options," 292 | echo "check the Wiki or join our IRC channel" 293 | break 294 | else 295 | if test $is_arg_valid -eq 0 296 | then 297 | err "Option '$arg' is not recognized" 298 | fi 299 | fi 300 | done 301 | } 302 | 303 | absolutify() { 304 | local file_path="$1" 305 | local file_path_dirname="$(dirname "$file_path")" 306 | local file_path_basename="$(basename "$file_path")" 307 | local file_abs_path="$(abs_path "$file_path_dirname")" 308 | local file_path="$file_abs_path/$file_path_basename" 309 | # This is the return value 310 | RETVAL="$file_path" 311 | } 312 | 313 | # Prints the absolute path of a directory to stdout 314 | abs_path() { 315 | local path="$1" 316 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 317 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 318 | # for good measure. 319 | (unset CDPATH && cd "$path" > /dev/null && pwd) 320 | } 321 | 322 | uninstall_legacy() { 323 | local _abs_libdir="$1" 324 | 325 | local _uninstalled_something=false 326 | 327 | # Replace commas in legacy manifest list with spaces 328 | _legacy_manifest_dirs=`echo "$TEMPLATE_LEGACY_MANIFEST_DIRS" | sed "s/,/ /g"` 329 | 330 | # Uninstall from legacy manifests 331 | local _md 332 | for _md in $_legacy_manifest_dirs; do 333 | # First, uninstall from the installation prefix. 334 | # Errors are warnings - try to rm everything in the manifest even if some fail. 335 | if [ -f "$_abs_libdir/$_md/manifest" ] 336 | then 337 | 338 | # iterate through installed manifest and remove files 339 | local _p; 340 | while read _p; do 341 | # the installed manifest contains absolute paths 342 | msg "removing legacy file $_p" 343 | if [ -f "$_p" ] 344 | then 345 | run rm -f "$_p" 346 | want_ok "failed to remove $_p" 347 | else 348 | warn "supposedly installed file $_p does not exist!" 349 | fi 350 | done < "$_abs_libdir/$_md/manifest" 351 | 352 | # If we fail to remove $md below, then the 353 | # installed manifest will still be full; the installed manifest 354 | # needs to be empty before install. 355 | msg "removing legacy manifest $_abs_libdir/$_md/manifest" 356 | run rm -f "$_abs_libdir/$_md/manifest" 357 | # For the above reason, this is a hard error 358 | need_ok "failed to remove installed manifest" 359 | 360 | # Remove $template_rel_manifest_dir directory 361 | msg "removing legacy manifest dir $_abs_libdir/$_md" 362 | run rm -R "$_abs_libdir/$_md" 363 | want_ok "failed to remove $_md" 364 | 365 | _uninstalled_something=true 366 | fi 367 | done 368 | 369 | RETVAL="$_uninstalled_something" 370 | } 371 | 372 | uninstall_components() { 373 | local _abs_libdir="$1" 374 | local _dest_prefix="$2" 375 | local _components="$3" 376 | 377 | # We're going to start by uninstalling existing components. This 378 | local _uninstalled_something=false 379 | 380 | # First, try removing any 'legacy' manifests from before 381 | # rust-installer 382 | uninstall_legacy "$_abs_libdir" 383 | assert_nz "$RETVAL", "RETVAL" 384 | if [ "$RETVAL" = true ]; then 385 | _uninstalled_something=true; 386 | fi 387 | 388 | # Load the version of the installed installer 389 | local _installed_version= 390 | if [ -f "$abs_libdir/$TEMPLATE_REL_MANIFEST_DIR/rust-installer-version" ]; then 391 | _installed_version=`cat "$_abs_libdir/$TEMPLATE_REL_MANIFEST_DIR/rust-installer-version"` 392 | 393 | # Sanity check 394 | if [ ! -n "$_installed_version" ]; then critical_err "rust installer version is empty"; fi 395 | fi 396 | 397 | # If there's something installed, then uninstall 398 | if [ -n "$_installed_version" ]; then 399 | # Check the version of the installed installer 400 | case "$_installed_version" in 401 | 402 | # If this is a previous version, then upgrade in place to the 403 | # current version before uninstalling. 404 | 2 ) 405 | # The only change between version 2 -> 3 is that components are placed 406 | # in subdirectories of the installer tarball. There are no changes 407 | # to the installed data format, so nothing to do. 408 | ;; 409 | 410 | # This is the current version. Nothing need to be done except uninstall. 411 | "$TEMPLATE_RUST_INSTALLER_VERSION") 412 | ;; 413 | 414 | # If this is an unknown (future) version then bail. 415 | * ) 416 | echo "The copy of $TEMPLATE_PRODUCT_NAME at $_dest_prefix was installed using an" 417 | echo "unknown version ($_installed_version) of rust-installer." 418 | echo "Uninstall it first with the installer used for the original installation" 419 | echo "before continuing." 420 | exit 1 421 | ;; 422 | esac 423 | 424 | local _md="$_abs_libdir/$TEMPLATE_REL_MANIFEST_DIR" 425 | local _installed_components="$(cat "$_md/components")" 426 | 427 | # Uninstall (our components only) before reinstalling 428 | local _available_component 429 | for _available_component in $_components; do 430 | local _installed_component 431 | for _installed_component in $_installed_components; do 432 | if [ "$_available_component" = "$_installed_component" ]; then 433 | msg "uninstalling component '$_available_component'" 434 | local _component_manifest="$_md/manifest-$_installed_component" 435 | 436 | # Sanity check: there should be a component manifest 437 | if [ ! -f "$_component_manifest" ]; then 438 | critical_err "installed component '$_installed_component' has no manifest" 439 | fi 440 | 441 | # Iterate through installed component manifest and remove files 442 | local _directive 443 | while read _directive; do 444 | 445 | local _command=`echo $_directive | cut -f1 -d:` 446 | local _file=`echo $_directive | cut -f2 -d:` 447 | 448 | # Sanity checks 449 | if [ ! -n "$_command" ]; then critical_err "malformed installation directive"; fi 450 | if [ ! -n "$_file" ]; then critical_err "malformed installation directive"; fi 451 | 452 | case "$_command" in 453 | file) 454 | verbose_msg "removing file $_file" 455 | if [ -f "$_file" ]; then 456 | run rm -f "$_file" 457 | want_ok "failed to remove $_file" 458 | else 459 | warn "supposedly installed file $_file does not exist!" 460 | fi 461 | ;; 462 | 463 | dir) 464 | verbose_msg "removing directory $_file" 465 | run rm -r "$_file" 466 | want_ok "unable to remove directory $_file" 467 | ;; 468 | 469 | *) 470 | critical_err "unknown installation directive" 471 | ;; 472 | esac 473 | 474 | done < "$_component_manifest" 475 | 476 | # Remove the installed component manifest 477 | verbose_msg "removing component manifest $_component_manifest" 478 | run rm "$_component_manifest" 479 | # This is a hard error because the installation is unrecoverable 480 | critical_need_ok "failed to remove installed manifest for component '$_installed_component'" 481 | 482 | # Update the installed component list 483 | local _modified_components="$(sed "/^$_installed_component\$/d" "$_md/components")" 484 | write_to_file "$_modified_components" "$_md/components" 485 | critical_need_ok "failed to update installed component list" 486 | fi 487 | done 488 | done 489 | 490 | # If there are no remaining components delete the manifest directory, 491 | # but only if we're doing an uninstall - if we're doing an install, 492 | # then leave the manifest directory around to hang onto the logs, 493 | # and any files not managed by the installer. 494 | if [ -n "${CFG_UNINSTALL-}" ]; then 495 | local _remaining_components="$(cat "$_md/components")" 496 | if [ ! -n "$_remaining_components" ]; then 497 | verbose_msg "removing manifest directory $_md" 498 | run rm -r "$_md" 499 | want_ok "failed to remove $_md" 500 | 501 | maybe_unconfigure_ld 502 | fi 503 | fi 504 | 505 | _uninstalled_something=true 506 | fi 507 | 508 | # There's no installed version. If we were asked to uninstall, then that's a problem. 509 | if [ -n "${CFG_UNINSTALL-}" -a "$_uninstalled_something" = false ] 510 | then 511 | err "unable to find installation manifest at $CFG_LIBDIR/$TEMPLATE_REL_MANIFEST_DIR" 512 | fi 513 | } 514 | 515 | install_components() { 516 | local _src_dir="$1" 517 | local _abs_libdir="$2" 518 | local _dest_prefix="$3" 519 | local _components="$4" 520 | 521 | local _component 522 | for _component in $_components; do 523 | 524 | msg "installing component '$_component'" 525 | 526 | # The file name of the manifest we're installing from 527 | local _input_manifest="$_src_dir/$_component/manifest.in" 528 | 529 | # Sanity check: do we have our input manifests? 530 | if [ ! -f "$_input_manifest" ]; then 531 | critical_err "manifest for $_component does not exist at $_input_manifest" 532 | fi 533 | 534 | # The installed manifest directory 535 | local _md="$_abs_libdir/$TEMPLATE_REL_MANIFEST_DIR" 536 | 537 | # The file name of the manifest we're going to create during install 538 | local _installed_manifest="$_md/manifest-$_component" 539 | 540 | # Create the installed manifest, which we will fill in with absolute file paths 541 | touch "$_installed_manifest" 542 | critical_need_ok "failed to create installed manifest" 543 | 544 | # Add this component to the installed component list 545 | append_to_file "$_component" "$_md/components" 546 | critical_need_ok "failed to update components list for $_component" 547 | 548 | # Now install, iterate through the new manifest and copy files 549 | local _directive 550 | while read _directive; do 551 | 552 | local _command=`echo $_directive | cut -f1 -d:` 553 | local _file=`echo $_directive | cut -f2 -d:` 554 | 555 | # Sanity checks 556 | if [ ! -n "$_command" ]; then critical_err "malformed installation directive"; fi 557 | if [ ! -n "$_file" ]; then critical_err "malformed installation directive"; fi 558 | 559 | # Decide the destination of the file 560 | local _file_install_path="$_dest_prefix/$_file" 561 | 562 | if echo "$_file" | grep "^etc/" > /dev/null 563 | then 564 | local _f="$(echo "$_file" | sed 's/^etc\///')" 565 | _file_install_path="$CFG_SYSCONFDIR/$_f" 566 | fi 567 | 568 | if echo "$_file" | grep "^bin/" > /dev/null 569 | then 570 | local _f="$(echo "$_file" | sed 's/^bin\///')" 571 | _file_install_path="$CFG_BINDIR/$_f" 572 | fi 573 | 574 | if echo "$_file" | grep "^lib/" > /dev/null 575 | then 576 | local _f="$(echo "$_file" | sed 's/^lib\///')" 577 | _file_install_path="$CFG_LIBDIR/$_f" 578 | fi 579 | 580 | if echo "$_file" | grep "^share" > /dev/null 581 | then 582 | local _f="$(echo "$_file" | sed 's/^share\///')" 583 | _file_install_path="$CFG_DATADIR/$_f" 584 | fi 585 | 586 | if echo "$_file" | grep "^share/man/" > /dev/null 587 | then 588 | local _f="$(echo "$_file" | sed 's/^share\/man\///')" 589 | _file_install_path="$CFG_MANDIR/$_f" 590 | fi 591 | 592 | # HACK: Try to support overriding --docdir. Paths with the form 593 | # "share/doc/$product/" can be redirected to a single --docdir 594 | # path. If the following detects that --docdir has been specified 595 | # then it will replace everything preceeding the "$product" path 596 | # component. The problem here is that the combined rust installer 597 | # contains two "products": rust and cargo; so the contents of those 598 | # directories will both be dumped into the same directory; and the 599 | # contents of those directories are _not_ disjoint. Since this feature 600 | # is almost entirely to support 'make install' anyway I don't expect 601 | # this problem to be a big deal in practice. 602 | if [ "$CFG_DOCDIR" != "" ] 603 | then 604 | if echo "$_file" | grep "^share/doc/" > /dev/null 605 | then 606 | local _f="$(echo "$_file" | sed 's/^share\/doc\/[^/]*\///')" 607 | _file_install_path="$CFG_DOCDIR/$_f" 608 | fi 609 | fi 610 | 611 | # Make sure there's a directory for it 612 | make_dir_recursive "$(dirname "$_file_install_path")" 613 | critical_need_ok "directory creation failed" 614 | 615 | # Make the path absolute so we can uninstall it later without 616 | # starting from the installation cwd 617 | absolutify "$_file_install_path" 618 | _file_install_path="$RETVAL" 619 | assert_nz "$_file_install_path" "file_install_path" 620 | 621 | case "$_command" in 622 | file ) 623 | 624 | verbose_msg "copying file $_file_install_path" 625 | 626 | maybe_backup_path "$_file_install_path" 627 | 628 | if echo "$_file" | grep "^bin/" > /dev/null || test -x "$_src_dir/$_component/$_file" 629 | then 630 | run cp "$_src_dir/$_component/$_file" "$_file_install_path" 631 | run chmod 755 "$_file_install_path" 632 | else 633 | run cp "$_src_dir/$_component/$_file" "$_file_install_path" 634 | run chmod 644 "$_file_install_path" 635 | fi 636 | critical_need_ok "file creation failed" 637 | 638 | # Update the manifest 639 | append_to_file "file:$_file_install_path" "$_installed_manifest" 640 | critical_need_ok "failed to update manifest" 641 | 642 | ;; 643 | 644 | dir ) 645 | 646 | verbose_msg "copying directory $_file_install_path" 647 | 648 | maybe_backup_path "$_file_install_path" 649 | 650 | run cp -R "$_src_dir/$_component/$_file" "$_file_install_path" 651 | critical_need_ok "failed to copy directory" 652 | 653 | # Set permissions. 0755 for dirs, 644 for files 654 | run chmod -R u+rwX,go+rX,go-w "$_file_install_path" 655 | critical_need_ok "failed to set permissions on directory" 656 | 657 | # Update the manifest 658 | append_to_file "dir:$_file_install_path" "$_installed_manifest" 659 | critical_need_ok "failed to update manifest" 660 | ;; 661 | 662 | *) 663 | critical_err "unknown installation directive" 664 | ;; 665 | esac 666 | done < "$_input_manifest" 667 | 668 | done 669 | } 670 | 671 | maybe_configure_ld() { 672 | local _abs_libdir="$1" 673 | 674 | local _ostype="$(uname -s)" 675 | assert_nz "$_ostype" "ostype" 676 | 677 | if [ "$_ostype" = "Linux" -a ! -n "${CFG_DISABLE_LDCONFIG-}" ]; then 678 | 679 | # Fedora-based systems do not configure the dynamic linker to look 680 | # /usr/local/lib, which is our default installation directory. To 681 | # make things just work, try to put that directory in 682 | # /etc/ld.so.conf.d/rust-installer-v1 so ldconfig picks it up. 683 | # Issue #30. 684 | # 685 | # This will get rm'd when the last component is uninstalled in 686 | # maybe_unconfigure_ld. 687 | if [ "$_abs_libdir" = "/usr/local/lib" -a -d "/etc/ld.so.conf.d" ]; then 688 | echo "$_abs_libdir" > "/etc/ld.so.conf.d/rust-installer-v1-$TEMPLATE_REL_MANIFEST_DIR.conf" 689 | if [ $? -ne 0 ]; then 690 | # This shouldn't happen if we've gotten this far 691 | # installing to /usr/local 692 | warn "failed to update /etc/ld.so.conf.d. this is unexpected" 693 | fi 694 | fi 695 | 696 | verbose_msg "running ldconfig" 697 | if [ -n "${CFG_VERBOSE-}" ]; then 698 | ldconfig 699 | else 700 | ldconfig 2> /dev/null 701 | fi 702 | if [ $? -ne 0 ] 703 | then 704 | warn "failed to run ldconfig. this may happen when not installing as root. run with --verbose to see the error" 705 | fi 706 | fi 707 | } 708 | 709 | maybe_unconfigure_ld() { 710 | local _ostype="$(uname -s)" 711 | assert_nz "$_ostype" "ostype" 712 | 713 | if [ "$_ostype" != "Linux" ]; then 714 | return 0 715 | fi 716 | 717 | rm "/etc/ld.so.conf.d/rust-installer-v1-$TEMPLATE_REL_MANIFEST_DIR.conf" 2> /dev/null 718 | # Above may fail since that file may not have been created on install 719 | } 720 | 721 | # Doing our own 'install'-like backup that is consistent across platforms 722 | maybe_backup_path() { 723 | local _file_install_path="$1" 724 | 725 | if [ -e "$_file_install_path" ]; then 726 | msg "backing up existing file at $_file_install_path" 727 | run mv -f "$_file_install_path" "$_file_install_path.old" 728 | critical_need_ok "failed to back up $_file_install_path" 729 | fi 730 | } 731 | 732 | install_uninstaller() { 733 | local _src_dir="$1" 734 | local _src_basename="$2" 735 | local _abs_libdir="$3" 736 | 737 | local _uninstaller="$_abs_libdir/$TEMPLATE_REL_MANIFEST_DIR/uninstall.sh" 738 | msg "creating uninstall script at $_uninstaller" 739 | run cp "$_src_dir/$_src_basename" "$_uninstaller" 740 | critical_need_ok "unable to install uninstaller" 741 | } 742 | 743 | do_preflight_sanity_checks() { 744 | local _src_dir="$1" 745 | local _dest_prefix="$2" 746 | 747 | # Sanity check: can we can write to the destination? 748 | verbose_msg "verifying destination is writable" 749 | make_dir_recursive "$CFG_LIBDIR" 750 | need_ok "can't write to destination. consider \`sudo\`." 751 | touch "$CFG_LIBDIR/rust-install-probe" > /dev/null 752 | if [ $? -ne 0 ] 753 | then 754 | err "can't write to destination. consider \`sudo\`." 755 | fi 756 | rm "$CFG_LIBDIR/rust-install-probe" 757 | need_ok "failed to remove install probe" 758 | 759 | # Sanity check: don't install to the directory containing the installer. 760 | # That would surely cause chaos. 761 | verbose_msg "verifying destination is not the same as source" 762 | local _prefix_dir="$(abs_path "$dest_prefix")" 763 | if [ "$_src_dir" = "$_dest_prefix" -a "${CFG_UNINSTALL-}" != 1 ]; then 764 | err "cannot install to same directory as installer" 765 | fi 766 | } 767 | 768 | verbose_msg "looking for install programs" 769 | verbose_msg 770 | 771 | need_cmd mkdir 772 | need_cmd printf 773 | need_cmd cut 774 | need_cmd grep 775 | need_cmd uname 776 | need_cmd tr 777 | need_cmd sed 778 | need_cmd chmod 779 | need_cmd env 780 | need_cmd pwd 781 | 782 | CFG_ARGS="${@:-}" 783 | 784 | HELP=0 785 | if [ "${1-}" = "--help" ] 786 | then 787 | HELP=1 788 | shift 789 | echo 790 | echo "Usage: $0 [options]" 791 | echo 792 | echo "Options:" 793 | echo 794 | else 795 | verbose_step_msg "processing arguments" 796 | fi 797 | 798 | OPTIONS="" 799 | BOOL_OPTIONS="" 800 | VAL_OPTIONS="" 801 | 802 | flag uninstall "only uninstall from the installation prefix" 803 | valopt destdir "" "set installation root" 804 | valopt prefix "/usr/local" "set installation prefix" 805 | 806 | # Avoid prepending an extra / to the prefix path if there's no destdir 807 | # NB: CFG vars here are undefined when passing --help 808 | if [ -z "${CFG_DESTDIR-}" ]; then 809 | CFG_DESTDIR_PREFIX="${CFG_PREFIX-}" 810 | else 811 | CFG_DESTDIR_PREFIX="$CFG_DESTDIR/$CFG_PREFIX" 812 | fi 813 | 814 | # NB This isn't quite the same definition as in `configure`. 815 | # just using 'lib' instead of configure's CFG_LIBDIR_RELATIVE 816 | valopt without "" "comma-separated list of components to not install" 817 | valopt components "" "comma-separated list of components to install" 818 | flag list-components "list available components" 819 | valopt sysconfdir "$CFG_DESTDIR_PREFIX/etc" "install system configuration files" 820 | valopt bindir "$CFG_DESTDIR_PREFIX/bin" "install binaries" 821 | valopt libdir "$CFG_DESTDIR_PREFIX/lib" "install libraries" 822 | valopt datadir "$CFG_DESTDIR_PREFIX/share" "install data" 823 | # NB We repeat datadir default value because we don't set CFG_DATADIR in --help 824 | valopt mandir "${CFG_DATADIR-"$CFG_DESTDIR_PREFIX/share"}/man" "install man pages in PATH" 825 | # NB See the docdir handling in install_components for an explanation of this 826 | # weird string 827 | valopt docdir "\" "install documentation in PATH" 828 | opt ldconfig 1 "run ldconfig after installation (Linux only)" 829 | opt verify 1 "obsolete" 830 | flag verbose "run with verbose output" 831 | 832 | if [ $HELP -eq 1 ] 833 | then 834 | echo 835 | exit 0 836 | fi 837 | 838 | verbose_step_msg "validating arguments" 839 | validate_opt 840 | 841 | # Template configuration. 842 | # These names surrounded by '%%` are replaced by sed when generating install.sh 843 | # FIXME: Might want to consider loading this from a file and not generating install.sh 844 | 845 | # Rust or Cargo 846 | TEMPLATE_PRODUCT_NAME=%%TEMPLATE_PRODUCT_NAME%% 847 | # rustlib or cargo 848 | TEMPLATE_REL_MANIFEST_DIR=%%TEMPLATE_REL_MANIFEST_DIR%% 849 | # 'Rust is ready to roll.' or 'Cargo is cool to cruise.' 850 | TEMPLATE_SUCCESS_MESSAGE=%%TEMPLATE_SUCCESS_MESSAGE%% 851 | # Locations to look for directories containing legacy, pre-versioned manifests 852 | TEMPLATE_LEGACY_MANIFEST_DIRS=%%TEMPLATE_LEGACY_MANIFEST_DIRS%% 853 | # The installer version 854 | TEMPLATE_RUST_INSTALLER_VERSION=%%TEMPLATE_RUST_INSTALLER_VERSION%% 855 | 856 | # OK, let's get installing ... 857 | 858 | # This is where we are installing from 859 | src_dir="$(abs_path $(dirname "$0"))" 860 | 861 | # The name of the script 862 | src_basename="$(basename "$0")" 863 | 864 | # If we've been run as 'uninstall.sh' (from the existing installation) 865 | # then we're doing a full uninstall, as opposed to the --uninstall flag 866 | # which just means 'uninstall my components'. 867 | if [ "$src_basename" = "uninstall.sh" ]; then 868 | if [ "${*:-}" != "" ]; then 869 | # Currently don't know what to do with arguments in this mode 870 | err "uninstall.sh does not take any arguments" 871 | fi 872 | CFG_UNINSTALL=1 873 | CFG_DESTDIR_PREFIX="$(abs_path "$src_dir/../../")" 874 | CFG_LIBDIR="$(abs_path "$src_dir/../")" 875 | fi 876 | 877 | # This is where we are installing to 878 | dest_prefix="$CFG_DESTDIR_PREFIX" 879 | 880 | # Open the components file to get the list of components to install. 881 | # NB: During install this components file is read from the installer's 882 | # source dir, during a full uninstall it's read from the manifest dir, 883 | # and thus contains all installed components. 884 | components=`cat "$src_dir/components"` 885 | 886 | # Sanity check: do we have components? 887 | if [ ! -n "$components" ]; then 888 | err "unable to find installation components" 889 | fi 890 | 891 | # If the user asked for a component list, do that and exit 892 | if [ -n "${CFG_LIST_COMPONENTS-}" ]; then 893 | echo 894 | echo "# Available components" 895 | echo 896 | for component in $components; do 897 | echo "* $component" 898 | done 899 | echo 900 | exit 0 901 | fi 902 | 903 | # If the user specified which components to install/uninstall, 904 | # then validate that they exist and select them for installation 905 | if [ -n "$CFG_COMPONENTS" ]; then 906 | # Remove commas 907 | user_components="$(echo "$CFG_COMPONENTS" | sed "s/,/ /g")" 908 | for user_component in $user_components; do 909 | found=false 910 | for my_component in $components; do 911 | if [ "$user_component" = "$my_component" ]; then 912 | found=true 913 | fi 914 | done 915 | if [ "$found" = false ]; then 916 | err "unknown component: $user_component" 917 | fi 918 | done 919 | components="$user_components" 920 | fi 921 | 922 | if [ -n "$CFG_WITHOUT" ]; then 923 | without_components="$(echo "$CFG_WITHOUT" | sed "s/,/ /g")" 924 | 925 | # This does **not** check that all components in without_components are 926 | # actually present in the list of available components. 927 | # 928 | # Currently that's considered good as it makes it easier to be compatible 929 | # with multiple Rust versions (which may change the exact list of 930 | # components) when writing install scripts. 931 | new_comp="" 932 | for component in $components; do 933 | found=false 934 | for my_component in $without_components; do 935 | if [ "$component" = "$my_component" ]; then 936 | found=true 937 | fi 938 | done 939 | if [ "$found" = false ]; then 940 | # If we didn't find the component in without, then add it to new list. 941 | new_comp="$new_comp $component" 942 | fi 943 | done 944 | components="$new_comp" 945 | fi 946 | 947 | if [ -z "$components" ]; then 948 | if [ -z "${CFG_UNINSTALL-}" ]; then 949 | err "no components selected for installation" 950 | else 951 | err "no components selected for uninstallation" 952 | fi 953 | fi 954 | 955 | do_preflight_sanity_checks "$src_dir" "$dest_prefix" 956 | 957 | # Using an absolute path to libdir in a few places so that the status 958 | # messages are consistently using absolute paths. 959 | absolutify "$CFG_LIBDIR" 960 | abs_libdir="$RETVAL" 961 | assert_nz "$abs_libdir" "abs_libdir" 962 | 963 | # Create the manifest directory, where we will put our logs 964 | make_dir_recursive "$abs_libdir/$TEMPLATE_REL_MANIFEST_DIR" 965 | need_ok "failed to create $TEMPLATE_REL_MANIFEST_DIR" 966 | 967 | # Log messages and commands 968 | init_logging "$abs_libdir" 969 | 970 | # First do any uninstallation, including from legacy manifests. This 971 | # will also upgrade the metadata of existing installs. 972 | uninstall_components "$abs_libdir" "$dest_prefix" "$components" 973 | 974 | # If we're only uninstalling then exit 975 | if [ -n "${CFG_UNINSTALL-}" ] 976 | then 977 | echo 978 | echo " $TEMPLATE_PRODUCT_NAME is uninstalled." 979 | echo 980 | exit 0 981 | fi 982 | 983 | # Create the manifest directory again! uninstall_legacy 984 | # may have deleted it. 985 | make_dir_recursive "$abs_libdir/$TEMPLATE_REL_MANIFEST_DIR" 986 | need_ok "failed to create $TEMPLATE_REL_MANIFEST_DIR" 987 | 988 | # Drop the version number into the manifest dir 989 | write_to_file "$TEMPLATE_RUST_INSTALLER_VERSION" "$abs_libdir/$TEMPLATE_REL_MANIFEST_DIR/rust-installer-version" 990 | critical_need_ok "failed to write installer version" 991 | 992 | # Install the uninstaller 993 | install_uninstaller "$src_dir" "$src_basename" "$abs_libdir" 994 | 995 | # Install each component 996 | install_components "$src_dir" "$abs_libdir" "$dest_prefix" "$components" 997 | 998 | # Make dynamic libraries available to the linker 999 | maybe_configure_ld "$abs_libdir" 1000 | 1001 | echo 1002 | echo " $TEMPLATE_SUCCESS_MESSAGE" 1003 | echo 1004 | 1005 | 1006 | -------------------------------------------------------------------------------- /src/remove_dir_all.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use std::io; 4 | use std::path::Path; 5 | 6 | #[cfg(not(windows))] 7 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { 8 | ::std::fs::remove_dir_all(path) 9 | } 10 | 11 | #[cfg(windows)] 12 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { 13 | win::remove_dir_all(path) 14 | } 15 | 16 | #[cfg(windows)] 17 | mod win { 18 | use winapi::ctypes::{c_uint, c_ushort}; 19 | use winapi::shared::minwindef::{BOOL, DWORD, FALSE, FILETIME, LPVOID}; 20 | use winapi::shared::winerror::{ 21 | ERROR_CALL_NOT_IMPLEMENTED, ERROR_INSUFFICIENT_BUFFER, ERROR_NO_MORE_FILES, 22 | }; 23 | use winapi::um::errhandlingapi::{GetLastError, SetLastError}; 24 | use winapi::um::fileapi::{ 25 | CreateFileW, FindFirstFileW, FindNextFileW, GetFileInformationByHandle, 26 | }; 27 | use winapi::um::fileapi::{BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW}; 28 | use winapi::um::fileapi::{FILE_BASIC_INFO, FILE_RENAME_INFO, TRUNCATE_EXISTING}; 29 | use winapi::um::fileapi::{OPEN_ALWAYS, OPEN_EXISTING}; 30 | use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; 31 | use winapi::um::ioapiset::DeviceIoControl; 32 | use winapi::um::libloaderapi::{GetModuleHandleW, GetProcAddress}; 33 | use winapi::um::minwinbase::{ 34 | FileBasicInfo, FileRenameInfo, FILE_INFO_BY_HANDLE_CLASS, WIN32_FIND_DATAW, 35 | }; 36 | use winapi::um::winbase::SECURITY_SQOS_PRESENT; 37 | use winapi::um::winbase::{ 38 | FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_REPARSE_POINT, 39 | }; 40 | use winapi::um::winioctl::FSCTL_GET_REPARSE_POINT; 41 | use winapi::um::winnt::{DELETE, FILE_ATTRIBUTE_DIRECTORY, HANDLE, LPCWSTR}; 42 | use winapi::um::winnt::{FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_REPARSE_POINT}; 43 | use winapi::um::winnt::{FILE_GENERIC_WRITE, FILE_WRITE_DATA, GENERIC_READ, GENERIC_WRITE}; 44 | use winapi::um::winnt::{FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES}; 45 | use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE}; 46 | use winapi::um::winnt::{IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, LARGE_INTEGER}; 47 | 48 | use std::ffi::{OsStr, OsString}; 49 | use std::io; 50 | use std::mem; 51 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; 52 | use std::path::{Path, PathBuf}; 53 | use std::ptr; 54 | use std::sync::Arc; 55 | 56 | pub fn remove_dir_all(path: &Path) -> io::Result<()> { 57 | // On Windows it is not enough to just recursively remove the contents of a 58 | // directory and then the directory itself. Deleting does not happen 59 | // instantaneously, but is scheduled. 60 | // To work around this, we move the file or directory to some `base_dir` 61 | // right before deletion to avoid races. 62 | // 63 | // As `base_dir` we choose the parent dir of the directory we want to 64 | // remove. We very probably have permission to create files here, as we 65 | // already need write permission in this dir to delete the directory. And it 66 | // should be on the same volume. 67 | // 68 | // To handle files with names like `CON` and `morse .. .`, and when a 69 | // directory structure is so deep it needs long path names the path is first 70 | // converted to a `//?/`-path with `get_path()`. 71 | // 72 | // To make sure we don't leave a moved file laying around if the process 73 | // crashes before we can delete the file, we do all operations on an file 74 | // handle. By opening a file with `FILE_FLAG_DELETE_ON_CLOSE` Windows will 75 | // always delete the file when the handle closes. 76 | // 77 | // All files are renamed to be in the `base_dir`, and have their name 78 | // changed to "rm-". After every rename the counter is increased. 79 | // Rename should not overwrite possibly existing files in the base dir. So 80 | // if it fails with `AlreadyExists`, we just increase the counter and try 81 | // again. 82 | // 83 | // For read-only files and directories we first have to remove the read-only 84 | // attribute before we can move or delete them. This also removes the 85 | // attribute from possible hardlinks to the file, so just before closing we 86 | // restore the read-only attribute. 87 | // 88 | // If 'path' points to a directory symlink or junction we should not 89 | // recursively remove the target of the link, but only the link itself. 90 | // 91 | // Moving and deleting is guaranteed to succeed if we are able to open the 92 | // file with `DELETE` permission. If others have the file open we only have 93 | // `DELETE` permission if they have specified `FILE_SHARE_DELETE`. We can 94 | // also delete the file now, but it will not disappear until all others have 95 | // closed the file. But no-one can open the file after we have flagged it 96 | // for deletion. 97 | 98 | // Open the path once to get the canonical path, file type and attributes. 99 | let (path, metadata) = { 100 | let mut opts = OpenOptions::new(); 101 | opts.access_mode(FILE_READ_ATTRIBUTES); 102 | opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); 103 | let file = File::open(path, &opts)?; 104 | (get_path(&file)?, file.file_attr()?) 105 | }; 106 | 107 | let mut ctx = RmdirContext { 108 | base_dir: match path.parent() { 109 | Some(dir) => dir, 110 | None => { 111 | return Err(io::Error::new( 112 | io::ErrorKind::PermissionDenied, 113 | "can't delete root directory", 114 | )) 115 | } 116 | }, 117 | readonly: metadata.perm().readonly(), 118 | counter: 0, 119 | }; 120 | 121 | let filetype = metadata.file_type(); 122 | if filetype.is_dir() { 123 | remove_dir_all_recursive(path.as_ref(), &mut ctx) 124 | } else if filetype.is_symlink_dir() { 125 | remove_item(path.as_ref(), &mut ctx) 126 | } else { 127 | Err(io::Error::new( 128 | io::ErrorKind::PermissionDenied, 129 | "Not a directory", 130 | )) 131 | } 132 | } 133 | 134 | fn readdir(p: &Path) -> io::Result { 135 | let root = p.to_path_buf(); 136 | let star = p.join("*"); 137 | let path = to_u16s(&star)?; 138 | 139 | unsafe { 140 | let mut wfd = mem::zeroed(); 141 | let find_handle = FindFirstFileW(path.as_ptr(), &mut wfd); 142 | if find_handle != INVALID_HANDLE_VALUE { 143 | Ok(ReadDir { 144 | handle: FindNextFileHandle(find_handle), 145 | root: Arc::new(root), 146 | first: Some(wfd), 147 | }) 148 | } else { 149 | Err(io::Error::last_os_error()) 150 | } 151 | } 152 | } 153 | 154 | struct RmdirContext<'a> { 155 | base_dir: &'a Path, 156 | readonly: bool, 157 | counter: u64, 158 | } 159 | 160 | fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { 161 | let dir_readonly = ctx.readonly; 162 | for child in readdir(path)? { 163 | let child = child?; 164 | let child_type = child.file_type()?; 165 | ctx.readonly = child.metadata()?.perm().readonly(); 166 | if child_type.is_dir() { 167 | remove_dir_all_recursive(&child.path(), ctx)?; 168 | } else { 169 | remove_item(&child.path().as_ref(), ctx)?; 170 | } 171 | } 172 | ctx.readonly = dir_readonly; 173 | remove_item(path, ctx) 174 | } 175 | 176 | fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { 177 | if !ctx.readonly { 178 | let mut opts = OpenOptions::new(); 179 | opts.access_mode(DELETE); 180 | opts.custom_flags( 181 | FILE_FLAG_BACKUP_SEMANTICS | // delete directory 182 | FILE_FLAG_OPEN_REPARSE_POINT | // delete symlink 183 | FILE_FLAG_DELETE_ON_CLOSE, 184 | ); 185 | let file = File::open(path, &opts)?; 186 | move_item(&file, ctx) 187 | } else { 188 | // remove read-only permision 189 | set_perm(&path, FilePermissions::new())?; 190 | // move and delete file, similar to !readonly. 191 | // only the access mode is different. 192 | let mut opts = OpenOptions::new(); 193 | opts.access_mode(DELETE | FILE_WRITE_ATTRIBUTES); 194 | opts.custom_flags( 195 | FILE_FLAG_BACKUP_SEMANTICS 196 | | FILE_FLAG_OPEN_REPARSE_POINT 197 | | FILE_FLAG_DELETE_ON_CLOSE, 198 | ); 199 | let file = File::open(path, &opts)?; 200 | move_item(&file, ctx)?; 201 | // restore read-only flag just in case there are other hard links 202 | let mut perm = FilePermissions::new(); 203 | perm.set_readonly(true); 204 | let _ = file.set_perm(perm); // ignore if this fails 205 | Ok(()) 206 | } 207 | } 208 | 209 | macro_rules! compat_fn { 210 | ($module:ident: $( 211 | fn $symbol:ident($($argname:ident: $argtype:ty),*) 212 | -> $rettype:ty { 213 | $($body:expr);* 214 | } 215 | )*) => ($( 216 | #[allow(unused_variables)] 217 | unsafe fn $symbol($($argname: $argtype),*) -> $rettype { 218 | use std::sync::atomic::{AtomicUsize, Ordering}; 219 | use std::mem; 220 | use std::ffi::CString; 221 | type F = unsafe extern "system" fn($($argtype),*) -> $rettype; 222 | 223 | lazy_static! { static ref PTR: AtomicUsize = AtomicUsize::new(0);} 224 | 225 | fn lookup(module: &str, symbol: &str) -> Option { 226 | let mut module: Vec = module.encode_utf16().collect(); 227 | module.push(0); 228 | let symbol = CString::new(symbol).unwrap(); 229 | unsafe { 230 | let handle = GetModuleHandleW(module.as_ptr()); 231 | match GetProcAddress(handle, symbol.as_ptr()) as usize { 232 | 0 => None, 233 | n => Some(n), 234 | } 235 | } 236 | } 237 | 238 | fn store_func(ptr: &AtomicUsize, module: &str, symbol: &str, 239 | fallback: usize) -> usize { 240 | let value = lookup(module, symbol).unwrap_or(fallback); 241 | ptr.store(value, Ordering::SeqCst); 242 | value 243 | } 244 | 245 | fn load() -> usize { 246 | store_func(&PTR, stringify!($module), stringify!($symbol), fallback as usize) 247 | } 248 | unsafe extern "system" fn fallback($($argname: $argtype),*) 249 | -> $rettype { 250 | $($body);* 251 | } 252 | 253 | let addr = match PTR.load(Ordering::SeqCst) { 254 | 0 => load(), 255 | n => n, 256 | }; 257 | mem::transmute::(addr)($($argname),*) 258 | } 259 | )*) 260 | } 261 | 262 | compat_fn! { 263 | kernel32: 264 | fn GetFinalPathNameByHandleW(_hFile: HANDLE, 265 | _lpszFilePath: LPCWSTR, 266 | _cchFilePath: DWORD, 267 | _dwFlags: DWORD) -> DWORD { 268 | SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 269 | } 270 | fn SetFileInformationByHandle(_hFile: HANDLE, 271 | _FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, 272 | _lpFileInformation: LPVOID, 273 | _dwBufferSize: DWORD) -> BOOL { 274 | SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 275 | } 276 | } 277 | 278 | fn cvt(i: i32) -> io::Result { 279 | if i == 0 { 280 | Err(io::Error::last_os_error()) 281 | } else { 282 | Ok(i) 283 | } 284 | } 285 | 286 | fn to_u16s>(s: S) -> io::Result> { 287 | fn inner(s: &OsStr) -> io::Result> { 288 | let mut maybe_result: Vec = s.encode_wide().collect(); 289 | if maybe_result.iter().any(|&u| u == 0) { 290 | return Err(io::Error::new( 291 | io::ErrorKind::InvalidInput, 292 | "strings passed to WinAPI cannot contain NULs", 293 | )); 294 | } 295 | maybe_result.push(0); 296 | Ok(maybe_result) 297 | } 298 | inner(s.as_ref()) 299 | } 300 | 301 | fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { 302 | match v.iter().position(|c| *c == 0) { 303 | // don't include the 0 304 | Some(i) => &v[..i], 305 | None => v, 306 | } 307 | } 308 | 309 | fn fill_utf16_buf(mut f1: F1, f2: F2) -> io::Result 310 | where 311 | F1: FnMut(*mut u16, DWORD) -> DWORD, 312 | F2: FnOnce(&[u16]) -> T, 313 | { 314 | // Start off with a stack buf but then spill over to the heap if we end up 315 | // needing more space. 316 | let mut stack_buf = [0u16; 512]; 317 | let mut heap_buf = Vec::new(); 318 | unsafe { 319 | let mut n = stack_buf.len(); 320 | loop { 321 | let buf = if n <= stack_buf.len() { 322 | &mut stack_buf[..] 323 | } else { 324 | let extra = n - heap_buf.len(); 325 | heap_buf.reserve(extra); 326 | heap_buf.set_len(n); 327 | &mut heap_buf[..] 328 | }; 329 | 330 | // This function is typically called on windows API functions which 331 | // will return the correct length of the string, but these functions 332 | // also return the `0` on error. In some cases, however, the 333 | // returned "correct length" may actually be 0! 334 | // 335 | // To handle this case we call `SetLastError` to reset it to 0 and 336 | // then check it again if we get the "0 error value". If the "last 337 | // error" is still 0 then we interpret it as a 0 length buffer and 338 | // not an actual error. 339 | SetLastError(0); 340 | let k = match f1(buf.as_mut_ptr(), n as DWORD) { 341 | 0 if GetLastError() == 0 => 0, 342 | 0 => return Err(io::Error::last_os_error()), 343 | n => n, 344 | } as usize; 345 | if k == n && GetLastError() == ERROR_INSUFFICIENT_BUFFER { 346 | n *= 2; 347 | } else if k >= n { 348 | n = k; 349 | } else { 350 | return Ok(f2(&buf[..k])); 351 | } 352 | } 353 | } 354 | } 355 | 356 | #[derive(Clone, PartialEq, Eq, Debug, Default)] 357 | struct FilePermissions { 358 | readonly: bool, 359 | } 360 | 361 | impl FilePermissions { 362 | fn new() -> FilePermissions { 363 | Default::default() 364 | } 365 | fn readonly(&self) -> bool { 366 | self.readonly 367 | } 368 | fn set_readonly(&mut self, readonly: bool) { 369 | self.readonly = readonly 370 | } 371 | } 372 | 373 | #[derive(Clone)] 374 | struct OpenOptions { 375 | // generic 376 | read: bool, 377 | write: bool, 378 | append: bool, 379 | truncate: bool, 380 | create: bool, 381 | create_new: bool, 382 | // system-specific 383 | custom_flags: u32, 384 | access_mode: Option, 385 | attributes: DWORD, 386 | share_mode: DWORD, 387 | security_qos_flags: DWORD, 388 | security_attributes: usize, // FIXME: should be a reference 389 | } 390 | 391 | impl OpenOptions { 392 | fn new() -> OpenOptions { 393 | OpenOptions { 394 | // generic 395 | read: false, 396 | write: false, 397 | append: false, 398 | truncate: false, 399 | create: false, 400 | create_new: false, 401 | // system-specific 402 | custom_flags: 0, 403 | access_mode: None, 404 | share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 405 | attributes: 0, 406 | security_qos_flags: 0, 407 | security_attributes: 0, 408 | } 409 | } 410 | fn custom_flags(&mut self, flags: u32) { 411 | self.custom_flags = flags; 412 | } 413 | fn access_mode(&mut self, access_mode: u32) { 414 | self.access_mode = Some(access_mode); 415 | } 416 | 417 | fn get_access_mode(&self) -> io::Result { 418 | const ERROR_INVALID_PARAMETER: i32 = 87; 419 | 420 | match (self.read, self.write, self.append, self.access_mode) { 421 | (_, _, _, Some(mode)) => Ok(mode), 422 | (true, false, false, None) => Ok(GENERIC_READ), 423 | (false, true, false, None) => Ok(GENERIC_WRITE), 424 | (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), 425 | (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), 426 | (true, _, true, None) => Ok(GENERIC_READ | (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), 427 | (false, false, false, None) => { 428 | Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)) 429 | } 430 | } 431 | } 432 | 433 | fn get_creation_mode(&self) -> io::Result { 434 | const ERROR_INVALID_PARAMETER: i32 = 87; 435 | 436 | match (self.write, self.append) { 437 | (true, false) => {} 438 | (false, false) => { 439 | if self.truncate || self.create || self.create_new { 440 | return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); 441 | } 442 | } 443 | (_, true) => { 444 | if self.truncate && !self.create_new { 445 | return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); 446 | } 447 | } 448 | } 449 | 450 | Ok(match (self.create, self.truncate, self.create_new) { 451 | (false, false, false) => OPEN_EXISTING, 452 | (true, false, false) => OPEN_ALWAYS, 453 | (false, true, false) => TRUNCATE_EXISTING, 454 | (true, true, false) => CREATE_ALWAYS, 455 | (_, _, true) => CREATE_NEW, 456 | }) 457 | } 458 | 459 | fn get_flags_and_attributes(&self) -> DWORD { 460 | self.custom_flags 461 | | self.attributes 462 | | self.security_qos_flags 463 | | if self.security_qos_flags != 0 { 464 | SECURITY_SQOS_PRESENT 465 | } else { 466 | 0 467 | } 468 | | if self.create_new { 469 | FILE_FLAG_OPEN_REPARSE_POINT 470 | } else { 471 | 0 472 | } 473 | } 474 | } 475 | 476 | struct File { 477 | handle: Handle, 478 | } 479 | 480 | impl File { 481 | fn open(path: &Path, opts: &OpenOptions) -> io::Result { 482 | let path = to_u16s(path)?; 483 | let handle = unsafe { 484 | CreateFileW( 485 | path.as_ptr(), 486 | opts.get_access_mode()?, 487 | opts.share_mode, 488 | opts.security_attributes as *mut _, 489 | opts.get_creation_mode()?, 490 | opts.get_flags_and_attributes(), 491 | ptr::null_mut(), 492 | ) 493 | }; 494 | if handle == INVALID_HANDLE_VALUE { 495 | Err(io::Error::last_os_error()) 496 | } else { 497 | Ok(File { 498 | handle: Handle::new(handle), 499 | }) 500 | } 501 | } 502 | 503 | fn file_attr(&self) -> io::Result { 504 | unsafe { 505 | let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); 506 | cvt(GetFileInformationByHandle(self.handle.raw(), &mut info))?; 507 | let mut attr = FileAttr { 508 | attributes: info.dwFileAttributes, 509 | creation_time: info.ftCreationTime, 510 | last_access_time: info.ftLastAccessTime, 511 | last_write_time: info.ftLastWriteTime, 512 | file_size: ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64), 513 | reparse_tag: 0, 514 | }; 515 | if attr.is_reparse_point() { 516 | let mut b = [0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; 517 | if let Ok((_, buf)) = self.reparse_point(&mut b) { 518 | attr.reparse_tag = buf.ReparseTag; 519 | } 520 | } 521 | Ok(attr) 522 | } 523 | } 524 | 525 | fn set_attributes(&self, attr: DWORD) -> io::Result<()> { 526 | let zero: LARGE_INTEGER = unsafe { mem::zeroed() }; 527 | 528 | let mut info = FILE_BASIC_INFO { 529 | CreationTime: zero, // do not change 530 | LastAccessTime: zero, // do not change 531 | LastWriteTime: zero, // do not change 532 | ChangeTime: zero, // do not change 533 | FileAttributes: attr, 534 | }; 535 | let size = mem::size_of_val(&info); 536 | cvt(unsafe { 537 | SetFileInformationByHandle( 538 | self.handle.raw(), 539 | FileBasicInfo, 540 | &mut info as *mut _ as *mut _, 541 | size as DWORD, 542 | ) 543 | })?; 544 | Ok(()) 545 | } 546 | 547 | fn rename(&self, new: &Path, replace: bool) -> io::Result<()> { 548 | // &self must be opened with DELETE permission 549 | use std::iter; 550 | #[cfg(target_arch = "x86")] 551 | const STRUCT_SIZE: usize = 12; 552 | #[cfg(target_arch = "x86_64")] 553 | const STRUCT_SIZE: usize = 20; 554 | 555 | // FIXME: check for internal NULs in 'new' 556 | let mut data: Vec = iter::repeat(0u16) 557 | .take(STRUCT_SIZE / 2) 558 | .chain(new.as_os_str().encode_wide()) 559 | .collect(); 560 | data.push(0); 561 | let size = data.len() * 2; 562 | 563 | unsafe { 564 | // Thanks to alignment guarantees on Windows this works 565 | // (8 for 32-bit and 16 for 64-bit) 566 | let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO; 567 | // The type of ReplaceIfExists is BOOL, but it actually expects a 568 | // BOOLEAN. This means true is -1, not c::TRUE. 569 | (*info).ReplaceIfExists = if replace { -1 } else { FALSE }; 570 | (*info).RootDirectory = ptr::null_mut(); 571 | (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD; 572 | cvt(SetFileInformationByHandle( 573 | self.handle().raw(), 574 | FileRenameInfo, 575 | data.as_mut_ptr() as *mut _ as *mut _, 576 | size as DWORD, 577 | ))?; 578 | Ok(()) 579 | } 580 | } 581 | fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { 582 | let attr = self.file_attr()?.attributes; 583 | if perm.readonly == (attr & FILE_ATTRIBUTE_READONLY != 0) { 584 | Ok(()) 585 | } else if perm.readonly { 586 | self.set_attributes(attr | FILE_ATTRIBUTE_READONLY) 587 | } else { 588 | self.set_attributes(attr & !FILE_ATTRIBUTE_READONLY) 589 | } 590 | } 591 | 592 | fn handle(&self) -> &Handle { 593 | &self.handle 594 | } 595 | 596 | fn reparse_point<'a>( 597 | &self, 598 | space: &'a mut [u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE], 599 | ) -> io::Result<(DWORD, &'a REPARSE_DATA_BUFFER)> { 600 | unsafe { 601 | let mut bytes = 0; 602 | cvt({ 603 | DeviceIoControl( 604 | self.handle.raw(), 605 | FSCTL_GET_REPARSE_POINT, 606 | ptr::null_mut(), 607 | 0, 608 | space.as_mut_ptr() as *mut _, 609 | space.len() as DWORD, 610 | &mut bytes, 611 | ptr::null_mut(), 612 | ) 613 | })?; 614 | Ok((bytes, &*(space.as_ptr() as *const REPARSE_DATA_BUFFER))) 615 | } 616 | } 617 | } 618 | 619 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 620 | enum FileType { 621 | Dir, 622 | File, 623 | SymlinkFile, 624 | SymlinkDir, 625 | ReparsePoint, 626 | MountPoint, 627 | } 628 | 629 | impl FileType { 630 | fn new(attrs: DWORD, reparse_tag: DWORD) -> FileType { 631 | match ( 632 | attrs & FILE_ATTRIBUTE_DIRECTORY != 0, 633 | attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0, 634 | reparse_tag, 635 | ) { 636 | (false, false, _) => FileType::File, 637 | (true, false, _) => FileType::Dir, 638 | (false, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkFile, 639 | (true, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkDir, 640 | (true, true, IO_REPARSE_TAG_MOUNT_POINT) => FileType::MountPoint, 641 | (_, true, _) => FileType::ReparsePoint, 642 | // Note: if a _file_ has a reparse tag of the type IO_REPARSE_TAG_MOUNT_POINT it is 643 | // invalid, as junctions always have to be dirs. We set the filetype to ReparsePoint 644 | // to indicate it is something symlink-like, but not something you can follow. 645 | } 646 | } 647 | 648 | fn is_dir(&self) -> bool { 649 | *self == FileType::Dir 650 | } 651 | fn is_symlink_dir(&self) -> bool { 652 | *self == FileType::SymlinkDir || *self == FileType::MountPoint 653 | } 654 | } 655 | 656 | impl DirEntry { 657 | fn new(root: &Arc, wfd: &WIN32_FIND_DATAW) -> Option { 658 | let first_bytes = &wfd.cFileName[0..3]; 659 | if first_bytes.starts_with(&[46, 0]) || first_bytes.starts_with(&[46, 46, 0]) { 660 | None 661 | } else { 662 | Some(DirEntry { 663 | root: root.clone(), 664 | data: *wfd, 665 | }) 666 | } 667 | } 668 | 669 | fn path(&self) -> PathBuf { 670 | self.root.join(&self.file_name()) 671 | } 672 | 673 | fn file_name(&self) -> OsString { 674 | let filename = truncate_utf16_at_nul(&self.data.cFileName); 675 | OsString::from_wide(filename) 676 | } 677 | 678 | fn file_type(&self) -> io::Result { 679 | Ok(FileType::new( 680 | self.data.dwFileAttributes, 681 | /* reparse_tag = */ self.data.dwReserved0, 682 | )) 683 | } 684 | 685 | fn metadata(&self) -> io::Result { 686 | Ok(FileAttr { 687 | attributes: self.data.dwFileAttributes, 688 | creation_time: self.data.ftCreationTime, 689 | last_access_time: self.data.ftLastAccessTime, 690 | last_write_time: self.data.ftLastWriteTime, 691 | file_size: ((self.data.nFileSizeHigh as u64) << 32) 692 | | (self.data.nFileSizeLow as u64), 693 | reparse_tag: if self.data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { 694 | // reserved unless this is a reparse point 695 | self.data.dwReserved0 696 | } else { 697 | 0 698 | }, 699 | }) 700 | } 701 | } 702 | 703 | struct DirEntry { 704 | root: Arc, 705 | data: WIN32_FIND_DATAW, 706 | } 707 | 708 | struct ReadDir { 709 | handle: FindNextFileHandle, 710 | root: Arc, 711 | first: Option, 712 | } 713 | 714 | impl Iterator for ReadDir { 715 | type Item = io::Result; 716 | fn next(&mut self) -> Option> { 717 | if let Some(first) = self.first.take() { 718 | if let Some(e) = DirEntry::new(&self.root, &first) { 719 | return Some(Ok(e)); 720 | } 721 | } 722 | unsafe { 723 | let mut wfd = mem::zeroed(); 724 | loop { 725 | if FindNextFileW(self.handle.0, &mut wfd) == 0 { 726 | if GetLastError() == ERROR_NO_MORE_FILES { 727 | return None; 728 | } else { 729 | return Some(Err(io::Error::last_os_error())); 730 | } 731 | } 732 | if let Some(e) = DirEntry::new(&self.root, &wfd) { 733 | return Some(Ok(e)); 734 | } 735 | } 736 | } 737 | } 738 | } 739 | 740 | #[derive(Clone)] 741 | struct FileAttr { 742 | attributes: DWORD, 743 | creation_time: FILETIME, 744 | last_access_time: FILETIME, 745 | last_write_time: FILETIME, 746 | file_size: u64, 747 | reparse_tag: DWORD, 748 | } 749 | 750 | impl FileAttr { 751 | fn perm(&self) -> FilePermissions { 752 | FilePermissions { 753 | readonly: self.attributes & FILE_ATTRIBUTE_READONLY != 0, 754 | } 755 | } 756 | 757 | fn file_type(&self) -> FileType { 758 | FileType::new(self.attributes, self.reparse_tag) 759 | } 760 | 761 | fn is_reparse_point(&self) -> bool { 762 | self.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 763 | } 764 | } 765 | 766 | #[repr(C)] 767 | struct REPARSE_DATA_BUFFER { 768 | ReparseTag: c_uint, 769 | ReparseDataLength: c_ushort, 770 | Reserved: c_ushort, 771 | rest: (), 772 | } 773 | 774 | const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; 775 | 776 | /// An owned container for `HANDLE` object, closing them on Drop. 777 | /// 778 | /// All methods are inherited through a `Deref` impl to `RawHandle` 779 | struct Handle(RawHandle); 780 | 781 | use std::ops::Deref; 782 | 783 | /// A wrapper type for `HANDLE` objects to give them proper Send/Sync inference 784 | /// as well as Rust-y methods. 785 | /// 786 | /// This does **not** drop the handle when it goes out of scope, use `Handle` 787 | /// instead for that. 788 | #[derive(Copy, Clone)] 789 | struct RawHandle(HANDLE); 790 | 791 | unsafe impl Send for RawHandle {} 792 | unsafe impl Sync for RawHandle {} 793 | 794 | impl Handle { 795 | fn new(handle: HANDLE) -> Handle { 796 | Handle(RawHandle::new(handle)) 797 | } 798 | } 799 | 800 | impl Deref for Handle { 801 | type Target = RawHandle; 802 | fn deref(&self) -> &RawHandle { 803 | &self.0 804 | } 805 | } 806 | 807 | impl Drop for Handle { 808 | fn drop(&mut self) { 809 | unsafe { 810 | let _ = CloseHandle(self.raw()); 811 | } 812 | } 813 | } 814 | 815 | impl RawHandle { 816 | fn new(handle: HANDLE) -> RawHandle { 817 | RawHandle(handle) 818 | } 819 | 820 | fn raw(&self) -> HANDLE { 821 | self.0 822 | } 823 | } 824 | 825 | struct FindNextFileHandle(HANDLE); 826 | 827 | fn get_path(f: &File) -> io::Result { 828 | fill_utf16_buf( 829 | |buf, sz| unsafe { 830 | GetFinalPathNameByHandleW(f.handle.raw(), buf, sz, VOLUME_NAME_DOS) 831 | }, 832 | |buf| PathBuf::from(OsString::from_wide(buf)), 833 | ) 834 | } 835 | 836 | fn move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()> { 837 | let mut tmpname = ctx.base_dir.join(format! {"rm-{}", ctx.counter}); 838 | ctx.counter += 1; 839 | // Try to rename the file. If it already exists, just retry with an other 840 | // filename. 841 | while let Err(err) = file.rename(tmpname.as_ref(), false) { 842 | if err.kind() != io::ErrorKind::AlreadyExists { 843 | return Err(err); 844 | }; 845 | tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); 846 | ctx.counter += 1; 847 | } 848 | Ok(()) 849 | } 850 | 851 | fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { 852 | let mut opts = OpenOptions::new(); 853 | opts.access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); 854 | opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); 855 | let file = File::open(path, &opts)?; 856 | file.set_perm(perm) 857 | } 858 | 859 | const VOLUME_NAME_DOS: DWORD = 0x0; 860 | } 861 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | 5 | if [ -x /bin/echo ]; then 6 | ECHO='/bin/echo' 7 | else 8 | ECHO='echo' 9 | fi 10 | 11 | # Prints the absolute path of a directory to stdout 12 | abs_path() { 13 | local path="$1" 14 | # Unset CDPATH because it causes havok: it makes the destination unpredictable 15 | # and triggers 'cd' to print the path to stdout. Route `cd`'s output to /dev/null 16 | # for good measure. 17 | (unset CDPATH && cd "$path" > /dev/null && pwd) 18 | } 19 | 20 | S="$(abs_path $(dirname $0))" 21 | 22 | TEST_DIR="$S/test" 23 | TMP_DIR="$S/tmp" 24 | WORK_DIR="$TMP_DIR/workdir" 25 | OUT_DIR="$TMP_DIR/outdir" 26 | PREFIX_DIR="$TMP_DIR/prefix" 27 | 28 | case $(uname -s) in 29 | 30 | MINGW* | MSYS*) 31 | WINDOWS=1 32 | ;; 33 | esac 34 | 35 | say() { 36 | echo "test: $1" 37 | } 38 | 39 | pre() { 40 | echo "test: $1" 41 | rm -Rf "$WORK_DIR" 42 | rm -Rf "$OUT_DIR" 43 | rm -Rf "$PREFIX_DIR" 44 | mkdir -p "$WORK_DIR" 45 | mkdir -p "$OUT_DIR" 46 | mkdir -p "$PREFIX_DIR" 47 | } 48 | 49 | need_ok() { 50 | if [ $? -ne 0 ] 51 | then 52 | echo 53 | echo "TEST FAILED!" 54 | echo 55 | exit 1 56 | fi 57 | } 58 | 59 | fail() { 60 | echo 61 | echo "$1" 62 | echo 63 | echo "TEST FAILED!" 64 | echo 65 | exit 1 66 | } 67 | 68 | try() { 69 | set +e 70 | _cmd="$@" 71 | _output=`$@ 2>&1` 72 | if [ $? -ne 0 ]; then 73 | echo \$ "$_cmd" 74 | # Using /bin/echo to avoid escaping 75 | $ECHO "$_output" 76 | echo 77 | echo "TEST FAILED!" 78 | echo 79 | exit 1 80 | else 81 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_CMD-}" ]; then 82 | echo \$ "$_cmd" 83 | fi 84 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_OUTPUT-}" ]; then 85 | $ECHO "$_output" 86 | fi 87 | fi 88 | set -e 89 | } 90 | 91 | expect_fail() { 92 | set +e 93 | _cmd="$@" 94 | _output=`$@ 2>&1` 95 | if [ $? -eq 0 ]; then 96 | echo \$ "$_cmd" 97 | # Using /bin/echo to avoid escaping 98 | $ECHO "$_output" 99 | echo 100 | echo "TEST FAILED!" 101 | echo 102 | exit 1 103 | else 104 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_CMD-}" ]; then 105 | echo \$ "$_cmd" 106 | fi 107 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_OUTPUT-}" ]; then 108 | $ECHO "$_output" 109 | fi 110 | fi 111 | set -e 112 | } 113 | 114 | expect_output_ok() { 115 | set +e 116 | local _expected="$1" 117 | shift 1 118 | _cmd="$@" 119 | _output=`$@ 2>&1` 120 | if [ $? -ne 0 ]; then 121 | echo \$ "$_cmd" 122 | # Using /bin/echo to avoid escaping 123 | $ECHO "$_output" 124 | echo 125 | echo "TEST FAILED!" 126 | echo 127 | exit 1 128 | elif ! echo "$_output" | grep -q "$_expected"; then 129 | echo \$ "$_cmd" 130 | $ECHO "$_output" 131 | echo 132 | echo "missing expected output '$_expected'" 133 | echo 134 | echo 135 | echo "TEST FAILED!" 136 | echo 137 | exit 1 138 | else 139 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_CMD-}" ]; then 140 | echo \$ "$_cmd" 141 | fi 142 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_OUTPUT-}" ]; then 143 | $ECHO "$_output" 144 | fi 145 | fi 146 | set -e 147 | } 148 | 149 | expect_output_fail() { 150 | set +e 151 | local _expected="$1" 152 | shift 1 153 | _cmd="$@" 154 | _output=`$@ 2>&1` 155 | if [ $? -eq 0 ]; then 156 | echo \$ "$_cmd" 157 | # Using /bin/echo to avoid escaping 158 | $ECHO "$_output" 159 | echo 160 | echo "TEST FAILED!" 161 | echo 162 | exit 1 163 | elif ! echo "$_output" | grep -q "$_expected"; then 164 | echo \$ "$_cmd" 165 | $ECHO "$_output" 166 | echo 167 | echo "missing expected output '$_expected'" 168 | echo 169 | echo 170 | echo "TEST FAILED!" 171 | echo 172 | exit 1 173 | else 174 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_CMD-}" ]; then 175 | echo \$ "$_cmd" 176 | fi 177 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_OUTPUT-}" ]; then 178 | $ECHO "$_output" 179 | fi 180 | fi 181 | set -e 182 | } 183 | 184 | expect_not_output_ok() { 185 | set +e 186 | local _expected="$1" 187 | shift 1 188 | _cmd="$@" 189 | _output=`$@ 2>&1` 190 | if [ $? -ne 0 ]; then 191 | echo \$ "$_cmd" 192 | # Using /bin/echo to avoid escaping 193 | $ECHO "$_output" 194 | echo 195 | echo "TEST FAILED!" 196 | echo 197 | exit 1 198 | elif echo "$_output" | grep -q "$_expected"; then 199 | echo \$ "$_cmd" 200 | $ECHO "$_output" 201 | echo 202 | echo "unexpected output '$_expected'" 203 | echo 204 | echo 205 | echo "TEST FAILED!" 206 | echo 207 | exit 1 208 | else 209 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_CMD-}" ]; then 210 | echo \$ "$_cmd" 211 | fi 212 | if [ -n "${VERBOSE-}" -o -n "${VERBOSE_OUTPUT-}" ]; then 213 | $ECHO "$_output" 214 | fi 215 | fi 216 | set -e 217 | } 218 | 219 | runtest() { 220 | local _testname="$1" 221 | if [ -n "${TESTNAME-}" ]; then 222 | if ! echo "$_testname" | grep -q "$TESTNAME"; then 223 | return 0 224 | fi 225 | fi 226 | 227 | pre "$_testname" 228 | "$_testname" 229 | } 230 | 231 | # Installation tests 232 | 233 | basic_install() { 234 | try sh "$S/gen-installer.sh" \ 235 | --image-dir="$TEST_DIR/image1" \ 236 | --work-dir="$WORK_DIR" \ 237 | --output-dir="$OUT_DIR" 238 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 239 | try test -e "$PREFIX_DIR/something-to-install" 240 | try test -e "$PREFIX_DIR/dir-to-install/foo" 241 | try test -e "$PREFIX_DIR/bin/program" 242 | try test -e "$PREFIX_DIR/bin/program2" 243 | try test -e "$PREFIX_DIR/bin/bad-bin" 244 | } 245 | runtest basic_install 246 | 247 | basic_uninstall() { 248 | try sh "$S/gen-installer.sh" \ 249 | --image-dir="$TEST_DIR/image1" \ 250 | --work-dir="$WORK_DIR" \ 251 | --output-dir="$OUT_DIR" 252 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 253 | try "$WORK_DIR/package/install.sh --uninstall" --prefix="$PREFIX_DIR" 254 | try test ! -e "$PREFIX_DIR/something-to-install" 255 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 256 | try test ! -e "$PREFIX_DIR/bin/program" 257 | try test ! -e "$PREFIX_DIR/bin/program2" 258 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 259 | try test ! -e "$PREFIX_DIR/lib/packagelib" 260 | } 261 | runtest basic_uninstall 262 | 263 | not_installed_files() { 264 | mkdir -p "$WORK_DIR/overlay" 265 | touch "$WORK_DIR/overlay/not-installed" 266 | try sh "$S/gen-installer.sh" \ 267 | --image-dir="$TEST_DIR/image1" \ 268 | --work-dir="$WORK_DIR" \ 269 | --output-dir="$OUT_DIR" \ 270 | --non-installed-overlay="$WORK_DIR/overlay" 271 | try test -e "$WORK_DIR/package/not-installed" 272 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 273 | try test ! -e "$PREFIX_DIR/not-installed" 274 | } 275 | runtest not_installed_files 276 | 277 | tarball_with_package_name() { 278 | try sh "$S/gen-installer.sh" \ 279 | --image-dir="$TEST_DIR/image1" \ 280 | --work-dir="$WORK_DIR" \ 281 | --output-dir="$OUT_DIR" \ 282 | --package-name=rustc-nightly 283 | try "$WORK_DIR/rustc-nightly/install.sh" --prefix="$PREFIX_DIR" 284 | try test -e "$OUT_DIR/rustc-nightly.tar.gz" 285 | try test -e "$OUT_DIR/rustc-nightly.tar.xz" 286 | } 287 | runtest tarball_with_package_name 288 | 289 | install_overwrite_backup() { 290 | try sh "$S/gen-installer.sh" \ 291 | --image-dir="$TEST_DIR/image1" \ 292 | --work-dir="$WORK_DIR" \ 293 | --output-dir="$OUT_DIR" 294 | try mkdir -p "$PREFIX_DIR/bin" 295 | touch "$PREFIX_DIR/bin/program" 296 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 297 | # The existing program was backed up by 'install' 298 | try test -e "$PREFIX_DIR/bin/program.old" 299 | } 300 | runtest install_overwrite_backup 301 | 302 | bulk_directory() { 303 | try sh "$S/gen-installer.sh" \ 304 | --image-dir="$TEST_DIR/image1" \ 305 | --work-dir="$WORK_DIR" \ 306 | --output-dir="$OUT_DIR" \ 307 | --bulk-dirs=dir-to-install 308 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 309 | try test -e "$PREFIX_DIR/something-to-install" 310 | try test -e "$PREFIX_DIR/dir-to-install/foo" 311 | try test -e "$PREFIX_DIR/bin/program" 312 | try test -e "$PREFIX_DIR/bin/program2" 313 | try test -e "$PREFIX_DIR/bin/bad-bin" 314 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" --uninstall 315 | try test ! -e "$PREFIX_DIR/dir-to-install" 316 | } 317 | runtest bulk_directory 318 | 319 | bulk_directory_overwrite() { 320 | try sh "$S/gen-installer.sh" \ 321 | --image-dir="$TEST_DIR/image1" \ 322 | --work-dir="$WORK_DIR" \ 323 | --output-dir="$OUT_DIR" \ 324 | --bulk-dirs=dir-to-install 325 | try mkdir -p "$PREFIX_DIR/dir-to-install" 326 | try touch "$PREFIX_DIR/dir-to-install/overwrite" 327 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 328 | # The file that used to exist in the directory no longer does 329 | try test ! -e "$PREFIX_DIR/dir-to-install/overwrite" 330 | # It was backed up 331 | try test -e "$PREFIX_DIR/dir-to-install.old/overwrite" 332 | } 333 | runtest bulk_directory_overwrite 334 | 335 | bulk_directory_overwrite_existing_backup() { 336 | try sh "$S/gen-installer.sh" \ 337 | --image-dir="$TEST_DIR/image1" \ 338 | --work-dir="$WORK_DIR" \ 339 | --output-dir="$OUT_DIR" \ 340 | --bulk-dirs=dir-to-install 341 | try mkdir -p "$PREFIX_DIR/dir-to-install" 342 | try touch "$PREFIX_DIR/dir-to-install/overwrite" 343 | # This time we've already got an existing backup of the overwritten directory. 344 | # The install should still succeed. 345 | try mkdir -p "$PREFIX_DIR/dir-to-install~" 346 | try touch "$PREFIX_DIR/dir-to-install~/overwrite" 347 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 348 | try test ! -e "$PREFIX_DIR/dir-to-install/overwrite" 349 | try test -e "$PREFIX_DIR/dir-to-install~/overwrite" 350 | } 351 | runtest bulk_directory_overwrite_existing_backup 352 | 353 | nested_bulk_directory() { 354 | try sh "$S/gen-installer.sh" \ 355 | --image-dir="$TEST_DIR/image4" \ 356 | --work-dir="$WORK_DIR" \ 357 | --output-dir="$OUT_DIR" \ 358 | --bulk-dirs=dir-to-install/qux 359 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 360 | try test -e "$PREFIX_DIR/dir-to-install/qux/bar" 361 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" --uninstall 362 | try test ! -e "$PREFIX_DIR/dir-to-install/qux" 363 | } 364 | runtest nested_bulk_directory 365 | 366 | only_bulk_directory_no_files() { 367 | try sh "$S/gen-installer.sh" \ 368 | --image-dir="$TEST_DIR/image5" \ 369 | --work-dir="$WORK_DIR" \ 370 | --output-dir="$OUT_DIR" \ 371 | --bulk-dirs=dir-to-install 372 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 373 | try test -e "$PREFIX_DIR/dir-to-install/foo" 374 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" --uninstall 375 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 376 | } 377 | runtest only_bulk_directory_no_files 378 | 379 | nested_not_installed_files() { 380 | mkdir -p "$WORK_DIR/overlay" 381 | touch "$WORK_DIR/overlay/not-installed" 382 | try sh "$S/gen-installer.sh" \ 383 | --image-dir="$TEST_DIR/image4" \ 384 | --work-dir="$WORK_DIR" \ 385 | --output-dir="$OUT_DIR" \ 386 | --non-installed-overlay="$WORK_DIR/overlay" 387 | try test -e "$WORK_DIR/package/not-installed" 388 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 389 | try test ! -e "$PREFIX_DIR/not-installed" 390 | } 391 | runtest nested_not_installed_files 392 | 393 | multiple_components() { 394 | try sh "$S/gen-installer.sh" \ 395 | --image-dir="$TEST_DIR/image1" \ 396 | --work-dir="$WORK_DIR/c1" \ 397 | --output-dir="$OUT_DIR/c1" \ 398 | --component-name=rustc 399 | try sh "$S/gen-installer.sh" \ 400 | --image-dir="$TEST_DIR/image3" \ 401 | --work-dir="$WORK_DIR/c2" \ 402 | --output-dir="$OUT_DIR/c2" \ 403 | --component-name=cargo 404 | try "$WORK_DIR/c1/package/install.sh" --prefix="$PREFIX_DIR" 405 | try "$WORK_DIR/c2/package/install.sh" --prefix="$PREFIX_DIR" 406 | try test -e "$PREFIX_DIR/something-to-install" 407 | try test -e "$PREFIX_DIR/dir-to-install/foo" 408 | try test -e "$PREFIX_DIR/bin/program" 409 | try test -e "$PREFIX_DIR/bin/program2" 410 | try test -e "$PREFIX_DIR/bin/bad-bin" 411 | try test -e "$PREFIX_DIR/bin/cargo" 412 | try "$WORK_DIR/c1/package/install.sh" --prefix="$PREFIX_DIR" --uninstall 413 | try test ! -e "$PREFIX_DIR/something-to-install" 414 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 415 | try test ! -e "$PREFIX_DIR/bin/program" 416 | try test ! -e "$PREFIX_DIR/bin/program2" 417 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 418 | try "$WORK_DIR/c2/package/install.sh" --prefix="$PREFIX_DIR" --uninstall 419 | try test ! -e "$PREFIX_DIR/bin/cargo" 420 | try test ! -e "$PREFIX_DIR/lib/packagelib" 421 | } 422 | runtest multiple_components 423 | 424 | uninstall_from_installed_script() { 425 | try sh "$S/gen-installer.sh" \ 426 | --image-dir="$TEST_DIR/image1" \ 427 | --work-dir="$WORK_DIR/c1" \ 428 | --output-dir="$OUT_DIR/c1" \ 429 | --component-name=rustc 430 | try sh "$S/gen-installer.sh" \ 431 | --image-dir="$TEST_DIR/image3" \ 432 | --work-dir="$WORK_DIR/c2" \ 433 | --output-dir="$OUT_DIR/c2" \ 434 | --component-name=cargo 435 | try "$WORK_DIR/c1/package/install.sh" --prefix="$PREFIX_DIR" 436 | try "$WORK_DIR/c2/package/install.sh" --prefix="$PREFIX_DIR" 437 | try test -e "$PREFIX_DIR/something-to-install" 438 | try test -e "$PREFIX_DIR/dir-to-install/foo" 439 | try test -e "$PREFIX_DIR/bin/program" 440 | try test -e "$PREFIX_DIR/bin/program2" 441 | try test -e "$PREFIX_DIR/bin/bad-bin" 442 | try test -e "$PREFIX_DIR/bin/cargo" 443 | # All components should be uninstalled by this script 444 | try sh "$PREFIX_DIR/lib/packagelib/uninstall.sh" 445 | try test ! -e "$PREFIX_DIR/something-to-install" 446 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 447 | try test ! -e "$PREFIX_DIR/bin/program" 448 | try test ! -e "$PREFIX_DIR/bin/program2" 449 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 450 | try test ! -e "$PREFIX_DIR/bin/cargo" 451 | try test ! -e "$PREFIX_DIR/lib/packagelib" 452 | } 453 | runtest uninstall_from_installed_script 454 | 455 | uninstall_from_installed_script_with_args_fails() { 456 | try sh "$S/gen-installer.sh" \ 457 | --image-dir="$TEST_DIR/image1" \ 458 | --work-dir="$WORK_DIR/c1" \ 459 | --output-dir="$OUT_DIR/c1" \ 460 | --component-name=rustc 461 | try "$WORK_DIR/c1/package/install.sh" --prefix="$PREFIX_DIR" 462 | expect_output_fail "uninstall.sh does not take any arguments" sh "$PREFIX_DIR/lib/packagelib/uninstall.sh" --prefix=foo 463 | } 464 | runtest uninstall_from_installed_script_with_args_fails 465 | 466 | # Combined installer tests 467 | 468 | combine_installers() { 469 | try sh "$S/gen-installer.sh" \ 470 | --image-dir="$TEST_DIR/image1" \ 471 | --work-dir="$WORK_DIR" \ 472 | --output-dir="$OUT_DIR" \ 473 | --package-name=rustc \ 474 | --component-name=rustc 475 | try sh "$S/gen-installer.sh" \ 476 | --image-dir="$TEST_DIR/image3" \ 477 | --work-dir="$WORK_DIR" \ 478 | --output-dir="$OUT_DIR" \ 479 | --package-name=cargo \ 480 | --component-name=cargo 481 | try sh "$S/combine-installers.sh" \ 482 | --work-dir="$WORK_DIR" \ 483 | --output-dir="$OUT_DIR" \ 484 | --package-name=rust \ 485 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" 486 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 487 | try test -e "$PREFIX_DIR/something-to-install" 488 | try test -e "$PREFIX_DIR/dir-to-install/foo" 489 | try test -e "$PREFIX_DIR/bin/program" 490 | try test -e "$PREFIX_DIR/bin/program2" 491 | try test -e "$PREFIX_DIR/bin/bad-bin" 492 | try test -e "$PREFIX_DIR/bin/cargo" 493 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 494 | try test ! -e "$PREFIX_DIR/something-to-install" 495 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 496 | try test ! -e "$PREFIX_DIR/bin/program" 497 | try test ! -e "$PREFIX_DIR/bin/program2" 498 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 499 | try test ! -e "$PREFIX_DIR/bin/cargo" 500 | try test ! -e "$PREFIX_DIR/lib/packagelib" 501 | } 502 | runtest combine_installers 503 | 504 | combine_three_installers() { 505 | try sh "$S/gen-installer.sh" \ 506 | --image-dir="$TEST_DIR/image1" \ 507 | --work-dir="$WORK_DIR" \ 508 | --output-dir="$OUT_DIR" \ 509 | --package-name=rustc \ 510 | --component-name=rustc 511 | try sh "$S/gen-installer.sh" \ 512 | --image-dir="$TEST_DIR/image3" \ 513 | --work-dir="$WORK_DIR" \ 514 | --output-dir="$OUT_DIR" \ 515 | --package-name=cargo \ 516 | --component-name=cargo 517 | try sh "$S/gen-installer.sh" \ 518 | --image-dir="$TEST_DIR/image4" \ 519 | --work-dir="$WORK_DIR" \ 520 | --output-dir="$OUT_DIR" \ 521 | --package-name=rust-docs \ 522 | --component-name=rust-docs 523 | try sh "$S/combine-installers.sh" \ 524 | --work-dir="$WORK_DIR" \ 525 | --output-dir="$OUT_DIR" \ 526 | --package-name=rust \ 527 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 528 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 529 | try test -e "$PREFIX_DIR/something-to-install" 530 | try test -e "$PREFIX_DIR/dir-to-install/foo" 531 | try test -e "$PREFIX_DIR/bin/program" 532 | try test -e "$PREFIX_DIR/bin/program2" 533 | try test -e "$PREFIX_DIR/bin/bad-bin" 534 | try test -e "$PREFIX_DIR/bin/cargo" 535 | try test -e "$PREFIX_DIR/dir-to-install/qux/bar" 536 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 537 | try test ! -e "$PREFIX_DIR/something-to-install" 538 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 539 | try test ! -e "$PREFIX_DIR/bin/program" 540 | try test ! -e "$PREFIX_DIR/bin/program2" 541 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 542 | try test ! -e "$PREFIX_DIR/bin/cargo" 543 | try test ! -e "$PREFIX_DIR/lib/packagelib" 544 | try test ! -e "$PREFIX_DIR/dir-to-install/qux/bar" 545 | } 546 | runtest combine_three_installers 547 | 548 | combine_installers_with_overlay() { 549 | try sh "$S/gen-installer.sh" \ 550 | --image-dir="$TEST_DIR/image1" \ 551 | --work-dir="$WORK_DIR" \ 552 | --output-dir="$OUT_DIR" \ 553 | --package-name=rustc \ 554 | --component-name=rustc 555 | try sh "$S/gen-installer.sh" \ 556 | --image-dir="$TEST_DIR/image3" \ 557 | --work-dir="$WORK_DIR" \ 558 | --output-dir="$OUT_DIR" \ 559 | --package-name=cargo \ 560 | --component-name=cargo 561 | mkdir -p "$WORK_DIR/overlay" 562 | touch "$WORK_DIR/overlay/README" 563 | try sh "$S/combine-installers.sh" \ 564 | --work-dir="$WORK_DIR" \ 565 | --output-dir="$OUT_DIR" \ 566 | --package-name=rust \ 567 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" \ 568 | --non-installed-overlay="$WORK_DIR/overlay" 569 | try test -e "$WORK_DIR/rust/README" 570 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 571 | try test ! -e "$PREFIX_DIR/README" 572 | } 573 | runtest combine_installers_with_overlay 574 | 575 | combined_with_bulk_dirs() { 576 | try sh "$S/gen-installer.sh" \ 577 | --image-dir="$TEST_DIR/image1" \ 578 | --work-dir="$WORK_DIR" \ 579 | --output-dir="$OUT_DIR" \ 580 | --package-name=rustc \ 581 | --component-name=rustc \ 582 | --bulk-dirs=dir-to-install 583 | try sh "$S/gen-installer.sh" \ 584 | --image-dir="$TEST_DIR/image3" \ 585 | --work-dir="$WORK_DIR" \ 586 | --output-dir="$OUT_DIR" \ 587 | --package-name=cargo \ 588 | --component-name=cargo 589 | try sh "$S/combine-installers.sh" \ 590 | --work-dir="$WORK_DIR" \ 591 | --output-dir="$OUT_DIR" \ 592 | --package-name=rust \ 593 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" 594 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 595 | try test -e "$PREFIX_DIR/dir-to-install/foo" 596 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 597 | try test ! -e "$PREFIX_DIR/dir-to-install" 598 | } 599 | runtest combined_with_bulk_dirs 600 | 601 | combine_install_with_separate_uninstall() { 602 | try sh "$S/gen-installer.sh" \ 603 | --image-dir="$TEST_DIR/image1" \ 604 | --work-dir="$WORK_DIR" \ 605 | --output-dir="$OUT_DIR" \ 606 | --package-name=rustc \ 607 | --component-name=rustc \ 608 | --rel-manifest-dir=rustlib 609 | try sh "$S/gen-installer.sh" \ 610 | --image-dir="$TEST_DIR/image3" \ 611 | --work-dir="$WORK_DIR" \ 612 | --output-dir="$OUT_DIR" \ 613 | --package-name=cargo \ 614 | --component-name=cargo \ 615 | --rel-manifest-dir=rustlib 616 | try sh "$S/combine-installers.sh" \ 617 | --work-dir="$WORK_DIR" \ 618 | --output-dir="$OUT_DIR" \ 619 | --package-name=rust \ 620 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" \ 621 | --rel-manifest-dir=rustlib 622 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 623 | try test -e "$PREFIX_DIR/something-to-install" 624 | try test -e "$PREFIX_DIR/dir-to-install/foo" 625 | try test -e "$PREFIX_DIR/bin/program" 626 | try test -e "$PREFIX_DIR/bin/program2" 627 | try test -e "$PREFIX_DIR/bin/bad-bin" 628 | try test -e "$PREFIX_DIR/bin/cargo" 629 | try "$WORK_DIR/rustc/install.sh --uninstall" --prefix="$PREFIX_DIR" 630 | try test ! -e "$PREFIX_DIR/something-to-install" 631 | try test ! -e "$PREFIX_DIR/dir-to-install/foo" 632 | try test ! -e "$PREFIX_DIR/bin/program" 633 | try test ! -e "$PREFIX_DIR/bin/program2" 634 | try test ! -e "$PREFIX_DIR/bin/bad-bin" 635 | try "$WORK_DIR/cargo/install.sh --uninstall" --prefix="$PREFIX_DIR" 636 | try test ! -e "$PREFIX_DIR/bin/cargo" 637 | try test ! -e "$PREFIX_DIR/lib/packagelib" 638 | } 639 | runtest combine_install_with_separate_uninstall 640 | 641 | select_components_to_install() { 642 | try sh "$S/gen-installer.sh" \ 643 | --image-dir="$TEST_DIR/image1" \ 644 | --work-dir="$WORK_DIR" \ 645 | --output-dir="$OUT_DIR" \ 646 | --package-name=rustc \ 647 | --component-name=rustc 648 | try sh "$S/gen-installer.sh" \ 649 | --image-dir="$TEST_DIR/image3" \ 650 | --work-dir="$WORK_DIR" \ 651 | --output-dir="$OUT_DIR" \ 652 | --package-name=cargo \ 653 | --component-name=cargo 654 | try sh "$S/gen-installer.sh" \ 655 | --image-dir="$TEST_DIR/image4" \ 656 | --work-dir="$WORK_DIR" \ 657 | --output-dir="$OUT_DIR" \ 658 | --package-name=rust-docs \ 659 | --component-name=rust-docs 660 | try sh "$S/combine-installers.sh" \ 661 | --work-dir="$WORK_DIR" \ 662 | --output-dir="$OUT_DIR" \ 663 | --package-name=rust \ 664 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 665 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --components=rustc 666 | try test -e "$PREFIX_DIR/bin/program" 667 | try test ! -e "$PREFIX_DIR/bin/cargo" 668 | try test ! -e "$PREFIX_DIR/baz" 669 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 670 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --components=cargo 671 | try test ! -e "$PREFIX_DIR/bin/program" 672 | try test -e "$PREFIX_DIR/bin/cargo" 673 | try test ! -e "$PREFIX_DIR/baz" 674 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 675 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --components=rust-docs 676 | try test ! -e "$PREFIX_DIR/bin/program" 677 | try test ! -e "$PREFIX_DIR/bin/cargo" 678 | try test -e "$PREFIX_DIR/baz" 679 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 680 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --components=rustc,cargo 681 | try test -e "$PREFIX_DIR/bin/program" 682 | try test -e "$PREFIX_DIR/bin/cargo" 683 | try test ! -e "$PREFIX_DIR/baz" 684 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=rustc,cargo,rust-docs 685 | try test ! -e "$PREFIX_DIR/bin/program" 686 | try test ! -e "$PREFIX_DIR/bin/cargo" 687 | try test ! -e "$PREFIX_DIR/baz" 688 | try test ! -e "$PREFIX_DIR/lib/packagelib" 689 | } 690 | runtest select_components_to_install 691 | 692 | select_components_to_uninstall() { 693 | try sh "$S/gen-installer.sh" \ 694 | --image-dir="$TEST_DIR/image1" \ 695 | --work-dir="$WORK_DIR" \ 696 | --output-dir="$OUT_DIR" \ 697 | --package-name=rustc \ 698 | --component-name=rustc 699 | try sh "$S/gen-installer.sh" \ 700 | --image-dir="$TEST_DIR/image3" \ 701 | --work-dir="$WORK_DIR" \ 702 | --output-dir="$OUT_DIR" \ 703 | --package-name=cargo \ 704 | --component-name=cargo 705 | try sh "$S/gen-installer.sh" \ 706 | --image-dir="$TEST_DIR/image4" \ 707 | --work-dir="$WORK_DIR" \ 708 | --output-dir="$OUT_DIR" \ 709 | --package-name=rust-docs \ 710 | --component-name=rust-docs 711 | try sh "$S/combine-installers.sh" \ 712 | --work-dir="$WORK_DIR" \ 713 | --output-dir="$OUT_DIR" \ 714 | --package-name=rust \ 715 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 716 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 717 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=rustc 718 | try test ! -e "$PREFIX_DIR/bin/program" 719 | try test -e "$PREFIX_DIR/bin/cargo" 720 | try test -e "$PREFIX_DIR/baz" 721 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 722 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=cargo 723 | try test -e "$PREFIX_DIR/bin/program" 724 | try test ! -e "$PREFIX_DIR/bin/cargo" 725 | try test -e "$PREFIX_DIR/baz" 726 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 727 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=rust-docs 728 | try test -e "$PREFIX_DIR/bin/program" 729 | try test -e "$PREFIX_DIR/bin/cargo" 730 | try test ! -e "$PREFIX_DIR/baz" 731 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 732 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=rustc,cargo 733 | try test ! -e "$PREFIX_DIR/bin/program" 734 | try test ! -e "$PREFIX_DIR/bin/cargo" 735 | try test -e "$PREFIX_DIR/baz" 736 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 737 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --components=rustc,cargo,rust-docs 738 | try test ! -e "$PREFIX_DIR/bin/program" 739 | try test ! -e "$PREFIX_DIR/bin/cargo" 740 | try test ! -e "$PREFIX_DIR/baz" 741 | try test ! -e "$PREFIX_DIR/lib/packagelib" 742 | } 743 | runtest select_components_to_uninstall 744 | 745 | invalid_component() { 746 | try sh "$S/gen-installer.sh" \ 747 | --image-dir="$TEST_DIR/image1" \ 748 | --work-dir="$WORK_DIR" \ 749 | --output-dir="$OUT_DIR" \ 750 | --package-name=rustc \ 751 | --component-name=rustc 752 | try sh "$S/gen-installer.sh" \ 753 | --image-dir="$TEST_DIR/image3" \ 754 | --work-dir="$WORK_DIR" \ 755 | --output-dir="$OUT_DIR" \ 756 | --package-name=cargo \ 757 | --component-name=cargo 758 | try sh "$S/gen-installer.sh" \ 759 | --image-dir="$TEST_DIR/image4" \ 760 | --work-dir="$WORK_DIR" \ 761 | --output-dir="$OUT_DIR" \ 762 | --package-name=rust-docs \ 763 | --component-name=rust-docs 764 | try sh "$S/combine-installers.sh" \ 765 | --work-dir="$WORK_DIR" \ 766 | --output-dir="$OUT_DIR" \ 767 | --package-name=rust \ 768 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 769 | expect_output_fail "unknown component" "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --components=foo 770 | } 771 | runtest invalid_component 772 | 773 | without_components() { 774 | try sh "$S/gen-installer.sh" \ 775 | --image-dir="$TEST_DIR/image1" \ 776 | --work-dir="$WORK_DIR" \ 777 | --output-dir="$OUT_DIR" \ 778 | --package-name=rustc \ 779 | --component-name=rustc 780 | try sh "$S/gen-installer.sh" \ 781 | --image-dir="$TEST_DIR/image3" \ 782 | --work-dir="$WORK_DIR" \ 783 | --output-dir="$OUT_DIR" \ 784 | --package-name=cargo \ 785 | --component-name=cargo 786 | try sh "$S/gen-installer.sh" \ 787 | --image-dir="$TEST_DIR/image4" \ 788 | --work-dir="$WORK_DIR" \ 789 | --output-dir="$OUT_DIR" \ 790 | --package-name=rust-docs \ 791 | --component-name=rust-docs 792 | try sh "$S/combine-installers.sh" \ 793 | --work-dir="$WORK_DIR" \ 794 | --output-dir="$OUT_DIR" \ 795 | --package-name=rust \ 796 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 797 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --without=rust-docs 798 | try test -e "$PREFIX_DIR/bin/program" 799 | try test -e "$PREFIX_DIR/bin/cargo" 800 | try test ! -e "$PREFIX_DIR/baz" 801 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 802 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --without=rust-docs,cargo 803 | try test -e "$PREFIX_DIR/bin/program" 804 | try test ! -e "$PREFIX_DIR/bin/cargo" 805 | try test ! -e "$PREFIX_DIR/baz" 806 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 807 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --without=rust-docs,rustc 808 | try test ! -e "$PREFIX_DIR/bin/program" 809 | try test -e "$PREFIX_DIR/bin/cargo" 810 | try test ! -e "$PREFIX_DIR/baz" 811 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" 812 | } 813 | runtest without_components 814 | 815 | # --uninstall --without is kind of weird, 816 | # --without causes components to remain installed 817 | uninstall_without_components() { 818 | try sh "$S/gen-installer.sh" \ 819 | --image-dir="$TEST_DIR/image1" \ 820 | --work-dir="$WORK_DIR" \ 821 | --output-dir="$OUT_DIR" \ 822 | --package-name=rustc \ 823 | --component-name=rustc 824 | try sh "$S/gen-installer.sh" \ 825 | --image-dir="$TEST_DIR/image3" \ 826 | --work-dir="$WORK_DIR" \ 827 | --output-dir="$OUT_DIR" \ 828 | --package-name=cargo \ 829 | --component-name=cargo 830 | try sh "$S/gen-installer.sh" \ 831 | --image-dir="$TEST_DIR/image4" \ 832 | --work-dir="$WORK_DIR" \ 833 | --output-dir="$OUT_DIR" \ 834 | --package-name=rust-docs \ 835 | --component-name=rust-docs 836 | try sh "$S/combine-installers.sh" \ 837 | --work-dir="$WORK_DIR" \ 838 | --output-dir="$OUT_DIR" \ 839 | --package-name=rust \ 840 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 841 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 842 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --without=rust-docs 843 | try test ! -e "$PREFIX_DIR/bin/program" 844 | try test ! -e "$PREFIX_DIR/bin/cargo" 845 | try test -e "$PREFIX_DIR/baz" 846 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 847 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --without=rust-docs,cargo 848 | try test ! -e "$PREFIX_DIR/bin/program" 849 | try test -e "$PREFIX_DIR/bin/cargo" 850 | try test -e "$PREFIX_DIR/baz" 851 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 852 | try "$WORK_DIR/rust/install.sh --uninstall" --prefix="$PREFIX_DIR" --without=rust-docs,rustc 853 | try test -e "$PREFIX_DIR/bin/program" 854 | try test ! -e "$PREFIX_DIR/bin/cargo" 855 | try test -e "$PREFIX_DIR/baz" 856 | } 857 | runtest uninstall_without_components 858 | 859 | without_any_components() { 860 | try sh "$S/gen-installer.sh" \ 861 | --image-dir="$TEST_DIR/image1" \ 862 | --work-dir="$WORK_DIR" \ 863 | --output-dir="$OUT_DIR" \ 864 | --package-name=rustc \ 865 | --component-name=rustc 866 | try sh "$S/gen-installer.sh" \ 867 | --image-dir="$TEST_DIR/image3" \ 868 | --work-dir="$WORK_DIR" \ 869 | --output-dir="$OUT_DIR" \ 870 | --package-name=cargo \ 871 | --component-name=cargo 872 | try sh "$S/gen-installer.sh" \ 873 | --image-dir="$TEST_DIR/image4" \ 874 | --work-dir="$WORK_DIR" \ 875 | --output-dir="$OUT_DIR" \ 876 | --package-name=rust-docs \ 877 | --component-name=rust-docs 878 | try sh "$S/combine-installers.sh" \ 879 | --work-dir="$WORK_DIR" \ 880 | --output-dir="$OUT_DIR" \ 881 | --package-name=rust \ 882 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 883 | expect_output_fail "no components selected for installation" \ 884 | "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --without=rust-docs,rustc,cargo 885 | } 886 | runtest without_any_components 887 | 888 | uninstall_without_any_components() { 889 | try sh "$S/gen-installer.sh" \ 890 | --image-dir="$TEST_DIR/image1" \ 891 | --work-dir="$WORK_DIR" \ 892 | --output-dir="$OUT_DIR" \ 893 | --package-name=rustc \ 894 | --component-name=rustc 895 | try sh "$S/gen-installer.sh" \ 896 | --image-dir="$TEST_DIR/image3" \ 897 | --work-dir="$WORK_DIR" \ 898 | --output-dir="$OUT_DIR" \ 899 | --package-name=cargo \ 900 | --component-name=cargo 901 | try sh "$S/gen-installer.sh" \ 902 | --image-dir="$TEST_DIR/image4" \ 903 | --work-dir="$WORK_DIR" \ 904 | --output-dir="$OUT_DIR" \ 905 | --package-name=rust-docs \ 906 | --component-name=rust-docs 907 | try sh "$S/combine-installers.sh" \ 908 | --work-dir="$WORK_DIR" \ 909 | --output-dir="$OUT_DIR" \ 910 | --package-name=rust \ 911 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 912 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" 913 | expect_output_fail "no components selected for uninstallation" \ 914 | "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" \ 915 | --uninstall --without=rust-docs,rustc,cargo 916 | } 917 | runtest uninstall_without_any_components 918 | 919 | list_components() { 920 | try sh "$S/gen-installer.sh" \ 921 | --image-dir="$TEST_DIR/image1" \ 922 | --work-dir="$WORK_DIR" \ 923 | --output-dir="$OUT_DIR" \ 924 | --package-name=rustc \ 925 | --component-name=rustc 926 | try sh "$S/gen-installer.sh" \ 927 | --image-dir="$TEST_DIR/image3" \ 928 | --work-dir="$WORK_DIR" \ 929 | --output-dir="$OUT_DIR" \ 930 | --package-name=cargo \ 931 | --component-name=cargo 932 | try sh "$S/gen-installer.sh" \ 933 | --image-dir="$TEST_DIR/image4" \ 934 | --work-dir="$WORK_DIR" \ 935 | --output-dir="$OUT_DIR" \ 936 | --package-name=rust-docs \ 937 | --component-name=rust-docs 938 | try sh "$S/combine-installers.sh" \ 939 | --work-dir="$WORK_DIR" \ 940 | --output-dir="$OUT_DIR" \ 941 | --package-name=rust \ 942 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 943 | expect_output_ok "rustc" "$WORK_DIR/rust/install.sh" --list-components 944 | expect_output_ok "cargo" "$WORK_DIR/rust/install.sh" --list-components 945 | expect_output_ok "rust-docs" "$WORK_DIR/rust/install.sh" --list-components 946 | } 947 | runtest list_components 948 | 949 | combined_remains() { 950 | try sh "$S/gen-installer.sh" \ 951 | --image-dir="$TEST_DIR/image1" \ 952 | --work-dir="$WORK_DIR" \ 953 | --output-dir="$OUT_DIR" \ 954 | --package-name=rustc \ 955 | --component-name=rustc 956 | try sh "$S/gen-installer.sh" \ 957 | --image-dir="$TEST_DIR/image3" \ 958 | --work-dir="$WORK_DIR" \ 959 | --output-dir="$OUT_DIR" \ 960 | --package-name=cargo \ 961 | --component-name=cargo 962 | try sh "$S/gen-installer.sh" \ 963 | --image-dir="$TEST_DIR/image4" \ 964 | --work-dir="$WORK_DIR" \ 965 | --output-dir="$OUT_DIR" \ 966 | --package-name=rust-docs \ 967 | --component-name=rust-docs 968 | try sh "$S/combine-installers.sh" \ 969 | --work-dir="$WORK_DIR" \ 970 | --output-dir="$OUT_DIR" \ 971 | --package-name=rust \ 972 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz,$OUT_DIR/rust-docs.tar.gz" 973 | for component in rustc cargo rust-docs; do 974 | # rustbuild wants the original extracted package intact too 975 | try test -d "$WORK_DIR/$component/$component" 976 | try test -d "$WORK_DIR/rust/$component" 977 | done 978 | } 979 | runtest combined_remains 980 | 981 | # Smoke tests 982 | 983 | cannot_write_error() { 984 | # chmod doesn't work on windows 985 | if [ ! -n "${WINDOWS-}" ]; then 986 | try sh "$S/gen-installer.sh" \ 987 | --image-dir="$TEST_DIR/image1" \ 988 | --work-dir="$WORK_DIR" \ 989 | --output-dir="$OUT_DIR" 990 | chmod u-w "$PREFIX_DIR" 991 | expect_fail "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 992 | chmod u+w "$PREFIX_DIR" 993 | fi 994 | } 995 | runtest cannot_write_error 996 | 997 | cannot_install_to_installer() { 998 | try sh "$S/gen-installer.sh" \ 999 | --image-dir="$TEST_DIR/image1" \ 1000 | --work-dir="$WORK_DIR" \ 1001 | --output-dir="$OUT_DIR" \ 1002 | --package-name=my-package 1003 | expect_output_fail "cannot install to same directory as installer" \ 1004 | "$WORK_DIR/my-package/install.sh" --prefix="$WORK_DIR/my-package" 1005 | } 1006 | runtest cannot_install_to_installer 1007 | 1008 | upgrade_from_future_installer_error() { 1009 | try sh "$S/gen-installer.sh" \ 1010 | --image-dir="$TEST_DIR/image1" \ 1011 | --work-dir="$WORK_DIR" \ 1012 | --output-dir="$OUT_DIR" \ 1013 | --rel-manifest-dir=rustlib 1014 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 1015 | echo 100 > "$PREFIX_DIR/lib/rustlib/rust-installer-version" 1016 | expect_fail "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 1017 | } 1018 | runtest upgrade_from_future_installer_error 1019 | 1020 | destdir() { 1021 | try sh "$S/gen-installer.sh" \ 1022 | --image-dir="$TEST_DIR/image1" \ 1023 | --work-dir="$WORK_DIR" \ 1024 | --output-dir="$OUT_DIR" 1025 | try "$WORK_DIR/package/install.sh" --destdir="$PREFIX_DIR/" --prefix=prefix 1026 | try test -e "$PREFIX_DIR/prefix/bin/program" 1027 | } 1028 | runtest destdir 1029 | 1030 | destdir_no_trailing_slash() { 1031 | try sh "$S/gen-installer.sh" \ 1032 | --image-dir="$TEST_DIR/image1" \ 1033 | --work-dir="$WORK_DIR" \ 1034 | --output-dir="$OUT_DIR" 1035 | try "$WORK_DIR/package/install.sh" --destdir="$PREFIX_DIR" --prefix=prefix 1036 | try test -e "$PREFIX_DIR/prefix/bin/program" 1037 | } 1038 | runtest destdir_no_trailing_slash 1039 | 1040 | disable_verify_noop() { 1041 | # Obsolete --disable-verify flag doesn't generate error 1042 | try sh "$S/gen-installer.sh" \ 1043 | --image-dir="$TEST_DIR/image1" \ 1044 | --work-dir="$WORK_DIR" \ 1045 | --output-dir="$OUT_DIR" 1046 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" --disable-verify 1047 | } 1048 | runtest disable_verify_noop 1049 | 1050 | create_log() { 1051 | try sh "$S/gen-installer.sh" \ 1052 | --image-dir="$TEST_DIR/image1" \ 1053 | --work-dir="$WORK_DIR" \ 1054 | --output-dir="$OUT_DIR" 1055 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 1056 | try test -e "$PREFIX_DIR/lib/packagelib/install.log" 1057 | local _log="$(cat "$PREFIX_DIR/lib/packagelib/install.log")" 1058 | if [ -z "$_log" ]; then 1059 | fail "log is empty" 1060 | fi 1061 | } 1062 | runtest create_log 1063 | 1064 | leave_log_after_failure() { 1065 | # chmod doesn't work on windows 1066 | if [ ! -n "${WINDOWS-}" ]; then 1067 | try sh "$S/gen-installer.sh" \ 1068 | --image-dir="$TEST_DIR/image1" \ 1069 | --work-dir="$WORK_DIR" \ 1070 | --output-dir="$OUT_DIR" 1071 | mkdir -p "$PREFIX_DIR/lib/packagelib" 1072 | touch "$PREFIX_DIR/lib/packagelib/components" 1073 | chmod u-w "$PREFIX_DIR/lib/packagelib/components" 1074 | expect_fail "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 1075 | chmod u+w "$PREFIX_DIR/lib/packagelib/components" 1076 | try test -e "$PREFIX_DIR/lib/packagelib/install.log" 1077 | local _log="$(cat "$PREFIX_DIR/lib/packagelib/install.log")" 1078 | if [ -z "$_log" ]; then 1079 | fail "log is empty" 1080 | fi 1081 | # script should tell user where the logs are 1082 | if ! grep -q "see logs at" "$PREFIX_DIR/lib/packagelib/install.log"; then 1083 | fail "missing log message" 1084 | fi 1085 | fi 1086 | } 1087 | runtest leave_log_after_failure 1088 | 1089 | # https://github.com/rust-lang/rust-installer/issues/22 1090 | help() { 1091 | try sh "$S/gen-installer.sh" \ 1092 | --image-dir="$TEST_DIR/image1" \ 1093 | --work-dir="$WORK_DIR" \ 1094 | --output-dir="$OUT_DIR" 1095 | try "$WORK_DIR/package/install.sh" --help 1096 | } 1097 | runtest help 1098 | 1099 | # https://github.com/rust-lang/rust-installer/issues/31 1100 | CDPATH_does_not_destroy_things() { 1101 | try sh "$S/gen-installer.sh" \ 1102 | --image-dir="$TEST_DIR/image1" \ 1103 | --work-dir="$WORK_DIR" \ 1104 | --output-dir="$OUT_DIR" 1105 | cd "$WORK_DIR" || exit 1 1106 | export CDPATH="../$(basename $WORK_DIR)/foo" 1107 | try sh "package/install.sh" --prefix="$PREFIX_DIR" 1108 | cd "$S" || exit 1 1109 | cd "$PREFIX_DIR" || exit 1 1110 | export CDPATH="../$(basename $PREFIX_DIR)" 1111 | try sh "lib/packagelib/uninstall.sh" 1112 | cd "$S" || exit 1 1113 | unset CDPATH 1114 | } 1115 | runtest CDPATH_does_not_destroy_things 1116 | 1117 | docdir_default() { 1118 | try sh "$S/gen-installer.sh" \ 1119 | --image-dir="$TEST_DIR/image-docdir1" \ 1120 | --work-dir="$WORK_DIR" \ 1121 | --output-dir="$OUT_DIR" 1122 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" 1123 | try test -e "$PREFIX_DIR/share/doc/rust/README" 1124 | try test -e "$PREFIX_DIR/share/doc/rust/rustdocs.txt" 1125 | } 1126 | runtest docdir_default 1127 | 1128 | docdir() { 1129 | try sh "$S/gen-installer.sh" \ 1130 | --image-dir="$TEST_DIR/image-docdir1" \ 1131 | --work-dir="$WORK_DIR" \ 1132 | --output-dir="$OUT_DIR" 1133 | try mkdir "$WORK_DIR/docdir" 1134 | try "$WORK_DIR/package/install.sh" --prefix="$PREFIX_DIR" --docdir="$WORK_DIR/docdir" 1135 | try test -e "$WORK_DIR/docdir/README" 1136 | try test -e "$WORK_DIR/docdir/rustdocs.txt" 1137 | } 1138 | runtest docdir 1139 | 1140 | docdir_combined() { 1141 | try sh "$S/gen-installer.sh" \ 1142 | --image-dir="$TEST_DIR/image-docdir1" \ 1143 | --work-dir="$WORK_DIR" \ 1144 | --output-dir="$OUT_DIR" \ 1145 | --package-name="rustc" \ 1146 | --component-name="rustc" 1147 | try sh "$S/gen-installer.sh" \ 1148 | --image-dir="$TEST_DIR/image-docdir2" \ 1149 | --work-dir="$WORK_DIR" \ 1150 | --output-dir="$OUT_DIR" \ 1151 | --package-name="cargo" \ 1152 | --component-name="cargo" 1153 | try sh "$S/combine-installers.sh" \ 1154 | --work-dir="$WORK_DIR" \ 1155 | --output-dir="$OUT_DIR" \ 1156 | --package-name=rust \ 1157 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" 1158 | try mkdir "$WORK_DIR/docdir" 1159 | try "$WORK_DIR/rust/install.sh" --prefix="$PREFIX_DIR" --docdir="$WORK_DIR/docdir" 1160 | try test -e "$WORK_DIR/docdir/README" 1161 | try test -e "$WORK_DIR/docdir/rustdocs.txt" 1162 | try test -e "$WORK_DIR/docdir/README" 1163 | try test -e "$WORK_DIR/docdir/cargodocs.txt" 1164 | } 1165 | runtest docdir_combined 1166 | 1167 | combine_installers_different_input_compression_formats() { 1168 | try sh "$S/gen-installer.sh" \ 1169 | --image-dir="$TEST_DIR/image1" \ 1170 | --work-dir="$WORK_DIR" \ 1171 | --output-dir="$OUT_DIR" \ 1172 | --package-name=rustc \ 1173 | --component-name=rustc \ 1174 | --compression-formats=xz 1175 | try sh "$S/gen-installer.sh" \ 1176 | --image-dir="$TEST_DIR/image3" \ 1177 | --work-dir="$WORK_DIR" \ 1178 | --output-dir="$OUT_DIR" \ 1179 | --package-name=cargo \ 1180 | --component-name=cargo \ 1181 | --compression-formats=gz 1182 | try sh "$S/combine-installers.sh" \ 1183 | --work-dir="$WORK_DIR" \ 1184 | --output-dir="$OUT_DIR" \ 1185 | --package-name=rust \ 1186 | --input-tarballs="$OUT_DIR/rustc.tar.xz,$OUT_DIR/cargo.tar.gz" 1187 | 1188 | try test -e "${OUT_DIR}/rust.tar.gz" 1189 | try test -e "${OUT_DIR}/rust.tar.xz" 1190 | } 1191 | runtest combine_installers_different_input_compression_formats 1192 | 1193 | generate_compression_formats_one() { 1194 | try sh "$S/gen-installer.sh" \ 1195 | --image-dir="$TEST_DIR/image1" \ 1196 | --work-dir="$WORK_DIR" \ 1197 | --output-dir="$OUT_DIR" \ 1198 | --package-name="rustc" \ 1199 | --component-name="rustc" \ 1200 | --compression-formats="xz" 1201 | 1202 | try test ! -e "${OUT_DIR}/rustc.tar.gz" 1203 | try test -e "${OUT_DIR}/rustc.tar.xz" 1204 | } 1205 | runtest generate_compression_formats_one 1206 | 1207 | generate_compression_formats_multiple() { 1208 | try sh "$S/gen-installer.sh" \ 1209 | --image-dir="$TEST_DIR/image1" \ 1210 | --work-dir="$WORK_DIR" \ 1211 | --output-dir="$OUT_DIR" \ 1212 | --package-name="rustc" \ 1213 | --component-name="rustc" \ 1214 | --compression-formats="gz,xz" 1215 | 1216 | try test -e "${OUT_DIR}/rustc.tar.gz" 1217 | try test -e "${OUT_DIR}/rustc.tar.xz" 1218 | } 1219 | runtest generate_compression_formats_multiple 1220 | 1221 | generate_compression_formats_error() { 1222 | expect_fail sh "$S/gen-installer.sh" \ 1223 | --image-dir="$TEST_DIR/image1" \ 1224 | --work-dir="$WORK_DIR" \ 1225 | --output-dir="$OUT_DIR" \ 1226 | --package-name="rustc" \ 1227 | --component-name="rustc" \ 1228 | --compression-formats="xz,foobar" 1229 | } 1230 | runtest generate_compression_formats_error 1231 | 1232 | combine_compression_formats_one() { 1233 | try sh "$S/gen-installer.sh" \ 1234 | --image-dir="$TEST_DIR/image1" \ 1235 | --work-dir="$WORK_DIR" \ 1236 | --output-dir="$OUT_DIR" \ 1237 | --package-name=rustc \ 1238 | --component-name=rustc 1239 | try sh "$S/gen-installer.sh" \ 1240 | --image-dir="$TEST_DIR/image3" \ 1241 | --work-dir="$WORK_DIR" \ 1242 | --output-dir="$OUT_DIR" \ 1243 | --package-name=cargo \ 1244 | --component-name=cargo 1245 | try sh "$S/combine-installers.sh" \ 1246 | --work-dir="$WORK_DIR" \ 1247 | --output-dir="$OUT_DIR" \ 1248 | --package-name=rust \ 1249 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" \ 1250 | --compression-formats=xz 1251 | 1252 | try test ! -e "${OUT_DIR}/rust.tar.gz" 1253 | try test -e "${OUT_DIR}/rust.tar.xz" 1254 | } 1255 | runtest combine_compression_formats_one 1256 | 1257 | combine_compression_formats_multiple() { 1258 | try sh "$S/gen-installer.sh" \ 1259 | --image-dir="$TEST_DIR/image1" \ 1260 | --work-dir="$WORK_DIR" \ 1261 | --output-dir="$OUT_DIR" \ 1262 | --package-name=rustc \ 1263 | --component-name=rustc 1264 | try sh "$S/gen-installer.sh" \ 1265 | --image-dir="$TEST_DIR/image3" \ 1266 | --work-dir="$WORK_DIR" \ 1267 | --output-dir="$OUT_DIR" \ 1268 | --package-name=cargo \ 1269 | --component-name=cargo 1270 | try sh "$S/combine-installers.sh" \ 1271 | --work-dir="$WORK_DIR" \ 1272 | --output-dir="$OUT_DIR" \ 1273 | --package-name=rust \ 1274 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" \ 1275 | --compression-formats=xz,gz 1276 | 1277 | try test -e "${OUT_DIR}/rust.tar.gz" 1278 | try test -e "${OUT_DIR}/rust.tar.xz" 1279 | } 1280 | runtest combine_compression_formats_multiple 1281 | 1282 | combine_compression_formats_error() { 1283 | try sh "$S/gen-installer.sh" \ 1284 | --image-dir="$TEST_DIR/image1" \ 1285 | --work-dir="$WORK_DIR" \ 1286 | --output-dir="$OUT_DIR" \ 1287 | --package-name=rustc \ 1288 | --component-name=rustc 1289 | try sh "$S/gen-installer.sh" \ 1290 | --image-dir="$TEST_DIR/image3" \ 1291 | --work-dir="$WORK_DIR" \ 1292 | --output-dir="$OUT_DIR" \ 1293 | --package-name=cargo \ 1294 | --component-name=cargo 1295 | expect_fail sh "$S/combine-installers.sh" \ 1296 | --work-dir="$WORK_DIR" \ 1297 | --output-dir="$OUT_DIR" \ 1298 | --package-name=rust \ 1299 | --input-tarballs="$OUT_DIR/rustc.tar.gz,$OUT_DIR/cargo.tar.gz" \ 1300 | --compression-formats=xz,foobar 1301 | } 1302 | runtest combine_compression_formats_error 1303 | 1304 | tarball_compression_formats_one() { 1305 | try cp -r "${TEST_DIR}/image1" "${WORK_DIR}/image" 1306 | try sh "$S/make-tarballs.sh" \ 1307 | --input="${WORK_DIR}/image" \ 1308 | --work-dir="${WORK_DIR}" \ 1309 | --output="${OUT_DIR}/rustc" \ 1310 | --compression-formats="xz" 1311 | 1312 | try test ! -e "${OUT_DIR}/rustc.tar.gz" 1313 | try test -e "${OUT_DIR}/rustc.tar.xz" 1314 | } 1315 | runtest tarball_compression_formats_one 1316 | 1317 | tarball_compression_formats_multiple() { 1318 | try cp -r "${TEST_DIR}/image1" "${WORK_DIR}/image" 1319 | try sh "$S/make-tarballs.sh" \ 1320 | --input="${WORK_DIR}/image" \ 1321 | --work-dir="${WORK_DIR}" \ 1322 | --output="${OUT_DIR}/rustc" \ 1323 | --compression-formats="xz,gz" 1324 | 1325 | try test -e "${OUT_DIR}/rustc.tar.gz" 1326 | try test -e "${OUT_DIR}/rustc.tar.xz" 1327 | } 1328 | runtest tarball_compression_formats_multiple 1329 | 1330 | tarball_compression_formats_error() { 1331 | try cp -r "${TEST_DIR}/image1" "${WORK_DIR}/image" 1332 | expect_fail sh "$S/make-tarballs.sh" \ 1333 | --input="${WORK_DIR}/image" \ 1334 | --work-dir="${WORK_DIR}" \ 1335 | --output="${OUT_DIR}/rustc" \ 1336 | --compression-formats="xz,foobar" 1337 | } 1338 | runtest tarball_compression_formats_error 1339 | 1340 | echo 1341 | echo "TOTAL SUCCESS!" 1342 | echo 1343 | --------------------------------------------------------------------------------