2016-03-16 19:59:34 +00:00
|
|
|
package mtree
|
|
|
|
|
2016-03-18 20:31:12 +00:00
|
|
|
import (
|
2016-07-23 03:44:16 +00:00
|
|
|
"encoding/json"
|
2016-03-18 20:31:12 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
)
|
|
|
|
|
2016-04-05 20:20:04 +00:00
|
|
|
// Result of a Check
|
2016-03-16 19:59:34 +00:00
|
|
|
type Result struct {
|
2016-07-20 22:09:27 +00:00
|
|
|
// list of any failures in the Check
|
|
|
|
Failures []Failure `json:"failures"`
|
2016-03-16 19:59:34 +00:00
|
|
|
}
|
|
|
|
|
2016-07-23 03:44:16 +00:00
|
|
|
// FailureType represents a type of Failure encountered when checking for
|
|
|
|
// discrepancies between a manifest and a directory state.
|
|
|
|
type FailureType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// None means no discrepancy (unused).
|
|
|
|
None FailureType = "none"
|
|
|
|
|
|
|
|
// Missing represents a discrepancy where an object is present in the
|
|
|
|
// manifest but is not present in the directory state being checked.
|
|
|
|
Missing FailureType = "missing"
|
|
|
|
|
|
|
|
// Modified represents a discrepancy where one or more of the keywords
|
|
|
|
// present in the manifest do not match the keywords generated for the same
|
|
|
|
// object in the directory state being checked.
|
|
|
|
Modified FailureType = "modified"
|
2016-07-22 17:36:04 +00:00
|
|
|
|
|
|
|
// Present represents a discrepancy where an object is not present in the
|
|
|
|
// manifest but is present in the directory state being checked.
|
|
|
|
Present FailureType = "present"
|
2016-07-23 03:44:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Failure represents a discrepancy between a manifest and the state it is
|
|
|
|
// being compared against. The discrepancy may take the form of a missing,
|
|
|
|
// erroneous or otherwise modified object.
|
|
|
|
type Failure interface {
|
|
|
|
// String returns a "pretty" formatting for a Failure. It's based on the
|
|
|
|
// BSD mtree(8) format, so that we are compatible.
|
|
|
|
String() string
|
|
|
|
|
|
|
|
// MarshalJSON ensures that we fulfil the JSON Marshaler interface.
|
|
|
|
MarshalJSON() ([]byte, error)
|
|
|
|
|
|
|
|
// Path returns the path (relative to the root of the tree) that this
|
|
|
|
// discrepancy refers to.
|
|
|
|
Path() string
|
|
|
|
|
|
|
|
// Type returns the type of failure that occurred.
|
|
|
|
Type() FailureType
|
|
|
|
}
|
|
|
|
|
|
|
|
// An object modified from the manifest state.
|
|
|
|
type modified struct {
|
|
|
|
path string
|
|
|
|
keyword string
|
|
|
|
expected string
|
|
|
|
got string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m modified) String() string {
|
|
|
|
return fmt.Sprintf("%q: keyword %q: expected %s; got %s", m.path, m.keyword, m.expected, m.got)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m modified) MarshalJSON() ([]byte, error) {
|
|
|
|
// Because of Go's reflection policies, we have to make an anonymous struct
|
|
|
|
// with the fields exported.
|
|
|
|
return json.Marshal(struct {
|
|
|
|
Type FailureType `json:"type"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
Keyword string `json:"keyword"`
|
|
|
|
Expected string `json:"expected"`
|
|
|
|
Got string `json:"got"`
|
|
|
|
}{
|
|
|
|
Type: m.Type(),
|
|
|
|
Path: m.path,
|
|
|
|
Keyword: m.keyword,
|
|
|
|
Expected: m.expected,
|
|
|
|
Got: m.got,
|
|
|
|
})
|
2016-04-05 15:44:55 +00:00
|
|
|
}
|
|
|
|
|
2016-07-23 03:44:16 +00:00
|
|
|
func (m modified) Path() string {
|
|
|
|
return m.path
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m modified) Type() FailureType {
|
|
|
|
return Modified
|
|
|
|
}
|
|
|
|
|
|
|
|
// An object that is listed in the manifest but is not present in the state.
|
|
|
|
type missing struct {
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m missing) String() string {
|
|
|
|
return fmt.Sprintf("%q: expected object missing", m.path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m missing) MarshalJSON() ([]byte, error) {
|
|
|
|
// Because of Go's reflection policies, we have to make an anonymous struct
|
|
|
|
// with the fields exported.
|
|
|
|
return json.Marshal(struct {
|
|
|
|
Type FailureType `json:"type"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
}{
|
|
|
|
Type: m.Type(),
|
|
|
|
Path: m.path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m missing) Path() string {
|
|
|
|
return m.path
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m missing) Type() FailureType {
|
|
|
|
return Missing
|
2016-04-05 15:44:55 +00:00
|
|
|
}
|
2016-03-18 20:31:12 +00:00
|
|
|
|
2016-07-22 17:36:04 +00:00
|
|
|
// An object that is not listed in the manifest but is present in the state.
|
|
|
|
type present struct {
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p present) String() string {
|
|
|
|
return fmt.Sprintf("%q: unexpected object present", p.path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p present) MarshalJSON() ([]byte, error) {
|
|
|
|
// Because of Go's reflection policies, we have to make an anonymous struct
|
|
|
|
// with the fields exported.
|
|
|
|
return json.Marshal(struct {
|
|
|
|
Type FailureType `json:"type"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
}{
|
|
|
|
Type: p.Type(),
|
|
|
|
Path: p.path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p present) Path() string {
|
|
|
|
return p.path
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p present) Type() FailureType {
|
|
|
|
return Missing
|
|
|
|
}
|
|
|
|
|
2016-04-12 20:49:52 +00:00
|
|
|
// Check a root directory path against the DirectoryHierarchy, regarding only
|
|
|
|
// the available keywords from the list and each entry in the hierarchy.
|
|
|
|
// If keywords is nil, the check all present in the DirectoryHierarchy
|
|
|
|
func Check(root string, dh *DirectoryHierarchy, keywords []string) (*Result, error) {
|
2016-03-18 20:31:12 +00:00
|
|
|
creator := dhCreator{DH: dh}
|
|
|
|
curDir, err := os.Getwd()
|
|
|
|
if err == nil {
|
|
|
|
defer os.Chdir(curDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chdir(root); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sort.Sort(byPos(creator.DH.Entries))
|
|
|
|
|
2016-04-05 15:44:55 +00:00
|
|
|
var result Result
|
2016-07-22 17:36:04 +00:00
|
|
|
seen := map[string]struct{}{
|
|
|
|
".": struct{}{},
|
|
|
|
}
|
2016-07-15 12:58:26 +00:00
|
|
|
for i, e := range creator.DH.Entries {
|
2016-03-18 20:31:12 +00:00
|
|
|
switch e.Type {
|
|
|
|
case SpecialType:
|
|
|
|
if e.Name == "/set" {
|
2016-07-15 12:58:26 +00:00
|
|
|
creator.curSet = &creator.DH.Entries[i]
|
2016-03-18 20:31:12 +00:00
|
|
|
} else if e.Name == "/unset" {
|
|
|
|
creator.curSet = nil
|
|
|
|
}
|
2016-03-23 20:58:16 +00:00
|
|
|
case RelativeType, FullType:
|
2016-07-14 20:33:23 +00:00
|
|
|
info, err := os.Lstat(e.Path())
|
2016-03-18 20:31:12 +00:00
|
|
|
if err != nil {
|
2016-07-23 03:44:16 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
result.Failures = append(result.Failures, missing{
|
|
|
|
path: e.Path(),
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
2016-03-18 20:31:12 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-03-23 20:58:16 +00:00
|
|
|
|
2016-07-22 17:36:04 +00:00
|
|
|
seen[e.Path()] = struct{}{}
|
|
|
|
|
2016-03-23 20:58:16 +00:00
|
|
|
var kvs KeyVals
|
|
|
|
if creator.curSet != nil {
|
|
|
|
kvs = MergeSet(creator.curSet.Keywords, e.Keywords)
|
|
|
|
} else {
|
|
|
|
kvs = NewKeyVals(e.Keywords)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, kv := range kvs {
|
|
|
|
keywordFunc, ok := KeywordFuncs[kv.Keyword()]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), e.Path())
|
|
|
|
}
|
2016-07-14 16:21:47 +00:00
|
|
|
if keywords != nil && !inSlice(kv.Keyword(), keywords) {
|
|
|
|
continue
|
|
|
|
}
|
2016-03-23 20:58:16 +00:00
|
|
|
curKeyVal, err := keywordFunc(filepath.Join(root, e.Path()), info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if string(kv) != curKeyVal {
|
2016-07-23 03:44:16 +00:00
|
|
|
result.Failures = append(result.Failures, modified{
|
|
|
|
path: e.Path(),
|
|
|
|
keyword: kv.Keyword(),
|
|
|
|
expected: kv.Value(),
|
|
|
|
got: KeyVal(curKeyVal).Value(),
|
|
|
|
})
|
2016-03-23 20:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-18 20:31:12 +00:00
|
|
|
}
|
|
|
|
}
|
2016-07-22 17:36:04 +00:00
|
|
|
|
|
|
|
// To find any paths we haven't seen already.
|
|
|
|
if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
|
|
|
/* We shouldn't get not-found errors. */
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := seen[path]; !ok {
|
|
|
|
result.Failures = append(result.Failures, present{
|
|
|
|
path: path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, fmt.Errorf("Unexpected error ocurred while walking directory: %s", err)
|
|
|
|
}
|
|
|
|
|
2016-04-05 15:44:55 +00:00
|
|
|
return &result, nil
|
2016-03-16 19:59:34 +00:00
|
|
|
}
|