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  }