blob: 43769e3c0a5ab46934aa365546bfe976b29e8573 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// This file contains implementations of various path functions we need to support
// without importing the path or strings packages.
package fdio
import "internal/bytealg"
// pathSplitForward has special path splitting semantics for vnode / service connect.
// It differs from path.Split primarily in that it begins searching for path
// separators from the front of the path, as opposed to from the back. The
// semantics for various other path types also differ:
// * Rooted paths, or paths not containing a file name (i.e. slash is the last
// character in the path) results in empty strings returned for directory and
// file.
// * Paths containing no slash return the whole path as the directory.
func pathSplitForward(path string) (dir, file string) {
slash := bytealg.IndexByte([]byte(path), '/')
if slash == 0 || slash == len(path)-1 {
return "", ""
} else if slash == -1 {
return path, ""
} else {
return path[:slash], path[slash+1:]
}
}
// pathSplit is an implementation of path.Split that additionally implements
// strings.LastIndex to avoid a dependency on both the path and strings
// package.
func pathSplit(path string) (dir, file string) {
var i int
for i = len(path) - 1; i >= 0; i-- {
if path[i] == '/' {
break
}
}
return path[:i+1], path[i+1:]
}
// pathDir is an implementation of path.Dir.
func pathDir(path string) string {
dir, _ := pathSplit(path)
return pathClean(dir)
}
// trimPrefix is an implementation of strings.TrimPrefix.
func trimPrefix(path, prefix string) string {
if path[:len(prefix)] == prefix {
return path[len(prefix):]
}
return path
}
// The following is copied verbatim from the path package to implement
// path.Clean. This is done to avoid a dependency on the path package,
// though it is unfortunate to have the code duplicated.
// A lazybuf is a lazily constructed path buffer.
// It supports append, reading previously appended bytes,
// and retrieving the final string. It does not allocate a buffer
// to hold the output until that output diverges from s.
type lazybuf struct {
s string
buf []byte
w int
}
func (b *lazybuf) index(i int) byte {
if b.buf != nil {
return b.buf[i]
}
return b.s[i]
}
func (b *lazybuf) append(c byte) {
if b.buf == nil {
if b.w < len(b.s) && b.s[b.w] == c {
b.w++
return
}
b.buf = make([]byte, len(b.s))
copy(b.buf, b.s[:b.w])
}
b.buf[b.w] = c
b.w++
}
func (b *lazybuf) string() string {
if b.buf == nil {
return b.s[:b.w]
}
return string(b.buf[:b.w])
}
// pathClean is copied verbatim from path.Clean.
func pathClean(path string) string {
if path == "" {
return "."
}
rooted := path[0] == '/'
n := len(path)
// Invariants:
// reading from path; r is index of next byte to process.
// writing to buf; w is index of next byte to write.
// dotdot is index in buf where .. must stop, either because
// it is the leading slash or it is a leading ../../.. prefix.
out := lazybuf{s: path}
r, dotdot := 0, 0
if rooted {
out.append('/')
r, dotdot = 1, 1
}
for r < n {
switch {
case path[r] == '/':
// empty path element
r++
case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
// . element
r++
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
// .. element: remove to last /
r += 2
switch {
case out.w > dotdot:
// can backtrack
out.w--
for out.w > dotdot && out.index(out.w) != '/' {
out.w--
}
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
if out.w > 0 {
out.append('/')
}
out.append('.')
out.append('.')
dotdot = out.w
}
default:
// real path element.
// add slash if needed
if rooted && out.w != 1 || !rooted && out.w != 0 {
out.append('/')
}
// copy element
for ; r < n && path[r] != '/'; r++ {
out.append(path[r])
}
}
}
// Turn empty string into "."
if out.w == 0 {
return "."
}
return out.string()
}