- Added tests for transient SQLite errors in emergency_handler_test.go. - Introduced validation tests for provider errors in notification_provider_handler_validation_test.go. - Implemented helper tests for settings handling in settings_handler_helpers_test.go. - Expanded backup_handler_test.go to include SQLite database setup and validation. - Improved system_permissions_handler_test.go with additional path repair tests. - Updated backup_service_test.go to ensure proper database handling and error checks during backup operations. - Refined import_handler_test.go with additional session validation tests.
176 lines
4.5 KiB
Go
176 lines
4.5 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
type PermissionCheck struct {
|
|
Path string `json:"path"`
|
|
Required string `json:"required"`
|
|
Exists bool `json:"exists"`
|
|
Writable bool `json:"writable"`
|
|
OwnerUID int `json:"owner_uid"`
|
|
OwnerGID int `json:"owner_gid"`
|
|
Mode string `json:"mode"`
|
|
Error string `json:"error,omitempty"`
|
|
ErrorCode string `json:"error_code,omitempty"`
|
|
}
|
|
|
|
func CheckPathPermissions(path, required string) PermissionCheck {
|
|
result := PermissionCheck{
|
|
Path: path,
|
|
Required: required,
|
|
}
|
|
|
|
if strings.ContainsRune(path, '\x00') {
|
|
result.Writable = false
|
|
result.Error = "invalid path"
|
|
result.ErrorCode = "permissions_invalid_path"
|
|
return result
|
|
}
|
|
|
|
cleanPath := filepath.Clean(path)
|
|
|
|
linkInfo, linkErr := os.Lstat(cleanPath)
|
|
if linkErr != nil {
|
|
result.Writable = false
|
|
result.Error = linkErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(linkErr)
|
|
return result
|
|
}
|
|
if linkInfo.Mode()&os.ModeSymlink != 0 {
|
|
result.Writable = false
|
|
result.Error = "symlink paths are not supported"
|
|
result.ErrorCode = "permissions_unsupported_type"
|
|
return result
|
|
}
|
|
|
|
info, err := os.Stat(cleanPath)
|
|
if err != nil {
|
|
result.Writable = false
|
|
result.Error = err.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(err)
|
|
return result
|
|
}
|
|
|
|
result.Exists = true
|
|
|
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
|
result.OwnerUID = int(stat.Uid)
|
|
result.OwnerGID = int(stat.Gid)
|
|
}
|
|
result.Mode = fmt.Sprintf("%04o", info.Mode().Perm())
|
|
|
|
if !info.IsDir() && !info.Mode().IsRegular() {
|
|
result.Writable = false
|
|
result.Error = "unsupported file type"
|
|
result.ErrorCode = "permissions_unsupported_type"
|
|
return result
|
|
}
|
|
|
|
if strings.Contains(required, "w") {
|
|
if info.IsDir() {
|
|
probeFile, probeErr := os.CreateTemp(cleanPath, "permcheck-*")
|
|
if probeErr != nil {
|
|
result.Writable = false
|
|
result.Error = probeErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(probeErr)
|
|
return result
|
|
}
|
|
if closeErr := probeFile.Close(); closeErr != nil {
|
|
result.Writable = false
|
|
result.Error = closeErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(closeErr)
|
|
return result
|
|
}
|
|
if removeErr := os.Remove(probeFile.Name()); removeErr != nil {
|
|
result.Writable = false
|
|
result.Error = removeErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(removeErr)
|
|
return result
|
|
}
|
|
result.Writable = true
|
|
return result
|
|
}
|
|
|
|
file, openErr := os.OpenFile(cleanPath, os.O_WRONLY, 0) // #nosec G304 -- cleanPath is normalized, existence-checked, non-symlink, and regular-file validated above.
|
|
if openErr != nil {
|
|
result.Writable = false
|
|
result.Error = openErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(openErr)
|
|
return result
|
|
}
|
|
if closeErr := file.Close(); closeErr != nil {
|
|
result.Writable = false
|
|
result.Error = closeErr.Error()
|
|
result.ErrorCode = MapDiagnosticErrorCode(closeErr)
|
|
return result
|
|
}
|
|
result.Writable = true
|
|
return result
|
|
}
|
|
|
|
result.Writable = false
|
|
return result
|
|
}
|
|
|
|
func MapDiagnosticErrorCode(err error) string {
|
|
switch {
|
|
case err == nil:
|
|
return ""
|
|
case os.IsNotExist(err):
|
|
return "permissions_missing_path"
|
|
case errors.Is(err, syscall.EROFS):
|
|
return "permissions_readonly"
|
|
case errors.Is(err, syscall.EACCES) || os.IsPermission(err):
|
|
return "permissions_write_denied"
|
|
default:
|
|
return "permissions_write_failed"
|
|
}
|
|
}
|
|
|
|
func MapSaveErrorCode(err error) (string, bool) {
|
|
switch {
|
|
case err == nil:
|
|
return "", false
|
|
case IsSQLiteReadOnlyError(err):
|
|
return "permissions_db_readonly", true
|
|
case IsSQLiteLockedError(err):
|
|
return "permissions_db_locked", true
|
|
case errors.Is(err, syscall.EROFS):
|
|
return "permissions_readonly", true
|
|
case errors.Is(err, syscall.EACCES) || os.IsPermission(err):
|
|
return "permissions_write_denied", true
|
|
case strings.Contains(strings.ToLower(err.Error()), "permission denied"):
|
|
return "permissions_write_denied", true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func IsSQLiteReadOnlyError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
msg := strings.ToLower(err.Error())
|
|
return strings.Contains(msg, "readonly") ||
|
|
strings.Contains(msg, "read-only") ||
|
|
strings.Contains(msg, "attempt to write a readonly database") ||
|
|
strings.Contains(msg, "sqlite_readonly")
|
|
}
|
|
|
|
func IsSQLiteLockedError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
msg := strings.ToLower(err.Error())
|
|
return strings.Contains(msg, "database is locked") ||
|
|
strings.Contains(msg, "sqlite_busy") ||
|
|
strings.Contains(msg, "database locked")
|
|
}
|