Array/Disk Access

Overview

Plugins can read disk information, share configurations, and array status to provide disk management features or integrate with Unraid’s storage system. This page covers how to access disk and array information from your plugin code.

Array Status

The $var Array

The $var array contains system state including array status:

<?
// Load system state
$var = parse_ini_file('/var/local/emhttp/var.ini');

// Or use global if already loaded
global $var;

// Array status
$arrayStatus = $var['fsState'];  // "Started", "Stopped", "Starting", "Stopping"
$mdState = $var['mdState'];      // "STARTED", "STOPPED", etc.

// Check if array is started
$arrayStarted = ($var['fsState'] === 'Started');

// Check if array is usable (not in transition)
$arrayReady = ($var['fsState'] === 'Started' && $var['mdState'] === 'STARTED');
?>

Common $var Array Properties

Property Description Values
fsState Array filesystem state Started, Stopped, Starting, Stopping
mdState MD device state STARTED, STOPPED, RESYNCING, etc.
mdNumDisks Number of array data disks Integer
mdNumDisabled Number of disabled disks Integer
mdNumMissing Number of missing disks Integer
mdResync Current resync position Block number (0 if not resyncing)
mdResyncSize Total resync size Block count
mdResyncAction Type of resync check, repair, sync

Parity Check Status

<?
$var = parse_ini_file('/var/local/emhttp/var.ini');

// Check if parity operation is in progress
$parityRunning = !empty($var['mdResync']) && $var['mdResync'] !== '0';

if ($parityRunning) {
    $current = $var['mdResync'];
    $total = $var['mdResyncSize'];
    $percent = round(($current / $total) * 100, 2);
    $action = $var['mdResyncAction'] ?? 'check';
    
    echo "Parity $action in progress: $percent%";
}
?>

Disk Information

Reading All Disk Data

<?
// Parse disks.ini with sections (true parameter)
$disks = parse_ini_file('/var/local/emhttp/disks.ini', true);

foreach ($disks as $diskName => $disk) {
    // $diskName: parity, parity2, disk1, disk2, cache, etc.
    echo "Disk: $diskName\n";
    echo "  Device: " . ($disk['device'] ?? 'unassigned') . "\n";
    echo "  ID: " . ($disk['id'] ?? 'unknown') . "\n";
    echo "  Size: " . ($disk['size'] ?? 0) . " KB\n";
    echo "  Status: " . ($disk['status'] ?? 'unknown') . "\n";
    echo "  Temp: " . ($disk['temp'] ?? '*') . "°C\n";
}
?>

Disk Properties Reference

Property Description Example
device Device name sda, nvme0n1
id Disk serial/ID WDC_WD40EFRX_12345
size Size in KB 3907029168
sizeSb Size in sectors 7814037168
status Disk status code DISK_OK, DISK_DSBL, DISK_NP
temp Temperature in Celsius 35, * (unknown)
fsType Filesystem type xfs, btrfs, reiserfs
fsSize Filesystem size Bytes
fsFree Free space Bytes
fsStatus Mount status Mounted, Unmounted
reads Read count Integer
writes Write count Integer
errors Error count Integer
numReads Total reads Integer
numWrites Total writes Integer

Disk Status Codes

Status Meaning
DISK_OK Disk is healthy
DISK_DSBL Disk is disabled
DISK_NP Disk not present (slot empty)
DISK_INVALID Disk is invalid
DISK_WRONG Wrong disk in slot
DISK_NEW New unformatted disk

Filtering Disk Types

<?
$disks = parse_ini_file('/var/local/emhttp/disks.ini', true);

// Get only array data disks
$arrayDisks = array_filter($disks, function($name) {
    return preg_match('/^disk\d+$/', $name);
}, ARRAY_FILTER_USE_KEY);

// Get parity disks
$parityDisks = array_filter($disks, function($name) {
    return strpos($name, 'parity') === 0;
}, ARRAY_FILTER_USE_KEY);

// Get cache pools
$cachePools = array_filter($disks, function($name) {
    return strpos($name, 'cache') === 0 || 
           (strpos($name, 'disk') !== 0 && strpos($name, 'parity') !== 0);
}, ARRAY_FILTER_USE_KEY);

// Get only assigned disks (with devices)
$assignedDisks = array_filter($disks, function($disk) {
    return !empty($disk['device']);
});
?>

Share Information

Reading All Shares

<?
$shares = parse_ini_file('/var/local/emhttp/shares.ini', true);

foreach ($shares as $shareName => $share) {
    echo "Share: $shareName\n";
    echo "  Free: " . formatBytes($share['free']) . "\n";
    echo "  Size: " . formatBytes($share['size']) . "\n";
    echo "  Cache: " . ($share['useCache'] ?? 'no') . "\n";
}

function formatBytes($bytes, $precision = 2) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    return round($bytes / pow(1024, $pow), $precision) . ' ' . $units[$pow];
}
?>

Share Properties

Property Description
name Share name
free Free space in bytes
size Total size in bytes
include Included disks (comma-separated)
exclude Excluded disks (comma-separated)
useCache Cache setting: yes, no, only, prefer
cow Copy-on-write setting
security Security level

User Shares vs Disk Shares

<?
// User share (combined view across all disks)
$userSharePath = "/mnt/user/ShareName";

// Disk-specific paths
$disk1Path = "/mnt/disk1/ShareName";
$disk2Path = "/mnt/disk2/ShareName";

// Cache disk path
$cachePath = "/mnt/cache/ShareName";

// Check if share exists on specific disk
function shareExistsOnDisk($shareName, $diskName) {
    $path = "/mnt/$diskName/$shareName";
    return is_dir($path);
}
?>

Checking Disk Space

Basic Disk Space

<?
function getDiskSpace($path) {
    if (!is_dir($path)) {
        return null;
    }
    
    $total = disk_total_space($path);
    $free = disk_free_space($path);
    $used = $total - $free;
    
    return [
        'total' => $total,
        'free' => $free,
        'used' => $used,
        'percent' => $total > 0 ? round(($used / $total) * 100, 1) : 0
    ];
}

// Check user share space
$appdata = getDiskSpace('/mnt/user/appdata');
if ($appdata) {
    echo "Appdata: {$appdata['percent']}% used ({$appdata['free']} free)";
}
?>

Array-Wide Space

<?
function getArraySpace() {
    $shares = parse_ini_file('/var/local/emhttp/shares.ini', true);
    
    $totalFree = 0;
    $totalSize = 0;
    
    foreach ($shares as $share) {
        $totalFree += $share['free'];
        $totalSize += $share['size'];
    }
    
    return [
        'total' => $totalSize,
        'free' => $totalFree,
        'used' => $totalSize - $totalFree,
        'percent' => $totalSize > 0 ? round((($totalSize - $totalFree) / $totalSize) * 100, 1) : 0
    ];
}
?>

SMART Data

Reading SMART Attributes

<?
function getSmartData($device) {
    // Ensure device path is safe
    if (!preg_match('/^\/dev\/[a-z]+$/', $device)) {
        return null;
    }
    
    $output = [];
    exec("smartctl -A " . escapeshellarg($device) . " 2>/dev/null", $output, $retval);
    
    if ($retval !== 0) {
        return null;
    }
    
    $attributes = [];
    foreach ($output as $line) {
        // Parse SMART attribute lines
        if (preg_match('/^\s*(\d+)\s+(\S+)\s+\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\S+\s+\S+\s+\S+\s+(.*)$/', $line, $matches)) {
            $attributes[$matches[2]] = [
                'id' => intval($matches[1]),
                'name' => $matches[2],
                'value' => intval($matches[3]),
                'worst' => intval($matches[4]),
                'thresh' => intval($matches[5]),
                'raw' => trim($matches[6])
            ];
        }
    }
    
    return $attributes;
}

// Key attributes to check
$smart = getSmartData('/dev/sda');
if ($smart) {
    $temp = $smart['Temperature_Celsius']['raw'] ?? 'Unknown';
    $reallocated = $smart['Reallocated_Sector_Ct']['raw'] ?? 0;
    $pending = $smart['Current_Pending_Sector']['raw'] ?? 0;
}
?>

SMART Health Check

<?
function isDiskHealthy($device) {
    exec("smartctl -H " . escapeshellarg($device) . " 2>/dev/null", $output, $retval);
    $outputStr = implode("\n", $output);
    return strpos($outputStr, 'PASSED') !== false;
}
?>

Spin-Up and Spin-Down

Check Disk State

<?
function isDiskSpunUp($device) {
    exec("hdparm -C " . escapeshellarg($device) . " 2>/dev/null", $output);
    $outputStr = implode("\n", $output);
    return strpos($outputStr, 'active') !== false;
}
?>

Be mindful of disk spin-up. Reading data from a spun-down disk will cause it to spin up, increasing wear and power usage.

Avoid Unnecessary Spin-Ups

<?
function readIfSpunUp($device, $path, $callback) {
    // Check if disk is already active
    exec("hdparm -C " . escapeshellarg($device) . " 2>/dev/null", $output);
    $isActive = strpos(implode("\n", $output), 'active') !== false;
    
    if ($isActive) {
        return $callback($path);
    }
    
    return null;  // Skip if disk is asleep
}
?>

Safe Practices

Require Array Started

<?
function requireArrayStarted() {
    $var = parse_ini_file('/var/local/emhttp/var.ini');
    
    if ($var['fsState'] !== 'Started') {
        http_response_code(503);
        die(json_encode([
            'error' => 'Array must be started for this operation'
        ]));
    }
}

// Use at start of scripts that need array access
requireArrayStarted();
?>

Validate Paths

<?
function isValidArrayPath($path) {
    // Resolve any symlinks and .. references
    $realPath = realpath($path);
    if ($realPath === false) {
        return false;
    }
    
    // Check against allowed prefixes
    $allowedPrefixes = [
        '/mnt/user/',
        '/mnt/disk',
        '/mnt/cache'
    ];
    
    foreach ($allowedPrefixes as $prefix) {
        if (strpos($realPath, $prefix) === 0) {
            return true;
        }
    }
    
    return false;
}

// Prevent path traversal attacks
$userPath = $_POST['path'] ?? '';
if (!isValidArrayPath($userPath)) {
    die("Invalid path");
}
?>

Handle Unmounted Disks

<?
function safeReadFromDisk($diskPath) {
    // Check if mounted
    if (!is_dir($diskPath)) {
        return ['error' => 'Disk not available'];
    }
    
    // Check if mount point (not empty directory)
    $mountOutput = shell_exec("mountpoint -q " . escapeshellarg($diskPath) . " && echo 'mounted'");
    if (trim($mountOutput) !== 'mounted') {
        return ['error' => 'Disk not mounted'];
    }
    
    // Safe to proceed
    return ['data' => scandir($diskPath)];
}
?>

Events for Array Changes

Use event scripts to respond to array state changes:

Event When Triggered Common Use
starting_array Array beginning to start Prepare resources
started_array Array fully started/mounted Start services, initialize data
stopping_array Array beginning to stop Stop services, save state
stopped_array Array fully stopped/unmounted Cleanup
unmounting_disk Before specific disk unmounts Release handles
disk_added New disk added Initialize disk

See Event Types Reference for details.