github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/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 "fmt" 20 "io" 21 "strconv" 22 "strings" 23 ) 24 25 // Lists is a structure holding relative paths to files, symlinks and 26 // directories. The members of this structure can be later combined 27 // with common.MapFilesToDirectories to get some meaningful paths. 28 type Lists struct { 29 Files []string 30 Symlinks []string 31 Dirs []string 32 } 33 34 type pair struct { 35 kind string 36 data *[]string 37 } 38 39 // ParseFilelist parses a given filelist. The filelist format is 40 // rather simple: 41 // <BLOCK> 42 // <BLOCK> 43 // ... 44 // 45 // Where "<BLOCK>" is as follows: 46 // <HEADER> 47 // <LIST> 48 // 49 // Where "<HEADER>" is as follows: 50 // <KIND> 51 // (<COUNT>) 52 // 53 // Where "<KIND>" is either "files", "symlinks" or "dirs" and 54 // "<COUNT>" tells how many items are in the following list. "<LIST>" 55 // is as follows: 56 // <1st ITEM> 57 // <2nd ITEM> 58 // ... 59 // <COUNTth ITEM> 60 // <EMPTY LINE> 61 func (list *Lists) ParseFilelist(filelist io.Reader) error { 62 scanner := bufio.NewScanner(filelist) 63 for { 64 kind, count, err := parseHeader(scanner) 65 if err != nil { 66 return fmt.Errorf("Failed to parse filelist: %v", err) 67 } 68 if kind == "" { 69 break 70 } 71 data := list.getDataForKind(kind) 72 if data == nil { 73 return fmt.Errorf("Failed to parse filelist: unknown kind %q, expected 'files', 'symlinks' or 'dirs'", kind) 74 } 75 items, err := parseList(scanner, count) 76 if err != nil { 77 return fmt.Errorf("Failed to parse filelist: %v", err) 78 } 79 *data = items 80 } 81 return nil 82 } 83 84 // parseList parses the list part of a block. It makes sure that there 85 // is an exactly expected count of items. 86 func parseList(scanner *bufio.Scanner, count int) ([]string, error) { 87 got := 0 88 items := make([]string, 0, count) 89 for { 90 if !scanner.Scan() { 91 if err := scanner.Err(); err != nil { 92 return nil, err 93 } 94 return nil, fmt.Errorf("expected either an empty line or a line with an item, unexpected EOF?") 95 } 96 line := scanner.Text() 97 if line == "" { 98 if got < count { 99 return nil, fmt.Errorf("too few items (declared %d, got %d)", count, got) 100 } 101 break 102 } 103 got++ 104 if got > count { 105 return nil, fmt.Errorf("too many items (declared %d)", count) 106 } 107 items = append(items, line) 108 } 109 return items, nil 110 } 111 112 // parseHeader parses the first two lines of a block described in 113 // ParseFilelist docs. So it returns a data kind (files, symlinks or 114 // dirs) and a count of elements in the following list. If the 115 // returned kind is empty then it means that there is no more entries 116 // (provided that there is no error either). 117 func parseHeader(scanner *bufio.Scanner) (string, int, error) { 118 if !scanner.Scan() { 119 if err := scanner.Err(); err != nil { 120 return "", 0, err 121 } 122 // no more entries in the file, just return empty kind 123 return "", 0, nil 124 } 125 kind := scanner.Text() 126 if kind == "" { 127 return "", 0, fmt.Errorf("got an empty kind, expected 'files', 'symlinks' or 'dirs'") 128 } 129 if !scanner.Scan() { 130 if err := scanner.Err(); err != nil { 131 return "", 0, err 132 } else { 133 return "", 0, fmt.Errorf("expected a line with a count, unexpected EOF?") 134 } 135 } 136 countReader := strings.NewReader(scanner.Text()) 137 count := 0 138 n, err := fmt.Fscanf(countReader, "(%d)", &count) 139 if err != nil { 140 return "", 0, err 141 } 142 if n != 1 { 143 return "", 0, fmt.Errorf("incorrectly formatted line with number of %s", kind) 144 } 145 return kind, count, nil 146 } 147 148 func (list *Lists) getDataForKind(kind string) *[]string { 149 switch kind { 150 case "files": 151 return &list.Files 152 case "symlinks": 153 return &list.Symlinks 154 case "dirs": 155 return &list.Dirs 156 } 157 return nil 158 } 159 160 // GenerateFilelist generates a filelist, duh. And writes it to a 161 // given writer. The format of generated file is described in 162 // filelist.ParseFilelist. 163 func (list *Lists) GenerateFilelist(out io.Writer) error { 164 w := bufio.NewWriter(out) 165 for _, pair := range list.getPairs() { 166 dLen := len(*pair.data) 167 toWrite := []string{ 168 pair.kind, 169 "\n(", 170 strconv.Itoa(dLen), 171 ")\n", 172 } 173 if dLen > 0 { 174 toWrite = append(toWrite, 175 strings.Join(*pair.data, "\n"), 176 "\n") 177 } 178 toWrite = append(toWrite, "\n") 179 for _, str := range toWrite { 180 if _, err := w.WriteString(str); err != nil { 181 return err 182 } 183 } 184 } 185 w.Flush() 186 return nil 187 } 188 189 func (list *Lists) getPairs() []pair { 190 return []pair{ 191 {kind: "files", data: &list.Files}, 192 {kind: "symlinks", data: &list.Symlinks}, 193 {kind: "dirs", data: &list.Dirs}, 194 } 195 }