github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/tools/common/filelist/filelist.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package filelist 16 17 import ( 18 "bufio" 19 "errors" 20 "fmt" 21 "io" 22 "strconv" 23 "strings" 24 25 "github.com/hashicorp/errwrap" 26 ) 27 28 // Lists is a structure holding relative paths to files, symlinks and 29 // directories. The members of this structure can be later combined 30 // with common.MapFilesToDirectories to get some meaningful paths. 31 type Lists struct { 32 Files []string 33 Symlinks []string 34 Dirs []string 35 } 36 37 type pair struct { 38 kind string 39 data *[]string 40 } 41 42 // ParseFilelist parses a given filelist. The filelist format is 43 // rather simple: 44 // <BLOCK> 45 // <BLOCK> 46 // ... 47 // 48 // Where "<BLOCK>" is as follows: 49 // <HEADER> 50 // <LIST> 51 // 52 // Where "<HEADER>" is as follows: 53 // <KIND> 54 // (<COUNT>) 55 // 56 // Where "<KIND>" is either "files", "symlinks" or "dirs" and 57 // "<COUNT>" tells how many items are in the following list. "<LIST>" 58 // is as follows: 59 // <1st ITEM> 60 // <2nd ITEM> 61 // ... 62 // <COUNTth ITEM> 63 // <EMPTY LINE> 64 func (list *Lists) ParseFilelist(filelist io.Reader) error { 65 scanner := bufio.NewScanner(filelist) 66 for { 67 kind, count, err := parseHeader(scanner) 68 if err != nil { 69 return errwrap.Wrap(errors.New("failed to parse filelist"), err) 70 } 71 if kind == "" { 72 break 73 } 74 data := list.getDataForKind(kind) 75 if data == nil { 76 return fmt.Errorf("failed to parse filelist: unknown kind %q, expected 'files', 'symlinks' or 'dirs'", kind) 77 } 78 items, err := parseList(scanner, count) 79 if err != nil { 80 return errwrap.Wrap(errors.New("failed to parse filelist"), err) 81 } 82 *data = items 83 } 84 return nil 85 } 86 87 // parseList parses the list part of a block. It makes sure that there 88 // is an exactly expected count of items. 89 func parseList(scanner *bufio.Scanner, count int) ([]string, error) { 90 got := 0 91 items := make([]string, 0, count) 92 for { 93 if !scanner.Scan() { 94 if err := scanner.Err(); err != nil { 95 return nil, err 96 } 97 return nil, fmt.Errorf("expected either an empty line or a line with an item, unexpected EOF?") 98 } 99 line := scanner.Text() 100 if line == "" { 101 if got < count { 102 return nil, fmt.Errorf("too few items (declared %d, got %d)", count, got) 103 } 104 break 105 } 106 got++ 107 if got > count { 108 return nil, fmt.Errorf("too many items (declared %d)", count) 109 } 110 items = append(items, line) 111 } 112 return items, nil 113 } 114 115 // parseHeader parses the first two lines of a block described in 116 // ParseFilelist docs. So it returns a data kind (files, symlinks or 117 // dirs) and a count of elements in the following list. If the 118 // returned kind is empty then it means that there is no more entries 119 // (provided that there is no error either). 120 func parseHeader(scanner *bufio.Scanner) (string, int, error) { 121 if !scanner.Scan() { 122 if err := scanner.Err(); err != nil { 123 return "", 0, err 124 } 125 // no more entries in the file, just return empty kind 126 return "", 0, nil 127 } 128 kind := scanner.Text() 129 if kind == "" { 130 return "", 0, fmt.Errorf("got an empty kind, expected 'files', 'symlinks' or 'dirs'") 131 } 132 if !scanner.Scan() { 133 if err := scanner.Err(); err != nil { 134 return "", 0, err 135 } else { 136 return "", 0, fmt.Errorf("expected a line with a count, unexpected EOF?") 137 } 138 } 139 countReader := strings.NewReader(scanner.Text()) 140 count := 0 141 n, err := fmt.Fscanf(countReader, "(%d)", &count) 142 if err != nil { 143 return "", 0, err 144 } 145 if n != 1 { 146 return "", 0, fmt.Errorf("incorrectly formatted line with number of %s", kind) 147 } 148 return kind, count, nil 149 } 150 151 func (list *Lists) getDataForKind(kind string) *[]string { 152 switch kind { 153 case "files": 154 return &list.Files 155 case "symlinks": 156 return &list.Symlinks 157 case "dirs": 158 return &list.Dirs 159 } 160 return nil 161 } 162 163 // GenerateFilelist generates a filelist, duh. And writes it to a 164 // given writer. The format of generated file is described in 165 // filelist.ParseFilelist. 166 func (list *Lists) GenerateFilelist(out io.Writer) error { 167 w := bufio.NewWriter(out) 168 for _, pair := range list.getPairs() { 169 dLen := len(*pair.data) 170 toWrite := []string{ 171 pair.kind, 172 "\n(", 173 strconv.Itoa(dLen), 174 ")\n", 175 } 176 if dLen > 0 { 177 toWrite = append(toWrite, 178 strings.Join(*pair.data, "\n"), 179 "\n") 180 } 181 toWrite = append(toWrite, "\n") 182 for _, str := range toWrite { 183 if _, err := w.WriteString(str); err != nil { 184 return err 185 } 186 } 187 } 188 w.Flush() 189 return nil 190 } 191 192 func (list *Lists) getPairs() []pair { 193 return []pair{ 194 {kind: "files", data: &list.Files}, 195 {kind: "symlinks", data: &list.Symlinks}, 196 {kind: "dirs", data: &list.Dirs}, 197 } 198 }