mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os: Implement symlink support for Windows
Fixes #5750. https://code.google.com/p/go/issues/detail?id=5750 os: Separate windows from posix. Implement windows support. path/filepath: Use the same implementation as other platforms syscall: Add/rework new APIs for Windows LGTM=alex.brainman R=golang-codereviews, alex.brainman, gobot, rsc, minux CC=golang-codereviews https://golang.org/cl/86160044
This commit is contained in:
parent
47fd6bd9b6
commit
cf521ce64f
18 changed files with 404 additions and 73 deletions
|
|
@ -13,26 +13,6 @@ import (
|
||||||
|
|
||||||
func sigpipe() // implemented in package runtime
|
func sigpipe() // implemented in package runtime
|
||||||
|
|
||||||
// Link creates newname as a hard link to the oldname file.
|
|
||||||
// If there is an error, it will be of type *LinkError.
|
|
||||||
func Link(oldname, newname string) error {
|
|
||||||
e := syscall.Link(oldname, newname)
|
|
||||||
if e != nil {
|
|
||||||
return &LinkError{"link", oldname, newname, e}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symlink creates newname as a symbolic link to oldname.
|
|
||||||
// If there is an error, it will be of type *LinkError.
|
|
||||||
func Symlink(oldname, newname string) error {
|
|
||||||
e := syscall.Symlink(oldname, newname)
|
|
||||||
if e != nil {
|
|
||||||
return &LinkError{"symlink", oldname, newname, e}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readlink returns the destination of the named symbolic link.
|
// Readlink returns the destination of the named symbolic link.
|
||||||
// If there is an error, it will be of type *PathError.
|
// If there is an error, it will be of type *PathError.
|
||||||
func Readlink(name string) (string, error) {
|
func Readlink(name string) (string, error) {
|
||||||
|
|
|
||||||
|
|
@ -316,3 +316,23 @@ func TempDir() string {
|
||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link creates newname as a hard link to the oldname file.
|
||||||
|
// If there is an error, it will be of type *LinkError.
|
||||||
|
func Link(oldname, newname string) error {
|
||||||
|
e := syscall.Link(oldname, newname)
|
||||||
|
if e != nil {
|
||||||
|
return &LinkError{"link", oldname, newname, e}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink creates newname as a symbolic link to oldname.
|
||||||
|
// If there is an error, it will be of type *LinkError.
|
||||||
|
func Symlink(oldname, newname string) error {
|
||||||
|
e := syscall.Symlink(oldname, newname)
|
||||||
|
if e != nil {
|
||||||
|
return &LinkError{"symlink", oldname, newname, e}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -493,3 +493,101 @@ func TempDir() string {
|
||||||
}
|
}
|
||||||
return string(utf16.Decode(dirw[0:n]))
|
return string(utf16.Decode(dirw[0:n]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link creates newname as a hard link to the oldname file.
|
||||||
|
// If there is an error, it will be of type *LinkError.
|
||||||
|
func Link(oldname, newname string) error {
|
||||||
|
n, err := syscall.UTF16PtrFromString(newname)
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"link", oldname, newname, err}
|
||||||
|
}
|
||||||
|
o, err := syscall.UTF16PtrFromString(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"link", oldname, newname, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := syscall.CreateHardLink(n, o, 0)
|
||||||
|
if e != nil {
|
||||||
|
return &LinkError{"link", oldname, newname, err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink creates newname as a symbolic link to oldname.
|
||||||
|
// If there is an error, it will be of type *LinkError.
|
||||||
|
func Symlink(oldname, newname string) error {
|
||||||
|
// CreateSymbolicLink is not supported before Windows Vista
|
||||||
|
if syscall.LoadCreateSymbolicLink() != nil {
|
||||||
|
return &LinkError{"symlink", oldname, newname, syscall.EWINDOWS}
|
||||||
|
}
|
||||||
|
|
||||||
|
// '/' does not work in link's content
|
||||||
|
oldname = fromSlash(oldname)
|
||||||
|
|
||||||
|
// need the exact location of the oldname when its relative to determine if its a directory
|
||||||
|
destpath := oldname
|
||||||
|
if !isAbs(oldname) {
|
||||||
|
destpath = dirname(newname) + `\` + oldname
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := Lstat(destpath)
|
||||||
|
isdir := err == nil && fi.IsDir()
|
||||||
|
|
||||||
|
n, err := syscall.UTF16PtrFromString(newname)
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlink", oldname, newname, err}
|
||||||
|
}
|
||||||
|
o, err := syscall.UTF16PtrFromString(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlink", oldname, newname, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
if isdir {
|
||||||
|
flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
|
||||||
|
}
|
||||||
|
err = syscall.CreateSymbolicLink(n, o, flags)
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlink", oldname, newname, err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromSlash(path string) string {
|
||||||
|
// Replace each '/' with '\\' if present
|
||||||
|
var pathbuf []byte
|
||||||
|
var lastSlash int
|
||||||
|
for i, b := range path {
|
||||||
|
if b == '/' {
|
||||||
|
if pathbuf == nil {
|
||||||
|
pathbuf = make([]byte, len(path))
|
||||||
|
}
|
||||||
|
copy(pathbuf[lastSlash:], path[lastSlash:i])
|
||||||
|
pathbuf[i] = '\\'
|
||||||
|
lastSlash = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pathbuf == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(pathbuf[lastSlash:], path[lastSlash:])
|
||||||
|
return string(pathbuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirname(path string) string {
|
||||||
|
vol := volumeName(path)
|
||||||
|
i := len(path) - 1
|
||||||
|
for i >= len(vol) && !IsPathSeparator(path[i]) {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
dir := path[len(vol) : i+1]
|
||||||
|
last := len(dir) - 1
|
||||||
|
if last > 0 && IsPathSeparator(dir[last]) {
|
||||||
|
dir = dir[:last]
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
return vol + dir
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var supportsSymlinks = true
|
||||||
|
|
||||||
var dot = []string{
|
var dot = []string{
|
||||||
"dir_unix.go",
|
"dir_unix.go",
|
||||||
"env.go",
|
"env.go",
|
||||||
|
|
@ -475,7 +477,7 @@ func TestReaddirStatFailures(t *testing.T) {
|
||||||
|
|
||||||
func TestHardLink(t *testing.T) {
|
func TestHardLink(t *testing.T) {
|
||||||
// Hardlinks are not supported under windows or Plan 9.
|
// Hardlinks are not supported under windows or Plan 9.
|
||||||
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
if runtime.GOOS == "plan9" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
from, to := "hardlinktestfrom", "hardlinktestto"
|
from, to := "hardlinktestfrom", "hardlinktestto"
|
||||||
|
|
@ -508,8 +510,12 @@ func TestHardLink(t *testing.T) {
|
||||||
|
|
||||||
func TestSymlink(t *testing.T) {
|
func TestSymlink(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "android", "nacl", "plan9", "windows":
|
case "android", "nacl", "plan9":
|
||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
case "windows":
|
||||||
|
if !supportsSymlinks {
|
||||||
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
from, to := "symlinktestfrom", "symlinktestto"
|
from, to := "symlinktestfrom", "symlinktestto"
|
||||||
Remove(from) // Just in case.
|
Remove(from) // Just in case.
|
||||||
|
|
@ -570,8 +576,12 @@ func TestSymlink(t *testing.T) {
|
||||||
|
|
||||||
func TestLongSymlink(t *testing.T) {
|
func TestLongSymlink(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows", "plan9", "nacl":
|
case "plan9", "nacl":
|
||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
case "windows":
|
||||||
|
if !supportsSymlinks {
|
||||||
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s := "0123456789abcdef"
|
s := "0123456789abcdef"
|
||||||
// Long, but not too long: a common limit is 255.
|
// Long, but not too long: a common limit is 255.
|
||||||
|
|
|
||||||
27
src/pkg/os/os_windows_test.go
Normal file
27
src/pkg/os/os_windows_test.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package os_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "symtest")
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to create temp directory: " + err.Error())
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = err.(*os.LinkError).Err
|
||||||
|
switch err {
|
||||||
|
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||||
|
supportsSymlinks = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -168,8 +168,12 @@ func TestRemoveAll(t *testing.T) {
|
||||||
|
|
||||||
func TestMkdirAllWithSymlink(t *testing.T) {
|
func TestMkdirAllWithSymlink(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "nacl", "plan9", "windows":
|
case "nacl", "plan9":
|
||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
case "windows":
|
||||||
|
if !supportsSymlinks {
|
||||||
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
|
tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,29 @@ func (file *File) Stat() (fi FileInfo, err error) {
|
||||||
// Stat returns a FileInfo structure describing the named file.
|
// Stat returns a FileInfo structure describing the named file.
|
||||||
// If there is an error, it will be of type *PathError.
|
// If there is an error, it will be of type *PathError.
|
||||||
func Stat(name string) (fi FileInfo, err error) {
|
func Stat(name string) (fi FileInfo, err error) {
|
||||||
|
for {
|
||||||
|
fi, err = Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi.Mode()&ModeSymlink == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name, err = Readlink(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat returns the FileInfo structure describing the named file.
|
||||||
|
// If the file is a symbolic link, the returned FileInfo
|
||||||
|
// describes the symbolic link. Lstat makes no attempt to follow the link.
|
||||||
|
// If there is an error, it will be of type *PathError.
|
||||||
|
func Lstat(name string) (fi FileInfo, err error) {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
|
return nil, &PathError{"Lstat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
|
||||||
}
|
}
|
||||||
if name == DevNull {
|
if name == DevNull {
|
||||||
return &devNullStat, nil
|
return &devNullStat, nil
|
||||||
|
|
@ -58,7 +79,7 @@ func Stat(name string) (fi FileInfo, err error) {
|
||||||
fs := &fileStat{name: basename(name)}
|
fs := &fileStat{name: basename(name)}
|
||||||
namep, e := syscall.UTF16PtrFromString(name)
|
namep, e := syscall.UTF16PtrFromString(name)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, &PathError{"Stat", name, e}
|
return nil, &PathError{"Lstat", name, e}
|
||||||
}
|
}
|
||||||
e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
|
e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
|
@ -72,15 +93,6 @@ func Stat(name string) (fi FileInfo, err error) {
|
||||||
return fs, nil
|
return fs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lstat returns the FileInfo structure describing the named file.
|
|
||||||
// If the file is a symbolic link, the returned FileInfo
|
|
||||||
// describes the symbolic link. Lstat makes no attempt to follow the link.
|
|
||||||
// If there is an error, it will be of type *PathError.
|
|
||||||
func Lstat(name string) (fi FileInfo, err error) {
|
|
||||||
// No links on Windows
|
|
||||||
return Stat(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// basename removes trailing slashes and the leading
|
// basename removes trailing slashes and the leading
|
||||||
// directory name and drive letter from path name.
|
// directory name and drive letter from path name.
|
||||||
func basename(name string) string {
|
func basename(name string) string {
|
||||||
|
|
@ -105,10 +117,6 @@ func basename(name string) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSlash(c uint8) bool {
|
|
||||||
return c == '\\' || c == '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAbs(path string) (b bool) {
|
func isAbs(path string) (b bool) {
|
||||||
v := volumeName(path)
|
v := volumeName(path)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
|
|
@ -118,7 +126,7 @@ func isAbs(path string) (b bool) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return isSlash(path[0])
|
return IsPathSeparator(path[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func volumeName(path string) (v string) {
|
func volumeName(path string) (v string) {
|
||||||
|
|
@ -133,20 +141,20 @@ func volumeName(path string) (v string) {
|
||||||
return path[:2]
|
return path[:2]
|
||||||
}
|
}
|
||||||
// is it UNC
|
// is it UNC
|
||||||
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
|
if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) &&
|
||||||
!isSlash(path[2]) && path[2] != '.' {
|
!IsPathSeparator(path[2]) && path[2] != '.' {
|
||||||
// first, leading `\\` and next shouldn't be `\`. its server name.
|
// first, leading `\\` and next shouldn't be `\`. its server name.
|
||||||
for n := 3; n < l-1; n++ {
|
for n := 3; n < l-1; n++ {
|
||||||
// second, next '\' shouldn't be repeated.
|
// second, next '\' shouldn't be repeated.
|
||||||
if isSlash(path[n]) {
|
if IsPathSeparator(path[n]) {
|
||||||
n++
|
n++
|
||||||
// third, following something characters. its share name.
|
// third, following something characters. its share name.
|
||||||
if !isSlash(path[n]) {
|
if !IsPathSeparator(path[n]) {
|
||||||
if path[n] == '.' {
|
if path[n] == '.' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for ; n < l; n++ {
|
for ; n < l; n++ {
|
||||||
if isSlash(path[n]) {
|
if IsPathSeparator(path[n]) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ func (fs *fileStat) Mode() (m FileMode) {
|
||||||
} else {
|
} else {
|
||||||
m |= 0666
|
m |= 0666
|
||||||
}
|
}
|
||||||
|
if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||||
|
m |= ModeSymlink
|
||||||
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,13 @@ var globSymlinkTests = []struct {
|
||||||
|
|
||||||
func TestGlobSymlink(t *testing.T) {
|
func TestGlobSymlink(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "nacl", "plan9", "windows":
|
case "nacl", "plan9":
|
||||||
t.Skipf("skipping on %s", runtime.GOOS)
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
case "windows":
|
||||||
|
if !supportsSymlinks {
|
||||||
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "globsymlink")
|
tmpDir, err := ioutil.TempDir("", "globsymlink")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var supportsSymlinks = true
|
||||||
|
|
||||||
type PathTest struct {
|
type PathTest struct {
|
||||||
path, result string
|
path, result string
|
||||||
}
|
}
|
||||||
|
|
@ -716,7 +718,7 @@ func TestEvalSymlinks(t *testing.T) {
|
||||||
if d.dest == "" {
|
if d.dest == "" {
|
||||||
err = os.Mkdir(path, 0755)
|
err = os.Mkdir(path, 0755)
|
||||||
} else {
|
} else {
|
||||||
if runtime.GOOS != "windows" {
|
if supportsSymlinks {
|
||||||
err = os.Symlink(d.dest, path)
|
err = os.Symlink(d.dest, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -726,7 +728,9 @@ func TestEvalSymlinks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests []EvalSymlinksTest
|
var tests []EvalSymlinksTest
|
||||||
if runtime.GOOS == "windows" {
|
if supportsSymlinks {
|
||||||
|
tests = EvalSymlinksTests
|
||||||
|
} else {
|
||||||
for _, d := range EvalSymlinksTests {
|
for _, d := range EvalSymlinksTests {
|
||||||
if d.path == d.dest {
|
if d.path == d.dest {
|
||||||
// will test only real files and directories
|
// will test only real files and directories
|
||||||
|
|
@ -739,15 +743,13 @@ func TestEvalSymlinks(t *testing.T) {
|
||||||
tests = append(tests, d2)
|
tests = append(tests, d2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tests = EvalSymlinksTests
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the symlink farm.
|
// Evaluate the symlink farm.
|
||||||
for _, d := range tests {
|
for _, d := range tests {
|
||||||
path := simpleJoin(tmpDir, d.path)
|
path := simpleJoin(tmpDir, d.path)
|
||||||
dest := simpleJoin(tmpDir, d.dest)
|
dest := simpleJoin(tmpDir, d.dest)
|
||||||
if filepath.IsAbs(d.dest) {
|
if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
|
||||||
dest = d.dest
|
dest = d.dest
|
||||||
}
|
}
|
||||||
if p, err := filepath.EvalSymlinks(path); err != nil {
|
if p, err := filepath.EvalSymlinks(path); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,29 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "symtest")
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to create temp directory: " + err.Error())
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = err.(*os.LinkError).Err
|
||||||
|
switch err {
|
||||||
|
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||||
|
supportsSymlinks = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWinSplitListTestsAreValid(t *testing.T) {
|
func TestWinSplitListTestsAreValid(t *testing.T) {
|
||||||
comspec := os.Getenv("ComSpec")
|
comspec := os.Getenv("ComSpec")
|
||||||
if comspec == "" {
|
if comspec == "" {
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,17 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package filepath
|
package filepath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func evalSymlinks(path string) (string, error) {
|
const utf8RuneSelf = 0x80
|
||||||
|
|
||||||
|
func walkSymlinks(path string) (string, error) {
|
||||||
const maxIter = 255
|
const maxIter = 255
|
||||||
originalPath := path
|
originalPath := path
|
||||||
// consume path by taking each frontmost path element,
|
// consume path by taking each frontmost path element,
|
||||||
|
|
@ -25,7 +24,13 @@ func evalSymlinks(path string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// find next path component, p
|
// find next path component, p
|
||||||
i := strings.IndexRune(path, Separator)
|
var i = -1
|
||||||
|
for j, c := range path {
|
||||||
|
if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
var p string
|
var p string
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
p, path = path, ""
|
p, path = path, ""
|
||||||
|
|
@ -47,7 +52,7 @@ func evalSymlinks(path string) (string, error) {
|
||||||
}
|
}
|
||||||
if fi.Mode()&os.ModeSymlink == 0 {
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
b.WriteString(p)
|
b.WriteString(p)
|
||||||
if path != "" {
|
if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
|
||||||
b.WriteRune(Separator)
|
b.WriteRune(Separator)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
@ -58,7 +63,7 @@ func evalSymlinks(path string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if IsAbs(dest) {
|
if IsAbs(dest) || os.IsPathSeparator(dest[0]) {
|
||||||
b.Reset()
|
b.Reset()
|
||||||
}
|
}
|
||||||
path = dest + string(Separator) + path
|
path = dest + string(Separator) + path
|
||||||
|
|
|
||||||
7
src/pkg/path/filepath/symlink_unix.go
Normal file
7
src/pkg/path/filepath/symlink_unix.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package filepath
|
||||||
|
|
||||||
|
func evalSymlinks(path string) (string, error) {
|
||||||
|
return walkSymlinks(path)
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,11 @@ func toLong(path string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalSymlinks(path string) (string, error) {
|
func evalSymlinks(path string) (string, error) {
|
||||||
|
path, err := walkSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
p, err := toShort(path)
|
p, err := toShort(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,10 @@ func NewCallbackCDecl(fn interface{}) uintptr
|
||||||
//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot
|
//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot
|
||||||
//sys Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32FirstW
|
//sys Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32FirstW
|
||||||
//sys Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32NextW
|
//sys Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32NextW
|
||||||
|
//sys DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBufferSize uint32, outBuffer *byte, outBufferSize uint32, bytesReturned *uint32, overlapped *Overlapped) (err error)
|
||||||
|
// This function returns 1 byte BOOLEAN rather than the 4 byte BOOL.
|
||||||
|
//sys CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) [failretval&0xff==0] = CreateSymbolicLinkW
|
||||||
|
//sys CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) [failretval&0xff==0] = CreateHardLinkW
|
||||||
|
|
||||||
// syscall interface implementation for other packages
|
// syscall interface implementation for other packages
|
||||||
|
|
||||||
|
|
@ -936,10 +940,9 @@ func Getppid() (ppid int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(brainman): fix all needed for os
|
// TODO(brainman): fix all needed for os
|
||||||
func Fchdir(fd Handle) (err error) { return EWINDOWS }
|
func Fchdir(fd Handle) (err error) { return EWINDOWS }
|
||||||
func Link(oldpath, newpath string) (err error) { return EWINDOWS }
|
func Link(oldpath, newpath string) (err error) { return EWINDOWS }
|
||||||
func Symlink(path, link string) (err error) { return EWINDOWS }
|
func Symlink(path, link string) (err error) { return EWINDOWS }
|
||||||
func Readlink(path string, buf []byte) (n int, err error) { return 0, EWINDOWS }
|
|
||||||
|
|
||||||
func Fchmod(fd Handle, mode uint32) (err error) { return EWINDOWS }
|
func Fchmod(fd Handle, mode uint32) (err error) { return EWINDOWS }
|
||||||
func Chown(path string, uid int, gid int) (err error) { return EWINDOWS }
|
func Chown(path string, uid int, gid int) (err error) { return EWINDOWS }
|
||||||
|
|
@ -965,3 +968,35 @@ func (s Signal) String() string {
|
||||||
}
|
}
|
||||||
return "signal " + itoa(int(s))
|
return "signal " + itoa(int(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadCreateSymbolicLink() error {
|
||||||
|
return procCreateSymbolicLinkW.Find()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link.
|
||||||
|
func Readlink(path string, buf []byte) (n int, err error) {
|
||||||
|
fd, err := CreateFile(StringToUTF16Ptr(path), GENERIC_READ, 0, nil, OPEN_EXISTING,
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer CloseHandle(fd)
|
||||||
|
|
||||||
|
rdbbuf := make([]byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
||||||
|
var bytesReturned uint32
|
||||||
|
err = DeviceIoControl(fd, FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
|
||||||
|
if uintptr(bytesReturned) < unsafe.Sizeof(*rdb) ||
|
||||||
|
rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK {
|
||||||
|
// the path is not a symlink but another type of reparse point
|
||||||
|
return -1, ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
s := UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rdb.PathBuffer[0]))[:rdb.PrintNameLength/2])
|
||||||
|
n = copy(buf, []byte(s))
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ var (
|
||||||
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
|
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
|
||||||
procProcess32FirstW = modkernel32.NewProc("Process32FirstW")
|
procProcess32FirstW = modkernel32.NewProc("Process32FirstW")
|
||||||
procProcess32NextW = modkernel32.NewProc("Process32NextW")
|
procProcess32NextW = modkernel32.NewProc("Process32NextW")
|
||||||
|
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
|
||||||
|
procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW")
|
||||||
|
procCreateHardLinkW = modkernel32.NewProc("CreateHardLinkW")
|
||||||
procWSAStartup = modws2_32.NewProc("WSAStartup")
|
procWSAStartup = modws2_32.NewProc("WSAStartup")
|
||||||
procWSACleanup = modws2_32.NewProc("WSACleanup")
|
procWSACleanup = modws2_32.NewProc("WSACleanup")
|
||||||
procWSAIoctl = modws2_32.NewProc("WSAIoctl")
|
procWSAIoctl = modws2_32.NewProc("WSAIoctl")
|
||||||
|
|
@ -1294,6 +1297,42 @@ func Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBufferSize uint32, outBuffer *byte, outBufferSize uint32, bytesReturned *uint32, overlapped *Overlapped) (err error) {
|
||||||
|
r1, _, e1 := Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), uintptr(ioControlCode), uintptr(unsafe.Pointer(inBuffer)), uintptr(inBufferSize), uintptr(unsafe.Pointer(outBuffer)), uintptr(outBufferSize), uintptr(unsafe.Pointer(bytesReturned)), uintptr(unsafe.Pointer(overlapped)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) {
|
||||||
|
r1, _, e1 := Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags))
|
||||||
|
if r1&0xff == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) {
|
||||||
|
r1, _, e1 := Syscall(procCreateHardLinkW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(unsafe.Pointer(existingfilename)), uintptr(reserved))
|
||||||
|
if r1&0xff == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {
|
func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {
|
||||||
r0, _, _ := Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0)
|
r0, _, _ := Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0)
|
||||||
if r0 != 0 {
|
if r0 != 0 {
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ var (
|
||||||
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
|
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
|
||||||
procProcess32FirstW = modkernel32.NewProc("Process32FirstW")
|
procProcess32FirstW = modkernel32.NewProc("Process32FirstW")
|
||||||
procProcess32NextW = modkernel32.NewProc("Process32NextW")
|
procProcess32NextW = modkernel32.NewProc("Process32NextW")
|
||||||
|
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
|
||||||
|
procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW")
|
||||||
|
procCreateHardLinkW = modkernel32.NewProc("CreateHardLinkW")
|
||||||
procWSAStartup = modws2_32.NewProc("WSAStartup")
|
procWSAStartup = modws2_32.NewProc("WSAStartup")
|
||||||
procWSACleanup = modws2_32.NewProc("WSACleanup")
|
procWSACleanup = modws2_32.NewProc("WSACleanup")
|
||||||
procWSAIoctl = modws2_32.NewProc("WSAIoctl")
|
procWSAIoctl = modws2_32.NewProc("WSAIoctl")
|
||||||
|
|
@ -1294,6 +1297,42 @@ func Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBufferSize uint32, outBuffer *byte, outBufferSize uint32, bytesReturned *uint32, overlapped *Overlapped) (err error) {
|
||||||
|
r1, _, e1 := Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), uintptr(ioControlCode), uintptr(unsafe.Pointer(inBuffer)), uintptr(inBufferSize), uintptr(unsafe.Pointer(outBuffer)), uintptr(outBufferSize), uintptr(unsafe.Pointer(bytesReturned)), uintptr(unsafe.Pointer(overlapped)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) {
|
||||||
|
r1, _, e1 := Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags))
|
||||||
|
if r1&0xff == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) {
|
||||||
|
r1, _, e1 := Syscall(procCreateHardLinkW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(unsafe.Pointer(existingfilename)), uintptr(reserved))
|
||||||
|
if r1&0xff == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {
|
func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {
|
||||||
r0, _, _ := Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0)
|
r0, _, _ := Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0)
|
||||||
if r0 != 0 {
|
if r0 != 0 {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const (
|
||||||
ERROR_OPERATION_ABORTED Errno = 995
|
ERROR_OPERATION_ABORTED Errno = 995
|
||||||
ERROR_IO_PENDING Errno = 997
|
ERROR_IO_PENDING Errno = 997
|
||||||
ERROR_NOT_FOUND Errno = 1168
|
ERROR_NOT_FOUND Errno = 1168
|
||||||
|
ERROR_PRIVILEGE_NOT_HELD Errno = 1314
|
||||||
WSAEACCES Errno = 10013
|
WSAEACCES Errno = 10013
|
||||||
WSAECONNRESET Errno = 10054
|
WSAECONNRESET Errno = 10054
|
||||||
)
|
)
|
||||||
|
|
@ -89,15 +90,16 @@ const (
|
||||||
FILE_APPEND_DATA = 0x00000004
|
FILE_APPEND_DATA = 0x00000004
|
||||||
FILE_WRITE_ATTRIBUTES = 0x00000100
|
FILE_WRITE_ATTRIBUTES = 0x00000100
|
||||||
|
|
||||||
FILE_SHARE_READ = 0x00000001
|
FILE_SHARE_READ = 0x00000001
|
||||||
FILE_SHARE_WRITE = 0x00000002
|
FILE_SHARE_WRITE = 0x00000002
|
||||||
FILE_SHARE_DELETE = 0x00000004
|
FILE_SHARE_DELETE = 0x00000004
|
||||||
FILE_ATTRIBUTE_READONLY = 0x00000001
|
FILE_ATTRIBUTE_READONLY = 0x00000001
|
||||||
FILE_ATTRIBUTE_HIDDEN = 0x00000002
|
FILE_ATTRIBUTE_HIDDEN = 0x00000002
|
||||||
FILE_ATTRIBUTE_SYSTEM = 0x00000004
|
FILE_ATTRIBUTE_SYSTEM = 0x00000004
|
||||||
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
|
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
|
||||||
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
|
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
|
||||||
FILE_ATTRIBUTE_NORMAL = 0x00000080
|
FILE_ATTRIBUTE_NORMAL = 0x00000080
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
|
||||||
|
|
||||||
INVALID_FILE_ATTRIBUTES = 0xffffffff
|
INVALID_FILE_ATTRIBUTES = 0xffffffff
|
||||||
|
|
||||||
|
|
@ -107,8 +109,9 @@ const (
|
||||||
OPEN_ALWAYS = 4
|
OPEN_ALWAYS = 4
|
||||||
TRUNCATE_EXISTING = 5
|
TRUNCATE_EXISTING = 5
|
||||||
|
|
||||||
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||||
FILE_FLAG_OVERLAPPED = 0x40000000
|
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
||||||
|
FILE_FLAG_OVERLAPPED = 0x40000000
|
||||||
|
|
||||||
HANDLE_FLAG_INHERIT = 0x00000001
|
HANDLE_FLAG_INHERIT = 0x00000001
|
||||||
STARTF_USESTDHANDLES = 0x00000100
|
STARTF_USESTDHANDLES = 0x00000100
|
||||||
|
|
@ -1066,3 +1069,24 @@ type TCPKeepalive struct {
|
||||||
Time uint32
|
Time uint32
|
||||||
Interval uint32
|
Interval uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
|
||||||
|
// SymbolicLinkReparseBuffer
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
Flags uint32
|
||||||
|
PathBuffer [1]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
FSCTL_GET_REPARSE_POINT = 0x900A8
|
||||||
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
|
||||||
|
IO_REPARSE_TAG_SYMLINK = 0xA000000C
|
||||||
|
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue