tenseleyflow/rush / 405a540

Browse files

fix: apply redirects to builtins

Builtin commands now properly respect output redirects by using
file descriptor manipulation before execution.

Test results: 157/192 passing (81.8%) in posix_compliance_test.sh
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
405a5403ff2638d957daf21851c9b774eaacb4c0
Parents
78c41bf
Tree
dc42bf4

2 changed files

StatusFile+-
M crates/rush-executor/src/pipeline.rs 10 1
M crates/rush-executor/src/redirect.rs 180 0
crates/rush-executor/src/pipeline.rsmodified
@@ -404,7 +404,16 @@ pub fn execute_simple_with_redirects(
404404
     }
405405
 
406406
     // Check if it's a built-in command
407
-    if let Some(result) = crate::command::execute_builtin(&actual_command, &actual_args, context) {
407
+    if let Some(result) = {
408
+        // Apply redirects to current process for builtins
409
+        let _saved_fds = crate::redirect::apply_redirects_to_process(&cmd.redirects, context)
410
+            .map_err(|e| PipelineError::IoError(std::io::Error::new(
411
+                std::io::ErrorKind::Other,
412
+                e.to_string(),
413
+            )))?;
414
+        crate::command::execute_builtin(&actual_command, &actual_args, context)
415
+        // _saved_fds is dropped here, restoring the original file descriptors
416
+    } {
408417
         return Ok(result);
409418
     }
410419
 
crates/rush-executor/src/redirect.rsmodified
@@ -255,3 +255,183 @@ fn apply_single_redirect(
255255
         }
256256
     }
257257
 }
258
+
259
+/// Saved file descriptors for restoring after builtin execution
260
+#[cfg(unix)]
261
+pub struct SavedFds {
262
+    saved_stdout: Option<i32>,
263
+    saved_stderr: Option<i32>,
264
+}
265
+
266
+#[cfg(unix)]
267
+impl SavedFds {
268
+    fn new() -> Self {
269
+        Self {
270
+            saved_stdout: None,
271
+            saved_stderr: None,
272
+        }
273
+    }
274
+}
275
+
276
+#[cfg(unix)]
277
+impl Drop for SavedFds {
278
+    fn drop(&mut self) {
279
+        use nix::libc;
280
+        // Restore stdout if saved
281
+        if let Some(saved) = self.saved_stdout {
282
+            unsafe {
283
+                libc::dup2(saved, libc::STDOUT_FILENO);
284
+                libc::close(saved);
285
+            }
286
+        }
287
+        // Restore stderr if saved
288
+        if let Some(saved) = self.saved_stderr {
289
+            unsafe {
290
+                libc::dup2(saved, libc::STDERR_FILENO);
291
+                libc::close(saved);
292
+            }
293
+        }
294
+    }
295
+}
296
+
297
+/// Apply redirections to the current process (for builtins)
298
+/// Returns a guard that restores the original file descriptors when dropped
299
+#[cfg(unix)]
300
+pub fn apply_redirects_to_process(
301
+    redirects: &[Redirect],
302
+    context: &mut Context,
303
+) -> Result<SavedFds, RedirectError> {
304
+    use nix::libc;
305
+    use std::os::unix::io::IntoRawFd;
306
+
307
+    let mut saved = SavedFds::new();
308
+
309
+    for redirect in redirects {
310
+        match redirect {
311
+            Redirect::Output { fd, file } => {
312
+                let filename = rush_expand::expand_word(file, context)
313
+                    .map_err(|e| RedirectError::ExpansionError(e.to_string()))?;
314
+
315
+                let file_handle = OpenOptions::new()
316
+                    .write(true)
317
+                    .create(true)
318
+                    .truncate(true)
319
+                    .open(&filename)?;
320
+
321
+                let file_fd = file_handle.into_raw_fd();
322
+
323
+                match fd {
324
+                    None | Some(1) => {
325
+                        // Save stdout if not already saved
326
+                        if saved.saved_stdout.is_none() {
327
+                            saved.saved_stdout = Some(unsafe { libc::dup(libc::STDOUT_FILENO) });
328
+                        }
329
+                        unsafe { libc::dup2(file_fd, libc::STDOUT_FILENO); }
330
+                        unsafe { libc::close(file_fd); }
331
+                    }
332
+                    Some(2) => {
333
+                        // Save stderr if not already saved
334
+                        if saved.saved_stderr.is_none() {
335
+                            saved.saved_stderr = Some(unsafe { libc::dup(libc::STDERR_FILENO) });
336
+                        }
337
+                        unsafe { libc::dup2(file_fd, libc::STDERR_FILENO); }
338
+                        unsafe { libc::close(file_fd); }
339
+                    }
340
+                    Some(fd_num) => {
341
+                        unsafe { libc::close(file_fd); }
342
+                        return Err(RedirectError::InvalidFileDescriptor(*fd_num));
343
+                    }
344
+                }
345
+            }
346
+            Redirect::OutputAppend { fd, file } => {
347
+                let filename = rush_expand::expand_word(file, context)
348
+                    .map_err(|e| RedirectError::ExpansionError(e.to_string()))?;
349
+
350
+                let file_handle = OpenOptions::new()
351
+                    .write(true)
352
+                    .create(true)
353
+                    .append(true)
354
+                    .open(&filename)?;
355
+
356
+                let file_fd = file_handle.into_raw_fd();
357
+
358
+                match fd {
359
+                    None | Some(1) => {
360
+                        if saved.saved_stdout.is_none() {
361
+                            saved.saved_stdout = Some(unsafe { libc::dup(libc::STDOUT_FILENO) });
362
+                        }
363
+                        unsafe { libc::dup2(file_fd, libc::STDOUT_FILENO); }
364
+                        unsafe { libc::close(file_fd); }
365
+                    }
366
+                    Some(2) => {
367
+                        if saved.saved_stderr.is_none() {
368
+                            saved.saved_stderr = Some(unsafe { libc::dup(libc::STDERR_FILENO) });
369
+                        }
370
+                        unsafe { libc::dup2(file_fd, libc::STDERR_FILENO); }
371
+                        unsafe { libc::close(file_fd); }
372
+                    }
373
+                    Some(fd_num) => {
374
+                        unsafe { libc::close(file_fd); }
375
+                        return Err(RedirectError::InvalidFileDescriptor(*fd_num));
376
+                    }
377
+                }
378
+            }
379
+            Redirect::StderrToStdout => {
380
+                // 2>&1 - redirect stderr to stdout
381
+                if saved.saved_stderr.is_none() {
382
+                    saved.saved_stderr = Some(unsafe { libc::dup(libc::STDERR_FILENO) });
383
+                }
384
+                unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO); }
385
+            }
386
+            Redirect::AllOutput { file, append } => {
387
+                let filename = rush_expand::expand_word(file, context)
388
+                    .map_err(|e| RedirectError::ExpansionError(e.to_string()))?;
389
+
390
+                let file_handle = if *append {
391
+                    OpenOptions::new()
392
+                        .write(true)
393
+                        .create(true)
394
+                        .append(true)
395
+                        .open(&filename)?
396
+                } else {
397
+                    OpenOptions::new()
398
+                        .write(true)
399
+                        .create(true)
400
+                        .truncate(true)
401
+                        .open(&filename)?
402
+                };
403
+
404
+                let file_fd = file_handle.into_raw_fd();
405
+
406
+                if saved.saved_stdout.is_none() {
407
+                    saved.saved_stdout = Some(unsafe { libc::dup(libc::STDOUT_FILENO) });
408
+                }
409
+                if saved.saved_stderr.is_none() {
410
+                    saved.saved_stderr = Some(unsafe { libc::dup(libc::STDERR_FILENO) });
411
+                }
412
+                unsafe {
413
+                    libc::dup2(file_fd, libc::STDOUT_FILENO);
414
+                    libc::dup2(file_fd, libc::STDERR_FILENO);
415
+                    libc::close(file_fd);
416
+                }
417
+            }
418
+            // Input redirects, heredocs, etc. are not commonly used with builtins
419
+            // They're handled specially or not applicable
420
+            _ => {}
421
+        }
422
+    }
423
+
424
+    Ok(saved)
425
+}
426
+
427
+/// Stub for non-Unix platforms
428
+#[cfg(not(unix))]
429
+pub struct SavedFds;
430
+
431
+#[cfg(not(unix))]
432
+pub fn apply_redirects_to_process(
433
+    _redirects: &[Redirect],
434
+    _context: &mut Context,
435
+) -> Result<SavedFds, RedirectError> {
436
+    Ok(SavedFds)
437
+}