diff --git a/initrd/directory.go b/initrd/directory.go index 8f301a020c..53d6666bb1 100644 --- a/initrd/directory.go +++ b/initrd/directory.go @@ -65,6 +65,12 @@ func (initrd *directory) Build(ctx context.Context) (string, error) { } } + if _, err := os.Stat(initrd.opts.output); err == nil { + if err = os.Remove(initrd.opts.output); err != nil { + return "", err + } + } + f, err := os.OpenFile(initrd.opts.output, os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return "", fmt.Errorf("could not open initramfs file: %w", err) @@ -75,6 +81,11 @@ func (initrd *directory) Build(ctx context.Context) (string, error) { writer := cpio.NewWriter(f) defer writer.Close() + ignoringItems, err := getKraftignoreItems(ctx, initrd) + if err != nil { + return "", err + } + if err := filepath.WalkDir(initrd.path, func(path string, d fs.DirEntry, err error) error { if err != nil { return fmt.Errorf("received error before parsing path: %w", err) @@ -86,6 +97,21 @@ func (initrd *directory) Build(ctx context.Context) (string, error) { } internal = "." + filepath.ToSlash(internal) + if len(ignoringItems) > 0 && path != initrd.path { + switch isExistInKraftignoreFile(internal, d, ignoringItems) { + case SkipDir: + log.G(ctx). + WithField("directory", internal). + Trace("ignoring from archiving") + return filepath.SkipDir + case Exist: + log.G(ctx). + WithField("file", internal). + Trace("ignoring from archiving") + return nil + } + } + info, err := d.Info() if err != nil { return fmt.Errorf("could not get directory entry info: %w", err) diff --git a/initrd/kraftignore.go b/initrd/kraftignore.go new file mode 100644 index 0000000000..1949d9d0b8 --- /dev/null +++ b/initrd/kraftignore.go @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2022, Unikraft GmbH and The KraftKit Authors. +// Licensed under the BSD-3-Clause License (the "License"). +// You may not use this file except in compliance with the License. +package initrd + +import ( + "bufio" + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "kraftkit.sh/log" +) + +// kraftignore filename +const KraftignoreFileName = ".kraftignore" + +type IgnoringFileType string + +const ( + Exist = IgnoringFileType("Exist") + NotExist = IgnoringFileType("NotExist") + SkipDir = IgnoringFileType("SkipDir") +) + +func getKraftignoreItems(ctx context.Context, initrd *directory) ([]string, error) { + if initrd.opts.kraftignorePath == "" { + cwd, err := os.Getwd() + if err != nil { + return []string{}, err + } + initrd.opts.kraftignorePath = filepath.Join(cwd, KraftignoreFileName) + } + + if _, err := os.Stat(initrd.opts.kraftignorePath); errors.Is(err, os.ErrNotExist) { + return []string{}, nil + } else if err != nil { + return []string{}, err + } + + kraftignoreFile, err := os.Open(initrd.opts.kraftignorePath) + if err != nil { + return []string{}, err + } + + defer func() { + kraftIgnoreErr := kraftignoreFile.Close() + if kraftIgnoreErr != nil { + if err != nil { + err = fmt.Errorf("%v\n%v", err, kraftIgnoreErr) + } else { + err = kraftIgnoreErr + } + } + }() + + kraftignoreScanner := bufio.NewScanner(kraftignoreFile) + kraftignoreScanner.Split(bufio.ScanLines) + var kraftignoreFileLines, ignoringItems []string + + for kraftignoreScanner.Scan() { + kraftignoreFileLines = append(kraftignoreFileLines, kraftignoreScanner.Text()) + } + + for lineNum, line := range kraftignoreFileLines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + items := strings.Split(line, " ") + for _, item := range items { + item = strings.Trim(item, `"`) + item = strings.Trim(item, `'`) + item = strings.TrimSpace(item) + if item == "" || item == "#" { + continue + } + + if hasGlobPatterns(item) { + log.G(ctx). + WithField("file", initrd.opts.kraftignorePath). + Warn("contains a glob pattern ", item, + " at line ", lineNum, + " which is not supported by Kraftkit") + continue + } + + if strings.HasPrefix(item, "..") { + item = strings.TrimPrefix(item, "..") + } + + item = filepath.Join(item) + if !strings.HasPrefix(item, "./") { + if !strings.HasPrefix(item, "/") { + item = "/" + item + } + item = "." + item + } + + item = strings.TrimSuffix(item, string(filepath.Separator)) + ignoringItems = append(ignoringItems, item) + } + } + + return ignoringItems, err +} + +func isExistInKraftignoreFile(internal string, pathInfo fs.DirEntry, kraftignoreItems []string) IgnoringFileType { + for _, ignoringItem := range kraftignoreItems { + if internal == ignoringItem { + if pathInfo.IsDir() { + return SkipDir + } + return Exist + } + } + return NotExist +} + +func hasGlobPatterns(item string) bool { + if strings.Contains(item, "*") || strings.Contains(item, "?") || + strings.Contains(item, "!") || strings.Contains(item, "[") || + strings.Contains(item, "{") { + return true + } + return false +} diff --git a/initrd/options.go b/initrd/options.go index 65cbf89dff..c27cb5d508 100644 --- a/initrd/options.go +++ b/initrd/options.go @@ -4,10 +4,15 @@ // You may not use this file except in compliance with the License. package initrd +import ( + "path/filepath" +) + type InitrdOptions struct { - output string - cacheDir string - arch string + output string + cacheDir string + arch string + kraftignorePath string } type InitrdOption func(*InitrdOptions) error @@ -41,3 +46,11 @@ func WithArchitecture(arch string) InitrdOption { return nil } } + +// WithKraftignorePath sets the path for .kraftignore. +func WithKraftignorePath(dir string) InitrdOption { + return func(opts *InitrdOptions) error { + opts.kraftignorePath = filepath.Join(dir, KraftignoreFileName) + return nil + } +}