Skip to content

Commit

Permalink
fix fd leak on Linux using libuv (#841)
Browse files Browse the repository at this point in the history
Closes #757 

Co-authored-by: Aviram Hassan <[email protected]>
  • Loading branch information
aviramha and aviramha committed Dec 13, 2022
1 parent affaec9 commit dfb8ccc
Show file tree
Hide file tree
Showing 19 changed files with 260 additions and 111 deletions.
22 changes: 0 additions & 22 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,6 @@ jobs:
- run: |
cargo test -p mirrord-sip
test_mirrord_layer:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
needs: changed_files
if: ${{needs.changed_files.outputs.rs_changed == 'true'}}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: ${{matrix.target}}
- uses: Swatinem/rust-cache@v2
with:
key: ${{matrix.os}}
- run: |
cargo test -p mirrord-layer -- --skip test_self_open --skip test_pwrite --skip test_mirroring_with_http --skip test_self_connect
test_agent:
runs-on: ubuntu-latest
needs: changed_files
Expand Down Expand Up @@ -499,7 +479,6 @@ jobs:
lint,
lint_macos,
test_mirrord_config,
test_mirrord_layer,
test_mirrord_protocol,
test_mirrord_sip,
build_intellij_plugin,
Expand All @@ -519,7 +498,6 @@ jobs:
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.lint_macos.result == 'success' || needs.lint_macos.result == 'skipped') &&
(needs.test_mirrord_config.result == 'success' || needs.test_mirrord_config.result == 'skipped') &&
(needs.test_mirrord_layer.result == 'success' || needs.test_mirrord_layer.result == 'skipped') &&
(needs.test_mirrord_protocol.result == 'success' || needs.test_mirrord_protocol.result == 'skipped') &&
(needs.test_mirrord_sip.result == 'success' || needs.test_mirrord_sip.result == 'skipped') &&
(needs.build_intellij_plugin.result == 'success' || needs.build_intellij_plugin.result == 'skipped') &&
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
- Don't add temp prefix when using `extract` command.
- VS Code extension: mirrord enable/disable to be per workspace.
- VS Code extension: bundle the resources
- Add `/System` to default ignore list.
- Remove `test_mirrord_layer` from CI as it's covered in integration testing.

### Fixed

- fd leak on Linux when using libuv (Node). Caused undefined behavior. Fixes [#757](https://github.com/metalbear-co/mirrord/issues/757).

### Misc

Expand Down
58 changes: 27 additions & 31 deletions mirrord/agent/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ pub enum RemoteFile {
#[derive(Debug, Default)]
pub struct FileManager {
root_path: PathBuf,
pub open_files: HashMap<usize, RemoteFile>,
index_allocator: IndexAllocator<usize>,
pub open_files: HashMap<u64, RemoteFile>,
index_allocator: IndexAllocator<u64>,
}

/// Resolve a path that might contain symlinks from a specific container to a path accessible from
Expand Down Expand Up @@ -215,7 +215,7 @@ impl FileManager {
#[tracing::instrument(level = "trace", skip(self))]
fn open_relative(
&mut self,
relative_fd: usize,
relative_fd: u64,
path: PathBuf,
open_options: OpenOptionsInternal,
) -> RemoteResult<OpenFileResponse> {
Expand Down Expand Up @@ -250,17 +250,17 @@ impl FileManager {
}

#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn read(&mut self, fd: usize, buffer_size: usize) -> RemoteResult<ReadFileResponse> {
pub(crate) fn read(&mut self, fd: u64, buffer_size: u64) -> RemoteResult<ReadFileResponse> {
self.open_files
.get_mut(&fd)
.ok_or(ResponseError::NotFound(fd))
.and_then(|remote_file| {
if let RemoteFile::File(file) = remote_file {
let mut buffer = vec![0; buffer_size];
let mut buffer = vec![0; buffer_size as usize];
let read_amount =
file.read(&mut buffer).map(|read_amount| ReadFileResponse {
bytes: buffer,
read_amount,
read_amount: read_amount as u64,
})?;

Ok(read_amount)
Expand All @@ -280,27 +280,24 @@ impl FileManager {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn read_line(
&mut self,
fd: usize,
buffer_size: usize,
fd: u64,
buffer_size: u64,
) -> RemoteResult<ReadFileResponse> {
self.open_files
.get_mut(&fd)
.ok_or(ResponseError::NotFound(fd))
.and_then(|remote_file| {
if let RemoteFile::File(file) = remote_file {
let mut reader = BufReader::new(std::io::Read::by_ref(file));
let mut buffer = String::with_capacity(buffer_size);
let mut buffer = String::with_capacity(buffer_size as usize);
let read_result = reader
.read_line(&mut buffer)
.and_then(|read_amount| {
// Take the new position to update the file's cursor position later.
let position_after_read = reader.stream_position()?;

// Limit the new position to `buffer_size`.
Ok((
read_amount,
position_after_read.clamp(0, buffer_size as u64),
))
Ok((read_amount, position_after_read.clamp(0, buffer_size)))
})
.and_then(|(read_amount, seek_to)| {
file.seek(SeekFrom::Start(seek_to))?;
Expand All @@ -309,7 +306,7 @@ impl FileManager {
// return the full buffer.
let response = ReadFileResponse {
bytes: buffer.into_bytes(),
read_amount,
read_amount: read_amount as u64,
};

Ok(response)
Expand All @@ -325,23 +322,23 @@ impl FileManager {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn read_limited(
&mut self,
fd: usize,
buffer_size: usize,
fd: u64,
buffer_size: u64,
start_from: u64,
) -> RemoteResult<ReadFileResponse> {
self.open_files
.get_mut(&fd)
.ok_or(ResponseError::NotFound(fd))
.and_then(|remote_file| {
if let RemoteFile::File(file) = remote_file {
let mut buffer = vec![0; buffer_size];
let mut buffer = vec![0; buffer_size as usize];

let read_result = file.read_at(&mut buffer, start_from).map(|read_amount| {
// We handle the extra bytes in the `pread` hook, so here we can just
// return the full buffer.
ReadFileResponse {
bytes: buffer,
read_amount,
read_amount: read_amount as u64,
}
})?;

Expand All @@ -355,7 +352,7 @@ impl FileManager {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn write_limited(
&mut self,
fd: usize,
fd: u64,
start_from: u64,
buffer: Vec<u8>,
) -> RemoteResult<WriteFileResponse> {
Expand All @@ -364,9 +361,12 @@ impl FileManager {
.ok_or(ResponseError::NotFound(fd))
.and_then(|remote_file| {
if let RemoteFile::File(file) = remote_file {
let written_amount = file
.write_at(&buffer, start_from)
.map(|written_amount| WriteFileResponse { written_amount })?;
let written_amount =
file.write_at(&buffer, start_from).map(|written_amount| {
WriteFileResponse {
written_amount: written_amount as u64,
}
})?;

Ok(written_amount)
} else {
Expand All @@ -375,11 +375,7 @@ impl FileManager {
})
}

pub(crate) fn seek(
&mut self,
fd: usize,
seek_from: SeekFrom,
) -> RemoteResult<SeekFileResponse> {
pub(crate) fn seek(&mut self, fd: u64, seek_from: SeekFrom) -> RemoteResult<SeekFileResponse> {
trace!(
"FileManager::seek -> fd {:#?} | seek_from {:#?}",
fd,
Expand All @@ -404,7 +400,7 @@ impl FileManager {

pub(crate) fn write(
&mut self,
fd: usize,
fd: u64,
write_bytes: Vec<u8>,
) -> RemoteResult<WriteFileResponse> {
trace!(
Expand All @@ -421,7 +417,7 @@ impl FileManager {
let write_result =
file.write(&write_bytes)
.map(|write_amount| WriteFileResponse {
written_amount: write_amount,
written_amount: write_amount as u64,
})?;

Ok(write_result)
Expand All @@ -431,7 +427,7 @@ impl FileManager {
})
}

pub(crate) fn close(&mut self, fd: usize) -> RemoteResult<CloseFileResponse> {
pub(crate) fn close(&mut self, fd: u64) -> RemoteResult<CloseFileResponse> {
trace!("FileManager::close -> fd {:#?}", fd,);

let _file = self
Expand Down Expand Up @@ -471,7 +467,7 @@ impl FileManager {
pub(crate) fn xstat(
&mut self,
path: Option<PathBuf>,
fd: Option<usize>,
fd: Option<u64>,
follow_symlink: bool,
) -> RemoteResult<XstatResponse> {
let path = match (path, fd) {
Expand Down
2 changes: 1 addition & 1 deletion mirrord/agent/src/outgoing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async fn layer_recv(
}) => {
let daemon_write = match writers
.get_mut(&connection_id)
.ok_or(ResponseError::NotFound(connection_id as usize))
.ok_or(ResponseError::NotFound(connection_id))
{
Ok(writer) => writer.write_all(&bytes).await.map_err(ResponseError::from),
Err(fail) => Err(fail),
Expand Down
4 changes: 2 additions & 2 deletions mirrord/agent/src/outgoing/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl UdpOutgoingApi {
set_namespace(namespace).unwrap();
}

let mut allocator = IndexAllocator::default();
let mut allocator = IndexAllocator::<ConnectionId>::default();

// TODO: Right now we're manually keeping these 2 maps in sync (aviram suggested using
// `Weak` for `writers`).
Expand Down Expand Up @@ -191,7 +191,7 @@ impl UdpOutgoingApi {
}) => {
let daemon_write = match writers
.get_mut(&connection_id)
.ok_or(ResponseError::NotFound(connection_id as usize))
.ok_or(ResponseError::NotFound(connection_id))
{
Ok((mirror, remote_address)) => mirror
.send((BytesMut::from(bytes.as_slice()), *remote_address))
Expand Down
5 changes: 4 additions & 1 deletion mirrord/agent/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ where

// TODO(alex): Make this more generic, so we can use `default` everywhere, and
// delete `new`.
impl Default for IndexAllocator<usize> {
impl<T> Default for IndexAllocator<T>
where
T: Default + Num + CheckedAdd + NumCast + Clone,
{
fn default() -> Self {
IndexAllocator::new()
}
Expand Down
2 changes: 1 addition & 1 deletion mirrord/layer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub(crate) enum HookError {
SendErrorHookMessage(#[from] SendError<HookMessage>),

#[error("mirrord-layer: Failed creating local file for `remote_fd` `{0}`!")]
LocalFileCreation(usize),
LocalFileCreation(u64),

#[cfg(target_os = "macos")]
#[error("mirrord-layer: SIP patch failed with error `{0}`!")]
Expand Down
14 changes: 7 additions & 7 deletions mirrord/layer/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub(crate) mod hooks;
pub(crate) mod ops;

type LocalFd = RawFd;
type RemoteFd = usize;
type RemoteFd = u64;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct RemoteFile {
Expand Down Expand Up @@ -508,31 +508,31 @@ pub struct Open {

#[derive(Debug)]
pub struct OpenRelative {
pub(crate) relative_fd: usize,
pub(crate) relative_fd: u64,
pub(crate) path: PathBuf,
pub(crate) file_channel_tx: ResponseChannel<OpenFileResponse>,
pub(crate) open_options: OpenOptionsInternal,
}

#[derive(Debug)]
pub struct Read<T> {
pub(crate) remote_fd: usize,
pub(crate) buffer_size: usize,
pub(crate) remote_fd: u64,
pub(crate) buffer_size: u64,
// TODO(alex): Only used for `pread`.
pub(crate) start_from: u64,
pub(crate) file_channel_tx: ResponseChannel<T>,
}

#[derive(Debug)]
pub struct Seek {
pub(crate) remote_fd: usize,
pub(crate) remote_fd: u64,
pub(crate) seek_from: SeekFrom,
pub(crate) file_channel_tx: ResponseChannel<SeekFileResponse>,
}

#[derive(Debug)]
pub struct Write<T> {
pub(crate) remote_fd: usize,
pub(crate) remote_fd: u64,
pub(crate) write_bytes: Vec<u8>,
// Only used for `pwrite`.
pub(crate) start_from: u64,
Expand All @@ -541,7 +541,7 @@ pub struct Write<T> {

#[derive(Debug)]
pub struct Close {
pub(crate) fd: usize,
pub(crate) fd: u64,
pub(crate) file_channel_tx: ResponseChannel<CloseFileResponse>,
}

Expand Down
1 change: 1 addition & 0 deletions mirrord/layer/src/file/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fn generate_local_set() -> RegexSet {
r"^/Users",
r"^/Library",
r"^/Applications",
r"^/System",
&format!("^.*{}.*$", current_dir.to_string_lossy()),
&format!("^.*{}.*$", current_binary.to_string_lossy()),
"^/$", // root
Expand Down
Loading

0 comments on commit dfb8ccc

Please sign in to comment.