Add signal support - exit on signal
This commit is contained in:
		
							parent
							
								
									b551b30300
								
							
						
					
					
						commit
						bf292bc0d1
					
				
					 31 changed files with 396 additions and 314 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -1,9 +1,6 @@ | |||
| [submodule "rust"] | ||||
| 	path = rust | ||||
| 	url = https://github.com/redox-os/rust.git | ||||
| [submodule "libstd"] | ||||
| 	path = libstd | ||||
| 	url = https://github.com/redox-os/libstd.git | ||||
| [submodule "ion"] | ||||
| 	path = programs/ion | ||||
| 	url = https://github.com/redox-os/ion.git | ||||
|  | @ -52,3 +49,6 @@ | |||
| [submodule "crates/docgen"] | ||||
| 	path = crates/docgen | ||||
| 	url = https://github.com/redox-os/docgen.git | ||||
| [submodule "libstd_real/openlibm"] | ||||
| 	path = libstd/openlibm | ||||
| 	url = https://github.com/redox-os/openlibm.git | ||||
|  |  | |||
							
								
								
									
										12
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -28,7 +28,6 @@ FORCE: | |||
| clean: | ||||
| 	cargo clean | ||||
| 	cargo clean --manifest-path libstd/Cargo.toml | ||||
| 	cargo clean --manifest-path libstd_real/Cargo.toml | ||||
| 	cargo clean --manifest-path drivers/ahcid/Cargo.toml | ||||
| 	cargo clean --manifest-path drivers/e1000d/Cargo.toml | ||||
| 	cargo clean --manifest-path drivers/ps2d/Cargo.toml | ||||
|  | @ -82,7 +81,6 @@ ref: FORCE | |||
| test: | ||||
| 	cargo test | ||||
| 	cargo test --manifest-path libstd/Cargo.toml | ||||
| 	cargo test --manifest-path libstd_real/Cargo.toml | ||||
| 	cargo test --manifest-path drivers/ahcid/Cargo.toml | ||||
| 	cargo test --manifest-path drivers/e1000d/Cargo.toml | ||||
| 	cargo test --manifest-path drivers/ps2d/Cargo.toml | ||||
|  | @ -114,7 +112,6 @@ test: | |||
| update: | ||||
| 	cargo update | ||||
| 	cargo update --manifest-path libstd/Cargo.toml | ||||
| 	cargo update --manifest-path libstd_real/Cargo.toml | ||||
| 	cargo update --manifest-path drivers/ahcid/Cargo.toml | ||||
| 	cargo update --manifest-path drivers/e1000d/Cargo.toml | ||||
| 	cargo update --manifest-path drivers/ps2d/Cargo.toml | ||||
|  | @ -314,13 +311,9 @@ $(BUILD)/libopenlibm.a: libstd/openlibm/libopenlibm.a | |||
| 	mkdir -p $(BUILD) | ||||
| 	cp $< $@ | ||||
| 
 | ||||
| #$(BUILD)/libstd.rlib: libstd/Cargo.toml libstd/src/** $(BUILD)/libcore.rlib $(BUILD)/liballoc.rlib $(BUILD)/librustc_unicode.rlib $(BUILD)/libcollections.rlib $(BUILD)/librand.rlib $(BUILD)/libopenlibm.a
 | ||||
| #	$(CARGO) rustc --verbose --manifest-path $< $(CARGOFLAGS) -o $@
 | ||||
| #	cp libstd/target/$(TARGET)/release/deps/*.rlib $(BUILD)
 | ||||
| 
 | ||||
| $(BUILD)/libstd.rlib: libstd_real/Cargo.toml rust/src/libstd/** $(BUILD)/libcore.rlib $(BUILD)/liballoc.rlib $(BUILD)/librustc_unicode.rlib $(BUILD)/libcollections.rlib $(BUILD)/librand.rlib $(BUILD)/libopenlibm.a | ||||
| $(BUILD)/libstd.rlib: libstd/Cargo.toml rust/src/libstd/** $(BUILD)/libcore.rlib $(BUILD)/liballoc.rlib $(BUILD)/librustc_unicode.rlib $(BUILD)/libcollections.rlib $(BUILD)/librand.rlib $(BUILD)/libopenlibm.a | ||||
| 	$(CARGO) rustc --verbose --manifest-path $< $(CARGOFLAGS) -o $@ | ||||
| 	cp libstd_real/target/$(TARGET)/release/deps/*.rlib $(BUILD) | ||||
| 	cp libstd/target/$(TARGET)/release/deps/*.rlib $(BUILD) | ||||
| 
 | ||||
| initfs/bin/%: drivers/%/Cargo.toml drivers/%/src/** $(BUILD)/libstd.rlib | ||||
| 	mkdir -p initfs/bin | ||||
|  | @ -438,6 +431,7 @@ coreutils: \ | |||
| 	filesystem/bin/false \
 | ||||
| 	filesystem/bin/free \
 | ||||
| 	filesystem/bin/head \
 | ||||
| 	filesystem/bin/kill \
 | ||||
| 	filesystem/bin/ls \
 | ||||
| 	filesystem/bin/mkdir \
 | ||||
| 	filesystem/bin/mv \
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| use core::mem; | ||||
| use core::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; | ||||
| 
 | ||||
| /// This must be used by the kernel to ensure that context switches are done atomically
 | ||||
|  | @ -65,6 +66,23 @@ impl Context { | |||
|         self.rsp = address; | ||||
|     } | ||||
| 
 | ||||
|     pub unsafe fn signal_stack(&mut self, handler: extern fn(usize), sig: u8) { | ||||
|         self.push_stack(sig as usize); | ||||
|         self.push_stack(handler as usize); | ||||
|         self.push_stack(signal_handler_wrapper as usize); | ||||
|     } | ||||
| 
 | ||||
|     pub unsafe fn push_stack(&mut self, value: usize) { | ||||
|         self.rsp -= mem::size_of::<usize>(); | ||||
|         *(self.rsp as *mut usize) = value; | ||||
|     } | ||||
| 
 | ||||
|     pub unsafe fn pop_stack(&mut self) -> usize { | ||||
|         let value = *(self.rsp as *const usize); | ||||
|         self.rsp += mem::size_of::<usize>(); | ||||
|         value | ||||
|     } | ||||
| 
 | ||||
|     /// Switch to the next context by restoring its stack and registers
 | ||||
|     #[cold] | ||||
|     #[inline(never)] | ||||
|  | @ -108,3 +126,61 @@ impl Context { | |||
|         asm!("mov rbp, $0" : : "r"(next.rbp) : "memory" : "intel", "volatile"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[repr(packed)] | ||||
| pub struct SignalHandlerStack { | ||||
|     r11: usize, | ||||
|     r10: usize, | ||||
|     r9: usize, | ||||
|     r8: usize, | ||||
|     rsi: usize, | ||||
|     rdi: usize, | ||||
|     rdx: usize, | ||||
|     rcx: usize, | ||||
|     rax: usize, | ||||
|     handler: extern fn(usize), | ||||
|     sig: usize, | ||||
|     rip: usize, | ||||
| } | ||||
| 
 | ||||
| #[naked] | ||||
| unsafe extern fn signal_handler_wrapper() { | ||||
|     #[inline(never)] | ||||
|     unsafe fn inner(stack: &SignalHandlerStack) { | ||||
|         (stack.handler)(stack.sig); | ||||
|     } | ||||
| 
 | ||||
|     // Push scratch registers
 | ||||
|     asm!("xchg bx, bx
 | ||||
|         push rax | ||||
|         push rcx | ||||
|         push rdx | ||||
|         push rdi | ||||
|         push rsi | ||||
|         push r8 | ||||
|         push r9 | ||||
|         push r10 | ||||
|         push r11" | ||||
|         : : : : "intel", "volatile"); | ||||
| 
 | ||||
|     // Get reference to stack variables
 | ||||
|     let rsp: usize; | ||||
|     asm!("" : "={rsp}"(rsp) : : : "intel", "volatile"); | ||||
| 
 | ||||
|     // Call inner rust function
 | ||||
|     inner(&*(rsp as *const SignalHandlerStack)); | ||||
| 
 | ||||
|     // Pop scratch registers, error code, and return
 | ||||
|     asm!("xchg bx, bx
 | ||||
|         pop r11 | ||||
|         pop r10 | ||||
|         pop r9 | ||||
|         pop r8 | ||||
|         pop rsi | ||||
|         pop rdi | ||||
|         pop rdx | ||||
|         pop rcx | ||||
|         pop rax | ||||
|         add rsp, 16" | ||||
|         : : : : "intel", "volatile"); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| use super::{halt, stack_trace}; | ||||
| 
 | ||||
| use interrupt::stack_trace; | ||||
| use syscall::flag::*; | ||||
| 
 | ||||
| extern { | ||||
|  | @ -8,9 +7,8 @@ extern { | |||
| 
 | ||||
| interrupt_stack!(divide_by_zero, stack, { | ||||
|     println!("Divide by zero fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGFPE); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGFPE); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(debug, stack, { | ||||
|  | @ -34,107 +32,92 @@ interrupt_stack!(overflow, stack, { | |||
| 
 | ||||
| interrupt_stack!(bound_range, stack, { | ||||
|     println!("Bound range exceeded fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(invalid_opcode, stack, { | ||||
|     println!("Invalid opcode fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGILL); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGILL); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(device_not_available, stack, { | ||||
|     println!("Device not available fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGILL); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGILL); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(double_fault, stack, { | ||||
|     println!("Double fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(invalid_tss, stack, { | ||||
|     println!("Invalid TSS fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(segment_not_present, stack, { | ||||
|     println!("Segment not present fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(stack_segment, stack, { | ||||
|     println!("Stack segment fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(protection, stack, { | ||||
|     println!("Protection fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(page, stack, { | ||||
|     let cr2: usize; | ||||
|     asm!("mov rax, cr2" : "={rax}"(cr2) : : : "intel", "volatile"); | ||||
|     println!("Page fault: {:>02X}:{:>016X} at {:>02X}:{:>016X}", stack.code, cr2, stack.cs, stack.rip); | ||||
|     ksignal(SIGSEGV); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGSEGV); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(fpu, stack, { | ||||
|     println!("FPU floating point fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGFPE); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGFPE); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(alignment_check, stack, { | ||||
|     println!("Alignment check fault: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGBUS); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGBUS); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(machine_check, stack, { | ||||
|     println!("Machine check fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGBUS); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGBUS); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(simd, stack, { | ||||
|     println!("SIMD floating point fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGFPE); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGFPE); | ||||
| }); | ||||
| 
 | ||||
| interrupt_stack!(virtualization, stack, { | ||||
|     println!("Virtualization fault at {:>02X}:{:>016X}", stack.cs, stack.rip); | ||||
|     ksignal(SIGBUS); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGBUS); | ||||
| }); | ||||
| 
 | ||||
| interrupt_error!(security, stack, { | ||||
|     println!("Security exception: {:X} at {:>02X}:{:>016X}", stack.code, stack.cs, stack.rip); | ||||
|     ksignal(SIGBUS); | ||||
|     stack_trace(); | ||||
|     loop { halt(); } | ||||
|     ksignal(SIGBUS); | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| use alloc::arc::Arc; | ||||
| use alloc::boxed::Box; | ||||
| use collections::{BTreeMap, Vec}; | ||||
| use collections::{BTreeMap, Vec, VecDeque}; | ||||
| use spin::Mutex; | ||||
| 
 | ||||
| use arch; | ||||
|  | @ -48,6 +48,8 @@ pub struct Context { | |||
|     pub vfork: bool, | ||||
|     /// Context is being waited on
 | ||||
|     pub waitpid: Arc<WaitMap<ContextId, usize>>, | ||||
|     /// Context should handle pending signals
 | ||||
|     pub pending: VecDeque<u8>, | ||||
|     /// Context should wake up at specified time
 | ||||
|     pub wake: Option<(u64, u64)>, | ||||
|     /// The architecture specific context
 | ||||
|  | @ -94,6 +96,7 @@ impl Context { | |||
|             cpu_id: None, | ||||
|             vfork: false, | ||||
|             waitpid: Arc::new(WaitMap::new()), | ||||
|             pending: VecDeque::new(), | ||||
|             wake: None, | ||||
|             arch: arch::context::Context::new(), | ||||
|             kfx: None, | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| use core::sync::atomic::Ordering; | ||||
| 
 | ||||
| use arch; | ||||
| use super::{contexts, Context, Status, CONTEXT_ID}; | ||||
| use context::{contexts, Context, Status, CONTEXT_ID}; | ||||
| use syscall; | ||||
| 
 | ||||
| /// Switch to the next context
 | ||||
| ///
 | ||||
|  | @ -20,6 +21,7 @@ pub unsafe fn switch() -> bool { | |||
| 
 | ||||
|     let from_ptr; | ||||
|     let mut to_ptr = 0 as *mut Context; | ||||
|     let mut to_sig = None; | ||||
|     { | ||||
|         let contexts = contexts(); | ||||
|         { | ||||
|  | @ -34,6 +36,10 @@ pub unsafe fn switch() -> bool { | |||
|                 // println!("{}: take {} {}", cpu_id, context.id, ::core::str::from_utf8_unchecked(&context.name.lock()));
 | ||||
|             } | ||||
| 
 | ||||
|             if context.status == Status::Blocked && ! context.pending.is_empty() { | ||||
|                 context.unblock(); | ||||
|             } | ||||
| 
 | ||||
|             if context.status == Status::Blocked && context.wake.is_some() { | ||||
|                 let wake = context.wake.expect("context::switch: wake not set"); | ||||
| 
 | ||||
|  | @ -57,6 +63,7 @@ pub unsafe fn switch() -> bool { | |||
|                 let mut context = context_lock.write(); | ||||
|                 if check_context(&mut context) { | ||||
|                     to_ptr = context.deref_mut() as *mut Context; | ||||
|                     to_sig = context.pending.pop_front(); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | @ -68,6 +75,7 @@ pub unsafe fn switch() -> bool { | |||
|                     let mut context = context_lock.write(); | ||||
|                     if check_context(&mut context) { | ||||
|                         to_ptr = context.deref_mut() as *mut Context; | ||||
|                         to_sig = context.pending.pop_front(); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|  | @ -91,7 +99,17 @@ pub unsafe fn switch() -> bool { | |||
|     // Unset global lock before switch, as arch is only usable by the current CPU at this time
 | ||||
|     arch::context::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst); | ||||
| 
 | ||||
|     if let Some(sig) = to_sig { | ||||
|         println!("Handle {}", sig); | ||||
|         (&mut *to_ptr).arch.signal_stack(signal_handler, sig); | ||||
|     } | ||||
| 
 | ||||
|     (&mut *from_ptr).arch.switch_to(&mut (&mut *to_ptr).arch); | ||||
| 
 | ||||
|     true | ||||
| } | ||||
| 
 | ||||
| extern fn signal_handler(signal: usize) { | ||||
|     println!("Signal handler: {}", signal); | ||||
|     syscall::exit(signal); | ||||
| } | ||||
|  |  | |||
|  | @ -113,6 +113,7 @@ pub extern fn ksignal(signal: usize) { | |||
|             println!("NAME {}", unsafe { ::core::str::from_utf8_unchecked(&context.name.lock()) }); | ||||
|         } | ||||
|     } | ||||
|     syscall::exit(signal & 0x7F); | ||||
| } | ||||
| 
 | ||||
| /// This is the kernel entry point for the primary CPU. The arch crate is responsible for calling this
 | ||||
|  |  | |||
							
								
								
									
										130
									
								
								kernel/syscall/driver.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								kernel/syscall/driver.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,130 @@ | |||
| use arch; | ||||
| use arch::memory::{allocate_frames, deallocate_frames, Frame}; | ||||
| use arch::paging::{entry, ActivePageTable, PhysicalAddress, VirtualAddress}; | ||||
| use context; | ||||
| use context::memory::Grant; | ||||
| use syscall::error::{Error, EFAULT, ENOMEM, EPERM, ESRCH, Result}; | ||||
| use syscall::flag::{MAP_WRITE, MAP_WRITE_COMBINE}; | ||||
| 
 | ||||
| fn enforce_root() -> Result<()> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     if context.euid == 0 { | ||||
|         Ok(()) | ||||
|     } else { | ||||
|         Err(Error::new(EPERM)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn iopl(_level: usize, _stack_base: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     //TODO
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| pub fn physalloc(size: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     allocate_frames((size + 4095)/4096).ok_or(Error::new(ENOMEM)).map(|frame| frame.start_address().get()) | ||||
| } | ||||
| 
 | ||||
| pub fn physfree(physical_address: usize, size: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     deallocate_frames(Frame::containing_address(PhysicalAddress::new(physical_address)), (size + 4095)/4096); | ||||
|     //TODO: Check that no double free occured
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| //TODO: verify exlusive access to physical memory
 | ||||
| pub fn physmap(physical_address: usize, size: usize, flags: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     if size == 0 { | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
| 
 | ||||
|         let mut grants = context.grants.lock(); | ||||
| 
 | ||||
|         let from_address = (physical_address/4096) * 4096; | ||||
|         let offset = physical_address - from_address; | ||||
|         let full_size = ((offset + size + 4095)/4096) * 4096; | ||||
|         let mut to_address = arch::USER_GRANT_OFFSET; | ||||
| 
 | ||||
|         let mut entry_flags = entry::PRESENT | entry::NO_EXECUTE | entry::USER_ACCESSIBLE; | ||||
|         if flags & MAP_WRITE == MAP_WRITE { | ||||
|             entry_flags |= entry::WRITABLE; | ||||
|         } | ||||
|         if flags & MAP_WRITE_COMBINE == MAP_WRITE_COMBINE { | ||||
|             entry_flags |= entry::HUGE_PAGE; | ||||
|         } | ||||
| 
 | ||||
|         for i in 0 .. grants.len() { | ||||
|             let start = grants[i].start_address().get(); | ||||
|             if to_address + full_size < start { | ||||
|                 grants.insert(i, Grant::physmap( | ||||
|                     PhysicalAddress::new(from_address), | ||||
|                     VirtualAddress::new(to_address), | ||||
|                     full_size, | ||||
|                     entry_flags | ||||
|                 )); | ||||
| 
 | ||||
|                 return Ok(to_address + offset); | ||||
|             } else { | ||||
|                 let pages = (grants[i].size() + 4095) / 4096; | ||||
|                 let end = start + pages * 4096; | ||||
|                 to_address = end; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         grants.push(Grant::physmap( | ||||
|             PhysicalAddress::new(from_address), | ||||
|             VirtualAddress::new(to_address), | ||||
|             full_size, | ||||
|             entry_flags | ||||
|         )); | ||||
| 
 | ||||
|         Ok(to_address + offset) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn physunmap(virtual_address: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     if virtual_address == 0 { | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
| 
 | ||||
|         let mut grants = context.grants.lock(); | ||||
| 
 | ||||
|         for i in 0 .. grants.len() { | ||||
|             let start = grants[i].start_address().get(); | ||||
|             let end = start + grants[i].size(); | ||||
|             if virtual_address >= start && virtual_address < end { | ||||
|                 grants.remove(i).unmap(); | ||||
| 
 | ||||
|                 return Ok(0); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Err(Error::new(EFAULT)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn virttophys(virtual_address: usize) -> Result<usize> { | ||||
|     enforce_root()?; | ||||
| 
 | ||||
|     let active_table = unsafe { ActivePageTable::new() }; | ||||
|     match active_table.translate(VirtualAddress::new(virtual_address)) { | ||||
|         Some(physical_address) => Ok(physical_address.get()), | ||||
|         None => Err(Error::new(EFAULT)) | ||||
|     } | ||||
| } | ||||
|  | @ -4,8 +4,10 @@ extern crate syscall; | |||
| 
 | ||||
| pub use self::syscall::{data, error, flag, number, scheme}; | ||||
| 
 | ||||
| pub use self::driver::*; | ||||
| pub use self::fs::*; | ||||
| pub use self::futex::futex; | ||||
| pub use self::privilege::*; | ||||
| pub use self::process::*; | ||||
| pub use self::time::*; | ||||
| pub use self::validate::*; | ||||
|  | @ -17,12 +19,18 @@ use self::number::*; | |||
| use context::ContextId; | ||||
| use scheme::FileHandle; | ||||
| 
 | ||||
| /// Driver syscalls
 | ||||
| pub mod driver; | ||||
| 
 | ||||
| /// Filesystem syscalls
 | ||||
| pub mod fs; | ||||
| 
 | ||||
| /// Fast userspace mutex
 | ||||
| pub mod futex; | ||||
| 
 | ||||
| /// Privilege syscalls
 | ||||
| pub mod privilege; | ||||
| 
 | ||||
| /// Process syscalls
 | ||||
| pub mod process; | ||||
| 
 | ||||
|  | @ -65,13 +73,14 @@ pub extern fn syscall(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize | |||
|                 SYS_CLOCK_GETTIME => clock_gettime(b, validate_slice_mut(c as *mut TimeSpec, 1).map(|time| &mut time[0])?), | ||||
|                 SYS_FUTEX => futex(validate_slice_mut(b as *mut i32, 1).map(|uaddr| &mut uaddr[0])?, c, d as i32, e, f as *mut i32), | ||||
|                 SYS_BRK => brk(b), | ||||
|                 SYS_EXIT => exit(b), | ||||
|                 SYS_WAITPID => waitpid(ContextId::from(b), c, d).map(ContextId::into), | ||||
|                 SYS_EXECVE => exec(validate_slice(b as *const u8, c)?, validate_slice(d as *const [usize; 2], e)?), | ||||
|                 SYS_CHDIR => chdir(validate_slice(b as *const u8, c)?), | ||||
|                 SYS_GETPID => getpid().map(ContextId::into), | ||||
|                 SYS_IOPL => iopl(b), | ||||
|                 SYS_CLONE => clone(b, stack).map(ContextId::into), | ||||
|                 SYS_EXIT => exit((b & 0xFF) << 8), | ||||
|                 SYS_KILL => kill(ContextId::from(b), c), | ||||
|                 SYS_WAITPID => waitpid(ContextId::from(b), c, d).map(ContextId::into), | ||||
|                 SYS_CHDIR => chdir(validate_slice(b as *const u8, c)?), | ||||
|                 SYS_EXECVE => exec(validate_slice(b as *const u8, c)?, validate_slice(d as *const [usize; 2], e)?), | ||||
|                 SYS_IOPL => iopl(b, stack), | ||||
|                 SYS_GETCWD => getcwd(validate_slice_mut(b as *mut u8, c)?), | ||||
|                 SYS_GETUID => getuid(), | ||||
|                 SYS_GETGID => getgid(), | ||||
|  |  | |||
							
								
								
									
										85
									
								
								kernel/syscall/privilege.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								kernel/syscall/privilege.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| use collections::Vec; | ||||
| 
 | ||||
| use context; | ||||
| use scheme; | ||||
| use syscall::error::*; | ||||
| use syscall::validate::validate_slice; | ||||
| 
 | ||||
| pub fn getegid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.egid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn geteuid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.euid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn getgid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.rgid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn getuid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.ruid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn setgid(gid: u32) -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let mut context = context_lock.write(); | ||||
|     if context.egid == 0 { | ||||
|         context.rgid = gid; | ||||
|         context.egid = gid; | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         Err(Error::new(EPERM)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn setuid(uid: u32) -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let mut context = context_lock.write(); | ||||
|     if context.euid == 0 { | ||||
|         context.ruid = uid; | ||||
|         context.euid = uid; | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         Err(Error::new(EPERM)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn setns(name_ptrs: &[[usize; 2]]) -> Result<usize> { | ||||
|     let mut names = Vec::new(); | ||||
|     for name_ptr in name_ptrs { | ||||
|         names.push(validate_slice(name_ptr[0] as *const u8, name_ptr[1])?); | ||||
|     } | ||||
| 
 | ||||
|     let from = { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
|         context.scheme_ns | ||||
|     }; | ||||
| 
 | ||||
|     let to = scheme::schemes_mut().setns(from, &names)?; | ||||
| 
 | ||||
|     { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let mut context = context_lock.write(); | ||||
|         context.scheme_ns = to; | ||||
|     } | ||||
| 
 | ||||
|     Ok(0) | ||||
| } | ||||
|  | @ -7,19 +7,18 @@ use core::ops::DerefMut; | |||
| use spin::Mutex; | ||||
| 
 | ||||
| use arch; | ||||
| use arch::memory::{allocate_frame, allocate_frames, deallocate_frames, Frame}; | ||||
| use arch::paging::{ActivePageTable, InactivePageTable, Page, PhysicalAddress, VirtualAddress, entry}; | ||||
| use arch::memory::allocate_frame; | ||||
| use arch::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, entry}; | ||||
| use arch::paging::temporary_page::TemporaryPage; | ||||
| use arch::start::usermode; | ||||
| use context; | ||||
| use context::ContextId; | ||||
| use context::memory::Grant; | ||||
| use elf::{self, program_header}; | ||||
| use scheme::{self, FileHandle}; | ||||
| use syscall; | ||||
| use syscall::data::Stat; | ||||
| use syscall::error::*; | ||||
| use syscall::flag::{CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, MAP_WRITE, MAP_WRITE_COMBINE, WNOHANG}; | ||||
| use syscall::flag::{CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, WNOHANG}; | ||||
| use syscall::validate::{validate_slice, validate_slice_mut}; | ||||
| 
 | ||||
| pub fn brk(address: usize) -> Result<usize> { | ||||
|  | @ -838,27 +837,6 @@ pub fn exit(status: usize) -> ! { | |||
|     unreachable!(); | ||||
| } | ||||
| 
 | ||||
| pub fn getegid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.egid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn geteuid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.euid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn getgid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.rgid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn getpid() -> Result<ContextId> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|  | @ -866,224 +844,29 @@ pub fn getpid() -> Result<ContextId> { | |||
|     Ok(context.id) | ||||
| } | ||||
| 
 | ||||
| pub fn getuid() -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let context = context_lock.read(); | ||||
|     Ok(context.ruid as usize) | ||||
| } | ||||
| 
 | ||||
| pub fn iopl(_level: usize) -> Result<usize> { | ||||
|     //TODO
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| pub fn kill(pid: ContextId, sig: usize) -> Result<usize> { | ||||
|     use syscall::flag::*; | ||||
|     let (ruid, euid) = { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
|         (context.ruid, context.euid) | ||||
|     }; | ||||
| 
 | ||||
|     let _context_lock = { | ||||
|     if sig > 0 && sig <= 0x7F { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.get(pid).ok_or(Error::new(ESRCH))?; | ||||
|         context_lock.clone() | ||||
|     }; | ||||
| 
 | ||||
|     let term = || { | ||||
|         println!("Terminate {:?}", pid); | ||||
|     }; | ||||
| 
 | ||||
|     let core = || { | ||||
|         println!("Core {:?}", pid); | ||||
|     }; | ||||
| 
 | ||||
|     let stop = || { | ||||
|         println!("Stop {:?}", pid); | ||||
|     }; | ||||
| 
 | ||||
|     let cont = || { | ||||
|         println!("Continue {:?}", pid); | ||||
|     }; | ||||
| 
 | ||||
|     match sig { | ||||
|         0 => (), | ||||
|         SIGHUP => term(), | ||||
|         SIGINT => term(), | ||||
|         SIGQUIT => core(), | ||||
|         SIGILL => core(), | ||||
|         SIGTRAP => core(), | ||||
|         SIGABRT => core(), | ||||
|         SIGBUS => core(), | ||||
|         SIGFPE => core(), | ||||
|         SIGKILL => term(), | ||||
|         SIGUSR1 => term(), | ||||
|         SIGSEGV => core(), | ||||
|         SIGPIPE => term(), | ||||
|         SIGALRM => term(), | ||||
|         SIGTERM => term(), | ||||
|         SIGSTKFLT => term(), | ||||
|         SIGCHLD => (), | ||||
|         SIGCONT => cont(), | ||||
|         SIGSTOP => stop(), | ||||
|         SIGTSTP => stop(), | ||||
|         SIGTTIN => stop(), | ||||
|         SIGTTOU => stop(), | ||||
|         SIGURG => (), | ||||
|         SIGXCPU => core(), | ||||
|         SIGXFSZ => core(), | ||||
|         SIGVTALRM => term(), | ||||
|         SIGPROF => term(), | ||||
|         SIGWINCH => (), | ||||
|         SIGIO => term(), | ||||
|         SIGPWR => term(), | ||||
|         SIGSYS => core(), | ||||
|         _ => return Err(Error::new(EINVAL)) | ||||
|     } | ||||
| 
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| pub fn physalloc(size: usize) -> Result<usize> { | ||||
|     allocate_frames((size + 4095)/4096).ok_or(Error::new(ENOMEM)).map(|frame| frame.start_address().get()) | ||||
| } | ||||
| 
 | ||||
| pub fn physfree(physical_address: usize, size: usize) -> Result<usize> { | ||||
|     deallocate_frames(Frame::containing_address(PhysicalAddress::new(physical_address)), (size + 4095)/4096); | ||||
|     //TODO: Check that no double free occured
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| //TODO: verify exlusive access to physical memory
 | ||||
| pub fn physmap(physical_address: usize, size: usize, flags: usize) -> Result<usize> { | ||||
|     if size == 0 { | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
| 
 | ||||
|         let mut grants = context.grants.lock(); | ||||
| 
 | ||||
|         let from_address = (physical_address/4096) * 4096; | ||||
|         let offset = physical_address - from_address; | ||||
|         let full_size = ((offset + size + 4095)/4096) * 4096; | ||||
|         let mut to_address = arch::USER_GRANT_OFFSET; | ||||
| 
 | ||||
|         let mut entry_flags = entry::PRESENT | entry::NO_EXECUTE | entry::USER_ACCESSIBLE; | ||||
|         if flags & MAP_WRITE == MAP_WRITE { | ||||
|             entry_flags |= entry::WRITABLE; | ||||
|         } | ||||
|         if flags & MAP_WRITE_COMBINE == MAP_WRITE_COMBINE { | ||||
|             entry_flags |= entry::HUGE_PAGE; | ||||
|         } | ||||
| 
 | ||||
|         for i in 0 .. grants.len() { | ||||
|             let start = grants[i].start_address().get(); | ||||
|             if to_address + full_size < start { | ||||
|                 grants.insert(i, Grant::physmap( | ||||
|                     PhysicalAddress::new(from_address), | ||||
|                     VirtualAddress::new(to_address), | ||||
|                     full_size, | ||||
|                     entry_flags | ||||
|                 )); | ||||
| 
 | ||||
|                 return Ok(to_address + offset); | ||||
|             } else { | ||||
|                 let pages = (grants[i].size() + 4095) / 4096; | ||||
|                 let end = start + pages * 4096; | ||||
|                 to_address = end; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         grants.push(Grant::physmap( | ||||
|             PhysicalAddress::new(from_address), | ||||
|             VirtualAddress::new(to_address), | ||||
|             full_size, | ||||
|             entry_flags | ||||
|         )); | ||||
| 
 | ||||
|         Ok(to_address + offset) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn physunmap(virtual_address: usize) -> Result<usize> { | ||||
|     if virtual_address == 0 { | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
| 
 | ||||
|         let mut grants = context.grants.lock(); | ||||
| 
 | ||||
|         for i in 0 .. grants.len() { | ||||
|             let start = grants[i].start_address().get(); | ||||
|             let end = start + grants[i].size(); | ||||
|             if virtual_address >= start && virtual_address < end { | ||||
|                 grants.remove(i).unmap(); | ||||
| 
 | ||||
|                 return Ok(0); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Err(Error::new(EFAULT)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn setgid(gid: u32) -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let mut context = context_lock.write(); | ||||
|     if context.egid == 0 { | ||||
|         context.rgid = gid; | ||||
|         context.egid = gid; | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         Err(Error::new(EPERM)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn setuid(uid: u32) -> Result<usize> { | ||||
|     let contexts = context::contexts(); | ||||
|     let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|     let mut context = context_lock.write(); | ||||
|     if context.euid == 0 { | ||||
|         context.ruid = uid; | ||||
|         context.euid = uid; | ||||
|         Ok(0) | ||||
|     } else { | ||||
|         Err(Error::new(EPERM)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn setns(name_ptrs: &[[usize; 2]]) -> Result<usize> { | ||||
|     let mut names = Vec::new(); | ||||
|     for name_ptr in name_ptrs { | ||||
|         names.push(validate_slice(name_ptr[0] as *const u8, name_ptr[1])?); | ||||
|     } | ||||
| 
 | ||||
|     let from = { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let context = context_lock.read(); | ||||
|         context.scheme_ns | ||||
|     }; | ||||
| 
 | ||||
|     let to = scheme::schemes_mut().setns(from, &names)?; | ||||
| 
 | ||||
|     { | ||||
|         let contexts = context::contexts(); | ||||
|         let context_lock = contexts.current().ok_or(Error::new(ESRCH))?; | ||||
|         let mut context = context_lock.write(); | ||||
|         context.scheme_ns = to; | ||||
|     } | ||||
| 
 | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| pub fn virttophys(virtual_address: usize) -> Result<usize> { | ||||
|     let active_table = unsafe { ActivePageTable::new() }; | ||||
|     match active_table.translate(VirtualAddress::new(virtual_address)) { | ||||
|         Some(physical_address) => Ok(physical_address.get()), | ||||
|         None => Err(Error::new(EFAULT)) | ||||
|         if euid == 0 | ||||
|         || euid == context.ruid | ||||
|         || ruid == context.ruid | ||||
|         { | ||||
|             context.pending.push_back(sig as u8); | ||||
|             Ok(0) | ||||
|         } else { | ||||
|             Err(Error::new(EPERM)) | ||||
|         } | ||||
|     } else { | ||||
|         Err(Error::new(EINVAL)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										1
									
								
								libstd
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								libstd
									
										
									
									
									
								
							|  | @ -1 +0,0 @@ | |||
| Subproject commit 18d61d7e8a469fb0a05a9db4d00970b7e45954ad | ||||
							
								
								
									
										1
									
								
								libstd/openlibm
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								libstd/openlibm
									
										
									
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | |||
| Subproject commit 3c837e79655c4be724efb945a6345ec97c07635c | ||||
|  | @ -1,36 +1,36 @@ | |||
| extern crate syscall; | ||||
| 
 | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Read}; | ||||
| use std::os::unix::process::CommandExt; | ||||
| use std::process::Command; | ||||
| 
 | ||||
| pub fn main() { | ||||
|     let names = [ | ||||
|         "file", | ||||
|         "rand", | ||||
|         "tcp", | ||||
|         "udp" | ||||
|     ]; | ||||
| 
 | ||||
|     let command = "sh"; | ||||
| 
 | ||||
|     let pid = unsafe { syscall::clone(0).unwrap() }; | ||||
|     if pid == 0 { | ||||
|         let rand = b"rand"; | ||||
|         syscall::setns(&[[rand.as_ptr() as usize, rand.len()]]).unwrap(); | ||||
| 
 | ||||
|         println!("Child Namespace:"); | ||||
|         let file = BufReader::new(File::open("sys:scheme").unwrap()); | ||||
|         for line in file.lines() { | ||||
|             let line = line.unwrap(); | ||||
|             println!("{}", line); | ||||
|         let mut name_ptrs = Vec::new(); | ||||
|         for name in names.iter() { | ||||
|             name_ptrs.push([name.as_ptr() as usize, name.len()]); | ||||
|         } | ||||
| 
 | ||||
|         let mut rand = File::open("rand:").unwrap(); | ||||
|         syscall::setns(&name_ptrs).unwrap(); | ||||
| 
 | ||||
|         let mut byte = [0]; | ||||
|         rand.read(&mut byte).unwrap(); | ||||
|         println!("Entering container: {}", command); | ||||
| 
 | ||||
|         println!("Rand: {}", byte[0]); | ||||
|         let err = Command::new(command).exec(); | ||||
| 
 | ||||
|         panic!("contain: failed to launch {}: {}", command, err); | ||||
|     } else { | ||||
|         let mut status = 0; | ||||
|         syscall::waitpid(pid, &mut status, 0).unwrap(); | ||||
| 
 | ||||
|         println!("Parent Namespace:"); | ||||
|         let file = BufReader::new(File::open("sys:scheme").unwrap()); | ||||
|         for line in file.lines() { | ||||
|             let line = line.unwrap(); | ||||
|             println!("{}", line); | ||||
|         } | ||||
|         println!("Exiting container: {:X}", status); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit ff47394f57a8847313ac0850e029f5d21393fee6 | ||||
| Subproject commit 4b58de0b3c66634f95668e47466014138c5f71bd | ||||
							
								
								
									
										2
									
								
								rust
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								rust
									
										
									
									
									
								
							|  | @ -1 +1 @@ | |||
| Subproject commit 267bc54fbd2cfeadde7a87fc2aa3fb975ff58b6c | ||||
| Subproject commit f01add1a3bc3d86ee62f5819fa6ed9f79d453665 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jeremy Soller
						Jeremy Soller