diff --git a/server/handlers/fstab/analyzer/analyzer.go b/server/handlers/fstab/analyzer/analyzer.go index 465ead5..77c9d98 100644 --- a/server/handlers/fstab/analyzer/analyzer.go +++ b/server/handlers/fstab/analyzer/analyzer.go @@ -14,17 +14,23 @@ type analyzerContext struct { func Analyze( document *shared.FstabDocument, ) []protocol.Diagnostic { - ctx := analyzerContext{ + ctx := &analyzerContext{ document: document, } - analyzeFieldAreFilled(&ctx) + analyzeFieldAreFilled(ctx) if len(ctx.diagnostics) > 0 { return ctx.diagnostics } - analyzeValuesAreValid(&ctx) + analyzeValuesAreValid(ctx) + + if len(ctx.diagnostics) > 0 { + return ctx.diagnostics + } + + analyzeFSCKField(ctx) return ctx.diagnostics } diff --git a/server/handlers/fstab/analyzer/fsck.go b/server/handlers/fstab/analyzer/fsck.go new file mode 100644 index 0000000..5150b23 --- /dev/null +++ b/server/handlers/fstab/analyzer/fsck.go @@ -0,0 +1,49 @@ +package analyzer + +import ( + "config-lsp/common" + "config-lsp/handlers/fstab/ast" + "config-lsp/handlers/fstab/fields" + "fmt" + "strings" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func analyzeFSCKField(ctx *analyzerContext) { + it := ctx.document.Config.Entries.Iterator() + + var rootEntry *ast.FstabEntry + + for it.Next() { + entry := it.Value().(*ast.FstabEntry) + + if entry.Fields != nil && entry.Fields.Fsck != nil && entry.Fields.Fsck.Value.Value == "1" { + fileSystem := strings.ToLower(entry.Fields.FilesystemType.Value.Value) + + if _, found := fields.FsckOneDisabledFilesystems[fileSystem]; found { + // From https://wiki.archlinux.org/title/Fstab + + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: entry.Fields.Fsck.ToLSPRange(), + Message: "If the root file system is btrfs or XFS, the fsck order should be set to 0 instead of 1. See fsck.btrfs(8) and fsck.xfs(8).", + Severity: &common.SeverityWarning, + }) + + continue + } + + if entry.Fields.Fsck.Value.Value == "1" { + if rootEntry != nil { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: entry.Fields.Fsck.ToLSPRange(), + Message: fmt.Sprintf("Only the root file system should have a fsck of 1. Other file systems should have a fsck of 2 or 0. The root file system is already using a fsck=1 on line %d", rootEntry.Fields.Start.Line), + Severity: &common.SeverityWarning, + }) + } else { + rootEntry = entry + } + } + } + } +} diff --git a/server/handlers/fstab/analyzer/fsck_test.go b/server/handlers/fstab/analyzer/fsck_test.go new file mode 100644 index 0000000..3e50ab0 --- /dev/null +++ b/server/handlers/fstab/analyzer/fsck_test.go @@ -0,0 +1,44 @@ +package analyzer + +import ( + testutils_test "config-lsp/handlers/fstab/test_utils" + "testing" +) + +func TestFSCKMultipleRoots( + t *testing.T, +) { + document := testutils_test.DocumentFromInput(t, ` +UUID=12345678-1234-1234-1234-123456789012 /boot ext4 defaults 0 1 +UUID=12345678-1234-1234-1234-123456789012 / btrfs defaults 0 1 +UUID=12345678-1234-1234-1234-123456789012 /home ext4 defaults 0 2 +`) + + ctx := &analyzerContext{ + document: document, + } + + analyzeFSCKField(ctx) + + if len(ctx.diagnostics) != 1 { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } +} + +func TestFSCKBtrfsUsingRoot( + t *testing.T, +) { + document := testutils_test.DocumentFromInput(t, ` +UUID=12345678-1234-1234-1234-123456789012 /boot btrfs defaults 0 1 +`) + + ctx := &analyzerContext{ + document: document, + } + + analyzeFSCKField(ctx) + + if len(ctx.diagnostics) != 1 { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } +} diff --git a/server/handlers/fstab/analyzer/values.go b/server/handlers/fstab/analyzer/values.go index bd7d8ba..62d9fac 100644 --- a/server/handlers/fstab/analyzer/values.go +++ b/server/handlers/fstab/analyzer/values.go @@ -33,8 +33,8 @@ func analyzeValuesAreValid( checkField(ctx, entry.Fields.Freq, fields.FreqField) } - if entry.Fields.Pass != nil { - checkField(ctx, entry.Fields.Pass, fields.PassField) + if entry.Fields.Fsck != nil { + checkField(ctx, entry.Fields.Fsck, fields.FsckField) } } } diff --git a/server/handlers/fstab/ast/fstab.go b/server/handlers/fstab/ast/fstab.go index 8286ba0..8355956 100644 --- a/server/handlers/fstab/ast/fstab.go +++ b/server/handlers/fstab/ast/fstab.go @@ -14,7 +14,7 @@ const ( FstabFieldFileSystemType FstabFieldName = "filesystemtype" FstabFieldOptions FstabFieldName = "options" FstabFieldFreq FstabFieldName = "freq" - FstabFieldPass FstabFieldName = "pass" + FstabFieldFsck FstabFieldName = "fsck" ) type FstabField struct { @@ -29,7 +29,7 @@ type FstabFields struct { FilesystemType *FstabField Options *FstabField Freq *FstabField - Pass *FstabField + Fsck *FstabField } type FstabEntry struct { diff --git a/server/handlers/fstab/ast/fstab_fields.go b/server/handlers/fstab/ast/fstab_fields.go index 77679b5..19dff2a 100644 --- a/server/handlers/fstab/ast/fstab_fields.go +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -20,7 +20,7 @@ import ( // LABEL=test ext4 defaults 0 0 func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName { // No fields defined, empty line - if e.Fields.Spec == nil && e.Fields.MountPoint == nil && e.Fields.FilesystemType == nil && e.Fields.Options == nil && e.Fields.Freq == nil && e.Fields.Pass == nil { + if e.Fields.Spec == nil && e.Fields.MountPoint == nil && e.Fields.FilesystemType == nil && e.Fields.Options == nil && e.Fields.Freq == nil && e.Fields.Fsck == nil { return FstabFieldSpec } @@ -41,8 +41,8 @@ func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName if e.Fields.Freq != nil && e.Fields.Freq.ContainsPosition(position) { return FstabFieldFreq } - if e.Fields.Pass != nil && e.Fields.Pass.ContainsPosition(position) { - return FstabFieldPass + if e.Fields.Fsck != nil && e.Fields.Fsck.ContainsPosition(position) { + return FstabFieldFsck } // Okay let's try to fetch the field by assuming the user is typing from left to right normally @@ -63,8 +63,8 @@ func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName return FstabFieldFreq } - if e.Fields.Freq != nil && e.Fields.Freq.IsPositionAfterEnd(position) && (e.Fields.Pass == nil || e.Fields.Pass.IsPositionBeforeEnd(position)) { - return FstabFieldPass + if e.Fields.Freq != nil && e.Fields.Freq.IsPositionAfterEnd(position) && (e.Fields.Fsck == nil || e.Fields.Fsck.IsPositionBeforeEnd(position)) { + return FstabFieldFsck } // Okay shit no idea, let's just give whatever is missing @@ -89,7 +89,7 @@ func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName return FstabFieldFreq } - return FstabFieldPass + return FstabFieldFsck } // LABEL=test /mnt/test btrfs subvol=backup,fat=32 [0] [0] @@ -122,7 +122,7 @@ func (e FstabEntry) getDefinedFieldsAmount() uint8 { if e.Fields.Freq != nil { definedAmount++ } - if e.Fields.Pass != nil { + if e.Fields.Fsck != nil { definedAmount++ } diff --git a/server/handlers/fstab/ast/listener.go b/server/handlers/fstab/ast/listener.go index a9f16b4..4cc4625 100644 --- a/server/handlers/fstab/ast/listener.go +++ b/server/handlers/fstab/ast/listener.go @@ -128,7 +128,7 @@ func (s *fstabParserListener) EnterPass(ctx *parser.PassContext) { text := ctx.GetText() value := commonparser.ParseRawString(text, commonparser.FullFeatures) - s.fstabContext.currentEntry.Fields.Pass = &FstabField{ + s.fstabContext.currentEntry.Fields.Fsck = &FstabField{ LocationRange: location, Value: value, } diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go index f24a6fa..3d17a87 100644 --- a/server/handlers/fstab/ast/parser.go +++ b/server/handlers/fstab/ast/parser.go @@ -161,7 +161,7 @@ func (c *FstabConfig) parseStatement( // FilesystemType: filesystemType, // Options: options, // Freq: freq, -// Pass: pass, +// Fsck: pass, // }, // } // diff --git a/server/handlers/fstab/ast/parser_test.go b/server/handlers/fstab/ast/parser_test.go index ac37136..9d5abdb 100644 --- a/server/handlers/fstab/ast/parser_test.go +++ b/server/handlers/fstab/ast/parser_test.go @@ -27,7 +27,7 @@ LABEL=example /mnt/example fat32 defaults 0 2 rawFirstEntry, _ := c.Entries.Get(uint32(0)) firstEntry := rawFirstEntry.(*FstabEntry) - if !(firstEntry.Fields.Spec.Value.Value == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Value == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Value == "ext4" && firstEntry.Fields.Options.Value.Value == "defaults" && firstEntry.Fields.Freq.Value.Value == "0" && firstEntry.Fields.Pass.Value.Value == "0") { + if !(firstEntry.Fields.Spec.Value.Value == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Value == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Value == "ext4" && firstEntry.Fields.Options.Value.Value == "defaults" && firstEntry.Fields.Freq.Value.Value == "0" && firstEntry.Fields.Fsck.Value.Value == "0") { t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults 0 0, got %v", firstEntry) } @@ -71,8 +71,8 @@ LABEL=example /mnt/example fat32 defaults 0 2 t.Errorf("Expected freq end to be 0:36, got %v", firstEntry.Fields.Freq.LocationRange.End) } - if !(firstEntry.Fields.Pass.LocationRange.Start.Line == 0 && firstEntry.Fields.Pass.LocationRange.Start.Character == 37) { - t.Errorf("Expected pass start to be 0:37, got %v", firstEntry.Fields.Pass.LocationRange.Start) + if !(firstEntry.Fields.Fsck.LocationRange.Start.Line == 0 && firstEntry.Fields.Fsck.LocationRange.Start.Character == 37) { + t.Errorf("Expected pass start to be 0:37, got %v", firstEntry.Fields.Fsck.LocationRange.Start) } field := firstEntry.GetFieldAtPosition(common.IndexPosition(0)) diff --git a/server/handlers/fstab/fields/fsck.go b/server/handlers/fstab/fields/fsck.go new file mode 100644 index 0000000..31cc7c6 --- /dev/null +++ b/server/handlers/fstab/fields/fsck.go @@ -0,0 +1,40 @@ +package fields + +import docvalues "config-lsp/doc-values" + +var FsckField = docvalues.EnumValue{ + EnforceValues: false, + Values: []docvalues.EnumString{ + docvalues.CreateEnumStringWithDoc( + "0", + "Defaults to zero (don’t check the filesystem) if not present.", + ), + docvalues.CreateEnumStringWithDoc( + "1", + "The root filesystem should be specified with a fs_passno of 1.", + ), + docvalues.CreateEnumStringWithDoc( + "2", + "Other filesystems [than the root filesystem] should have a fs_passno of 2.", + ), + }, +} + +var FsckFieldWhenDisabledFilesystems = docvalues.EnumValue{ + EnforceValues: false, + Values: []docvalues.EnumString{ + docvalues.CreateEnumStringWithDoc( + "0", + "Defaults to zero (don’t check the filesystem) if not present.", + ), + docvalues.CreateEnumStringWithDoc( + "2", + "Other filesystems [than the root filesystem] should have a fs_passno of 2.", + ), + }, +} + +var FsckOneDisabledFilesystems = map[string]struct{}{ + "btrfs": {}, + "xfs": {}, +} diff --git a/server/handlers/fstab/fields/pass.go b/server/handlers/fstab/fields/pass.go deleted file mode 100644 index 5306a11..0000000 --- a/server/handlers/fstab/fields/pass.go +++ /dev/null @@ -1,26 +0,0 @@ -package fields - -import docvalues "config-lsp/doc-values" - -var PassField = docvalues.OrValue{ - Values: []docvalues.DeprecatedValue{ - docvalues.EnumValue{ - EnforceValues: false, - Values: []docvalues.EnumString{ - docvalues.CreateEnumStringWithDoc( - "0", - "Defaults to zero (don’t check the filesystem) if not present.", - ), - docvalues.CreateEnumStringWithDoc( - "1", - "The root filesystem should be specified with a fs_passno of 1.", - ), - docvalues.CreateEnumStringWithDoc( - "2", - "Other filesystems [than the root filesystem] should have a fs_passno of 2.", - ), - }, - }, - docvalues.NumberValue{}, - }, -} diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index c2d00f5..40c62bf 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -4,7 +4,9 @@ import ( "config-lsp/common" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/fields" + "config-lsp/utils" "fmt" + "strings" "github.com/tliron/glsp/protocol_3_16" ) @@ -84,13 +86,21 @@ func GetCompletion( value, cursor, ), nil - case ast.FstabFieldPass: - value, cursor := getFieldSafely(entry.Fields.Pass, cursor) + case ast.FstabFieldFsck: + value, cursor := getFieldSafely(entry.Fields.Fsck, cursor) - return fields.PassField.DeprecatedFetchCompletions( - value, - cursor, - ), nil + if entry.Fields.FilesystemType != nil && + utils.KeyExists(fields.FsckOneDisabledFilesystems, strings.ToLower(entry.Fields.FilesystemType.Value.Value)) { + return fields.FsckFieldWhenDisabledFilesystems.DeprecatedFetchCompletions( + value, + cursor, + ), nil + } else { + return fields.FsckField.DeprecatedFetchCompletions( + value, + cursor, + ), nil + } } return nil, nil diff --git a/server/handlers/fstab/handlers/hover.go b/server/handlers/fstab/handlers/hover.go index 0216a62..f825ffe 100644 --- a/server/handlers/fstab/handlers/hover.go +++ b/server/handlers/fstab/handlers/hover.go @@ -42,7 +42,7 @@ func GetHoverInfo( return &hover, nil case ast.FstabFieldFreq: return &FreqHoverField, nil - case ast.FstabFieldPass: + case ast.FstabFieldFsck: return &PassHoverField, nil }