Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions deps/ntfind/src/argv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use windows_sys::Win32::System::Environment::GetCommandLineW;

use crate::MSG_USAGE;
use crate::buffer::WideString;
use crate::io::{exit, stderr_handle, write_stderr_str, write_stdout_str, write_to_handle};
use crate::io::{StatusResult, stderr_handle, write_stderr_str, write_stdout_str, write_to_handle};
use crate::path::{directory_exists, full_path, path_state, query_device_len};

unsafe extern "C" {
Expand Down Expand Up @@ -51,15 +51,15 @@ impl Default for FindState {
}

/// ARGUMENT_LEXEMIZER::PrepareToParse + DoParsing + MULTIPLE_PATH_ARGUMENT.
pub fn parse_command_line() -> (FindState, Vec<WideString>) {
pub fn parse_command_line() -> StatusResult<(FindState, Vec<WideString>)> {
// QUIRK / BUG: ulib has two `ARGUMENT_LEXEMIZER::PutSeparators` overloads.
// The PCWSTRING one folds `_WhiteSpace` and `_SwitchChars` into `_SeparatorString`,
// while the PCSTR one has those two lines commented out. find.cxx passes a narrow string.
// So, the only reason '/' is not treated as a flag in "forward/slash/paths" is a coincidence.

// Technically this could be trivially folded into the loop below.
// I left it like it works in the original for now.
let lexemes = prepare_to_parse();
let lexemes = prepare_to_parse()?;

// ARGUMENT_LEXEMIZER::DoParsing, heavily modified & inlined.

Expand Down Expand Up @@ -124,10 +124,10 @@ pub fn parse_command_line() -> (FindState, Vec<WideString>) {
if lexemes.next().is_some() {
if seen.invalid_switch {
write_stderr_str("FIND: Invalid switch\r\n");
exit(2);
return Err(2);
} else {
write_stderr_str("FIND: Parameter format not correct\r\n");
exit(2);
return Err(2);
}
}

Expand All @@ -137,17 +137,17 @@ pub fn parse_command_line() -> (FindState, Vec<WideString>) {
w.push_wide(&failed_pattern);
w.push_str("\r\n");
write_to_handle(stderr_handle(), &w);
exit(2);
return Err(2);
}

if seen.help {
write_stdout_str(MSG_USAGE);
exit(0);
return Err(0);
}

if !seen.string_pattern {
write_stderr_str("FIND: Parameter format not correct\r\n");
exit(2);
return Err(2);
}

state.case_sensitive = !seen.case_insensitive;
Expand All @@ -156,7 +156,7 @@ pub fn parse_command_line() -> (FindState, Vec<WideString>) {
state.output_line_numbers = seen.display_numbers;
state.skip_offline_files = !seen.offline && !seen.off;

(state, paths)
Ok((state, paths))
}

fn raw_command_line() -> &'static [u16] {
Expand Down Expand Up @@ -194,7 +194,7 @@ impl Lexeme {
}

/// Contains logic from `ARGUMENT_LEXEMIZER::PrepareToParse`.
fn prepare_to_parse() -> Vec<Lexeme> {
fn prepare_to_parse() -> StatusResult<Vec<Lexeme>> {
let cmd_line = raw_command_line();
let mut lexemes = Vec::new();
let mut pos = 0;
Expand Down Expand Up @@ -231,7 +231,7 @@ fn prepare_to_parse() -> Vec<Lexeme> {
}
None => {
write_stderr_str("FIND: Parameter format not correct\r\n");
exit(2);
return Err(2);
}
}
}
Expand All @@ -244,7 +244,7 @@ fn prepare_to_parse() -> Vec<Lexeme> {
lexemes.push(collapse_quoted_lexeme(&cmd_line[tok_start..pos]));
}

lexemes
Ok(lexemes)
}

fn collapse_quoted_lexeme(raw: &'static [u16]) -> Lexeme {
Expand Down
52 changes: 28 additions & 24 deletions deps/ntfind/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use windows_sys::Win32::Globalization::{
};
use windows_sys::Win32::Storage::FileSystem::{FILE_TYPE_DISK, GetFileType, ReadFile};

use crate::io::{exit, input_code_page, write_stderr_str};
use crate::io::StatusResult;
use crate::io::{input_code_page, write_stderr_str};

/// A helper for wchar_t string buffers. It's rather crude in this state, but it gets the job done.
/// The original find would rely heavily on ulib's `PATH` type instead.
Expand Down Expand Up @@ -196,29 +197,29 @@ impl BufferStream {
}
}

pub fn read_line(&mut self) -> Option<&[u16]> {
pub fn read_line(&mut self) -> StatusResult<Option<&[u16]>> {
if self.stream_type == StreamType::Unknown {
self.detect_stream_type();
self.detect_stream_type()?;
}

let line = if self.stream_type == StreamType::Unicode {
self.read_line_unicode()?
self.read_line_unicode()
} else {
self.read_line_ansi()?
self.read_line_ansi()
};

Some(line.strip_suffix(&[0x0D]).unwrap_or(line))
line.map(|opt| opt.map(|line| line.strip_suffix(&[0x0D]).unwrap_or(line)))
}

/// Detect whether the stream is ANSI or UTF-16LE.
/// This replicates `BUFFER_STREAM::DetermineStreamType` and `BUFFER_STREAM::GetBuffer`.
fn detect_stream_type(&mut self) {
fn detect_stream_type(&mut self) -> StatusResult<()> {
unsafe {
self.stream_type = StreamType::Ansi;

self.fill_buffer();
self.fill_buffer()?;
if self.buffer.is_empty() {
return;
return Ok(());
}

// QUIRK / BUG:
Expand All @@ -245,11 +246,11 @@ impl BufferStream {
if self.buffer.len() >= 2 {
self.beg = 2; // skip BOM
}
return;
return Ok(());
}

if is_unicode == 0 {
return;
return Ok(());
}

let flags_match = if GetFileType(self.handle) == FILE_TYPE_DISK {
Expand All @@ -264,20 +265,22 @@ impl BufferStream {
if flags_match && !self.buffer.iter().any(|&b| IsDBCSLeadByte(b) != 0) {
self.stream_type = StreamType::Unicode;
}

Ok(())
}
}

fn read_line_ansi(&mut self) -> Option<&[u16]> {
fn read_line_ansi(&mut self) -> StatusResult<Option<&[u16]>> {
loop {
if self.eof {
return if self.beg >= self.buffer.len() {
return Ok(if self.beg >= self.buffer.len() {
None
} else {
let beg = self.beg;
let end = self.buffer.len();
self.beg = self.buffer.len();
Some(self.convert_to_wide(beg, end))
};
});
}

// Look for a line terminator and if found, yield that line.
Expand All @@ -289,16 +292,16 @@ impl BufferStream {
let end = self.scan + off;
self.beg = end + 1;
self.scan = self.beg;
return Some(self.convert_to_wide(beg, end));
return Ok(Some(self.convert_to_wide(beg, end)));
}

// `buffer[beg..]` has no terminator. Remember that for the next scan.
self.scan = self.buffer.len();
self.fill_buffer();
self.fill_buffer()?;
}
}

fn read_line_unicode(&mut self) -> Option<&[u16]> {
fn read_line_unicode(&mut self) -> StatusResult<Option<&[u16]>> {
// QUIRK / BUG:
// ulib's `BUFFER_STREAM::ReadString` does this, right after finding a newline with `wcscspn`:
// if((BytesInBuffer & 0xfffe) != BytesInBuffer){
Expand Down Expand Up @@ -328,29 +331,29 @@ impl BufferStream {
};

if self.eof {
return if beg >= wide.len() {
return Ok(if beg >= wide.len() {
None
} else {
self.beg = self.buffer.len();
Some(&wide[beg..])
};
});
}

// Look for a line terminator and if found, yield that line.
if let Some(off) = wide[scan..].iter().position(|&c| c == 0x00 || c == 0x0A) {
let end = scan + off;
self.beg = (end + 1) * 2;
self.scan = self.beg;
return Some(&wide[beg..end]);
return Ok(Some(&wide[beg..end]));
}

// `buffer[beg..]` has no terminator. Remember that for the next scan.
self.scan = self.buffer.len();
self.fill_buffer();
self.fill_buffer()?;
}
}

fn fill_buffer(&mut self) {
fn fill_buffer(&mut self) -> StatusResult<()> {
unsafe {
// Move the partial line to the beginning of the buffer.
// The idea is that read() calls will either be very slow, and this doesn't matter,
Expand Down Expand Up @@ -378,14 +381,15 @@ impl BufferStream {
let err = GetLastError();
if err == ERROR_BROKEN_PIPE {
self.eof = true;
return;
return Ok(());
}
write_stderr_str("Unable to read file\r\n");
exit(2);
return Err(2);
}

self.buffer.set_len(self.buffer.len() + bytes_read as usize);
self.eof = bytes_read == 0;
Ok(())
}
}

Expand Down
27 changes: 15 additions & 12 deletions deps/ntfind/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use core::hint::unreachable_unchecked;
use core::ptr;

use alloc::vec::Vec;
Expand All @@ -12,10 +11,24 @@ use windows_sys::Win32::System::Console::{
GetConsoleCP, GetConsoleMode, GetStdHandle, STD_ERROR_HANDLE, STD_HANDLE, STD_OUTPUT_HANDLE,
WriteConsoleW,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, TerminateProcess};

use crate::buffer::WideString;

pub type StatusResult<T> = core::result::Result<T, i32>;

pub trait IntoStatus {
fn into_status(self) -> i32;
}

impl<T> IntoStatus for core::result::Result<T, i32> {
fn into_status(self) -> i32 {
match self {
Ok(_) => 0,
Err(code) => code,
}
}
}

pub struct OutputHandle {
handle: HANDLE,
is_console: bool,
Expand Down Expand Up @@ -157,13 +170,3 @@ pub fn write_stdout_str(s: &str) {
pub fn write_stderr_str(s: &str) {
write_str(stderr_handle(), s);
}

/// Our own std::process::exit. It had not other place to live at.
pub fn exit(code: u32) -> ! {
unsafe {
// TerminateProcess is technically preferable nowadays, because it genuinely exits faster.
// Since we're an executable we have nothing to loose by using it.
TerminateProcess(GetCurrentProcess(), code);
unreachable_unchecked();
}
}
25 changes: 16 additions & 9 deletions deps/ntfind/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ use windows_sys::Win32::System::Console::{GetStdHandle, STD_INPUT_HANDLE};

use crate::argv::parse_command_line;
use crate::buffer::{BufferStream, WideString};
use crate::io::{exit, io_init, stderr_handle, stdout_handle, write_stderr_str, write_to_handle};
use crate::io::{
IntoStatus as _, StatusResult, io_init, stderr_handle, stdout_handle, write_stderr_str,
write_to_handle,
};
use crate::path::is_drive_path;

const MSG_USAGE: &str = concat!(
Expand All @@ -46,14 +49,18 @@ const MSG_USAGE: &str = concat!(
"or piped from another command.\r\n"
);

pub fn ntfind_main() -> ! {
pub fn ntfind_main() -> i32 {
main().into_status()
}

fn main() -> StatusResult<()> {
io_init();

let (mut state, mut files) = parse_command_line();
let (mut state, mut files) = parse_command_line()?;

if files.is_empty() {
let h_stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
let lines_found = search_stream(&mut state, h_stdin);
let lines_found = search_stream(&mut state, h_stdin)?;

if !state.output_lines {
let mut out = WideString::new();
Expand Down Expand Up @@ -132,7 +139,7 @@ pub fn ntfind_main() -> ! {
write_to_handle(stdout_handle(), &out);
}

let lines_found = search_stream(&mut state, h_file);
let lines_found = search_stream(&mut state, h_file)?;

if !state.output_lines {
out.clear();
Expand All @@ -153,10 +160,10 @@ pub fn ntfind_main() -> ! {
}
}

exit(if state.found_any { 0 } else { 1 });
if state.found_any { Ok(()) } else { Err(1) }
}

fn search_stream(state: &mut argv::FindState, handle: HANDLE) -> u32 {
fn search_stream(state: &mut argv::FindState, handle: HANDLE) -> StatusResult<u32> {
let mut line_count: u32 = 0;
let mut found_count: u32 = 0;
let pattern_len = state.pattern.len();
Expand All @@ -168,7 +175,7 @@ fn search_stream(state: &mut argv::FindState, handle: HANDLE) -> u32 {
NORM_IGNORECASE
};

while let Some(line) = reader.read_line() {
while let Some(line) = reader.read_line()? {
line_count += 1;

// Look for pattern in the current line. A 0-length pattern ("") never matches.
Expand Down Expand Up @@ -217,7 +224,7 @@ fn search_stream(state: &mut argv::FindState, handle: HANDLE) -> u32 {
state.found_any = true;
}

found_count
Ok(found_count)
}

/// Matches `IsDos5CompatibleFileName` in file.cxx.
Expand Down
8 changes: 7 additions & 1 deletion deps/ntfind/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

#![no_main]

use windows_sys::Win32::System::Threading::{GetCurrentProcess, TerminateProcess};

#[unsafe(no_mangle)]
extern "C" fn main() -> ! {
find::ntfind_main();
unsafe {
let status = ntfind_main();
TerminateProcess(GetCurrentProcess(), status as u32);
core::hint::unreachable_unchecked();
}
}
Loading