| 1 | |
| 2 |
|
| 3 | |
| 4 |
|
| 5 | use core::mem::{size_of, size_of_val, MaybeUninit};
|
| 6 | use core::ops::Range;
|
| 7 |
|
| 8 | use kernel::{
|
| 9 | bindings,
|
| 10 | fs::file::{File, FileDescriptorReservation},
|
| 11 | prelude::*,
|
| 12 | sync::{aref::ARef, Arc},
|
| 13 | transmute::{AsBytes, FromBytes},
|
| 14 | uaccess::UserSliceReader,
|
| 15 | uapi,
|
| 16 | };
|
| 17 |
|
| 18 | use crate::{
|
| 19 | deferred_close::DeferredFdCloser,
|
| 20 | defs::*,
|
| 21 | node::{Node, NodeRef},
|
| 22 | process::Process,
|
| 23 | DArc,
|
| 24 | };
|
| 25 |
|
| 26 | #[derive(Default)]
|
| 27 | pub(crate) struct AllocationInfo {
|
| 28 | |
| 29 | pub(crate) offsets: Option<Range<usize>>,
|
| 30 | |
| 31 | |
| 32 | pub(crate) target_node: Option<NodeRef>,
|
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | pub(crate) oneway_node: Option<DArc<Node>>,
|
| 38 | |
| 39 | pub(crate) clear_on_free: bool,
|
| 40 | |
| 41 | file_list: FileList,
|
| 42 | }
|
| 43 |
|
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | pub(crate) struct Allocation {
|
| 53 | pub(crate) offset: usize,
|
| 54 | size: usize,
|
| 55 | pub(crate) ptr: usize,
|
| 56 | pub(crate) process: Arc<Process>,
|
| 57 | allocation_info: Option<AllocationInfo>,
|
| 58 | free_on_drop: bool,
|
| 59 | #[allow(dead_code)]
|
| 60 | pub(crate) debug_id: usize,
|
| 61 | }
|
| 62 |
|
| 63 | impl Allocation {
|
| 64 | pub(crate) fn new(
|
| 65 | process: Arc<Process>,
|
| 66 | debug_id: usize,
|
| 67 | offset: usize,
|
| 68 | size: usize,
|
| 69 | ptr: usize,
|
| 70 | ) -> Self {
|
| 71 | Self {
|
| 72 | process,
|
| 73 | offset,
|
| 74 | size,
|
| 75 | ptr,
|
| 76 | debug_id,
|
| 77 | allocation_info: None,
|
| 78 | free_on_drop: true,
|
| 79 | }
|
| 80 | }
|
| 81 |
|
| 82 | fn size_check(&self, offset: usize, size: usize) -> Result {
|
| 83 | let overflow_fail = offset.checked_add(size).is_none();
|
| 84 | let cmp_size_fail = offset.wrapping_add(size) > self.size;
|
| 85 | if overflow_fail || cmp_size_fail {
|
| 86 | return Err(EFAULT);
|
| 87 | }
|
| 88 | Ok(())
|
| 89 | }
|
| 90 |
|
| 91 | pub(crate) fn copy_into(
|
| 92 | &self,
|
| 93 | reader: &mut UserSliceReader,
|
| 94 | offset: usize,
|
| 95 | size: usize,
|
| 96 | ) -> Result {
|
| 97 | self.size_check(offset, size)?;
|
| 98 |
|
| 99 | |
| 100 | |
| 101 | unsafe {
|
| 102 | self.process
|
| 103 | .pages
|
| 104 | .copy_from_user_slice(reader, self.offset + offset, size)
|
| 105 | }
|
| 106 | }
|
| 107 |
|
| 108 | pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> {
|
| 109 | self.size_check(offset, size_of::<T>())?;
|
| 110 |
|
| 111 | |
| 112 | |
| 113 | unsafe { self.process.pages.read(self.offset + offset) }
|
| 114 | }
|
| 115 |
|
| 116 | pub(crate) fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result {
|
| 117 | self.size_check(offset, size_of_val::<T>(obj))?;
|
| 118 |
|
| 119 | |
| 120 | |
| 121 | unsafe { self.process.pages.write(self.offset + offset, obj) }
|
| 122 | }
|
| 123 |
|
| 124 | pub(crate) fn fill_zero(&self) -> Result {
|
| 125 | |
| 126 | |
| 127 | unsafe { self.process.pages.fill_zero(self.offset, self.size) }
|
| 128 | }
|
| 129 |
|
| 130 | pub(crate) fn keep_alive(mut self) {
|
| 131 | self.process
|
| 132 | .buffer_make_freeable(self.offset, self.allocation_info.take());
|
| 133 | self.free_on_drop = false;
|
| 134 | }
|
| 135 |
|
| 136 | pub(crate) fn set_info(&mut self, info: AllocationInfo) {
|
| 137 | self.allocation_info = Some(info);
|
| 138 | }
|
| 139 |
|
| 140 | pub(crate) fn get_or_init_info(&mut self) -> &mut AllocationInfo {
|
| 141 | self.allocation_info.get_or_insert_with(Default::default)
|
| 142 | }
|
| 143 |
|
| 144 | pub(crate) fn set_info_offsets(&mut self, offsets: Range<usize>) {
|
| 145 | self.get_or_init_info().offsets = Some(offsets);
|
| 146 | }
|
| 147 |
|
| 148 | pub(crate) fn set_info_oneway_node(&mut self, oneway_node: DArc<Node>) {
|
| 149 | self.get_or_init_info().oneway_node = Some(oneway_node);
|
| 150 | }
|
| 151 |
|
| 152 | pub(crate) fn set_info_clear_on_drop(&mut self) {
|
| 153 | self.get_or_init_info().clear_on_free = true;
|
| 154 | }
|
| 155 |
|
| 156 | pub(crate) fn set_info_target_node(&mut self, target_node: NodeRef) {
|
| 157 | self.get_or_init_info().target_node = Some(target_node);
|
| 158 | }
|
| 159 |
|
| 160 | pub(crate) fn take_oneway_node(&mut self) -> Option<DArc<Node>> {
|
| 161 | if let Some(info) = self.allocation_info.as_mut() {
|
| 162 | info.oneway_node.take()
|
| 163 | } else {
|
| 164 | None
|
| 165 | }
|
| 166 | }
|
| 167 |
|
| 168 | |
| 169 | pub(crate) fn info_add_fd_reserve(&mut self, num_fds: usize) -> Result {
|
| 170 | self.get_or_init_info()
|
| 171 | .file_list
|
| 172 | .files_to_translate
|
| 173 | .reserve(num_fds, GFP_KERNEL)?;
|
| 174 |
|
| 175 | Ok(())
|
| 176 | }
|
| 177 |
|
| 178 | pub(crate) fn info_add_fd(
|
| 179 | &mut self,
|
| 180 | file: ARef<File>,
|
| 181 | buffer_offset: usize,
|
| 182 | close_on_free: bool,
|
| 183 | ) -> Result {
|
| 184 | self.get_or_init_info().file_list.files_to_translate.push(
|
| 185 | FileEntry {
|
| 186 | file,
|
| 187 | buffer_offset,
|
| 188 | close_on_free,
|
| 189 | },
|
| 190 | GFP_KERNEL,
|
| 191 | )?;
|
| 192 |
|
| 193 | Ok(())
|
| 194 | }
|
| 195 |
|
| 196 | pub(crate) fn set_info_close_on_free(&mut self, cof: FdsCloseOnFree) {
|
| 197 | self.get_or_init_info().file_list.close_on_free = cof.0;
|
| 198 | }
|
| 199 |
|
| 200 | pub(crate) fn translate_fds(&mut self) -> Result<TranslatedFds> {
|
| 201 | let file_list = match self.allocation_info.as_mut() {
|
| 202 | Some(info) => &mut info.file_list,
|
| 203 | None => return Ok(TranslatedFds::new()),
|
| 204 | };
|
| 205 |
|
| 206 | let files = core::mem::take(&mut file_list.files_to_translate);
|
| 207 |
|
| 208 | let num_close_on_free = files.iter().filter(|entry| entry.close_on_free).count();
|
| 209 | let mut close_on_free = KVec::with_capacity(num_close_on_free, GFP_KERNEL)?;
|
| 210 |
|
| 211 | let mut reservations = KVec::with_capacity(files.len(), GFP_KERNEL)?;
|
| 212 | for file_info in files {
|
| 213 | let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?;
|
| 214 | let fd = res.reserved_fd();
|
| 215 | self.write::<u32>(file_info.buffer_offset, &fd)?;
|
| 216 | crate::trace::trace_transaction_fd_recv(self.debug_id, fd, file_info.buffer_offset);
|
| 217 |
|
| 218 | reservations.push(
|
| 219 | Reservation {
|
| 220 | res,
|
| 221 | file: file_info.file,
|
| 222 | },
|
| 223 | GFP_KERNEL,
|
| 224 | )?;
|
| 225 | if file_info.close_on_free {
|
| 226 | close_on_free.push(fd, GFP_KERNEL)?;
|
| 227 | }
|
| 228 | }
|
| 229 |
|
| 230 | Ok(TranslatedFds {
|
| 231 | reservations,
|
| 232 | close_on_free: FdsCloseOnFree(close_on_free),
|
| 233 | })
|
| 234 | }
|
| 235 |
|
| 236 | |
| 237 | pub(crate) fn looper_need_return_on_free(&self) -> bool {
|
| 238 | |
| 239 | |
| 240 | match self.allocation_info {
|
| 241 | Some(ref info) => !info.file_list.close_on_free.is_empty(),
|
| 242 | None => false,
|
| 243 | }
|
| 244 | }
|
| 245 | }
|
| 246 |
|
| 247 | impl Drop for Allocation {
|
| 248 | fn drop(&mut self) {
|
| 249 | if !self.free_on_drop {
|
| 250 | return;
|
| 251 | }
|
| 252 |
|
| 253 | if let Some(mut info) = self.allocation_info.take() {
|
| 254 | if let Some(oneway_node) = info.oneway_node.as_ref() {
|
| 255 | oneway_node.pending_oneway_finished();
|
| 256 | }
|
| 257 |
|
| 258 | info.target_node = None;
|
| 259 |
|
| 260 | if let Some(offsets) = info.offsets.clone() {
|
| 261 | let view = AllocationView::new(self, offsets.start);
|
| 262 | for i in offsets.step_by(size_of::<u64>()) {
|
| 263 | if view.cleanup_object(i).is_err() {
|
| 264 | pr_warn!("Error cleaning up object at offset {}\n", i)
|
| 265 | }
|
| 266 | }
|
| 267 | }
|
| 268 |
|
| 269 | if self.process.task == kernel::current!().group_leader() {
|
| 270 | for &fd in &info.file_list.close_on_free {
|
| 271 | let closer = match DeferredFdCloser::new(GFP_KERNEL) {
|
| 272 | Ok(closer) => closer,
|
| 273 | Err(kernel::alloc::AllocError) => {
|
| 274 | |
| 275 | break;
|
| 276 | }
|
| 277 | };
|
| 278 |
|
| 279 | |
| 280 | |
| 281 | |
| 282 | |
| 283 | let _ = closer.close_fd(fd);
|
| 284 | }
|
| 285 | }
|
| 286 |
|
| 287 | if info.clear_on_free {
|
| 288 | if let Err(e) = self.fill_zero() {
|
| 289 | pr_warn!("Failed to clear data on free: {:?}", e);
|
| 290 | }
|
| 291 | }
|
| 292 | }
|
| 293 |
|
| 294 | self.process.buffer_raw_free(self.ptr);
|
| 295 | }
|
| 296 | }
|
| 297 |
|
| 298 | |
| 299 | |
| 300 | |
| 301 | |
| 302 | |
| 303 | #[repr(transparent)]
|
| 304 | pub(crate) struct NewAllocation(pub(crate) Allocation);
|
| 305 |
|
| 306 | impl NewAllocation {
|
| 307 | pub(crate) fn success(self) -> Allocation {
|
| 308 | |
| 309 | |
| 310 | |
| 311 | unsafe { core::mem::transmute(self) }
|
| 312 | }
|
| 313 | }
|
| 314 |
|
| 315 | impl core::ops::Deref for NewAllocation {
|
| 316 | type Target = Allocation;
|
| 317 | fn deref(&self) -> &Allocation {
|
| 318 | &self.0
|
| 319 | }
|
| 320 | }
|
| 321 |
|
| 322 | impl core::ops::DerefMut for NewAllocation {
|
| 323 | fn deref_mut(&mut self) -> &mut Allocation {
|
| 324 | &mut self.0
|
| 325 | }
|
| 326 | }
|
| 327 |
|
| 328 | |
| 329 | |
| 330 | |
| 331 | |
| 332 | pub(crate) struct AllocationView<'a> {
|
| 333 | pub(crate) alloc: &'a mut Allocation,
|
| 334 | limit: usize,
|
| 335 | }
|
| 336 |
|
| 337 | impl<'a> AllocationView<'a> {
|
| 338 | pub(crate) fn new(alloc: &'a mut Allocation, limit: usize) -> Self {
|
| 339 | AllocationView { alloc, limit }
|
| 340 | }
|
| 341 |
|
| 342 | pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> {
|
| 343 | if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit {
|
| 344 | return Err(EINVAL);
|
| 345 | }
|
| 346 | self.alloc.read(offset)
|
| 347 | }
|
| 348 |
|
| 349 | pub(crate) fn write<T: AsBytes>(&self, offset: usize, obj: &T) -> Result {
|
| 350 | if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit {
|
| 351 | return Err(EINVAL);
|
| 352 | }
|
| 353 | self.alloc.write(offset, obj)
|
| 354 | }
|
| 355 |
|
| 356 | pub(crate) fn copy_into(
|
| 357 | &self,
|
| 358 | reader: &mut UserSliceReader,
|
| 359 | offset: usize,
|
| 360 | size: usize,
|
| 361 | ) -> Result {
|
| 362 | if offset.checked_add(size).ok_or(EINVAL)? > self.limit {
|
| 363 | return Err(EINVAL);
|
| 364 | }
|
| 365 | self.alloc.copy_into(reader, offset, size)
|
| 366 | }
|
| 367 |
|
| 368 | pub(crate) fn transfer_binder_object(
|
| 369 | &self,
|
| 370 | offset: usize,
|
| 371 | obj: &uapi::flat_binder_object,
|
| 372 | strong: bool,
|
| 373 | node_ref: NodeRef,
|
| 374 | ) -> Result {
|
| 375 | let mut newobj = FlatBinderObject::default();
|
| 376 | let node = node_ref.node.clone();
|
| 377 | if Arc::ptr_eq(&node_ref.node.owner, &self.alloc.process) {
|
| 378 | |
| 379 | |
| 380 | let (ptr, cookie) = node.get_id();
|
| 381 | newobj.hdr.type_ = if strong {
|
| 382 | BINDER_TYPE_BINDER
|
| 383 | } else {
|
| 384 | BINDER_TYPE_WEAK_BINDER
|
| 385 | };
|
| 386 | newobj.flags = obj.flags;
|
| 387 | newobj.__bindgen_anon_1.binder = ptr as uapi::binder_uintptr_t;
|
| 388 | newobj.cookie = cookie as uapi::binder_uintptr_t;
|
| 389 | self.write(offset, &newobj)?;
|
| 390 | |
| 391 | |
| 392 | node.update_refcount(true, 1, strong);
|
| 393 | } else {
|
| 394 | |
| 395 | |
| 396 | let handle = self
|
| 397 | .alloc
|
| 398 | .process
|
| 399 | .as_arc_borrow()
|
| 400 | .insert_or_update_handle(node_ref, false)?;
|
| 401 | newobj.hdr.type_ = if strong {
|
| 402 | BINDER_TYPE_HANDLE
|
| 403 | } else {
|
| 404 | BINDER_TYPE_WEAK_HANDLE
|
| 405 | };
|
| 406 | newobj.flags = obj.flags;
|
| 407 | newobj.__bindgen_anon_1.handle = handle;
|
| 408 | if self.write(offset, &newobj).is_err() {
|
| 409 | |
| 410 | let _ = self
|
| 411 | .alloc
|
| 412 | .process
|
| 413 | .as_arc_borrow()
|
| 414 | .update_ref(handle, false, strong);
|
| 415 | return Err(EINVAL);
|
| 416 | }
|
| 417 | }
|
| 418 |
|
| 419 | Ok(())
|
| 420 | }
|
| 421 |
|
| 422 | fn cleanup_object(&self, index_offset: usize) -> Result {
|
| 423 | let offset = self.alloc.read::<u64>(index_offset)?;
|
| 424 | let offset: usize = offset.try_into().map_err(|_| EINVAL)?;
|
| 425 | let header = self.read::<BinderObjectHeader>(offset)?;
|
| 426 | match header.type_ {
|
| 427 | BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => {
|
| 428 | let obj = self.read::<FlatBinderObject>(offset)?;
|
| 429 | let strong = header.type_ == BINDER_TYPE_BINDER;
|
| 430 | |
| 431 | |
| 432 | let ptr = unsafe { obj.__bindgen_anon_1.binder };
|
| 433 | let cookie = obj.cookie;
|
| 434 | self.alloc.process.update_node(ptr, cookie, strong);
|
| 435 | Ok(())
|
| 436 | }
|
| 437 | BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => {
|
| 438 | let obj = self.read::<FlatBinderObject>(offset)?;
|
| 439 | let strong = header.type_ == BINDER_TYPE_HANDLE;
|
| 440 | |
| 441 | |
| 442 | let handle = unsafe { obj.__bindgen_anon_1.handle };
|
| 443 | self.alloc
|
| 444 | .process
|
| 445 | .as_arc_borrow()
|
| 446 | .update_ref(handle, false, strong)
|
| 447 | }
|
| 448 | _ => Ok(()),
|
| 449 | }
|
| 450 | }
|
| 451 | }
|
| 452 |
|
| 453 | |
| 454 | |
| 455 | |
| 456 | |
| 457 | |
| 458 | |
| 459 | #[repr(C)]
|
| 460 | pub(crate) union BinderObject {
|
| 461 | hdr: uapi::binder_object_header,
|
| 462 | fbo: uapi::flat_binder_object,
|
| 463 | fdo: uapi::binder_fd_object,
|
| 464 | bbo: uapi::binder_buffer_object,
|
| 465 | fdao: uapi::binder_fd_array_object,
|
| 466 | }
|
| 467 |
|
| 468 | |
| 469 | pub(crate) enum BinderObjectRef<'a> {
|
| 470 | Binder(&'a mut uapi::flat_binder_object),
|
| 471 | Handle(&'a mut uapi::flat_binder_object),
|
| 472 | Fd(&'a mut uapi::binder_fd_object),
|
| 473 | Ptr(&'a mut uapi::binder_buffer_object),
|
| 474 | Fda(&'a mut uapi::binder_fd_array_object),
|
| 475 | }
|
| 476 |
|
| 477 | impl BinderObject {
|
| 478 | pub(crate) fn read_from(reader: &mut UserSliceReader) -> Result<BinderObject> {
|
| 479 | let object = Self::read_from_inner(|slice| {
|
| 480 | let read_len = usize::min(slice.len(), reader.len());
|
| 481 | reader.clone_reader().read_slice(&mut slice[..read_len])?;
|
| 482 | Ok(())
|
| 483 | })?;
|
| 484 |
|
| 485 | |
| 486 | |
| 487 | |
| 488 | |
| 489 | |
| 490 | |
| 491 | |
| 492 | |
| 493 | |
| 494 | |
| 495 | |
| 496 | reader.skip(object.size())?;
|
| 497 |
|
| 498 | Ok(object)
|
| 499 | }
|
| 500 |
|
| 501 | |
| 502 | |
| 503 | |
| 504 | pub(crate) fn read_from_inner<R>(reader: R) -> Result<BinderObject>
|
| 505 | where
|
| 506 | R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result<()>,
|
| 507 | {
|
| 508 | let mut obj = MaybeUninit::<BinderObject>::zeroed();
|
| 509 |
|
| 510 | |
| 511 | |
| 512 | |
| 513 | |
| 514 | (reader)(unsafe { &mut *obj.as_mut_ptr().cast() })?;
|
| 515 |
|
| 516 | |
| 517 | let type_ = unsafe { obj.assume_init_ref().hdr.type_ };
|
| 518 | if Self::type_to_size(type_).is_none() {
|
| 519 | |
| 520 | return Err(EINVAL);
|
| 521 | }
|
| 522 |
|
| 523 | |
| 524 | |
| 525 | unsafe { Ok(obj.assume_init()) }
|
| 526 | }
|
| 527 |
|
| 528 | pub(crate) fn as_ref(&mut self) -> BinderObjectRef<'_> {
|
| 529 | use BinderObjectRef::*;
|
| 530 | |
| 531 | |
| 532 | unsafe {
|
| 533 | match self.hdr.type_ {
|
| 534 | BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => Binder(&mut self.fbo),
|
| 535 | BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => Handle(&mut self.fbo),
|
| 536 | BINDER_TYPE_FD => Fd(&mut self.fdo),
|
| 537 | BINDER_TYPE_PTR => Ptr(&mut self.bbo),
|
| 538 | BINDER_TYPE_FDA => Fda(&mut self.fdao),
|
| 539 | |
| 540 | |
| 541 | _ => core::hint::unreachable_unchecked(),
|
| 542 | }
|
| 543 | }
|
| 544 | }
|
| 545 |
|
| 546 | pub(crate) fn size(&self) -> usize {
|
| 547 | |
| 548 | let type_ = unsafe { self.hdr.type_ };
|
| 549 |
|
| 550 | |
| 551 | unsafe { Self::type_to_size(type_).unwrap_unchecked() }
|
| 552 | }
|
| 553 |
|
| 554 | fn type_to_size(type_: u32) -> Option<usize> {
|
| 555 | match type_ {
|
| 556 | BINDER_TYPE_WEAK_BINDER => Some(size_of::<uapi::flat_binder_object>()),
|
| 557 | BINDER_TYPE_BINDER => Some(size_of::<uapi::flat_binder_object>()),
|
| 558 | BINDER_TYPE_WEAK_HANDLE => Some(size_of::<uapi::flat_binder_object>()),
|
| 559 | BINDER_TYPE_HANDLE => Some(size_of::<uapi::flat_binder_object>()),
|
| 560 | BINDER_TYPE_FD => Some(size_of::<uapi::binder_fd_object>()),
|
| 561 | BINDER_TYPE_PTR => Some(size_of::<uapi::binder_buffer_object>()),
|
| 562 | BINDER_TYPE_FDA => Some(size_of::<uapi::binder_fd_array_object>()),
|
| 563 | _ => None,
|
| 564 | }
|
| 565 | }
|
| 566 | }
|
| 567 |
|
| 568 | #[derive(Default)]
|
| 569 | struct FileList {
|
| 570 | files_to_translate: KVec<FileEntry>,
|
| 571 | close_on_free: KVec<u32>,
|
| 572 | }
|
| 573 |
|
| 574 | struct FileEntry {
|
| 575 | |
| 576 | file: ARef<File>,
|
| 577 | |
| 578 | buffer_offset: usize,
|
| 579 | |
| 580 | close_on_free: bool,
|
| 581 | }
|
| 582 |
|
| 583 | pub(crate) struct TranslatedFds {
|
| 584 | reservations: KVec<Reservation>,
|
| 585 | |
| 586 | |
| 587 | close_on_free: FdsCloseOnFree,
|
| 588 | }
|
| 589 |
|
| 590 | struct Reservation {
|
| 591 | res: FileDescriptorReservation,
|
| 592 | file: ARef<File>,
|
| 593 | }
|
| 594 |
|
| 595 | impl TranslatedFds {
|
| 596 | pub(crate) fn new() -> Self {
|
| 597 | Self {
|
| 598 | reservations: KVec::new(),
|
| 599 | close_on_free: FdsCloseOnFree(KVec::new()),
|
| 600 | }
|
| 601 | }
|
| 602 |
|
| 603 | pub(crate) fn commit(self) -> FdsCloseOnFree {
|
| 604 | for entry in self.reservations {
|
| 605 | entry.res.fd_install(entry.file);
|
| 606 | }
|
| 607 |
|
| 608 | self.close_on_free
|
| 609 | }
|
| 610 | }
|
| 611 |
|
| 612 | pub(crate) struct FdsCloseOnFree(KVec<u32>);
|