{"record":{"id":"fb9ee2c32e18fce2","repo":"getgrav/grav","slug":"cannot-read-file","errorCode":null,"errorMessage":"Cannot read file","messagePattern":"Cannot read file","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"system/src/Grav/Common/Helpers/LogViewer.php","lineNumber":81,"sourceCode":"        $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));\n\n        fseek($f, -1, SEEK_END);\n        if (fread($f, 1) !== \"\\n\") {\n            --$lines;\n        }\n\n        // Start reading\n        $output = '';\n        // While we would like more\n        while (ftell($f) > 0 && $lines >= 0) {\n            // Figure out how far back we should jump\n            $seek = min(ftell($f), $buffer);\n            // Do the jump (backwards, relative to where we are)\n            fseek($f, -$seek, SEEK_CUR);\n            // Read a chunk and prepend it to our output\n            $chunk = fread($f, $seek);\n            if ($chunk === false) {\n                throw new \\RuntimeException('Cannot read file');\n            }\n            $output = $chunk . $output;\n            // Jump back to where we started reading\n            fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);\n            // Decrease our line counter\n            $lines -= substr_count($chunk, \"\\n\");\n        }\n        // While we have too many lines\n        // (Because of buffer size we might have read too many)\n        while ($lines++ < 0) {\n            // Find first newline and remove all text before that\n            $output = substr($output, strpos($output, \"\\n\") + 1);\n        }\n        // Close file and return\n        fclose($f);\n\n        return trim($output);\n    }","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Helpers/LogViewer.php#L63-L99","documentation":"LogViewer reads a log file backwards in buffer-sized chunks to extract the last N lines; if fread() ever returns false mid-loop (line 81) it throws 'Cannot read file'. fread only fails like this when the open handle became unreadable or the read was invalid — typically the file was deleted/replaced under the handle, permissions changed, or the handle went stale.","triggerScenarios":"Calling LogViewer's tail extraction while the log file is rotated, truncated, or deleted under the open handle; reading a log whose permissions changed after fopen; handles on flaky network/container storage.","commonSituations":"Admin log viewer hit at the exact moment logrotate (or Grav's own log rotation) truncates the file; multiple FPM workers where one rotates while another reads; containerized environments with ephemeral log mounts.","solutions":["Catch the RuntimeException in your controller and re-open/retry the read once — rotation windows are transient and usually clear immediately.","Right before reading, verify the file is still readable (is_readable) and its size/inode are sane; skip the read if the file vanished.","In the UI, degrade gracefully: show 'log temporarily unavailable' rather than a 500 when this fires."],"exampleFix":"// before\n$content = LogViewer::extractTail($filepath, $lines);\n\n// after\ntry {\n    $content = LogViewer::extractTail($filepath, $lines);\n} catch (\\RuntimeException $e) {\n    $content = ''; // file rotated/unreadable mid-read; retry on next request\n}","handlingStrategy":"retry","validationCode":"if (!is_readable($filepath) || filesize($filepath) === 0) {\n    return ''; // nothing to tail right now\n}","typeGuard":null,"tryCatchPattern":"try {\n    $content = LogViewer::extractTail($filepath, $lines);\n} catch (\\RuntimeException $e) {\n    clearstatcache();\n    if (is_readable($filepath)) {\n        $content = LogViewer::extractTail($filepath, $lines); // one retry after rotation\n    } else {\n        $content = '';\n    }\n}","preventionTips":["Treat log reads as best-effort: always wrap viewer calls in try/catch.","Read logs soon after checking stat; do not hold handles across requests.","In UI, degrade to 'log temporarily unavailable' instead of erroring out."],"tags":["logging","files","log-viewer","race-condition","permissions"],"backgroundTag":"file-read-failure","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}