main 6af0a61

validation.go RawHistoryFolder

1package disk
2
3import (
4	"errors"
5	"fmt"
6	"strings"
7)
8
9// Errors specific to disk validation
10var (
11	// ErrInvalidDiskPath is returned when disk path validation fails
12	ErrInvalidDiskPath = errors.New("invalid disk path")
13
14	// ErrInvalidPartitionPath is returned when partition path validation fails
15	ErrInvalidPartitionPath = errors.New("invalid partition path")
16)
17
18// DeterminePartitionPath determines the correct partition device path based on disk type
19// For NVMe disks: /dev/nvme0n1 → /dev/nvme0n1p1, /dev/nvme0n1p2
20// For SATA/SSD: /dev/sda → /dev/sda1, /dev/sda2
21// For MMC: /dev/mmcblk0 → /dev/mmcblk0p1, /dev/mmcblk0p2
22func DeterminePartitionPath(diskPath string, partitionNumber int) (string, error) {
23	if diskPath == "" {
24		return "", fmt.Errorf("%w: disk path cannot be empty", ErrInvalidDiskPath)
25	}
26
27	if partitionNumber < 1 || partitionNumber > 128 {
28		return "", fmt.Errorf("%w: partition number must be between 1 and 128", ErrInvalidPartitionPath)
29	}
30
31	// Check if disk path is valid
32	if !strings.HasPrefix(diskPath, "/dev/") {
33		return "", fmt.Errorf("%w: disk path must start with /dev/", ErrInvalidDiskPath)
34	}
35
36	// NVMe disks use 'p' prefix: nvme0n1p1, nvme0n1p2
37	if strings.Contains(diskPath, "nvme") {
38		return fmt.Sprintf("%sp%d", diskPath, partitionNumber), nil
39	}
40
41	// MMC disks use 'p' prefix: mmcblk0p1, mmcblk0p2
42	if strings.Contains(diskPath, "mmcblk") {
43		return fmt.Sprintf("%sp%d", diskPath, partitionNumber), nil
44	}
45
46	// SATA/SSD disks use direct numbering: sda1, sda2, vda1, vda2
47	return fmt.Sprintf("%s%d", diskPath, partitionNumber), nil
48}
49
50// ValidateDiskPath validates a disk device path
51func ValidateDiskPath(diskPath string) error {
52	if diskPath == "" {
53		return fmt.Errorf("%w: path cannot be empty", ErrInvalidDiskPath)
54	}
55
56	if !strings.HasPrefix(diskPath, "/dev/") {
57		return fmt.Errorf("%w: path must start with /dev/", ErrInvalidDiskPath)
58	}
59
60	// Check for common disk patterns
61	validPrefixes := []string{
62		"/dev/sd",     // SATA/SCSI
63		"/dev/hd",     // IDE (legacy)
64		"/dev/vd",     // Virtio
65		"/dev/nvme",   // NVMe
66		"/dev/mmcblk", // MMC
67	}
68
69	valid := false
70	for _, prefix := range validPrefixes {
71		if strings.HasPrefix(diskPath, prefix) {
72			valid = true
73			break
74		}
75	}
76
77	if !valid {
78		return fmt.Errorf("%w: unrecognized disk device type", ErrInvalidDiskPath)
79	}
80
81	// Length check
82	if len(diskPath) > 64 {
83		return fmt.Errorf("%w: path too long (max 64 chars)", ErrInvalidDiskPath)
84	}
85
86	return nil
87}
88
89// ValidatePartitionPath validates a partition device path
90func ValidatePartitionPath(partitionPath string) error {
91	if partitionPath == "" {
92		return fmt.Errorf("%w: path cannot be empty", ErrInvalidPartitionPath)
93	}
94
95	if !strings.HasPrefix(partitionPath, "/dev/") {
96		return fmt.Errorf("%w: path must start with /dev/", ErrInvalidPartitionPath)
97	}
98
99	// Length check
100	if len(partitionPath) > 64 {
101		return fmt.Errorf("%w: path too long (max 64 chars)", ErrInvalidPartitionPath)
102	}
103
104	return nil
105}
106
107// IsDiskPath checks if the given path looks like a disk (not a partition)
108func IsDiskPath(path string) bool {
109	// Partitions usually end with a digit or 'p' + digit
110	// Disks typically don't (except nvme and mmcblk which end with a digit)
111	if strings.Contains(path, "nvme") {
112		// NVMe disk: /dev/nvme0n1
113		// NVMe partition: /dev/nvme0n1p1
114		return !strings.Contains(path, "p")
115	}
116
117	if strings.Contains(path, "mmcblk") {
118		// MMC disk: /dev/mmcblk0
119		// MMC partition: /dev/mmcblk0p1
120		return !strings.Contains(path, "p")
121	}
122
123	// For SATA/SCSI: /dev/sda (disk) vs /dev/sda1 (partition)
124	// Check if last character is a digit
125	if len(path) == 0 {
126		return false
127	}
128
129	lastChar := path[len(path)-1]
130	return lastChar < '0' || lastChar > '9'
131}
132
133// ExtractDiskFromPartition extracts the disk path from a partition path
134// Examples:
135//   - /dev/sda1 → /dev/sda
136//   - /dev/nvme0n1p1 → /dev/nvme0n1
137//   - /dev/mmcblk0p1 → /dev/mmcblk0
138func ExtractDiskFromPartition(partitionPath string) (string, error) {
139	if partitionPath == "" {
140		return "", fmt.Errorf("%w: path cannot be empty", ErrInvalidPartitionPath)
141	}
142
143	// NVMe partitions: must have 'p' separator (e.g., nvme0n1p1)
144	// If no 'p', it's already a disk (e.g., nvme0n1)
145	if strings.Contains(partitionPath, "nvme") {
146		if strings.Contains(partitionPath, "p") {
147			// Find last 'p' and remove everything after it
148			lastP := strings.LastIndex(partitionPath, "p")
149			return partitionPath[:lastP], nil
150		}
151		// No 'p' means it's already a disk path
152		return partitionPath, nil
153	}
154
155	// MMC partitions: must have 'p' separator (e.g., mmcblk0p1)
156	// If no 'p', it's already a disk (e.g., mmcblk0)
157	if strings.Contains(partitionPath, "mmcblk") {
158		if strings.Contains(partitionPath, "p") {
159			// Find last 'p' and remove everything after it
160			lastP := strings.LastIndex(partitionPath, "p")
161			return partitionPath[:lastP], nil
162		}
163		// No 'p' means it's already a disk path
164		return partitionPath, nil
165	}
166
167	// SATA/SCSI: remove trailing digits
168	// Find where digits start from the end
169	i := len(partitionPath) - 1
170	for i >= 0 && partitionPath[i] >= '0' && partitionPath[i] <= '9' {
171		i--
172	}
173
174	if i == len(partitionPath)-1 {
175		// No digits found - might already be a disk path
176		return partitionPath, nil
177	}
178
179	return partitionPath[:i+1], nil
180}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close