...

Source file src/github.com/Microsoft/hcsshim/internal/safefile/safeopen.go

Documentation: github.com/Microsoft/hcsshim/internal/safefile

     1  //go:build windows
     2  
     3  package safefile
     4  
     5  import (
     6  	"errors"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"syscall"
    12  	"unicode/utf16"
    13  	"unsafe"
    14  
    15  	"github.com/Microsoft/hcsshim/internal/longpath"
    16  	"github.com/Microsoft/hcsshim/internal/winapi"
    17  
    18  	winio "github.com/Microsoft/go-winio"
    19  )
    20  
    21  func OpenRoot(path string) (*os.File, error) {
    22  	longpath, err := longpath.LongAbs(path)
    23  	if err != nil {
    24  		return nil, err
    25  	}
    26  	return winio.OpenForBackup(longpath, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, syscall.OPEN_EXISTING)
    27  }
    28  
    29  func cleanGoStringRelativePath(path string) (string, error) {
    30  	path = filepath.Clean(path)
    31  	if strings.Contains(path, ":") {
    32  		// Since alternate data streams must follow the file they
    33  		// are attached to, finding one here (out of order) is invalid.
    34  		return "", errors.New("path contains invalid character `:`")
    35  	}
    36  	fspath := filepath.FromSlash(path)
    37  	if len(fspath) > 0 && fspath[0] == '\\' {
    38  		return "", errors.New("expected relative path")
    39  	}
    40  	return fspath, nil
    41  }
    42  
    43  func ntRelativePath(path string) ([]uint16, error) {
    44  	fspath, err := cleanGoStringRelativePath(path)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	path16 := utf16.Encode(([]rune)(fspath))
    50  	if len(path16) > 32767 {
    51  		return nil, syscall.ENAMETOOLONG
    52  	}
    53  
    54  	return path16, nil
    55  }
    56  
    57  // openRelativeInternal opens a relative path from the given root, failing if
    58  // any of the intermediate path components are reparse points.
    59  func openRelativeInternal(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) {
    60  	var (
    61  		h    uintptr
    62  		iosb winapi.IOStatusBlock
    63  		oa   winapi.ObjectAttributes
    64  	)
    65  
    66  	cleanRelativePath, err := cleanGoStringRelativePath(path)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	if root == nil || root.Fd() == 0 {
    72  		return nil, errors.New("missing root directory")
    73  	}
    74  
    75  	pathUnicode, err := winapi.NewUnicodeString(cleanRelativePath)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	oa.Length = unsafe.Sizeof(oa)
    81  	oa.ObjectName = pathUnicode
    82  	oa.RootDirectory = uintptr(root.Fd())
    83  	oa.Attributes = winapi.OBJ_DONT_REPARSE
    84  	status := winapi.NtCreateFile(
    85  		&h,
    86  		accessMask|syscall.SYNCHRONIZE,
    87  		&oa,
    88  		&iosb,
    89  		nil,
    90  		0,
    91  		shareFlags,
    92  		createDisposition,
    93  		winapi.FILE_OPEN_FOR_BACKUP_INTENT|winapi.FILE_SYNCHRONOUS_IO_NONALERT|flags,
    94  		nil,
    95  		0,
    96  	)
    97  	if status != 0 {
    98  		return nil, winapi.RtlNtStatusToDosError(status)
    99  	}
   100  
   101  	fullPath, err := longpath.LongAbs(filepath.Join(root.Name(), path))
   102  	if err != nil {
   103  		syscall.Close(syscall.Handle(h))
   104  		return nil, err
   105  	}
   106  
   107  	return os.NewFile(h, fullPath), nil
   108  }
   109  
   110  // OpenRelative opens a relative path from the given root, failing if
   111  // any of the intermediate path components are reparse points.
   112  func OpenRelative(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) {
   113  	f, err := openRelativeInternal(path, root, accessMask, shareFlags, createDisposition, flags)
   114  	if err != nil {
   115  		err = &os.PathError{Op: "open", Path: filepath.Join(root.Name(), path), Err: err}
   116  	}
   117  	return f, err
   118  }
   119  
   120  // LinkRelative creates a hard link from oldname to newname (relative to oldroot
   121  // and newroot), failing if any of the intermediate path components are reparse
   122  // points.
   123  func LinkRelative(oldname string, oldroot *os.File, newname string, newroot *os.File) error {
   124  	// Open the old file.
   125  	oldf, err := openRelativeInternal(
   126  		oldname,
   127  		oldroot,
   128  		syscall.FILE_WRITE_ATTRIBUTES,
   129  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   130  		winapi.FILE_OPEN,
   131  		0,
   132  	)
   133  	if err != nil {
   134  		return &os.LinkError{Op: "link", Old: filepath.Join(oldroot.Name(), oldname), New: filepath.Join(newroot.Name(), newname), Err: err}
   135  	}
   136  	defer oldf.Close()
   137  
   138  	// Open the parent of the new file.
   139  	var parent *os.File
   140  	parentPath := filepath.Dir(newname)
   141  	if parentPath != "." {
   142  		parent, err = openRelativeInternal(
   143  			parentPath,
   144  			newroot,
   145  			syscall.GENERIC_READ,
   146  			syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   147  			winapi.FILE_OPEN,
   148  			winapi.FILE_DIRECTORY_FILE)
   149  		if err != nil {
   150  			return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: err}
   151  		}
   152  		defer parent.Close()
   153  
   154  		fi, err := winio.GetFileBasicInfo(parent)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		if (fi.FileAttributes & syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
   159  			return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: winapi.RtlNtStatusToDosError(winapi.STATUS_REPARSE_POINT_ENCOUNTERED)}
   160  		}
   161  	} else {
   162  		parent = newroot
   163  	}
   164  
   165  	// Issue an NT call to create the link. This will be safe because NT will
   166  	// not open any more directories to create the link, so it cannot walk any
   167  	// more reparse points.
   168  	newbase := filepath.Base(newname)
   169  	newbase16, err := ntRelativePath(newbase)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	size := int(unsafe.Offsetof(winapi.FileLinkInformation{}.FileName)) + len(newbase16)*2
   175  	linkinfoBuffer := winapi.LocalAlloc(0, size)
   176  	defer winapi.LocalFree(linkinfoBuffer)
   177  
   178  	linkinfo := (*winapi.FileLinkInformation)(unsafe.Pointer(linkinfoBuffer))
   179  	linkinfo.RootDirectory = parent.Fd()
   180  	linkinfo.FileNameLength = uint32(len(newbase16) * 2)
   181  	copy(winapi.Uint16BufferToSlice(&linkinfo.FileName[0], len(newbase16)), newbase16)
   182  
   183  	var iosb winapi.IOStatusBlock
   184  	status := winapi.NtSetInformationFile(
   185  		oldf.Fd(),
   186  		&iosb,
   187  		linkinfoBuffer,
   188  		uint32(size),
   189  		winapi.FileLinkInformationClass,
   190  	)
   191  	if status != 0 {
   192  		return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(parent.Name(), newbase), Err: winapi.RtlNtStatusToDosError(status)}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  // deleteOnClose marks a file to be deleted when the handle is closed.
   199  func deleteOnClose(f *os.File) error {
   200  	disposition := winapi.FileDispositionInformationEx{Flags: winapi.FILE_DISPOSITION_DELETE}
   201  	var iosb winapi.IOStatusBlock
   202  	status := winapi.NtSetInformationFile(
   203  		f.Fd(),
   204  		&iosb,
   205  		uintptr(unsafe.Pointer(&disposition)),
   206  		uint32(unsafe.Sizeof(disposition)),
   207  		winapi.FileDispositionInformationExClass,
   208  	)
   209  	if status != 0 {
   210  		return winapi.RtlNtStatusToDosError(status)
   211  	}
   212  	return nil
   213  }
   214  
   215  // clearReadOnly clears the readonly attribute on a file.
   216  func clearReadOnly(f *os.File) error {
   217  	bi, err := winio.GetFileBasicInfo(f)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	if bi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY == 0 {
   222  		return nil
   223  	}
   224  	sbi := winio.FileBasicInfo{
   225  		FileAttributes: bi.FileAttributes &^ syscall.FILE_ATTRIBUTE_READONLY,
   226  	}
   227  	if sbi.FileAttributes == 0 {
   228  		sbi.FileAttributes = syscall.FILE_ATTRIBUTE_NORMAL
   229  	}
   230  	return winio.SetFileBasicInfo(f, &sbi)
   231  }
   232  
   233  // RemoveRelative removes a file or directory relative to a root, failing if any
   234  // intermediate path components are reparse points.
   235  func RemoveRelative(path string, root *os.File) error {
   236  	f, err := openRelativeInternal(
   237  		path,
   238  		root,
   239  		winapi.FILE_READ_ATTRIBUTES|winapi.FILE_WRITE_ATTRIBUTES|winapi.DELETE,
   240  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   241  		winapi.FILE_OPEN,
   242  		winapi.FILE_OPEN_REPARSE_POINT)
   243  	if err == nil {
   244  		defer f.Close()
   245  		err = deleteOnClose(f)
   246  		if err == syscall.ERROR_ACCESS_DENIED {
   247  			// Maybe the file is marked readonly. Clear the bit and retry.
   248  			_ = clearReadOnly(f)
   249  			err = deleteOnClose(f)
   250  		}
   251  	}
   252  	if err != nil {
   253  		return &os.PathError{Op: "remove", Path: filepath.Join(root.Name(), path), Err: err}
   254  	}
   255  	return nil
   256  }
   257  
   258  // RemoveAllRelative removes a directory tree relative to a root, failing if any
   259  // intermediate path components are reparse points.
   260  func RemoveAllRelative(path string, root *os.File) error {
   261  	fi, err := LstatRelative(path, root)
   262  	if err != nil {
   263  		if os.IsNotExist(err) {
   264  			return nil
   265  		}
   266  		return err
   267  	}
   268  	fileAttributes := fi.Sys().(*syscall.Win32FileAttributeData).FileAttributes
   269  	if fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 || fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   270  		// If this is a reparse point, it can't have children. Simple remove will do.
   271  		err := RemoveRelative(path, root)
   272  		if err == nil || os.IsNotExist(err) {
   273  			return nil
   274  		}
   275  		return err
   276  	}
   277  
   278  	// It is necessary to use os.Open as Readdirnames does not work with
   279  	// OpenRelative. This is safe because the above lstatrelative fails
   280  	// if the target is outside the root, and we know this is not a
   281  	// symlink from the above FILE_ATTRIBUTE_REPARSE_POINT check.
   282  	fd, err := os.Open(filepath.Join(root.Name(), path))
   283  	if err != nil {
   284  		if os.IsNotExist(err) {
   285  			// Race. It was deleted between the Lstat and Open.
   286  			// Return nil per RemoveAll's docs.
   287  			return nil
   288  		}
   289  		return err
   290  	}
   291  
   292  	// Remove contents & return first error.
   293  	for {
   294  		names, err1 := fd.Readdirnames(100)
   295  		for _, name := range names {
   296  			err1 := RemoveAllRelative(path+string(os.PathSeparator)+name, root)
   297  			if err == nil {
   298  				err = err1
   299  			}
   300  		}
   301  		if err1 == io.EOF {
   302  			break
   303  		}
   304  		// If Readdirnames returned an error, use it.
   305  		if err == nil {
   306  			err = err1
   307  		}
   308  		if len(names) == 0 {
   309  			break
   310  		}
   311  	}
   312  	fd.Close()
   313  
   314  	// Remove directory.
   315  	err1 := RemoveRelative(path, root)
   316  	if err1 == nil || os.IsNotExist(err1) {
   317  		return nil
   318  	}
   319  	if err == nil {
   320  		err = err1
   321  	}
   322  	return err
   323  }
   324  
   325  // MkdirRelative creates a directory relative to a root, failing if any
   326  // intermediate path components are reparse points.
   327  func MkdirRelative(path string, root *os.File) error {
   328  	f, err := openRelativeInternal(
   329  		path,
   330  		root,
   331  		0,
   332  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   333  		winapi.FILE_CREATE,
   334  		winapi.FILE_DIRECTORY_FILE)
   335  	if err == nil {
   336  		f.Close()
   337  	} else {
   338  		err = &os.PathError{Op: "mkdir", Path: filepath.Join(root.Name(), path), Err: err}
   339  	}
   340  	return err
   341  }
   342  
   343  // MkdirAllRelative creates each directory in the path relative to a root, failing if
   344  // any existing intermediate path components are reparse points.
   345  func MkdirAllRelative(path string, root *os.File) error {
   346  	pathParts := strings.Split(filepath.Clean(path), (string)(filepath.Separator))
   347  	for index := range pathParts {
   348  		partialPath := filepath.Join(pathParts[0 : index+1]...)
   349  		stat, err := LstatRelative(partialPath, root)
   350  
   351  		if err != nil {
   352  			if os.IsNotExist(err) {
   353  				if err := MkdirRelative(partialPath, root); err != nil {
   354  					return err
   355  				}
   356  				continue
   357  			}
   358  			return err
   359  		}
   360  
   361  		if !stat.IsDir() {
   362  			fullPath := filepath.Join(root.Name(), partialPath)
   363  			return &os.PathError{Op: "mkdir", Path: fullPath, Err: syscall.ENOTDIR}
   364  		}
   365  	}
   366  
   367  	return nil
   368  }
   369  
   370  // LstatRelative performs a stat operation on a file relative to a root, failing
   371  // if any intermediate path components are reparse points.
   372  func LstatRelative(path string, root *os.File) (os.FileInfo, error) {
   373  	f, err := openRelativeInternal(
   374  		path,
   375  		root,
   376  		winapi.FILE_READ_ATTRIBUTES,
   377  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   378  		winapi.FILE_OPEN,
   379  		winapi.FILE_OPEN_REPARSE_POINT)
   380  	if err != nil {
   381  		return nil, &os.PathError{Op: "stat", Path: filepath.Join(root.Name(), path), Err: err}
   382  	}
   383  	defer f.Close()
   384  	return f.Stat()
   385  }
   386  
   387  // EnsureNotReparsePointRelative validates that a given file (relative to a
   388  // root) and all intermediate path components are not a reparse points.
   389  func EnsureNotReparsePointRelative(path string, root *os.File) error {
   390  	// Perform an open with OBJ_DONT_REPARSE but without specifying FILE_OPEN_REPARSE_POINT.
   391  	f, err := OpenRelative(
   392  		path,
   393  		root,
   394  		0,
   395  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   396  		winapi.FILE_OPEN,
   397  		0)
   398  	if err != nil {
   399  		return err
   400  	}
   401  	f.Close()
   402  	return nil
   403  }
   404  

View as plain text