Skip to main content

strat9_kernel/syscall/
mmap.rs

1//! Memory-management syscall handlers: mmap, munmap, brk.
2//!
3//! Implements:
4//!  - [`sys_mmap`]   – map anonymous virtual memory (SYS_MMAP = 100)
5//!  - [`sys_munmap`] – unmap a virtual memory range (SYS_MUNMAP = 101)
6//!  - [`sys_brk`]    – set / query the program break / heap top (SYS_BRK = 102)
7//!  - [`sys_mremap`] – resize/remap an existing region (SYS_MREMAP = 103)
8//!  - [`sys_mprotect`] – change page permissions (SYS_MPROTECT = 104)
9
10use crate::{
11    arch::xshim::VirtAddr,
12    memory::address_space::{VmaFlags, VmaType},
13    process::current_task_clone,
14    syscall::error::SyscallError,
15};
16use core::sync::atomic::Ordering;
17use strat9_abi::data::MemoryRegionInfo as MemoryRegionInfoAbi;
18
19// ================================================================================
20// Virtual address layout constants
21// ================================================================================
22
23/// Base virtual address for the heap (`brk`-managed region).
24pub const BRK_BASE: u64 = 0x0000_0000_2000_0000; // 512 MiB
25
26/// Initial hint address for anonymous `mmap` allocations.
27pub const MMAP_BASE: u64 = 0x0000_0000_6000_0000; // 1.5 GiB
28
29/// Exclusive upper bound of the canonical user-space address range.
30const USER_SPACE_END: u64 = crate::memory::userslice::USER_SPACE_END;
31
32// ================================================================================
33// PROT flags (arg3 of mmap)
34// ================================================================================
35
36const PROT_READ: u32 = 1 << 0;
37const PROT_WRITE: u32 = 1 << 1;
38const PROT_EXEC: u32 = 1 << 2;
39
40// ================================================================================
41// MAP flags (arg4 of mmap)
42// ================================================================================
43
44const MAP_SHARED: u32 = 1 << 0;
45const MAP_PRIVATE: u32 = 1 << 1;
46const MAP_FIXED: u32 = 1 << 4;
47const MAP_ANONYMOUS: u32 = 1 << 5;
48const MAP_HUGETLB: u32 = 1 << 11; // Standard Linux flag for huge pages
49const MAP_FIXED_NOREPLACE: u32 = 1 << 20; // Linux-compatible extension bit.
50
51const MREMAP_MAYMOVE: u64 = 1 << 0;
52
53// ================================================================================
54// Helpers
55// ================================================================================
56
57/// Round `addr` up to the nearest 4 KiB page boundary.
58#[inline]
59fn page_align_up(addr: u64) -> u64 {
60    (addr.wrapping_add(4095)) & !4095u64
61}
62
63/// Round `addr` up to the nearest 2 MiB boundary.
64#[inline]
65fn huge_page_align_up(addr: u64) -> u64 {
66    (addr.wrapping_add((2 * 1024 * 1024) - 1)) & !((2 * 1024 * 1024) - 1)
67}
68
69/// Convert POSIX protection flags to `VmaFlags`.
70fn prot_to_vma_flags(prot: u32) -> VmaFlags {
71    VmaFlags {
72        readable: prot & PROT_READ != 0,
73        writable: prot & PROT_WRITE != 0,
74        executable: prot & PROT_EXEC != 0,
75        user_accessible: true,
76    }
77}
78
79/// Convert `VmaFlags` into ABI protection bits.
80fn vma_flags_to_prot(flags: VmaFlags) -> u32 {
81    (if flags.readable { PROT_READ } else { 0 })
82        | (if flags.writable { PROT_WRITE } else { 0 })
83        | (if flags.executable { PROT_EXEC } else { 0 })
84}
85
86// ================================================================================
87// sys_mmap
88// ================================================================================
89
90/// SYS_MMAP (100): map anonymous virtual memory.
91///
92/// Only `MAP_ANONYMOUS` mappings are supported at this stage; file-backed mmaps
93/// return `NotImplemented`.  Both `MAP_PRIVATE` and `MAP_SHARED` are accepted
94/// for anonymous memory (they are equivalent when there is no backing file).
95///
96/// Returns the mapped virtual address on success, or a negative error code.
97pub fn sys_mmap(
98    addr: u64,
99    len: u64,
100    prot: u32,
101    flags: u32,
102    fd_raw: u64,
103    offset: u64,
104) -> Result<u64, SyscallError> {
105    //  Validate arguments
106    if len == 0 {
107        return Err(SyscallError::InvalidArgument);
108    }
109
110    let known_flags =
111        MAP_SHARED | MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS | MAP_HUGETLB | MAP_FIXED_NOREPLACE;
112    if flags & !known_flags != 0 {
113        return Err(SyscallError::InvalidArgument);
114    }
115
116    let is_huge = flags & MAP_HUGETLB != 0;
117    let page_size = if is_huge {
118        crate::memory::address_space::VmaPageSize::Huge
119    } else {
120        crate::memory::address_space::VmaPageSize::Small
121    };
122    let page_bytes = page_size.bytes();
123
124    // File-backed mappings: MAP_PRIVATE + fd => copy file data into anonymous pages.
125    if flags & MAP_ANONYMOUS == 0 {
126        let fd = fd_raw as u32;
127        let file_offset = offset;
128
129        let is_private = flags & MAP_PRIVATE != 0;
130        let is_shared = flags & MAP_SHARED != 0;
131        if is_private == is_shared {
132            return Err(SyscallError::InvalidArgument);
133        }
134        if !is_private {
135            log::warn!("sys_mmap: file-backed MAP_SHARED not yet supported");
136            return Err(SyscallError::NotImplemented);
137        }
138        if prot & !(PROT_READ | PROT_WRITE | PROT_EXEC) != 0 {
139            return Err(SyscallError::InvalidArgument);
140        }
141
142        let len_aligned = if is_huge {
143            huge_page_align_up(len)
144        } else {
145            page_align_up(len)
146        };
147        if len_aligned == 0 {
148            return Err(SyscallError::InvalidArgument);
149        }
150        let n_pages = (len_aligned / page_bytes) as usize;
151
152        let task = current_task_clone().ok_or(SyscallError::Fault)?;
153        let open_file = {
154            let fd_table = unsafe { &*task.process.fd_table.get() };
155            fd_table.get(fd)?
156        };
157        let addr_space = task.process.address_space_arc();
158
159        let target = if flags & MAP_FIXED != 0 {
160            if addr % page_bytes != 0 || addr == 0 {
161                return Err(SyscallError::InvalidArgument);
162            }
163            if addr.saturating_add(len_aligned) > USER_SPACE_END {
164                return Err(SyscallError::InvalidArgument);
165            }
166            if flags & MAP_FIXED_NOREPLACE != 0 {
167                if addr_space.has_mapping_in_range(addr, len_aligned) {
168                    return Err(SyscallError::AlreadyExists);
169                }
170            } else {
171                addr_space
172                    .unmap_range(addr, len_aligned)
173                    .map_err(|_| SyscallError::InvalidArgument)?;
174            }
175            addr
176        } else {
177            let hint = if addr != 0 {
178                addr
179            } else {
180                task.process.mmap_hint.load(Ordering::Relaxed)
181            };
182            addr_space
183                .find_free_vma_range(hint, n_pages, page_size)
184                .or_else(|| {
185                    addr_space.find_free_vma_range(crate::kaslr::mmap_base(), n_pages, page_size)
186                })
187                .ok_or(SyscallError::OutOfMemory)?
188        };
189
190        let vma_flags = prot_to_vma_flags(prot);
191        addr_space
192            .map_region(target, n_pages, vma_flags, VmaType::Anonymous, page_size)
193            .map_err(|_| SyscallError::OutOfMemory)?;
194
195        // Copy file content into the mapped pages via HHDM.
196        let read_len = len as usize;
197        let mut kbuf = [0u8; 4096];
198        let mut file_off = file_offset;
199        let mut dst_off = 0usize;
200        while dst_off < read_len {
201            let chunk = core::cmp::min(4096, read_len - dst_off);
202            let n = open_file.pread(file_off, &mut kbuf[..chunk]).unwrap_or(0);
203            if n == 0 {
204                break;
205            }
206            let mut written = 0;
207            while written < n {
208                let vaddr = target + (dst_off + written) as u64;
209                let page_off = (vaddr & 0xFFF) as usize;
210                let to_write = core::cmp::min(n - written, 4096 - page_off);
211                let phys = addr_space
212                    .translate(VirtAddr::new(vaddr))
213                    .ok_or(SyscallError::Fault)?;
214                let hhdm_ptr = crate::memory::phys_to_virt(phys.as_u64()) as *mut u8;
215                unsafe {
216                    core::ptr::copy_nonoverlapping(kbuf.as_ptr().add(written), hhdm_ptr, to_write);
217                }
218                written += to_write;
219            }
220            file_off += n as u64;
221            dst_off += n;
222        }
223
224        if flags & MAP_FIXED == 0 {
225            let new_hint = target.saturating_add(len_aligned);
226            let _ = task
227                .process
228                .mmap_hint
229                .fetch_max(new_hint, Ordering::Relaxed);
230        }
231
232        log::trace!(
233            "sys_mmap: file-backed {:#x}..{:#x} (fd={}, off={:#x})",
234            target,
235            target + len_aligned,
236            fd,
237            file_offset,
238        );
239        return Ok(target);
240    }
241
242    let is_private = flags & MAP_PRIVATE != 0;
243    let is_shared = flags & MAP_SHARED != 0;
244    // Exactly one of MAP_PRIVATE / MAP_SHARED.
245    if is_private == is_shared {
246        return Err(SyscallError::InvalidArgument);
247    }
248
249    // Anonymous mapping currently requires page-aligned zero offset.
250    if offset != 0 {
251        return Err(SyscallError::InvalidArgument);
252    }
253
254    // Reject unknown PROT bits.
255    if prot & !(PROT_READ | PROT_WRITE | PROT_EXEC) != 0 {
256        return Err(SyscallError::InvalidArgument);
257    }
258
259    // Round len up to a page boundary.  Overflow of len itself is caught here.
260    let len_aligned = if is_huge {
261        huge_page_align_up(len)
262    } else {
263        page_align_up(len)
264    };
265    if len_aligned == 0 {
266        // len was so large that aligning it overflowed to 0.
267        return Err(SyscallError::InvalidArgument);
268    }
269    let n_pages = (len_aligned / page_bytes) as usize;
270
271    //  Determine the target virtual address
272    let task = current_task_clone().ok_or(SyscallError::Fault)?;
273    let addr_space = task.process.address_space_arc();
274
275    let target = if flags & MAP_FIXED != 0 {
276        // MAP_FIXED: the caller demands this exact page-aligned address.
277        if addr % page_bytes != 0 || addr == 0 {
278            return Err(SyscallError::InvalidArgument);
279        }
280        if addr.saturating_add(len_aligned) > USER_SPACE_END {
281            return Err(SyscallError::InvalidArgument);
282        }
283        if flags & MAP_FIXED_NOREPLACE != 0 {
284            // MAP_FIXED_NOREPLACE: fail if any mapping overlaps.
285            if addr_space.has_mapping_in_range(addr, len_aligned) {
286                return Err(SyscallError::AlreadyExists);
287            }
288        } else {
289            // Linux MAP_FIXED semantics: unmap overlaps before remap.
290            addr_space
291                .unmap_range(addr, len_aligned)
292                .map_err(|_| SyscallError::InvalidArgument)?;
293        }
294        addr
295    } else {
296        // Hint-based: use addr as a hint when non-zero, else use mmap_hint.
297        let hint = if addr != 0 {
298            addr
299        } else {
300            task.process.mmap_hint.load(Ordering::Relaxed)
301        };
302
303        // Try the hint first, then fall back to KASLR-randomized base.
304        addr_space
305            .find_free_vma_range(hint, n_pages, page_size)
306            .or_else(|| {
307                addr_space.find_free_vma_range(crate::kaslr::mmap_base(), n_pages, page_size)
308            })
309            .ok_or(SyscallError::OutOfMemory)?
310    };
311
312    //  Map the region (lazily)
313    let vma_flags = prot_to_vma_flags(prot);
314    addr_space
315        .reserve_region(target, n_pages, vma_flags, VmaType::Anonymous, page_size)
316        .map_err(|_| SyscallError::OutOfMemory)?;
317
318    //  Advance mmap_hint past the new mapping (non-fixed only)
319    if flags & MAP_FIXED == 0 {
320        let new_hint = target.saturating_add(len_aligned);
321        // Atomically advance: only update if it moves forward.
322        let _ = task
323            .process
324            .mmap_hint
325            .fetch_max(new_hint, Ordering::Relaxed);
326    }
327
328    log::trace!(
329        "sys_mmap: mapped {:#x}..{:#x} ({} pages, prot={:#x}, flags={:#x})",
330        target,
331        target + len_aligned,
332        n_pages,
333        prot,
334        flags,
335    );
336
337    Ok(target)
338}
339
340// ================================================================================
341// sys_munmap
342// ================================================================================
343
344/// SYS_MUNMAP (101): unmap a virtual memory range.
345///
346/// `addr` must be page-aligned.  `len` is rounded up to a page boundary.
347/// Unmapping an address range that contains no mappings is silently ignored
348/// (POSIX behaviour).
349pub fn sys_munmap(addr: u64, len: u64) -> Result<u64, SyscallError> {
350    if addr == 0 || addr & 0xFFF != 0 {
351        return Err(SyscallError::InvalidArgument);
352    }
353    if len == 0 {
354        return Err(SyscallError::InvalidArgument);
355    }
356
357    let len_aligned = page_align_up(len);
358    if len_aligned == 0 {
359        return Err(SyscallError::InvalidArgument);
360    }
361    if addr.saturating_add(len_aligned) > USER_SPACE_END {
362        return Err(SyscallError::InvalidArgument);
363    }
364
365    let task = current_task_clone().ok_or(SyscallError::Fault)?;
366    task.process
367        .address_space_arc()
368        .unmap_range(addr, len_aligned)
369        .map_err(|_| SyscallError::InvalidArgument)?;
370
371    log::trace!(
372        "sys_munmap: unmapped {:#x}..{:#x}",
373        addr,
374        addr + len_aligned
375    );
376
377    Ok(0)
378}
379
380/// SYS_MREMAP (103): resize an existing mapping.
381///
382/// Current support:
383/// - Shrink in place.
384/// - Grow in place when the following range is free.
385/// - If `MREMAP_MAYMOVE` is set and growth in place fails, relocate only when
386///   the source mapping is still fully lazy (no present pages yet).
387pub fn sys_mremap(
388    old_addr: u64,
389    old_size: u64,
390    new_size: u64,
391    flags: u64,
392) -> Result<u64, SyscallError> {
393    if old_size == 0 || new_size == 0 {
394        return Err(SyscallError::InvalidArgument);
395    }
396    if flags & !MREMAP_MAYMOVE != 0 {
397        return Err(SyscallError::InvalidArgument);
398    }
399
400    let task = current_task_clone().ok_or(SyscallError::Fault)?;
401    let addr_space = task.process.address_space_arc();
402    let vma = addr_space
403        .region_by_start(old_addr)
404        .ok_or(SyscallError::Fault)?;
405
406    let page_bytes = vma.page_size.bytes();
407    if old_addr % page_bytes != 0 {
408        return Err(SyscallError::InvalidArgument);
409    }
410
411    let old_len_aligned = if vma.page_size == crate::memory::address_space::VmaPageSize::Huge {
412        huge_page_align_up(old_size)
413    } else {
414        page_align_up(old_size)
415    };
416    let new_len_aligned = if vma.page_size == crate::memory::address_space::VmaPageSize::Huge {
417        huge_page_align_up(new_size)
418    } else {
419        page_align_up(new_size)
420    };
421    if old_len_aligned == 0 || new_len_aligned == 0 {
422        return Err(SyscallError::InvalidArgument);
423    }
424
425    let tracked_len = (vma.page_count as u64)
426        .checked_mul(page_bytes)
427        .ok_or(SyscallError::InvalidArgument)?;
428    if old_len_aligned != tracked_len {
429        return Err(SyscallError::InvalidArgument);
430    }
431
432    if new_len_aligned == old_len_aligned {
433        return Ok(old_addr);
434    }
435
436    if new_len_aligned < old_len_aligned {
437        let tail_addr = old_addr
438            .checked_add(new_len_aligned)
439            .ok_or(SyscallError::InvalidArgument)?;
440        let tail_len = old_len_aligned - new_len_aligned;
441        addr_space
442            .unmap_range(tail_addr, tail_len)
443            .map_err(|_| SyscallError::InvalidArgument)?;
444        return Ok(old_addr);
445    }
446
447    let grow_len = new_len_aligned - old_len_aligned;
448    let grow_start = old_addr
449        .checked_add(old_len_aligned)
450        .ok_or(SyscallError::InvalidArgument)?;
451
452    if !addr_space.has_mapping_in_range(grow_start, grow_len) {
453        let grow_pages = (grow_len / page_bytes) as usize;
454        addr_space
455            .reserve_region(
456                grow_start,
457                grow_pages,
458                vma.flags,
459                vma.vma_type,
460                vma.page_size,
461            )
462            .map_err(|_| SyscallError::OutOfMemory)?;
463        return Ok(old_addr);
464    }
465
466    if flags & MREMAP_MAYMOVE == 0 {
467        return Err(SyscallError::OutOfMemory);
468    }
469
470    let has_present_pages = addr_space
471        .any_mapped_in_range(old_addr, old_len_aligned, vma.page_size)
472        .map_err(|_| SyscallError::InvalidArgument)?;
473    if has_present_pages {
474        return Err(SyscallError::OutOfMemory);
475    }
476
477    let new_pages = (new_len_aligned / page_bytes) as usize;
478    let new_addr = addr_space
479        .find_free_vma_range(crate::kaslr::mmap_base(), new_pages, vma.page_size)
480        .ok_or(SyscallError::OutOfMemory)?;
481
482    addr_space
483        .unmap_range(old_addr, old_len_aligned)
484        .map_err(|_| SyscallError::InvalidArgument)?;
485    addr_space
486        .reserve_region(new_addr, new_pages, vma.flags, vma.vma_type, vma.page_size)
487        .map_err(|_| SyscallError::OutOfMemory)?;
488    Ok(new_addr)
489}
490
491/// SYS_MPROTECT (104): change permissions in an existing mapping range.
492pub fn sys_mprotect(addr: u64, len: u64, prot: u64) -> Result<u64, SyscallError> {
493    if len == 0 || addr == 0 || addr & 0xFFF != 0 {
494        return Err(SyscallError::InvalidArgument);
495    }
496    let prot_u32 = u32::try_from(prot).map_err(|_| SyscallError::InvalidArgument)?;
497    if prot_u32 & !(PROT_READ | PROT_WRITE | PROT_EXEC) != 0 {
498        return Err(SyscallError::InvalidArgument);
499    }
500
501    let len_aligned = page_align_up(len);
502    if len_aligned == 0 {
503        return Err(SyscallError::InvalidArgument);
504    }
505    if addr.saturating_add(len_aligned) > USER_SPACE_END {
506        return Err(SyscallError::InvalidArgument);
507    }
508
509    let task = current_task_clone().ok_or(SyscallError::Fault)?;
510    let addr_space = task.process.address_space_arc();
511    let flags = prot_to_vma_flags(prot_u32);
512
513    addr_space
514        .protect_range(addr, len_aligned, flags)
515        .map_err(|_| SyscallError::InvalidArgument)?;
516
517    Ok(0)
518}
519
520/// SYS_MEM_REGION_EXPORT (105): export a tracked region as a public handle.
521pub fn sys_mem_region_export(addr: u64) -> Result<u64, SyscallError> {
522    let task = current_task_clone().ok_or(SyscallError::Fault)?;
523    let address_space = task.process.address_space_arc();
524    let handle_cap = crate::capability::CapId::new();
525    let resource_id = crate::memory::memory_region_registry()
526        .export_region(&address_space, addr, handle_cap)
527        .map_err(|error| match error {
528            crate::memory::RegionCapError::InvalidRegion
529            | crate::memory::RegionCapError::IncompleteRegion
530            | crate::memory::RegionCapError::InvalidAddress => SyscallError::InvalidArgument,
531            crate::memory::RegionCapError::PermissionDenied => SyscallError::PermissionDenied,
532            crate::memory::RegionCapError::OutOfMemory => SyscallError::OutOfMemory,
533            crate::memory::RegionCapError::InconsistentState => SyscallError::IoError,
534            crate::memory::RegionCapError::NotFound => SyscallError::NotFound,
535        })?;
536
537    let cap = crate::capability::Capability {
538        id: handle_cap,
539        resource_type: crate::capability::ResourceType::MemoryRegion,
540        permissions: crate::capability::CapPermissions {
541            read: true,
542            write: true,
543            execute: true,
544            grant: true,
545            revoke: true,
546        },
547        resource: resource_id as usize,
548        badge: handle_cap.as_u64(),
549    };
550    let cap_id = unsafe { (&mut *task.process.capabilities.get()).insert(cap) };
551    Ok(cap_id.as_u64())
552}
553
554/// SYS_MEM_REGION_MAP (106): map an exported region into the caller.
555pub fn sys_mem_region_map(handle: u64, addr_hint: u64, out_ptr: u64) -> Result<u64, SyscallError> {
556    crate::silo::enforce_cap_for_current_task(handle)?;
557    if out_ptr == 0 {
558        return Err(SyscallError::Fault);
559    }
560
561    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
562    let caps = unsafe { &*task.process.capabilities.get() };
563    let cap = caps
564        .get(crate::capability::CapId::from_raw(handle))
565        .ok_or(SyscallError::BadHandle)?;
566    if cap.resource_type != crate::capability::ResourceType::MemoryRegion {
567        return Err(SyscallError::BadHandle);
568    }
569
570    let requested_flags = VmaFlags {
571        readable: cap.permissions.read,
572        writable: cap.permissions.write,
573        executable: cap.permissions.execute,
574        user_accessible: true,
575    };
576    let address_space = task.process.address_space_arc();
577    let (base, size) = crate::memory::memory_region_registry()
578        .map_region(
579            cap.resource as u64,
580            &address_space,
581            addr_hint,
582            requested_flags,
583        )
584        .map_err(|error| match error {
585            crate::memory::RegionCapError::NotFound => SyscallError::NotFound,
586            crate::memory::RegionCapError::InvalidRegion
587            | crate::memory::RegionCapError::IncompleteRegion
588            | crate::memory::RegionCapError::InvalidAddress => SyscallError::InvalidArgument,
589            crate::memory::RegionCapError::PermissionDenied => SyscallError::PermissionDenied,
590            crate::memory::RegionCapError::OutOfMemory => SyscallError::OutOfMemory,
591            crate::memory::RegionCapError::InconsistentState => SyscallError::IoError,
592        })?;
593
594    let user = crate::memory::UserSliceWrite::new(out_ptr, core::mem::size_of::<u64>())?;
595    user.copy_from(&base.to_ne_bytes());
596    Ok(size)
597}
598
599/// SYS_MEM_REGION_INFO (107): query metadata about an exported region.
600pub fn sys_mem_region_info(handle: u64, out_ptr: u64) -> Result<u64, SyscallError> {
601    crate::silo::enforce_cap_for_current_task(handle)?;
602    if out_ptr == 0 {
603        return Err(SyscallError::Fault);
604    }
605
606    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
607    let caps = unsafe { &*task.process.capabilities.get() };
608    let cap = caps
609        .get(crate::capability::CapId::from_raw(handle))
610        .ok_or(SyscallError::BadHandle)?;
611    if cap.resource_type != crate::capability::ResourceType::MemoryRegion {
612        return Err(SyscallError::BadHandle);
613    }
614
615    let info = crate::memory::memory_region_registry()
616        .info(cap.resource as u64)
617        .ok_or(SyscallError::NotFound)?;
618    let abi = MemoryRegionInfoAbi {
619        size: info.size,
620        page_size: info.page_size.bytes(),
621        flags: vma_flags_to_prot(info.flags),
622        _reserved: 0,
623    };
624    let user =
625        crate::memory::UserSliceWrite::new(out_ptr, core::mem::size_of::<MemoryRegionInfoAbi>())?;
626    let bytes = unsafe {
627        core::slice::from_raw_parts(
628            &abi as *const MemoryRegionInfoAbi as *const u8,
629            core::mem::size_of::<MemoryRegionInfoAbi>(),
630        )
631    };
632    user.copy_from(bytes);
633    Ok(0)
634}
635
636// ================================================================================
637// sys_brk
638// ================================================================================
639
640/// SYS_BRK (102): set or query the program break (top of heap).
641///
642/// Calling convention (matches Linux):
643///
644/// | `addr`          | Behaviour                                              |
645/// |-----------------|--------------------------------------------------------|
646/// | `0`             | Query : return current break unchanged.                |
647/// | `> current_brk` | Extend heap; new pages are zero-filled RW anonymous.   |
648/// | `< current_brk` | Shrink heap; backing pages are freed.                  |
649/// | `< BRK_BASE`    | Invalid : return current break unchanged (Linux compat).|
650///
651/// On any error (OOM, out-of-range) the **unchanged** break is returned rather
652/// than a negative code : this is the Linux `brk(2)` contract.
653pub fn sys_brk(addr: u64) -> Result<u64, SyscallError> {
654    let task = current_task_clone().ok_or(SyscallError::Fault)?;
655
656    //  Lazy initialisation ======================================================================================================================================================
657    // `task.process.brk == 0` means this task has never called brk.  The heap starts
658    // empty at BRK_BASE; no pages are mapped yet.
659    let current_brk = {
660        let raw = task.process.brk.load(Ordering::Relaxed);
661        if raw == 0 {
662            task.process.brk.store(BRK_BASE, Ordering::Relaxed);
663            BRK_BASE
664        } else {
665            raw
666        }
667    };
668
669    //  Query ==============================
670    if addr == 0 {
671        return Ok(current_brk);
672    }
673
674    //  Range checks ==========================================================================================================================================================================
675    // Reject attempts to move the break below the heap base or into kernel AS.
676    if addr < BRK_BASE || addr >= USER_SPACE_END {
677        return Ok(current_brk); // return unchanged (Linux behaviour)
678    }
679
680    //  Compute page-aligned extents ========================================================================================================================
681    // The heap occupies [BRK_BASE, page_align_up(current_brk)).
682    // Any bytes in the last partial page are already backed but not accounted
683    // for in the page-end calculation : they stay mapped on shrink.
684    let old_page_end = page_align_up(current_brk);
685    let new_page_end = page_align_up(addr);
686
687    if new_page_end > old_page_end {
688        //  Grow: map [old_page_end, new_page_end) ================================================================================
689        let n_pages = ((new_page_end - old_page_end) / 4096) as usize;
690        let vma_flags = VmaFlags {
691            readable: true,
692            writable: true,
693            executable: false,
694            user_accessible: true,
695        };
696        if task
697            .process
698            .address_space_arc()
699            .reserve_region(
700                old_page_end,
701                n_pages,
702                vma_flags,
703                VmaType::Anonymous,
704                crate::memory::address_space::VmaPageSize::Small,
705            )
706            .is_err()
707        {
708            // OOM : return the unchanged break (Linux behaviour).
709            return Ok(current_brk);
710        }
711        log::trace!(
712            "sys_brk: grow {:#x}..{:#x} ({} pages, lazy)",
713            old_page_end,
714            new_page_end,
715            n_pages,
716        );
717    } else if new_page_end < old_page_end {
718        //  Shrink: unmap [new_page_end, old_page_end) ============================================================
719        let len = old_page_end - new_page_end;
720        if task
721            .process
722            .address_space_arc()
723            .unmap_range(new_page_end, len)
724            .is_err()
725        {
726            return Ok(current_brk);
727        }
728        log::trace!(
729            "sys_brk: shrink {:#x}..{:#x} (-{} pages)",
730            new_page_end,
731            old_page_end,
732            len / 4096,
733        );
734    }
735    // If new_page_end == old_page_end, only the sub-page byte offset changed;
736    // no page-table operations are needed.
737
738    //  Commit the new exact-byte program break ==========================================================================================
739    task.process.brk.store(addr, Ordering::Relaxed);
740    Ok(addr)
741}