github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/asserts/findwildcard.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package asserts
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  )
    31  
    32  /*
    33  findWildcard invokes foundCb once for each parent directory of regular files matching:
    34  
    35  <top>/<descendantWithWildcard[0]>/<descendantWithWildcard[1]>...
    36  
    37  where each descendantWithWildcard component can contain the * wildcard.
    38  
    39  One of the descendantWithWildcard components except the last
    40  can be "#>" or "#<", in which case that level is assumed to have names
    41  that can be parsed as positive integers, which will be enumerated in
    42  ascending (#>) or descending order respectively (#<); if seqnum != -1
    43  then only the values >seqnum or respectively <seqnum will be
    44  considered.
    45  
    46  foundCb is invoked with the paths of the found regular files relative to top (that means top/ is excluded).
    47  
    48  Unlike filepath.Glob any I/O operation error stops the walking and bottoms out, so does a foundCb invocation that returns an error.
    49  */
    50  func findWildcard(top string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error {
    51  	return findWildcardDescend(top, top, descendantWithWildcard, seqnum, foundCb)
    52  }
    53  
    54  func findWildcardBottom(top, current string, pat string, names []string, foundCb func(relpath []string) error) error {
    55  	var hits []string
    56  	for _, name := range names {
    57  		ok, err := filepath.Match(pat, name)
    58  		if err != nil {
    59  			return fmt.Errorf("findWildcard: invoked with malformed wildcard: %v", err)
    60  		}
    61  		if !ok {
    62  			continue
    63  		}
    64  		fn := filepath.Join(current, name)
    65  		finfo, err := os.Stat(fn)
    66  		if os.IsNotExist(err) {
    67  			continue
    68  		}
    69  		if err != nil {
    70  			return err
    71  		}
    72  		if !finfo.Mode().IsRegular() {
    73  			return fmt.Errorf("expected a regular file: %v", fn)
    74  		}
    75  		relpath, err := filepath.Rel(top, fn)
    76  		if err != nil {
    77  			return fmt.Errorf("findWildcard: unexpected to fail at computing rel path of descendant")
    78  		}
    79  		hits = append(hits, relpath)
    80  	}
    81  	if len(hits) == 0 {
    82  		return nil
    83  	}
    84  	return foundCb(hits)
    85  }
    86  
    87  func findWildcardDescend(top, current string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error {
    88  	k := descendantWithWildcard[0]
    89  	if k == "#>" || k == "#<" {
    90  		if len(descendantWithWildcard) == 1 {
    91  			return fmt.Errorf("findWildcard: sequence wildcard (#>|<#) cannot be the last component")
    92  		}
    93  		return findWildcardSequence(top, current, k, descendantWithWildcard[1:], seqnum, foundCb)
    94  	}
    95  	if len(descendantWithWildcard) > 1 && strings.IndexByte(k, '*') == -1 {
    96  		return findWildcardDescend(top, filepath.Join(current, k), descendantWithWildcard[1:], seqnum, foundCb)
    97  	}
    98  
    99  	d, err := os.Open(current)
   100  	// ignore missing directory, higher level will produce
   101  	// NotFoundError as needed
   102  	if os.IsNotExist(err) {
   103  		return nil
   104  	}
   105  	if err != nil {
   106  		return err
   107  	}
   108  	defer d.Close()
   109  	names, err := d.Readdirnames(-1)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	if len(descendantWithWildcard) == 1 {
   114  		return findWildcardBottom(top, current, k, names, foundCb)
   115  	}
   116  	for _, name := range names {
   117  		ok, err := filepath.Match(k, name)
   118  		if err != nil {
   119  			return fmt.Errorf("findWildcard: invoked with malformed wildcard: %v", err)
   120  		}
   121  		if ok {
   122  			err = findWildcardDescend(top, filepath.Join(current, name), descendantWithWildcard[1:], seqnum, foundCb)
   123  			if err != nil {
   124  				return err
   125  			}
   126  		}
   127  	}
   128  	return nil
   129  }
   130  
   131  func findWildcardSequence(top, current, seqWildcard string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error {
   132  	filter := func(i int) bool { return true }
   133  	if seqnum != -1 {
   134  		if seqWildcard == "#>" {
   135  			filter = func(i int) bool { return i > seqnum }
   136  		} else { // "#<", guaranteed by the caller
   137  			filter = func(i int) bool { return i < seqnum }
   138  		}
   139  	}
   140  
   141  	d, err := os.Open(current)
   142  	// ignore missing directory, higher level will produce
   143  	// NotFoundError as needed
   144  	if os.IsNotExist(err) {
   145  		return nil
   146  	}
   147  	if err != nil {
   148  		return err
   149  	}
   150  	defer d.Close()
   151  	var seq []int
   152  	for {
   153  		names, err := d.Readdirnames(100)
   154  		if err == io.EOF {
   155  			break
   156  		}
   157  		if err != nil {
   158  			return err
   159  		}
   160  		for _, n := range names {
   161  			sqn, err := strconv.Atoi(n)
   162  			if err != nil || sqn < 0 || prefixZeros(n) {
   163  				return fmt.Errorf("cannot parse %q name as a valid sequence number", filepath.Join(current, n))
   164  			}
   165  			if filter(sqn) {
   166  				seq = append(seq, sqn)
   167  			}
   168  		}
   169  	}
   170  	sort.Ints(seq)
   171  
   172  	var start, direction int
   173  	if seqWildcard == "#>" {
   174  		start = 0
   175  		direction = 1
   176  	} else {
   177  		start = len(seq) - 1
   178  		direction = -1
   179  	}
   180  	for i := start; i >= 0 && i < len(seq); i += direction {
   181  		err = findWildcardDescend(top, filepath.Join(current, strconv.Itoa(seq[i])), descendantWithWildcard, -1, foundCb)
   182  		if err != nil {
   183  			return err
   184  		}
   185  	}
   186  	return nil
   187  }