Viewing: clear.rs

// SPDX-License-Identifier: MIT

// Copyright (c) 2025 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use std::ffi::CString;

use super::error::{ChangelogError, Result as ChangelogResult};
use crate::error::cvt_rc_m;
use changelog_sys::llapi_changelog_clear;

/// Clear changelog records up to a specific record index for a given MDT and consumer.
///
/// This function wraps the `llapi_changelog_clear` function to safely clear processed
/// changelog records, allowing the Lustre filesystem to reclaim space used by old
/// changelog entries.
///
/// # Arguments
/// * `mdt_name` - The MDT device name (e.g., "lustre-MDT0000")
/// * `consumer_id` - The changelog consumer identifier (e.g., `"cl1"`)
/// * `end_record` - The highest record index to clear (inclusive)
///
/// # Returns
/// * `Ok(())` if the clear operation succeeded
/// * `Err(ChangelogError)` if the operation failed
///
/// # Examples
/// ```rust,no_run
/// use rustreapi::changelog::changelog_clear;
///
/// // Clear records up to index 1000 for consumer "cl1" on MDT0000
/// changelog_clear("lustre-MDT0000", "cl1", 1000)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Safety
/// This function is safe to call, but the underlying Lustre API requires:
/// - The MDT name must be valid and accessible
/// - The consumer ID must exist and be registered with the MDT
/// - The caller must have appropriate permissions to clear changelog records
pub fn changelog_clear(mdt_name: &str, consumer_id: &str, end_record: u64) -> ChangelogResult<()> {
    let mdt_cstring = CString::new(mdt_name).map_err(|e| ChangelogError::InvalidMdtName {
        mdt_name: mdt_name.to_string(),
        source: Box::new(e),
    })?;

    let consumer_cstring =
        CString::new(consumer_id).map_err(|e| ChangelogError::InvalidConsumerId {
            consumer_id: consumer_id.to_string(),
            source: Box::new(e),
        })?;

    // TODO check for overflow `(end_record as i64) < 0`
    let rc = unsafe {
        llapi_changelog_clear(
            mdt_cstring.as_ptr(),
            consumer_cstring.as_ptr(),
            end_record as i64,
        )
    };

    cvt_rc_m(
        rc,
        format!(
            "Failed to clear changelog records for MDT {} consumer {} up to record {}",
            mdt_name, consumer_id, end_record
        ),
    )
    .map_err(|e| ChangelogError::ClearFailed {
        mdt_name: mdt_name.to_string(),
        consumer_id: consumer_id.to_string(),
        end_record,
        source: Box::new(e),
    })?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_changelog_clear_invalid_names() {
        // Test with null bytes in strings
        assert!(changelog_clear("test\0mdt", "cl1", 100).is_err());
        assert!(changelog_clear("test-mdt", "cl\x01", 100).is_err());
    }

    #[test]
    fn test_changelog_clear_parameters() {
        // These will fail in practice but should not panic
        let result = changelog_clear("nonexistent-MDT0000", "cl999", 0);
        assert!(result.is_err());
    }
}