github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/fspath/fspath.go (about)

     1  // Copyright 2019 The gVisor 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 fspath provides efficient tools for working with file paths in
    16  // Linux-compatible filesystem implementations.
    17  package fspath
    18  
    19  import (
    20  	"strings"
    21  )
    22  
    23  const pathSep = '/'
    24  
    25  // Parse parses a pathname as described by path_resolution(7), except that
    26  // empty pathnames will be parsed successfully to a Path for which
    27  // Path.Absolute == Path.Dir == Path.HasComponents() == false. (This is
    28  // necessary to support AT_EMPTY_PATH.)
    29  func Parse(pathname string) Path {
    30  	if len(pathname) == 0 {
    31  		return Path{}
    32  	}
    33  	// Skip leading path separators.
    34  	i := 0
    35  	for pathname[i] == pathSep {
    36  		i++
    37  		if i == len(pathname) {
    38  			// pathname consists entirely of path separators.
    39  			return Path{
    40  				Absolute: true,
    41  				Dir:      true,
    42  			}
    43  		}
    44  	}
    45  	// Skip trailing path separators. This is required by Iterator.Next. This
    46  	// loop is guaranteed to terminate with j >= 0 because otherwise the
    47  	// pathname would consist entirely of path separators, so we would have
    48  	// returned above.
    49  	j := len(pathname) - 1
    50  	for pathname[j] == pathSep {
    51  		j--
    52  	}
    53  	// Find the end of the first path component.
    54  	firstEnd := i + 1
    55  	for firstEnd != len(pathname) && pathname[firstEnd] != pathSep {
    56  		firstEnd++
    57  	}
    58  	return Path{
    59  		Begin: Iterator{
    60  			partialPathname: pathname[i : j+1],
    61  			end:             firstEnd - i,
    62  		},
    63  		Absolute: i != 0,
    64  		Dir:      j != len(pathname)-1,
    65  	}
    66  }
    67  
    68  // Path contains the information contained in a pathname string.
    69  //
    70  // Path is copyable by value. The zero value for Path is equivalent to
    71  // fspath.Parse(""), i.e. the empty path.
    72  type Path struct {
    73  	// Begin is an iterator to the first path component in the relative part of
    74  	// the path.
    75  	//
    76  	// Path doesn't store information about path components after the first
    77  	// since this would require allocation.
    78  	Begin Iterator
    79  
    80  	// If true, the path is absolute, such that lookup should begin at the
    81  	// filesystem root. If false, the path is relative, such that where lookup
    82  	// begins is unspecified.
    83  	Absolute bool
    84  
    85  	// If true, the pathname contains trailing path separators, so the last
    86  	// path component must exist and resolve to a directory.
    87  	Dir bool
    88  }
    89  
    90  // String returns a pathname string equivalent to p. Note that the returned
    91  // string is not necessarily equal to the string p was parsed from; in
    92  // particular, redundant path separators will not be present.
    93  func (p Path) String() string {
    94  	var b strings.Builder
    95  	if p.Absolute {
    96  		b.WriteByte(pathSep)
    97  	}
    98  	sep := false
    99  	for pit := p.Begin; pit.Ok(); pit = pit.Next() {
   100  		if sep {
   101  			b.WriteByte(pathSep)
   102  		}
   103  		b.WriteString(pit.String())
   104  		sep = true
   105  	}
   106  	// Don't return "//" for Parse("/").
   107  	if p.Dir && p.Begin.Ok() {
   108  		b.WriteByte(pathSep)
   109  	}
   110  	return b.String()
   111  }
   112  
   113  // HasComponents returns true if p contains a non-zero number of path
   114  // components.
   115  func (p Path) HasComponents() bool {
   116  	return p.Begin.Ok()
   117  }
   118  
   119  // An Iterator represents either a path component in a Path or a terminal
   120  // iterator indicating that the end of the path has been reached.
   121  //
   122  // Iterator is immutable and copyable by value. The zero value of Iterator is
   123  // valid, and represents a terminal iterator.
   124  type Iterator struct {
   125  	// partialPathname is a substring of the original pathname beginning at the
   126  	// start of the represented path component and ending immediately after the
   127  	// end of the last path component in the pathname. If partialPathname is
   128  	// empty, the PathnameIterator is terminal.
   129  	//
   130  	// See TestParseIteratorPartialPathnames in fspath_test.go for a worked
   131  	// example.
   132  	partialPathname string
   133  
   134  	// end is the offset into partialPathname of the first byte after the end
   135  	// of the represented path component.
   136  	end int
   137  }
   138  
   139  // Ok returns true if it is not terminal.
   140  func (it Iterator) Ok() bool {
   141  	return len(it.partialPathname) != 0
   142  }
   143  
   144  // String returns the path component represented by it.
   145  //
   146  // Preconditions: it.Ok().
   147  func (it Iterator) String() string {
   148  	return it.partialPathname[:it.end]
   149  }
   150  
   151  // Next returns an iterator to the path component after it. If it is the last
   152  // component in the path, Next returns a terminal iterator.
   153  //
   154  // Preconditions: it.Ok().
   155  func (it Iterator) Next() Iterator {
   156  	if it.end == len(it.partialPathname) {
   157  		// End of the path.
   158  		return Iterator{}
   159  	}
   160  	// Skip path separators. Since Parse trims trailing path separators, if we
   161  	// aren't at the end of the path, there is definitely another path
   162  	// component.
   163  	i := it.end + 1
   164  	for {
   165  		if it.partialPathname[i] != pathSep {
   166  			break
   167  		}
   168  		i++
   169  	}
   170  	nextPartialPathname := it.partialPathname[i:]
   171  	// Find the end of this path component.
   172  	nextEnd := 1
   173  	for nextEnd < len(nextPartialPathname) && nextPartialPathname[nextEnd] != pathSep {
   174  		nextEnd++
   175  	}
   176  	return Iterator{
   177  		partialPathname: nextPartialPathname,
   178  		end:             nextEnd,
   179  	}
   180  }
   181  
   182  // NextOk is equivalent to it.Next().Ok(), but is faster.
   183  //
   184  // Preconditions: it.Ok().
   185  func (it Iterator) NextOk() bool {
   186  	return it.end != len(it.partialPathname)
   187  }