From 8854430651c8b34be6b6cdfdf0a66078fdedc18a Mon Sep 17 00:00:00 2001 From: holygits Date: Sun, 3 Feb 2019 14:53:04 +1300 Subject: [PATCH] Better error handling for file io Add FileNotFoundError Add PermissionError Minor idomatic clean ups --- vm/src/exceptions.rs | 8 ++++++++ vm/src/stdlib/io.rs | 35 +++++++++++++---------------------- vm/src/stdlib/os.rs | 40 +++++++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 72d640521..52bac927b 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -96,6 +96,8 @@ pub struct ExceptionZoo { pub value_error: PyObjectRef, pub import_error: PyObjectRef, pub module_not_found_error: PyObjectRef, + pub file_not_found_error: PyObjectRef, + pub permission_error: PyObjectRef, } impl ExceptionZoo { @@ -138,6 +140,10 @@ impl ExceptionZoo { let import_error = create_type("ImportError", &type_type, &exception_type, &dict_type); let module_not_found_error = create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type); + let file_not_found_error = + create_type("FileNotFoundError", &type_type, &import_error, &dict_type); + let permission_error = + create_type("PermissionError", &type_type, &import_error, &dict_type); ExceptionZoo { base_exception_type: base_exception_type, @@ -155,6 +161,8 @@ impl ExceptionZoo { value_error: value_error, import_error: import_error, module_not_found_error: module_not_found_error, + file_not_found_error: file_not_found_error, + permission_error: permission_error, } } } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index c639195ec..6831b8bbc 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -81,10 +81,8 @@ fn buffered_reader_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //bytes are returned (when the end of the file is reached). while length == buff_size { let raw_read = vm.get_method(raw.clone(), &"readinto".to_string()).unwrap(); - match vm.invoke(raw_read, PyFuncArgs::new(vec![buffer.clone()], vec![])) { - Ok(_) => {} - Err(_) => return Err(vm.new_value_error("IO Error".to_string())), - } + vm.invoke(raw_read, PyFuncArgs::new(vec![buffer.clone()], vec![])) + .map_err(|_| vm.new_value_error("IO Error".to_string()))?; //Copy bytes from the buffer vector into the results vector match buffer.borrow_mut().payload { @@ -110,18 +108,15 @@ fn file_io_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); - let rust_mode = match mode { - Some(m) => objstr::get_value(m), - None => "r".to_string(), - }; + let rust_mode = mode.map_or("r".to_string(), |m| objstr::get_value(m)); match compute_c_flag(&rust_mode).to_bigint() { Some(os_mode) => { let args = vec![name.clone(), vm.ctx.new_int(os_mode)]; - let fileno = os::os_open(vm, PyFuncArgs::new(args, vec![])); + let file_no = os::os_open(vm, PyFuncArgs::new(args, vec![]))?; vm.ctx.set_attr(&file_io, "name", name.clone()); - vm.ctx.set_attr(&file_io, "fileno", fileno.unwrap()); + vm.ctx.set_attr(&file_io, "file_no", file_no); Ok(vm.get_none()) } None => Err(vm.new_type_error(format!("invalid mode {}", rust_mode))), @@ -166,8 +161,8 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let py_length = vm.invoke(len_method.unwrap(), PyFuncArgs::default()); let length = objint::get_value(&py_length.unwrap()).to_u64().unwrap(); - let fileno = file_io.get_attr("fileno").unwrap(); - let raw_fd = objint::get_value(&fileno).to_i32().unwrap(); + let file_no = file_io.get_attr("file_no").unwrap(); + let raw_fd = objint::get_value(&file_no).to_i32().unwrap(); //unsafe block - creates file handle from the UNIX file descriptor //raw_fd is supported on UNIX only. This will need to be extended @@ -189,7 +184,7 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let new_handle = f.into_inner().into_raw_fd().to_bigint(); vm.ctx - .set_attr(&file_io, "fileno", vm.ctx.new_int(new_handle.unwrap())); + .set_attr(&file_io, "file_no", vm.ctx.new_int(new_handle.unwrap())); Ok(vm.get_none()) } @@ -200,8 +195,8 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(file_io, None), (obj, Some(vm.ctx.bytes_type()))] ); - let fileno = file_io.get_attr("fileno").unwrap(); - let raw_fd = objint::get_value(&fileno).to_i32().unwrap(); + let file_no = file_io.get_attr("file_no").unwrap(); + let raw_fd = objint::get_value(&file_no).to_i32().unwrap(); //unsafe block - creates file handle from the UNIX file descriptor //raw_fd is supported on UNIX only. This will need to be extended @@ -215,7 +210,7 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //reset raw fd on the FileIO object let new_handle = handle.into_raw_fd().to_bigint(); vm.ctx - .set_attr(&file_io, "fileno", vm.ctx.new_int(new_handle.unwrap())); + .set_attr(&file_io, "file_no", vm.ctx.new_int(new_handle.unwrap())); //return number of bytes written Ok(vm.ctx.new_int(len.to_bigint().unwrap())) @@ -280,11 +275,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let module = mk_module(&vm.ctx); //mode is optional: 'rt' is the default mode (open from reading text) - let rust_mode = if let Some(m) = mode { - objstr::get_value(m) - } else { - "rt".to_string() - }; + let rust_mode = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); let mut raw_modes = HashSet::new(); @@ -322,7 +313,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vec![file.clone(), vm.ctx.new_str(modes[0].to_string())], vec![], ); - let file_io = vm.invoke(file_io_class, file_args).unwrap(); + let file_io = vm.invoke(file_io_class, file_args)?; //Create Buffered class to consume FileIO. The type of buffered class depends on //the operation in the mode. diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 443952cb0..0c6bab2ee 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,4 +1,5 @@ use std::fs::OpenOptions; +use std::io::ErrorKind; use std::os::unix::io::IntoRawFd; use num_bigint::ToBigInt; @@ -22,25 +23,34 @@ pub fn os_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); + let fname = objstr::get_value(&name); + let handle = match objint::get_value(mode).to_u16().unwrap() { - 0 => OpenOptions::new().read(true).open(objstr::get_value(&name)), - 1 => OpenOptions::new() - .write(true) - .open(objstr::get_value(&name)), - 512 => OpenOptions::new() - .write(true) - .create(true) - .open(objstr::get_value(&name)), - _ => OpenOptions::new().read(true).open(objstr::get_value(&name)), - }; + 0 => OpenOptions::new().read(true).open(&fname), + 1 => OpenOptions::new().write(true).open(&fname), + 512 => OpenOptions::new().write(true).create(true).open(&fname), + _ => OpenOptions::new().read(true).open(&fname), + } + .map_err(|err| match err.kind() { + ErrorKind::NotFound => { + let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); + vm.new_exception(exc_type, format!("No such file or directory: {}", &fname)) + } + ErrorKind::PermissionDenied => { + let exc_type = vm.ctx.exceptions.permission_error.clone(); + vm.new_exception(exc_type, format!("Permission denied: {}", &fname)) + } + _ => vm.new_value_error("Unhandled file IO error".to_string()), + })?; //raw_fd is supported on UNIX only. This will need to be extended //to support windows - i.e. raw file_handles - if let Ok(f) = handle { - Ok(vm.ctx.new_int(f.into_raw_fd().to_bigint().unwrap())) - } else { - Err(vm.new_value_error("Bad file descriptor".to_string())) - } + Ok(vm.ctx.new_int( + handle + .into_raw_fd() + .to_bigint() + .expect("Invalid file descriptor"), + )) } pub fn mk_module(ctx: &PyContext) -> PyObjectRef {