init commit

This commit is contained in:
Zoritle
2021-01-18 14:26:44 +08:00
commit 60e3190020
10 changed files with 304 additions and 0 deletions

4
.cargo/config.toml Normal file
View File

@@ -0,0 +1,4 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
/.vscode
*.exe

44
Cargo.lock generated Normal file
View File

@@ -0,0 +1,44 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "fs-err"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcd1163ae48bda72a20ae26d66a04d3094135cadab911cff418ae5e33f253431"
[[package]]
name = "rshim"
version = "0.1.0"
dependencies = [
"fs-err",
"unicode-bom",
"winapi",
]
[[package]]
name = "unicode-bom"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

19
Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "rshim"
version = "0.1.0"
authors = ["anonymous <anonymous@example.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
opt-level = "z"
panic = "abort"
[dependencies]
fs-err = "2.5.0"
unicode-bom = "1"
[dependencies.winapi]
version = "0.3"
features = ["wincon","consoleapi","minwindef"]

9
LICENSE-MIT Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2019 Grégoire Geis
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

10
LICENSE-UNLICENSE Normal file
View File

@@ -0,0 +1,10 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

35
README.MD Normal file
View File

@@ -0,0 +1,35 @@
# `rshim`
rshim is the [`shim`](https://github.com/71/scoop-better-shimexe) program written in rust.
## Why this exist?
There are several versions of `shim`:
1. the official [`shim.cs`](https://github.com/lukesampson/scoop/blob/master/supporting/shimexe/shim.cs) was written in c# and required an instantiation of a .NET command line app every time it was started therefore is slower than directly executing. And it dose not handle Ctrl+C event correctly.
2. [`shim.c`](https://github.com/71/scoop-better-shimexe) is ok with performance and Ctrl+C event handling,but sometimes suffer memory violation (randomly exited with return code `3221226356`).
3. [`shim.cpp`](https://github.com/kiennq/scoop-better-shimexe) didn't work on my machine, calling it with any executable result in an infinite recursion of creating subprocess until eating all memory for unknown subtle reason.
### `rshim` :
1. less probability with bug and undefined behavior.
2. properly handle variety errors.
3. friendly error message.
4. properly handle Ctrl+C events.
5. properly handle utf-8 with or without bom in shim file.
## Installation
First install [rust](https://rustup.rs/)
```shell
cargo build --release
```
Close any running process with existed shim.
then
```shell
./repshims.bat
```
## Executable Size
Without `crt-static` enabled: around `200kb`
With `crt-static` enabled: around `300kb`

15
repshims.bat Normal file
View File

@@ -0,0 +1,15 @@
@echo off
if not defined SCOOP set SCOOP=%USERPROFILE%\scoop
for %%x in ("%SCOOP%\shims\*.exe") do (
echo Replacing %%x by new shim.
copy /B /Y shim.exe "%%~x" >NUL
)
if not defined SCOOP_GLOBAL set SCOOP_GLOBAL=%ProgramData%\scoop
for %%x in ("%SCOOP_GLOBAL%\shims\*.exe") do (
echo Replacing %%x by new shim.
copy /B /Y shim.exe "%%~x" >NUL
)

76
src/main.rs Normal file
View File

@@ -0,0 +1,76 @@
use std::{
env,
process::{exit, Command},
};
use winapi::{
shared::minwindef::{BOOL, DWORD, FALSE, TRUE},
um::{consoleapi, wincon},
};
unsafe extern "system" fn routine_handler(evt: DWORD) -> BOOL {
match evt {
wincon::CTRL_C_EVENT => TRUE, //eprintln!("ctrl_c handled!"),
wincon::CTRL_BREAK_EVENT => TRUE, //eprintln!("ctrl_break handled!"),
wincon::CTRL_CLOSE_EVENT => TRUE, //eprintln!("ctrl_close handled!"),
wincon::CTRL_LOGOFF_EVENT => TRUE, //eprintln!("ctrl_logoff handled!"),
wincon::CTRL_SHUTDOWN_EVENT => TRUE, //eprintln!("ctrl_shutdown handled!"),
other => {
eprintln!("unknown event number: {}, unhandled!", other);
return FALSE;
}
}
}
mod shims;
use shims::Shim;
const EXIT_FAILED_LOAD_SHIM: i32 = 1;
const EXIT_FAILED_SPAWN_PROG: i32 = 2;
const EXIT_FAILED_WAIT_PROG: i32 = 3;
const EXIT_PROG_TERMINATED: i32 = 4;
fn main() {
let res: BOOL = unsafe { consoleapi::SetConsoleCtrlHandler(Some(routine_handler), TRUE) };
if res == FALSE {
eprintln!("shim: register Ctrl handler failed.");
}
let calling_args: Vec<_> = env::args().skip(1).collect();
let shim = match Shim::init() {
Ok(v) => v,
Err(e) => {
eprintln!("Error while loading shim: {}", e);
exit(EXIT_FAILED_LOAD_SHIM);
}
};
let args = if let Some(mut shim_args) = shim.args {
shim_args.extend_from_slice(calling_args.as_slice());
shim_args
} else {
calling_args
};
let mut cmd = match Command::new(&shim.target_path).args(&args).spawn() {
Ok(v) => v,
Err(e) => {
eprintln!(
"Error while spawning target program `{}`: {}",
shim.target_path.to_string_lossy(),
e
);
exit(EXIT_FAILED_SPAWN_PROG);
}
};
let status = match cmd.wait() {
Ok(v) => v,
Err(e) => {
eprintln!(
"Error while waiting target program `{}`: {}",
shim.target_path.to_string_lossy(),
e
);
exit(EXIT_FAILED_WAIT_PROG);
}
};
exit(status.code().unwrap_or(EXIT_PROG_TERMINATED))
}

89
src/shims.rs Normal file
View File

@@ -0,0 +1,89 @@
use fs_err as fs;
use std::{
collections::HashMap,
env,
io::{Error, ErrorKind},
path::{Path, PathBuf},
};
pub struct Shim {
pub target_path: PathBuf,
pub args: Option<Vec<String>>,
}
impl Shim {
pub fn init() -> Result<Self, Error> {
let shim_path = get_shim_file_path()?;
let kvs = parse_shim_file(&shim_path)?;
let target_path = match kvs.get("path") {
Some(p) => PathBuf::from(p),
None => {
return Err(Error::new(
ErrorKind::NotFound,
format!("no path key in {}", shim_path.to_string_lossy()),
))
}
};
let args = kvs.get("args").map(|a| {
a.split_whitespace()
.map(|s| s.to_string())
.collect::<Vec<_>>()
});
Ok(Self { target_path, args })
}
}
fn get_shim_file_path() -> Result<PathBuf, Error> {
let mut current_exe = env::current_exe().map_err(|e| {
Error::new(
ErrorKind::Other,
format!("acquiring shim executable path: {}", e),
)
})?;
if !current_exe.set_extension("shim") {
return Err(Error::new(
ErrorKind::Other,
format!("{} is not a file", current_exe.to_string_lossy()),
));
}
Ok(current_exe)
}
use unicode_bom::Bom;
fn parse_shim_file(shim_path: &Path) -> Result<HashMap<String, String>, Error> {
let mut kvs = HashMap::new();
let raw_content = fs::read_to_string(shim_path).map_err(|e| {
Error::new(
ErrorKind::Other,
format!("reading {}: {}", shim_path.to_string_lossy(), e),
)
})?;
//NOTE: expedient trick for utf-8 with bom
let bom = Bom::from(raw_content.as_bytes());
for line in raw_content[bom.len()..]
.lines()
.filter(|l| !l.trim().is_empty())
{
let mut components = line.split("=");
let key = match components.next() {
Some(k) => k.trim(),
None => {
return Err(Error::new(
ErrorKind::InvalidData,
format!("invalid line in shim file: {}", line),
));
}
};
let value = match components.next() {
Some(v) => v.trim(),
None => {
return Err(Error::new(
ErrorKind::InvalidData,
format!("invalid line in shim file: {}", line),
));
}
};
kvs.insert(key.to_string(), value.to_string());
}
Ok(kvs)
}