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  }