feature: simple macro to auto-impl VnodeImpl items
This commit is contained in:
parent
47ef7e29fe
commit
dd8033b51c
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -64,6 +64,14 @@ dependencies = [
|
|||||||
"unsafe_unwrap",
|
"unsafe_unwrap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel"
|
name = "kernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -230,5 +238,6 @@ dependencies = [
|
|||||||
name = "vfs"
|
name = "vfs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"fs-macros",
|
||||||
"libsys",
|
"libsys",
|
||||||
]
|
]
|
||||||
|
@ -10,6 +10,7 @@ edition = "2018"
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"fs/fat32",
|
"fs/fat32",
|
||||||
|
"fs/macros",
|
||||||
"fs/memfs",
|
"fs/memfs",
|
||||||
"fs/vfs",
|
"fs/vfs",
|
||||||
"kernel",
|
"kernel",
|
||||||
|
13
fs/macros/Cargo.toml
Normal file
13
fs/macros/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "fs-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "^1.0.81", features = ["full"] }
|
||||||
|
quote = "^1.0.10"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
119
fs/macros/src/lib.rs
Normal file
119
fs/macros/src/lib.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
extern crate syn;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quote;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::ToTokens;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use syn::{parse_macro_input, ImplItem, ItemImpl, Ident};
|
||||||
|
|
||||||
|
fn impl_inode_fn<T: ToTokens>(name: &str, behavior: T) -> ImplItem {
|
||||||
|
ImplItem::Verbatim(match name {
|
||||||
|
"create" => quote! {
|
||||||
|
fn create(&mut self, _at: VnodeRef, _name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remove" => quote! {
|
||||||
|
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lookup" => quote! {
|
||||||
|
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stat" => quote! {
|
||||||
|
fn stat(&mut self, _at: VnodeRef, _stat: &mut Stat) -> Result<(), Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"truncate" => quote! {
|
||||||
|
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size" => quote! {
|
||||||
|
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read" => quote! {
|
||||||
|
fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result<usize, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"write" => quote! {
|
||||||
|
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"open" => quote! {
|
||||||
|
fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result<usize, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close" => quote! {
|
||||||
|
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ioctl" => quote! {
|
||||||
|
fn ioctl(&mut self, _node: VnodeRef, _cmd: IoctlCmd, _ptr: usize, _len: usize) -> Result<usize, Errno> {
|
||||||
|
#behavior
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("TODO implement {:?}", name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let mut impl_item = parse_macro_input!(input as ItemImpl);
|
||||||
|
let mut missing = HashSet::<String>::new();
|
||||||
|
let behavior = if attr.is_empty() {
|
||||||
|
"unimplemented".to_string()
|
||||||
|
} else {
|
||||||
|
parse_macro_input!(attr as Ident).to_string()
|
||||||
|
};
|
||||||
|
let behavior = match behavior.as_str() {
|
||||||
|
"unimplemented" => quote! { unimplemented!() },
|
||||||
|
"panic" => quote! { panic!() },
|
||||||
|
"error" => quote! { Err(libsys::error::Errno::NotImplemented) },
|
||||||
|
_ => panic!("Unknown #[auto_inode] behavior: {:?}", behavior)
|
||||||
|
};
|
||||||
|
|
||||||
|
missing.insert("create".to_string());
|
||||||
|
missing.insert("remove".to_string());
|
||||||
|
missing.insert("lookup".to_string());
|
||||||
|
missing.insert("open".to_string());
|
||||||
|
missing.insert("close".to_string());
|
||||||
|
missing.insert("truncate".to_string());
|
||||||
|
missing.insert("read".to_string());
|
||||||
|
missing.insert("write".to_string());
|
||||||
|
missing.insert("stat".to_string());
|
||||||
|
missing.insert("size".to_string());
|
||||||
|
missing.insert("ioctl".to_string());
|
||||||
|
|
||||||
|
for item in &impl_item.items {
|
||||||
|
match item {
|
||||||
|
ImplItem::Method(method) => {
|
||||||
|
let name = &method.sig.ident.to_string();
|
||||||
|
if missing.contains(name) {
|
||||||
|
missing.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected impl item"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in &missing {
|
||||||
|
impl_item
|
||||||
|
.items
|
||||||
|
.push(impl_inode_fn(item, behavior.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_item.to_token_stream().into()
|
||||||
|
}
|
@ -7,3 +7,4 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsys = { path = "../../libsys" }
|
libsys = { path = "../../libsys" }
|
||||||
|
fs-macros = { path = "../macros" }
|
||||||
|
@ -25,19 +25,8 @@ pub struct CharDeviceWrapper {
|
|||||||
device: &'static dyn CharDevice,
|
device: &'static dyn CharDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[auto_inode(error)]
|
||||||
impl VnodeImpl for CharDeviceWrapper {
|
impl VnodeImpl for CharDeviceWrapper {
|
||||||
fn create(&mut self, _at: VnodeRef, _name: &str, _kind: VnodeKind) -> Result<VnodeRef, Errno> {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self, _node: VnodeRef, _opts: OpenFlags) -> Result<usize, Errno> {
|
fn open(&mut self, _node: VnodeRef, _opts: OpenFlags) -> Result<usize, Errno> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
@ -54,18 +43,6 @@ impl VnodeImpl for CharDeviceWrapper {
|
|||||||
self.device.write(true, data)
|
self.device.write(true, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stat(&mut self, _node: VnodeRef, _stat: &mut Stat) -> Result<(), Errno> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ioctl(&mut self, _node: VnodeRef, cmd: IoctlCmd, ptr: usize, len: usize) -> Result<usize, Errno> {
|
fn ioctl(&mut self, _node: VnodeRef, cmd: IoctlCmd, ptr: usize, len: usize) -> Result<usize, Errno> {
|
||||||
self.device.ioctl(cmd, ptr, len)
|
self.device.ioctl(cmd, ptr, len)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ use alloc::rc::Rc;
|
|||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::cmp::min;
|
use core::cmp::min;
|
||||||
use libsys::{
|
use libsys::{
|
||||||
|
error::Errno,
|
||||||
traits::{Read, Seek, SeekDir, Write},
|
traits::{Read, Seek, SeekDir, Write},
|
||||||
error::Errno
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NormalFile {
|
struct NormalFile {
|
||||||
@ -135,11 +135,13 @@ impl Drop for File {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
use crate::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
||||||
|
use libsys::{stat::OpenFlags, ioctl::IoctlCmd, stat::Stat};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
|
|
||||||
struct DummyInode;
|
struct DummyInode;
|
||||||
|
|
||||||
|
#[auto_inode]
|
||||||
impl VnodeImpl for DummyInode {
|
impl VnodeImpl for DummyInode {
|
||||||
fn create(
|
fn create(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -152,25 +154,17 @@ mod tests {
|
|||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
|
fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result<usize, Errno> {
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||||
Err(Errno::NotImplemented)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
println!("read {}", pos);
|
println!("read {} at {}", data.len(), pos);
|
||||||
let len = 123;
|
let len = 123;
|
||||||
if pos >= len {
|
if pos >= len {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
@ -185,41 +179,24 @@ mod tests {
|
|||||||
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
||||||
Err(Errno::NotImplemented)
|
Err(Errno::NotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_normal_read() {
|
fn test_normal_read() {
|
||||||
let node = Vnode::new("", VnodeKind::Regular, 0);
|
let node = Vnode::new("", VnodeKind::Regular, 0);
|
||||||
node.set_data(Box::new(DummyInode {}));
|
node.set_data(Box::new(DummyInode {}));
|
||||||
let mut file = node.open().unwrap();
|
let mut file = node.open(OpenFlags::O_RDONLY).unwrap();
|
||||||
|
|
||||||
match &file.inner {
|
|
||||||
FileInner::Normal(inner) => {
|
|
||||||
assert!(Rc::ptr_eq(&inner.vnode, &node));
|
|
||||||
assert_eq!(inner.pos, 0);
|
|
||||||
}
|
|
||||||
_ => panic!("Invalid file.inner"),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buf = [0u8; 4096];
|
let mut buf = [0u8; 4096];
|
||||||
|
|
||||||
assert_eq!(file.read(&mut buf[0..32]).unwrap(), 32);
|
assert_eq!(file.borrow_mut().read(&mut buf[0..32]).unwrap(), 32);
|
||||||
for i in 0..32 {
|
for i in 0..32 {
|
||||||
assert_eq!((i & 0xFF) as u8, buf[i]);
|
assert_eq!((i & 0xFF) as u8, buf[i]);
|
||||||
}
|
}
|
||||||
assert_eq!(file.read(&mut buf[0..64]).unwrap(), 64);
|
assert_eq!(file.borrow_mut().read(&mut buf[0..64]).unwrap(), 64);
|
||||||
for i in 0..64 {
|
for i in 0..64 {
|
||||||
assert_eq!(((i + 32) & 0xFF) as u8, buf[i]);
|
assert_eq!(((i + 32) & 0xFF) as u8, buf[i]);
|
||||||
}
|
}
|
||||||
assert_eq!(file.read(&mut buf[0..64]).unwrap(), 27);
|
assert_eq!(file.borrow_mut().read(&mut buf[0..64]).unwrap(), 27);
|
||||||
for i in 0..27 {
|
for i in 0..27 {
|
||||||
assert_eq!(((i + 96) & 0xFF) as u8, buf[i]);
|
assert_eq!(((i + 96) & 0xFF) as u8, buf[i]);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{FileMode, FileRef, OpenFlags, VnodeKind, VnodeRef};
|
use crate::{FileMode, FileRef, OpenFlags, VnodeKind, VnodeRef};
|
||||||
use libsys::{
|
use libsys::{
|
||||||
error::Errno,
|
error::Errno,
|
||||||
path::{path_component_left, path_component_right}
|
path::{path_component_left, path_component_right},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// I/O context structure
|
/// I/O context structure
|
||||||
@ -119,9 +119,11 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Vnode, VnodeImpl, VnodeKind};
|
use crate::{Vnode, VnodeImpl, VnodeKind};
|
||||||
use alloc::{boxed::Box, rc::Rc};
|
use alloc::{boxed::Box, rc::Rc};
|
||||||
|
use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
|
||||||
|
|
||||||
pub struct DummyInode;
|
pub struct DummyInode;
|
||||||
|
|
||||||
|
#[auto_inode]
|
||||||
impl VnodeImpl for DummyInode {
|
impl VnodeImpl for DummyInode {
|
||||||
fn create(
|
fn create(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -134,37 +136,9 @@ mod tests {
|
|||||||
Ok(vnode)
|
Ok(vnode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||||
Err(Errno::DoesNotExist)
|
Err(Errno::DoesNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result<usize, Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate fs_macros;
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
pub use libsys::stat::{FileMode, OpenFlags, Stat};
|
pub use libsys::stat::{FileMode, OpenFlags, Stat};
|
||||||
|
@ -405,8 +405,10 @@ impl fmt::Debug for Vnode {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use libsys::{stat::OpenFlags, ioctl::IoctlCmd, stat::Stat};
|
||||||
pub struct DummyInode;
|
pub struct DummyInode;
|
||||||
|
|
||||||
|
#[auto_inode]
|
||||||
impl VnodeImpl for DummyInode {
|
impl VnodeImpl for DummyInode {
|
||||||
fn create(
|
fn create(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -426,30 +428,6 @@ mod tests {
|
|||||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||||
Err(Errno::DoesNotExist)
|
Err(Errno::DoesNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result<usize, Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
|
||||||
Err(Errno::NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -469,10 +447,10 @@ mod tests {
|
|||||||
|
|
||||||
root.set_data(Box::new(DummyInode {}));
|
root.set_data(Box::new(DummyInode {}));
|
||||||
|
|
||||||
let node = root.mkdir("test", FileMode::default_dir()).unwrap();
|
let node = root.create("test", FileMode::default_dir(), VnodeKind::Directory).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
root.mkdir("test", FileMode::default_dir()).unwrap_err(),
|
root.create("test", FileMode::default_dir(), VnodeKind::Directory).unwrap_err(),
|
||||||
Errno::AlreadyExists
|
Errno::AlreadyExists
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user