config: implement config reloading
This commit is contained in:
parent
aa19aab915
commit
7b40b42990
14 changed files with 188 additions and 50 deletions
|
|
@ -4,17 +4,17 @@ use {
|
|||
config,
|
||||
drm::on_graphics_initialized,
|
||||
get_timer, get_workspace,
|
||||
input::{create_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
||||
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
||||
keyboard::{
|
||||
mods::{Modifiers, ALT, CTRL, SHIFT},
|
||||
syms::{
|
||||
SYM_Super_L, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, SYM_q,
|
||||
SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14, SYM_F15,
|
||||
SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22, SYM_F23,
|
||||
SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
|
||||
SYM_r, SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14,
|
||||
SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22,
|
||||
SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
|
||||
},
|
||||
},
|
||||
quit,
|
||||
quit, reload,
|
||||
status::set_status,
|
||||
switch_to_vt,
|
||||
Axis::{Horizontal, Vertical},
|
||||
|
|
@ -58,6 +58,8 @@ fn configure_seat(s: Seat) {
|
|||
|
||||
s.bind(MOD | SYM_q, quit);
|
||||
|
||||
s.bind(MOD | SHIFT | SYM_r, reload);
|
||||
|
||||
let fnkeys = [
|
||||
SYM_F1, SYM_F2, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, SYM_F10, SYM_F11,
|
||||
SYM_F12,
|
||||
|
|
@ -78,7 +80,7 @@ fn configure_seat(s: Seat) {
|
|||
}
|
||||
|
||||
pub fn configure() {
|
||||
let seat = create_seat("default");
|
||||
let seat = get_seat("default");
|
||||
configure_seat(seat);
|
||||
let handle_input_device = move |device: InputDevice| {
|
||||
device.set_seat(seat);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ pub(crate) struct Client {
|
|||
on_graphics_initialized: Cell<Option<Box<dyn FnOnce()>>>,
|
||||
on_new_connector: RefCell<Option<Rc<dyn Fn(Connector)>>>,
|
||||
bufs: RefCell<Vec<Vec<u8>>>,
|
||||
reload: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
|
|
@ -124,6 +125,7 @@ pub unsafe extern "C" fn init(
|
|||
on_graphics_initialized: Default::default(),
|
||||
on_new_connector: Default::default(),
|
||||
bufs: Default::default(),
|
||||
reload: Cell::new(false),
|
||||
});
|
||||
let init = slice::from_raw_parts(init, size);
|
||||
client.handle_init_msg(init);
|
||||
|
|
@ -169,6 +171,14 @@ impl Client {
|
|||
self.with_response(|| self.send(msg))
|
||||
}
|
||||
|
||||
pub fn reload(&self) {
|
||||
self.send(&ClientMessage::Reload);
|
||||
}
|
||||
|
||||
pub fn is_reload(&self) -> bool {
|
||||
self.reload.get()
|
||||
}
|
||||
|
||||
pub fn spawn(&self, command: &Command) {
|
||||
let env = command
|
||||
.env
|
||||
|
|
@ -358,9 +368,9 @@ impl Client {
|
|||
self.send(&ClientMessage::FocusParent { seat });
|
||||
}
|
||||
|
||||
pub fn create_seat(&self, name: &str) -> Seat {
|
||||
let res = self.send_with_response(&ClientMessage::CreateSeat { name });
|
||||
get_response!(res, Seat(0), CreateSeat, seat);
|
||||
pub fn get_seat(&self, name: &str) -> Seat {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeat { name });
|
||||
get_response!(res, Seat(0), GetSeat, seat);
|
||||
seat
|
||||
}
|
||||
|
||||
|
|
@ -531,8 +541,10 @@ impl Client {
|
|||
}
|
||||
};
|
||||
match msg {
|
||||
ServerMessage::Configure => {
|
||||
ServerMessage::Configure { reload } => {
|
||||
self.reload.set(reload);
|
||||
(self.configure)();
|
||||
self.reload.set(false);
|
||||
}
|
||||
ServerMessage::Response { response } => {
|
||||
self.response.borrow_mut().push(response);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ use {
|
|||
|
||||
#[derive(Encode, BorrowDecode, Debug)]
|
||||
pub enum ServerMessage {
|
||||
Configure,
|
||||
Configure {
|
||||
reload: bool,
|
||||
},
|
||||
GraphicsInitialized,
|
||||
Response {
|
||||
response: Response,
|
||||
|
|
@ -53,7 +55,7 @@ pub enum ClientMessage<'a> {
|
|||
file: Option<&'a str>,
|
||||
line: Option<u32>,
|
||||
},
|
||||
CreateSeat {
|
||||
GetSeat {
|
||||
name: &'a str,
|
||||
},
|
||||
Quit,
|
||||
|
|
@ -237,6 +239,7 @@ pub enum ClientMessage<'a> {
|
|||
GetFullscreen {
|
||||
seat: Seat,
|
||||
},
|
||||
Reload,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug)]
|
||||
|
|
@ -258,7 +261,7 @@ pub enum Response {
|
|||
ParseKeymap {
|
||||
keymap: Keymap,
|
||||
},
|
||||
CreateSeat {
|
||||
GetSeat {
|
||||
seat: Seat,
|
||||
},
|
||||
GetInputDevices {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use {
|
|||
};
|
||||
|
||||
pub fn init() {
|
||||
log::set_logger(&Logger).unwrap();
|
||||
let _ = log::set_logger(&Logger);
|
||||
log::set_max_level(LevelFilter::Trace);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,9 +161,9 @@ pub fn input_devices() -> Vec<InputDevice> {
|
|||
|
||||
pub fn remove_all_seats() {}
|
||||
|
||||
pub fn create_seat(name: &str) -> Seat {
|
||||
pub fn get_seat(name: &str) -> Seat {
|
||||
let mut res = Seat(0);
|
||||
(|| res = get!().create_seat(name))();
|
||||
(|| res = get!().get_seat(name))();
|
||||
res
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -121,3 +121,11 @@ impl Timer {
|
|||
get!().on_timer_tick(self, f);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload() {
|
||||
get!().reload()
|
||||
}
|
||||
|
||||
pub fn is_reload() -> bool {
|
||||
get!(false).is_reload()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ use {
|
|||
},
|
||||
user_session::import_environment,
|
||||
utils::{
|
||||
clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser, oserror::OsError,
|
||||
queue::AsyncQueue, run_toplevel::RunToplevel, tri::Try,
|
||||
clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser, numcell::NumCell,
|
||||
oserror::OsError, queue::AsyncQueue, run_toplevel::RunToplevel, tri::Try,
|
||||
},
|
||||
wheel::{Wheel, WheelError},
|
||||
xkbcommon::XkbContext,
|
||||
|
|
@ -174,6 +174,8 @@ fn start_compositor2(
|
|||
serial: Default::default(),
|
||||
idle_inhibitor_ids: Default::default(),
|
||||
run_toplevel,
|
||||
config_dir: config_dir(),
|
||||
config_file_id: NumCell::new(1),
|
||||
});
|
||||
create_dummy_output(&state);
|
||||
let acceptor = Acceptor::install(&state)?;
|
||||
|
|
@ -220,6 +222,7 @@ async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
|
|||
}
|
||||
|
||||
let config = load_config(&state, is_test);
|
||||
config.configure(false);
|
||||
state.config.set(Some(Rc::new(config)));
|
||||
|
||||
let _geh = start_global_event_handlers(&state, &backend);
|
||||
|
|
@ -237,19 +240,10 @@ fn load_config(state: &Rc<State>, #[allow(unused_variables)] for_test: bool) ->
|
|||
if for_test {
|
||||
// todo
|
||||
}
|
||||
let config_dir = if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
|
||||
format!("{}/jay", xdg)
|
||||
} else if let Ok(home) = env::var("HOME") {
|
||||
format!("{}/.config/jay", home)
|
||||
} else {
|
||||
log::warn!("Neither XDG_CONFIG_HOME nor HOME are set. Using default config.");
|
||||
return ConfigProxy::default(state);
|
||||
};
|
||||
let config_path = format!("{}/config.so", config_dir);
|
||||
match unsafe { ConfigProxy::from_file(&config_path, state) } {
|
||||
match ConfigProxy::from_config_dir(state) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("Could not load {}: {}", config_path, ErrorFmt(e));
|
||||
log::warn!("Could not load config.so: {}", ErrorFmt(e));
|
||||
log::warn!("Using default config");
|
||||
ConfigProxy::default(state)
|
||||
}
|
||||
|
|
@ -386,3 +380,14 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
dummy_output.show_workspace(&dummy_workspace);
|
||||
state.dummy_output.set(Some(dummy_output));
|
||||
}
|
||||
|
||||
fn config_dir() -> Option<String> {
|
||||
if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
|
||||
Some(format!("{}/jay", xdg))
|
||||
} else if let Ok(home) = env::var("HOME") {
|
||||
Some(format!("{}/.config/jay", home))
|
||||
} else {
|
||||
log::warn!("Neither XDG_CONFIG_HOME nor HOME are set. Using default config.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ use {
|
|||
config::handler::ConfigProxyHandler,
|
||||
ifs::wl_seat::SeatId,
|
||||
state::State,
|
||||
utils::{numcell::NumCell, ptr_ext::PtrExt},
|
||||
utils::{
|
||||
clonecell::CloneCell, numcell::NumCell, oserror::OsError, ptr_ext::PtrExt,
|
||||
unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
||||
},
|
||||
},
|
||||
jay_config::{
|
||||
_private::{
|
||||
|
|
@ -29,15 +32,36 @@ pub enum ConfigError {
|
|||
CouldNotLoadLibrary(#[source] libloading::Error),
|
||||
#[error("Config library does not contain the entry symbol")]
|
||||
LibraryDoesNotContainEntry(#[source] libloading::Error),
|
||||
#[error("Could not determine the config directory")]
|
||||
ConfigDirNotSet,
|
||||
#[error("Could not link the config file")]
|
||||
LinkConfigFile(#[source] OsError),
|
||||
#[error("XDG_RUNTIME_DIR is not set")]
|
||||
XrdNotSet,
|
||||
}
|
||||
|
||||
pub struct ConfigProxy {
|
||||
handler: Rc<ConfigProxyHandler>,
|
||||
handler: CloneCell<Option<Rc<ConfigProxyHandler>>>,
|
||||
}
|
||||
|
||||
impl ConfigProxy {
|
||||
fn send(&self, msg: &ServerMessage) {
|
||||
if let Some(handler) = self.handler.get() {
|
||||
handler.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy(&self) {
|
||||
if let Some(handler) = self.handler.take() {
|
||||
unsafe {
|
||||
handler.do_drop();
|
||||
(handler.unref)(handler.client_data.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke_shortcut(&self, seat: SeatId, modsym: &ModifiedKeySym) {
|
||||
self.handler.send(&ServerMessage::InvokeShortcut {
|
||||
self.send(&ServerMessage::InvokeShortcut {
|
||||
seat: Seat(seat.raw() as _),
|
||||
mods: modsym.mods,
|
||||
sym: modsym.sym,
|
||||
|
|
@ -45,52 +69,49 @@ impl ConfigProxy {
|
|||
}
|
||||
|
||||
pub fn new_connector(&self, connector: ConnectorId) {
|
||||
self.handler.send(&ServerMessage::NewConnector {
|
||||
self.send(&ServerMessage::NewConnector {
|
||||
device: Connector(connector.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn del_connector(&self, connector: ConnectorId) {
|
||||
self.handler.send(&ServerMessage::DelConnector {
|
||||
self.send(&ServerMessage::DelConnector {
|
||||
device: Connector(connector.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn connector_connected(&self, connector: ConnectorId) {
|
||||
self.handler.send(&ServerMessage::ConnectorConnect {
|
||||
self.send(&ServerMessage::ConnectorConnect {
|
||||
device: Connector(connector.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn connector_disconnected(&self, connector: ConnectorId) {
|
||||
self.handler.send(&ServerMessage::ConnectorDisconnect {
|
||||
self.send(&ServerMessage::ConnectorDisconnect {
|
||||
device: Connector(connector.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new_input_device(&self, dev: InputDeviceId) {
|
||||
self.handler.send(&ServerMessage::NewInputDevice {
|
||||
self.send(&ServerMessage::NewInputDevice {
|
||||
device: InputDevice(dev.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn del_input_device(&self, dev: InputDeviceId) {
|
||||
self.handler.send(&ServerMessage::DelInputDevice {
|
||||
self.send(&ServerMessage::DelInputDevice {
|
||||
device: InputDevice(dev.raw() as _),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn graphics_initialized(&self) {
|
||||
self.handler.send(&ServerMessage::GraphicsInitialized);
|
||||
self.send(&ServerMessage::GraphicsInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConfigProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.handler.do_drop();
|
||||
(self.handler.unref)(self.handler.client_data.get());
|
||||
}
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,8 +161,13 @@ impl ConfigProxy {
|
|||
);
|
||||
data.client_data.set(client_data);
|
||||
}
|
||||
data.send(&ServerMessage::Configure);
|
||||
Self { handler: data }
|
||||
Self {
|
||||
handler: CloneCell::new(Some(data)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(&self, reload: bool) {
|
||||
self.send(&ServerMessage::Configure { reload });
|
||||
}
|
||||
|
||||
pub fn default(state: &Rc<State>) -> Self {
|
||||
|
|
@ -154,9 +180,44 @@ impl ConfigProxy {
|
|||
Self::new(None, &entry, state)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
|
||||
let dir = match state.config_dir.as_deref() {
|
||||
Some(d) => d,
|
||||
_ => return Err(ConfigError::ConfigDirNotSet),
|
||||
};
|
||||
let file = format!("{}/config.so", dir);
|
||||
unsafe { Self::from_file(&file, state) }
|
||||
}
|
||||
|
||||
pub unsafe fn from_file(path: &str, state: &Rc<State>) -> Result<Self, ConfigError> {
|
||||
let lib = match Library::new(path) {
|
||||
// Here we have to do a bit of a dance to support reloading. glibc will
|
||||
// never load a library twice unless it has been unloaded in between.
|
||||
// glibc identifies libraries by their file path and by their inode
|
||||
// number. If either of those match, glibc considers the libraries
|
||||
// identical. If the inode has not changed then this is not a problem
|
||||
// for us since we don't want glibc to do any unnecessary work.
|
||||
// However, if the user has created a new config with a new inode, then
|
||||
// glibc will still not reload the library if we try to load it from
|
||||
// the canonical location ~/.config/jay/config.so since it already has
|
||||
// a library with that path loaded. To work around this, create a
|
||||
// temporary symlink with an incrementing number and load the library
|
||||
// from there.
|
||||
let xrd = match xrd() {
|
||||
Some(x) => x,
|
||||
_ => return Err(ConfigError::XrdNotSet),
|
||||
};
|
||||
let link = format!(
|
||||
"{}/.jay_config.so.{}.{}",
|
||||
xrd,
|
||||
uapi::getpid(),
|
||||
state.config_file_id.fetch_add(1)
|
||||
);
|
||||
let _ = uapi::unlink(link.as_str());
|
||||
if let Err(e) = uapi::symlink(path, link.as_str()) {
|
||||
return Err(ConfigError::LinkConfigFile(e.into()));
|
||||
}
|
||||
let _unlink = UnlinkOnDrop(&link);
|
||||
let lib = match Library::new(&link) {
|
||||
Ok(l) => l,
|
||||
Err(e) => return Err(ConfigError::CouldNotLoadLibrary(e)),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use {
|
|||
self, ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId,
|
||||
},
|
||||
compositor::MAX_EXTENTS,
|
||||
config::ConfigProxy,
|
||||
ifs::wl_seat::{SeatId, WlSeatGlobal},
|
||||
state::{ConnectorData, DeviceHandlerData, OutputData, State},
|
||||
tree::{ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase},
|
||||
|
|
@ -121,11 +122,19 @@ impl ConfigProxyHandler {
|
|||
log::log!(level, "{:?}", debug);
|
||||
}
|
||||
|
||||
fn handle_create_seat(&self, name: &str) {
|
||||
fn handle_get_seat(&self, name: &str) {
|
||||
for seat in self.state.globals.seats.lock().values() {
|
||||
if seat.seat_name() == name {
|
||||
self.respond(Response::GetSeat {
|
||||
seat: Seat(seat.id().raw() as _),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
let global_name = self.state.globals.name();
|
||||
let seat = WlSeatGlobal::new(global_name, name, &self.state);
|
||||
self.state.globals.add_global(&self.state, &seat);
|
||||
self.respond(Response::CreateSeat {
|
||||
self.respond(Response::GetSeat {
|
||||
seat: Seat(seat.id().raw() as _),
|
||||
});
|
||||
}
|
||||
|
|
@ -143,6 +152,25 @@ impl ConfigProxyHandler {
|
|||
res
|
||||
}
|
||||
|
||||
fn handle_reload(&self) {
|
||||
log::info!("Reloading config");
|
||||
let config = match ConfigProxy::from_config_dir(&self.state) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::error!("Cannot reload config: {}", ErrorFmt(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Some(config) = self.state.config.take() {
|
||||
config.destroy();
|
||||
for seat in self.state.globals.seats.lock().values() {
|
||||
seat.clear_shortcuts();
|
||||
}
|
||||
}
|
||||
config.configure(true);
|
||||
self.state.config.set(Some(Rc::new(config)));
|
||||
}
|
||||
|
||||
fn handle_get_fullscreen(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
self.respond(Response::GetFullscreen {
|
||||
|
|
@ -784,7 +812,7 @@ impl ConfigProxyHandler {
|
|||
file,
|
||||
line,
|
||||
} => self.handle_log_request(level, msg, file, line),
|
||||
ClientMessage::CreateSeat { name } => self.handle_create_seat(name),
|
||||
ClientMessage::GetSeat { name } => self.handle_get_seat(name),
|
||||
ClientMessage::ParseKeymap { keymap } => {
|
||||
self.handle_parse_keymap(keymap).wrn("parse_keymap")?
|
||||
}
|
||||
|
|
@ -915,6 +943,7 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::GetFullscreen { seat } => {
|
||||
self.handle_get_fullscreen(seat).wrn("get_fullscreen")?
|
||||
}
|
||||
ClientMessage::Reload => self.handle_reload(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -619,6 +619,10 @@ impl WlSeatGlobal {
|
|||
self.id
|
||||
}
|
||||
|
||||
pub fn seat_name(&self) -> &str {
|
||||
&self.seat_name
|
||||
}
|
||||
|
||||
fn bind_(
|
||||
self: Rc<Self>,
|
||||
id: WlSeatId,
|
||||
|
|
|
|||
|
|
@ -464,6 +464,10 @@ impl WlSeatGlobal {
|
|||
self.apply_changes();
|
||||
}
|
||||
|
||||
pub fn clear_shortcuts(&self) {
|
||||
self.shortcuts.clear();
|
||||
}
|
||||
|
||||
pub fn add_shortcut(&self, mods: Modifiers, keysym: KeySym) {
|
||||
self.shortcuts.set((mods.0, keysym.0), mods);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ pub struct State {
|
|||
pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
|
||||
pub serial: NumCell<Wrapping<u32>>,
|
||||
pub run_toplevel: Rc<RunToplevel>,
|
||||
pub config_dir: Option<String>,
|
||||
pub config_file_id: NumCell<u64>,
|
||||
}
|
||||
|
||||
impl Debug for State {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub mod stack;
|
|||
pub mod syncqueue;
|
||||
pub mod tri;
|
||||
pub mod trim;
|
||||
pub mod unlink_on_drop;
|
||||
pub mod vasprintf;
|
||||
pub mod vec_ext;
|
||||
pub mod vecstorage;
|
||||
|
|
|
|||
7
src/utils/unlink_on_drop.rs
Normal file
7
src/utils/unlink_on_drop.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct UnlinkOnDrop<'a>(pub &'a str);
|
||||
|
||||
impl<'a> Drop for UnlinkOnDrop<'a> {
|
||||
fn drop(&mut self) {
|
||||
let _ = uapi::unlink(self.0);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue