Skip to main content

winx_code_agent/utils/
mmap.rs

1// File lengths are validated against explicit u64 ceilings before these casts;
2// mmap APIs then require platform-sized usize lengths.
3#![allow(unsafe_code, clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4
5use memmap2::{Mmap, MmapOptions};
6use rayon::prelude::*;
7use std::cmp::min;
8use std::fs::File;
9use std::io::{BufReader, Read, Seek, SeekFrom};
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use tracing::{debug, info, trace, warn};
13
14use crate::errors::{Result, WinxError};
15
16/// Maximum file size for direct reading (10MB)
17pub const DIRECT_READ_THRESHOLD: u64 = 10_000_000;
18
19/// Maximum file size for single memory mapping (1GB)
20pub const MAX_MMAP_SIZE: u64 = 1_000_000_000;
21
22/// Maximum file size for segmented memory mapping (4GB)
23pub const MAX_SEGMENTED_MMAP_SIZE: u64 = 4_000_000_000;
24
25/// Segment size for large file memory mapping (256MB)
26pub const SEGMENT_SIZE: u64 = 256_000_000;
27
28const DIRECT_READ_CHUNK_SIZE: usize = 1_048_576;
29const STREAMING_CHUNK_SIZE: usize = 4_194_304;
30
31/// Upper bound on the up-front `Vec::with_capacity` hint derived from (untrusted)
32/// file metadata. A sparse or racing file can report a size far larger than the
33/// bytes it will actually yield; pre-allocating that size lets a caller force a
34/// multi-GB allocation before a single byte is read, and under `panic = "abort"`
35/// an allocation failure takes down the whole server. We cap the hint and let
36/// the `Vec` grow as bytes actually arrive (this mirrors `read_streaming`).
37const MAX_PREALLOC_BYTES: usize = 64 * 1024 * 1024;
38
39/// Read file contents optimally based on file size
40///
41/// This function chooses the optimal reading strategy based on file size:
42/// - Small files: Direct read with standard File I/O
43/// - Medium files: Memory-mapped reading for performance
44/// - Large files: Segmented memory-mapped reading
45/// - Extreme files: Windowed access with streaming
46///
47/// # Arguments
48///
49/// * `path` - Path to the file to read
50/// * `max_file_size` - Maximum allowed file size
51///
52/// # Returns
53///
54/// A vector containing the file contents
55///
56/// # Errors
57///
58/// Returns an error if the file cannot be read or exceeds the size limit
59pub fn read_file_optimized(path: &Path, max_file_size: u64) -> Result<Vec<u8>> {
60    // Get file metadata
61    let file = File::open(path).map_err(|e| WinxError::FileAccessError {
62        path: path.to_path_buf(),
63        message: format!("Error opening file: {e}"),
64    })?;
65
66    let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
67        path: path.to_path_buf(),
68        message: format!("Failed to get file metadata: {e}"),
69    })?;
70
71    // Check file size
72    let file_size = metadata.len();
73    if file_size > max_file_size {
74        return Err(WinxError::FileTooLarge {
75            path: path.to_path_buf(),
76            size: file_size,
77            max_size: max_file_size,
78        });
79    }
80
81    // Choose reading strategy based on file size
82    if file_size < DIRECT_READ_THRESHOLD {
83        debug!("Using direct read for file: {}", path.display());
84        read_direct(&file, file_size, path)
85    } else if file_size < MAX_MMAP_SIZE {
86        debug!("Using memory-mapped read for file: {}", path.display());
87        read_mmap(&file, path)
88    } else if file_size < MAX_SEGMENTED_MMAP_SIZE {
89        debug!("Using segmented memory-mapped read for file: {}", path.display());
90        read_segmented_mmap(&file, file_size, path)
91    } else {
92        debug!("Using streaming read for extremely large file: {}", path.display());
93        read_streaming(&file, file_size, path)
94    }
95}
96
97/// Read file contents directly using standard I/O
98///
99/// This is efficient for small files.
100///
101/// # Arguments
102///
103/// * `file` - Open file handle
104/// * `file_size` - Size of the file
105/// * `path` - Path to the file (for error reporting)
106///
107/// # Returns
108///
109/// A vector containing the file contents
110///
111/// # Errors
112///
113/// Returns an error if the file cannot be read
114fn read_direct(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
115    // For very small files (< 1MB), use an optimized approach
116    if file_size < 1_000_000 {
117        // Pre-allocate an exact-sized buffer
118        let mut buffer = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
119
120        // Create a mutable file handle and seek to the beginning
121        let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
122            path: path.to_path_buf(),
123            message: format!("Error cloning file handle: {e}"),
124        })?;
125
126        file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
127            path: path.to_path_buf(),
128            message: format!("Error seeking to start of file: {e}"),
129        })?;
130
131        // Use a BufReader with an appropriate buffer size (4K-64K)
132        let mut reader = BufReader::with_capacity(min(file_size as usize, 64 * 1024), file_handle);
133
134        // Read directly to the end
135        reader.read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
136            path: path.to_path_buf(),
137            message: format!("Error reading file: {e}"),
138        })?;
139
140        return Ok(buffer);
141    }
142
143    // For larger files, use a chunked reading approach with progress tracking.
144    // Cap the up-front hint to MAX_PREALLOC_BYTES like the small-file branch above
145    // (line 114) and read_streaming: file_size comes from untrusted metadata and a
146    // sparse/racing file can report a size far bigger than it yields, turning this
147    // into a forced multi-GB allocation (OOM = abort under panic = "abort").
148    let mut buffer = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
149
150    // Create a mutable file handle and seek to the beginning
151    let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
152        path: path.to_path_buf(),
153        message: format!("Error cloning file handle: {e}"),
154    })?;
155
156    file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
157        path: path.to_path_buf(),
158        message: format!("Error seeking to start of file: {e}"),
159    })?;
160
161    let mut reader = BufReader::with_capacity(262_144, file_handle); // 256KB buffer
162
163    let mut chunk = vec![0; DIRECT_READ_CHUNK_SIZE];
164    let mut bytes_read = 0;
165
166    loop {
167        match reader.read(&mut chunk) {
168            Ok(0) => break, // EOF
169            Ok(n) => {
170                buffer.extend_from_slice(&chunk[..n]);
171                bytes_read += n as u64;
172
173                // Log progress for larger files
174                if file_size > 5_000_000 && bytes_read % 5_000_000 < DIRECT_READ_CHUNK_SIZE as u64 {
175                    trace!(
176                        "Read progress for {}: {}MB/{}MB ({}%)",
177                        path.display(),
178                        bytes_read / 1_000_000,
179                        file_size / 1_000_000,
180                        bytes_read * 100 / file_size
181                    );
182                }
183            }
184            Err(e) => {
185                return Err(WinxError::FileAccessError {
186                    path: path.to_path_buf(),
187                    message: format!("Error reading file chunk: {e}"),
188                });
189            }
190        }
191    }
192
193    Ok(buffer)
194}
195
196/// Read file contents using memory mapping
197///
198/// This is efficient for larger files as it avoids loading the entire
199/// file into memory at once.
200///
201/// # Arguments
202///
203/// * `file` - Open file handle
204/// * `path` - Path to the file (for error reporting)
205///
206/// # Returns
207///
208/// A vector containing the file contents
209///
210/// # Errors
211///
212/// Returns an error if the file cannot be mapped
213fn read_mmap(file: &File, path: &Path) -> Result<Vec<u8>> {
214    // Check for empty file to avoid mmap error
215    if file.metadata().map_or(0, |m| m.len()) == 0 {
216        return Ok(Vec::new());
217    }
218
219    // SAFETY: Memory mapping a file is inherently unsafe because:
220    // - The file could be modified by another process during access
221    // - The file could be truncated, causing access to invalid memory
222    // We mitigate these risks by:
223    // - Using the mapped data immediately and converting to Vec<u8>
224    // - Not holding the mmap across async boundaries
225    // - File size was verified before this call
226    let mmap = unsafe { MmapOptions::new().map(file) }.map_err(|e| WinxError::FileAccessError {
227        path: path.to_path_buf(),
228        message: format!("Failed to memory-map file: {e}"),
229    })?;
230
231    // Copy the mapped bytes into an owned Vec. Copying out of an mmap is
232    // bandwidth-bound (a memcpy), so the old Rayon "parallel" path bought
233    // nothing: it allocated the file twice — a zero-filled `result` plus a
234    // Vec of per-chunk `to_vec()`s — only to memcpy between them. `to_vec()`
235    // allocates exactly `mmap.len()` (the real mapped size, not an untrusted
236    // metadata hint) and copies once.
237    Ok(mmap.to_vec())
238}
239
240/// Read large file with segmented memory mapping
241///
242/// This function reads a large file using multiple memory mapped segments,
243/// which allows handling files larger than the maximum mapping size.
244///
245/// # Arguments
246///
247/// * `file` - Open file handle
248/// * `file_size` - Size of the file
249/// * `path` - Path to the file (for error reporting)
250///
251/// # Returns
252///
253/// A vector containing the file contents
254///
255/// # Errors
256///
257/// Returns an error if the file cannot be read or mapped
258fn read_segmented_mmap(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
259    // Calculate number of segments needed
260    let segment_count = file_size.div_ceil(SEGMENT_SIZE);
261    debug!(
262        "Reading file {} in {} segments of {}MB each",
263        path.display(),
264        segment_count,
265        SEGMENT_SIZE / 1_000_000
266    );
267
268    // Pre-allocate result vector (capped — see MAX_PREALLOC_BYTES)
269    let mut result = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
270
271    // Process each segment
272    for i in 0..segment_count {
273        let segment_start = i * SEGMENT_SIZE;
274        let segment_size = min(SEGMENT_SIZE, file_size - segment_start);
275
276        info!(
277            "Processing segment {}/{} of file {} ({:.1}%)",
278            i + 1,
279            segment_count,
280            path.display(),
281            (segment_start as f64 / file_size as f64) * 100.0
282        );
283
284        // SAFETY: the shared file handle stays valid for the whole loop; mmap
285        // uses the explicit `.offset()`, not the fd seek position, so segments
286        // never conflict and no per-segment open()/seek() is needed. Bounds come
287        // from the verified file size; data is copied to Vec, not held.
288        let segment_mmap = unsafe {
289            MmapOptions::new().offset(segment_start).len(segment_size as usize).map(file)
290        }
291        .map_err(|e| WinxError::FileAccessError {
292            path: path.to_path_buf(),
293            message: format!("Failed to memory-map file segment {i}: {e}"),
294        })?;
295
296        // Append segment data to result
297        result.extend_from_slice(&segment_mmap);
298    }
299
300    Ok(result)
301}
302
303/// Read extremely large file with streaming
304///
305/// This function reads an extremely large file using a streaming approach,
306/// which minimizes memory usage by processing the file in small chunks.
307///
308/// # Arguments
309///
310/// * `file` - Open file handle
311/// * `file_size` - Size of the file
312/// * `path` - Path to the file (for error reporting)
313///
314/// # Returns
315///
316/// A vector containing the file contents
317///
318/// # Errors
319///
320/// Returns an error if the file cannot be read
321fn read_streaming(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
322    warn!(
323        "Reading extremely large file ({}GB) with streaming approach: {}",
324        file_size / 1_000_000_000,
325        path.display()
326    );
327
328    // For extreme files, pre-allocate a bounded vector and grow as bytes arrive.
329    // Cap at MAX_PREALLOC_BYTES (not file_size, not 1GB): file_size is untrusted
330    // metadata, and a multi-GB up-front Vec is exactly the OOM-via-abort vector the
331    // cap exists to close. The extra reallocs on a genuinely huge file are rare and
332    // cheap next to crashing the server.
333    let initial_capacity = min(file_size as usize, MAX_PREALLOC_BYTES);
334    let mut buffer = Vec::with_capacity(initial_capacity);
335
336    let mut reader = BufReader::with_capacity(STREAMING_CHUNK_SIZE, file);
337    let mut chunk = vec![0; STREAMING_CHUNK_SIZE];
338    let mut bytes_read = 0;
339
340    loop {
341        match reader.read(&mut chunk) {
342            Ok(0) => break, // EOF
343            Ok(n) => {
344                buffer.extend_from_slice(&chunk[..n]);
345                bytes_read += n as u64;
346
347                // Log progress every 100MB
348                if bytes_read % 100_000_000 < STREAMING_CHUNK_SIZE as u64 {
349                    info!(
350                        "Read progress for large file {}: {:.2}GB/{:.2}GB ({:.1}%)",
351                        path.display(),
352                        bytes_read as f64 / 1_000_000_000.0,
353                        file_size as f64 / 1_000_000_000.0,
354                        bytes_read as f64 * 100.0 / file_size as f64
355                    );
356                }
357            }
358            Err(e) => {
359                return Err(WinxError::FileAccessError {
360                    path: path.to_path_buf(),
361                    message: format!("Error reading file chunk at position {bytes_read}: {e}"),
362                });
363            }
364        }
365    }
366
367    Ok(buffer)
368}
369
370/// Read a specific segment of a file
371///
372/// This function reads a specific segment of a file using memory mapping
373/// or direct I/O, depending on the segment size.
374///
375/// # Arguments
376///
377/// * `path` - Path to the file
378/// * `offset` - Starting offset in bytes
379/// * `length` - Length of segment to read in bytes
380/// * `max_file_size` - Maximum allowed file size
381///
382/// # Returns
383///
384/// A vector containing the file segment contents
385///
386/// # Errors
387///
388/// Returns an error if the file cannot be read or exceeds the size limit
389pub fn read_file_segment(
390    path: &Path,
391    offset: u64,
392    length: u64,
393    max_file_size: u64,
394) -> Result<Vec<u8>> {
395    // Get file metadata
396    let file = File::open(path).map_err(|e| WinxError::FileAccessError {
397        path: path.to_path_buf(),
398        message: format!("Error opening file: {e}"),
399    })?;
400
401    let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
402        path: path.to_path_buf(),
403        message: format!("Failed to get file metadata: {e}"),
404    })?;
405
406    // Check file size
407    let file_size = metadata.len();
408    if file_size > max_file_size {
409        return Err(WinxError::FileTooLarge {
410            path: path.to_path_buf(),
411            size: file_size,
412            max_size: max_file_size,
413        });
414    }
415
416    // Validate offset and length
417    if offset >= file_size {
418        return Err(WinxError::FileAccessError {
419            path: path.to_path_buf(),
420            message: format!("Offset {offset} exceeds file size {file_size}"),
421        });
422    }
423
424    // Adjust length if needed to stay within file bounds
425    let length = min(length, file_size - offset);
426
427    // Choose reading strategy based on segment size
428    if length < DIRECT_READ_THRESHOLD {
429        debug!("Using direct read for file segment: {}", path.display());
430        read_segment_direct(&file, offset, length, path)
431    } else {
432        debug!("Using memory-mapped read for file segment: {}", path.display());
433        read_segment_mmap(&file, offset, length, path)
434    }
435}
436
437/// Read a file segment directly using standard I/O
438///
439/// # Arguments
440///
441/// * `file` - Open file handle
442/// * `offset` - Starting offset in bytes
443/// * `length` - Length of segment to read in bytes
444/// * `path` - Path to the file (for error reporting)
445///
446/// # Returns
447///
448/// A vector containing the file segment contents
449///
450/// # Errors
451///
452/// Returns an error if the file segment cannot be read
453fn read_segment_direct(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
454    // Create a new file object that can be seeked
455    let mut seekable_file = file.try_clone().map_err(|e| WinxError::FileAccessError {
456        path: path.to_path_buf(),
457        message: format!("Failed to clone file handle: {e}"),
458    })?;
459
460    // Seek to the specified offset
461    seekable_file.seek(SeekFrom::Start(offset)).map_err(|e| WinxError::FileAccessError {
462        path: path.to_path_buf(),
463        message: format!("Failed to seek to offset {offset}: {e}"),
464    })?;
465
466    // Read the specified length
467    let mut buffer = Vec::with_capacity(length as usize);
468    let reader = BufReader::with_capacity(min(length as usize, 64 * 1024), seekable_file);
469
470    // Use take to limit the read to the specified length
471    reader.take(length).read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
472        path: path.to_path_buf(),
473        message: format!("Error reading file segment: {e}"),
474    })?;
475
476    Ok(buffer)
477}
478
479/// Read a file segment using memory mapping
480///
481/// # Arguments
482///
483/// * `file` - Open file handle
484/// * `offset` - Starting offset in bytes
485/// * `length` - Length of segment to read in bytes
486/// * `path` - Path to the file (for error reporting)
487///
488/// # Returns
489///
490/// A vector containing the file segment contents
491///
492/// # Errors
493///
494/// Returns an error if the file segment cannot be mapped
495fn read_segment_mmap(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
496    // SAFETY: Memory mapping is safe here because:
497    // - Offset and length were validated against file size by caller
498    // - Data is immediately copied to Vec<u8>, not held
499    // - File handle remains valid for duration of the map operation
500    let segment_mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(file) }
501        .map_err(|e| WinxError::FileAccessError {
502            path: path.to_path_buf(),
503            message: format!("Failed to memory-map file segment: {e}"),
504        })?;
505
506    // Copy segment data to result
507    Ok(segment_mmap.to_vec())
508}
509
510/// Read a text file as a string using the optimal reading strategy
511///
512/// This function reads a file as text, using the most efficient strategy
513/// based on the file size.
514///
515/// # Arguments
516///
517/// * `path` - Path to the file to read
518/// * `max_file_size` - Maximum allowed file size
519///
520/// # Returns
521///
522/// A string containing the file contents
523///
524/// # Errors
525///
526/// Returns an error if the file cannot be read or exceeds the size limit
527pub fn read_file_to_string(path: &Path, max_file_size: u64) -> Result<String> {
528    let bytes = read_file_optimized(path, max_file_size)?;
529
530    String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
531        path: path.to_path_buf(),
532        message: format!("Failed to decode file as UTF-8: {e}"),
533    })
534}
535
536/// Read a text file in a parallel, line-by-line fashion
537///
538/// This processes lines in parallel using Rayon for faster processing
539/// of large text files.
540///
541/// # Arguments
542///
543/// * `path` - Path to the file to read
544/// * `max_file_size` - Maximum allowed file size
545/// * `line_processor` - Function to process each line
546///
547/// # Returns
548///
549/// Result indicating success or failure
550///
551/// # Errors
552///
553/// Returns an error if the file cannot be read or exceeds the size limit
554pub fn process_text_file_parallel<F>(
555    path: &Path,
556    max_file_size: u64,
557    line_processor: F,
558) -> Result<()>
559where
560    F: Fn(&str) + Sync,
561{
562    let content = read_file_to_string(path, max_file_size)?;
563
564    // For larger files, use parallel processing
565    if content.len() > 1_000_000 {
566        // 1MB
567        content.par_lines().for_each(|line| {
568            line_processor(line);
569        });
570    } else {
571        // For smaller files, process sequentially
572        content.lines().for_each(|line| {
573            line_processor(line);
574        });
575    }
576
577    Ok(())
578}
579
580/// Read a text file segment as a string
581///
582/// # Arguments
583///
584/// * `path` - Path to the file to read
585/// * `offset` - Starting offset in bytes
586/// * `length` - Length of segment to read in bytes
587/// * `max_file_size` - Maximum allowed file size
588///
589/// # Returns
590///
591/// A string containing the file segment contents
592///
593/// # Errors
594///
595/// Returns an error if the file segment cannot be read
596pub fn read_file_segment_to_string(
597    path: &Path,
598    offset: u64,
599    length: u64,
600    max_file_size: u64,
601) -> Result<String> {
602    let bytes = read_file_segment(path, offset, length, max_file_size)?;
603
604    String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
605        path: path.to_path_buf(),
606        message: format!("Failed to decode file segment as UTF-8: {e}"),
607    })
608}
609
610/// `ShareableMap` provides a thread-safe memory-mapped file access
611///
612/// This is useful for providing read-only access to multiple threads
613/// without copying the data, especially for large files.
614#[derive(Clone)]
615pub struct ShareableMap {
616    /// The memory-mapped file data
617    data: Arc<Mmap>,
618    /// The path to the mapped file
619    path: PathBuf,
620}
621
622impl ShareableMap {
623    /// Create a new `ShareableMap` from a file
624    ///
625    /// # Arguments
626    ///
627    /// * `path` - Path to the file to map
628    ///
629    /// # Returns
630    ///
631    /// A Result containing the `ShareableMap` or an error
632    ///
633    /// # Errors
634    ///
635    /// Returns an error if the file cannot be mapped
636    pub fn new(path: &Path) -> Result<Self> {
637        let file = File::open(path).map_err(|e| WinxError::FileAccessError {
638            path: path.to_path_buf(),
639            message: format!("Error opening file: {e}"),
640        })?;
641
642        // Check for empty file
643        if file
644            .metadata()
645            .map_err(|e| WinxError::FileAccessError {
646                path: path.to_path_buf(),
647                message: format!("Failed to get metadata: {e}"),
648            })?
649            .len()
650            == 0
651        {
652            return Err(WinxError::FileAccessError {
653                path: path.to_path_buf(),
654                message: "Cannot memory map empty file".to_string(),
655            });
656        }
657
658        // SAFETY: ShareableMap wraps the Mmap in Arc for thread-safe sharing.
659        // The mapped data is read-only and the Arc ensures the Mmap outlives
660        // all references. Users must be aware the underlying file should not
661        // be modified while ShareableMap is in use.
662        let mmap =
663            unsafe { MmapOptions::new().map(&file) }.map_err(|e| WinxError::FileAccessError {
664                path: path.to_path_buf(),
665                message: format!("Failed to memory-map file: {e}"),
666            })?;
667
668        Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
669    }
670
671    /// Create a new `ShareableMap` for a segment of a file
672    ///
673    /// # Arguments
674    ///
675    /// * `path` - Path to the file to map
676    /// * `offset` - Starting offset in bytes
677    /// * `length` - Length of segment to map in bytes
678    ///
679    /// # Returns
680    ///
681    /// A Result containing the `ShareableMap` or an error
682    ///
683    /// # Errors
684    ///
685    /// Returns an error if the file segment cannot be mapped
686    pub fn new_segment(path: &Path, offset: u64, length: u64) -> Result<Self> {
687        if length == 0 {
688            return Err(WinxError::FileAccessError {
689                path: path.to_path_buf(),
690                message: "Cannot memory map segment of length 0".to_string(),
691            });
692        }
693
694        let file = File::open(path).map_err(|e| WinxError::FileAccessError {
695            path: path.to_path_buf(),
696            message: format!("Error opening file: {e}"),
697        })?;
698
699        // SAFETY: Same as ShareableMap::new, plus:
700        // - Caller is responsible for ensuring offset+length is within file bounds
701        // - The segment mapping is wrapped in Arc for safe sharing
702        let mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(&file) }
703            .map_err(|e| WinxError::FileAccessError {
704                path: path.to_path_buf(),
705                message: format!("Failed to memory-map file segment: {e}"),
706            })?;
707
708        Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
709    }
710
711    /// Get the data as a byte slice
712    pub fn as_slice(&self) -> &[u8] {
713        &self.data
714    }
715
716    /// Get the path to the mapped file
717    pub fn path(&self) -> &Path {
718        &self.path
719    }
720
721    /// Get the size of the mapped data
722    pub fn len(&self) -> usize {
723        self.data.len()
724    }
725
726    /// Check if the mapped data is empty
727    pub fn is_empty(&self) -> bool {
728        self.data.is_empty()
729    }
730}
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735    use std::io::Write;
736    use tempfile::NamedTempFile;
737
738    fn create_test_file(size: usize) -> Result<(NamedTempFile, Vec<u8>)> {
739        let mut file = NamedTempFile::new()?;
740        let mut data = Vec::with_capacity(size);
741
742        // Fill with pattern data (more realistic than zeros)
743        for i in 0..size {
744            data.push((i % 256) as u8);
745        }
746
747        file.write_all(&data)?;
748        file.flush()?;
749
750        Ok((file, data))
751    }
752
753    #[test]
754    fn test_direct_read_small_file() -> Result<()> {
755        let size = 10 * 1024; // 10KB
756        let (file, expected_data) = create_test_file(size)?;
757
758        let result = read_direct(file.as_file(), size as u64, file.path())?;
759        assert_eq!(result, expected_data);
760        Ok(())
761    }
762
763    #[test]
764    fn test_mmap_read() -> Result<()> {
765        let size = 1024 * 1024; // 1MB
766        let (file, expected_data) = create_test_file(size)?;
767
768        let result = read_mmap(file.as_file(), file.path())?;
769        assert_eq!(result, expected_data);
770        Ok(())
771    }
772
773    #[test]
774    fn test_file_segment_read() -> Result<()> {
775        let size = 1024 * 1024; // 1MB
776        let (file, data) = create_test_file(size)?;
777
778        // Read a segment from the middle
779        let offset = 100 * 1024; // 100KB
780        let length = 200 * 1024; // 200KB
781        let expected_segment = &data[offset as usize..(offset + length) as usize];
782
783        let result = read_segment_direct(file.as_file(), offset, length, file.path())?;
784        assert_eq!(result, expected_segment);
785
786        let result = read_segment_mmap(file.as_file(), offset, length, file.path())?;
787        assert_eq!(result, expected_segment);
788        Ok(())
789    }
790
791    #[test]
792    fn test_shareable_map() -> Result<()> {
793        let size = 100 * 1024; // 100KB
794        let (file, data) = create_test_file(size)?;
795
796        let map = ShareableMap::new(file.path())?;
797        assert_eq!(map.as_slice(), &data);
798
799        // Test segment
800        let offset = 10 * 1024; // 10KB
801        let length = 20 * 1024; // 20KB
802        let segment_map = ShareableMap::new_segment(file.path(), offset, length)?;
803        assert_eq!(segment_map.as_slice(), &data[offset as usize..(offset + length) as usize]);
804        Ok(())
805    }
806
807    #[test]
808    fn test_parallel_processing() -> Result<()> {
809        // Create a test file with lines
810        let mut file = NamedTempFile::new()?;
811        let mut lines = Vec::new();
812
813        for i in 0..1000 {
814            let line = format!("Line {i}\n");
815            file.write_all(line.as_bytes())?;
816            lines.push(format!("Line {i}"));
817        }
818        file.flush()?;
819
820        // Test parallel processing
821        let processed_lines = std::sync::Mutex::new(Vec::new());
822
823        process_text_file_parallel(file.path(), 1_000_000, |line| {
824            if let Ok(mut lines) = processed_lines.lock() {
825                lines.push(line.to_string());
826            }
827        })?;
828
829        // Verify results (order may differ due to parallel processing)
830        let result =
831            processed_lines.lock().map_err(|error| WinxError::ResourceAllocationError {
832                message: format!("Failed to lock processed lines: {error}"),
833            })?;
834        assert_eq!(result.len(), lines.len());
835
836        // Check that all lines are present
837        for line in &lines {
838            assert!(result.contains(line));
839        }
840        Ok(())
841    }
842}