summaryrefslogtreecommitdiffhomepage
path: root/pkg/fspath/fspath.go
blob: 4c983d5fd290eab9cb91c384cb65ae5bcaa80fbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package fspath provides efficient tools for working with file paths in
// Linux-compatible filesystem implementations.
package fspath

import (
	"strings"
)

const pathSep = '/'

// Parse parses a pathname as described by path_resolution(7), except that
// empty pathnames will be parsed successfully to a Path for which
// Path.Absolute == Path.Dir == Path.HasComponents() == false. (This is
// necessary to support AT_EMPTY_PATH.)
func Parse(pathname string) Path {
	if len(pathname) == 0 {
		return Path{}
	}
	// Skip leading path separators.
	i := 0
	for pathname[i] == pathSep {
		i++
		if i == len(pathname) {
			// pathname consists entirely of path separators.
			return Path{
				Absolute: true,
				Dir:      true,
			}
		}
	}
	// Skip trailing path separators. This is required by Iterator.Next. This
	// loop is guaranteed to terminate with j >= 0 because otherwise the
	// pathname would consist entirely of path separators, so we would have
	// returned above.
	j := len(pathname) - 1
	for pathname[j] == pathSep {
		j--
	}
	// Find the end of the first path component.
	firstEnd := i + 1
	for firstEnd != len(pathname) && pathname[firstEnd] != pathSep {
		firstEnd++
	}
	return Path{
		Begin: Iterator{
			partialPathname: pathname[i : j+1],
			end:             firstEnd - i,
		},
		Absolute: i != 0,
		Dir:      j != len(pathname)-1,
	}
}

// Path contains the information contained in a pathname string.
//
// Path is copyable by value. The zero value for Path is equivalent to
// fspath.Parse(""), i.e. the empty path.
type Path struct {
	// Begin is an iterator to the first path component in the relative part of
	// the path.
	//
	// Path doesn't store information about path components after the first
	// since this would require allocation.
	Begin Iterator

	// If true, the path is absolute, such that lookup should begin at the
	// filesystem root. If false, the path is relative, such that where lookup
	// begins is unspecified.
	Absolute bool

	// If true, the pathname contains trailing path separators, so the last
	// path component must exist and resolve to a directory.
	Dir bool
}

// String returns a pathname string equivalent to p. Note that the returned
// string is not necessarily equal to the string p was parsed from; in
// particular, redundant path separators will not be present.
func (p Path) String() string {
	var b strings.Builder
	if p.Absolute {
		b.WriteByte(pathSep)
	}
	sep := false
	for pit := p.Begin; pit.Ok(); pit = pit.Next() {
		if sep {
			b.WriteByte(pathSep)
		}
		b.WriteString(pit.String())
		sep = true
	}
	// Don't return "//" for Parse("/").
	if p.Dir && p.Begin.Ok() {
		b.WriteByte(pathSep)
	}
	return b.String()
}

// HasComponents returns true if p contains a non-zero number of path
// components.
func (p Path) HasComponents() bool {
	return p.Begin.Ok()
}

// An Iterator represents either a path component in a Path or a terminal
// iterator indicating that the end of the path has been reached.
//
// Iterator is immutable and copyable by value. The zero value of Iterator is
// valid, and represents a terminal iterator.
type Iterator struct {
	// partialPathname is a substring of the original pathname beginning at the
	// start of the represented path component and ending immediately after the
	// end of the last path component in the pathname. If partialPathname is
	// empty, the PathnameIterator is terminal.
	//
	// See TestParseIteratorPartialPathnames in fspath_test.go for a worked
	// example.
	partialPathname string

	// end is the offset into partialPathname of the first byte after the end
	// of the represented path component.
	end int
}

// Ok returns true if it is not terminal.
func (it Iterator) Ok() bool {
	return len(it.partialPathname) != 0
}

// String returns the path component represented by it.
//
// Preconditions: it.Ok().
func (it Iterator) String() string {
	return it.partialPathname[:it.end]
}

// Next returns an iterator to the path component after it. If it is the last
// component in the path, Next returns a terminal iterator.
//
// Preconditions: it.Ok().
func (it Iterator) Next() Iterator {
	if it.end == len(it.partialPathname) {
		// End of the path.
		return Iterator{}
	}
	// Skip path separators. Since Parse trims trailing path separators, if we
	// aren't at the end of the path, there is definitely another path
	// component.
	i := it.end + 1
	for {
		if it.partialPathname[i] != pathSep {
			break
		}
		i++
	}
	nextPartialPathname := it.partialPathname[i:]
	// Find the end of this path component.
	nextEnd := 1
	for nextEnd < len(nextPartialPathname) && nextPartialPathname[nextEnd] != pathSep {
		nextEnd++
	}
	return Iterator{
		partialPathname: nextPartialPathname,
		end:             nextEnd,
	}
}

// NextOk is equivalent to it.Next().Ok(), but is faster.
//
// Preconditions: it.Ok().
func (it Iterator) NextOk() bool {
	return it.end != len(it.partialPathname)
}