Home | History | Annotate | Download | only in os
      1 // Copyright 2009 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package os
      6 
      7 import (
      8 	"internal/syscall/windows"
      9 	"syscall"
     10 	"unsafe"
     11 )
     12 
     13 // Stat returns the FileInfo structure describing file.
     14 // If there is an error, it will be of type *PathError.
     15 func (file *File) Stat() (FileInfo, error) {
     16 	if file == nil {
     17 		return nil, ErrInvalid
     18 	}
     19 
     20 	if file.isdir() {
     21 		// I don't know any better way to do that for directory
     22 		return Stat(file.dirinfo.path)
     23 	}
     24 	if file.name == DevNull {
     25 		return &devNullStat, nil
     26 	}
     27 
     28 	ft, err := file.pfd.GetFileType()
     29 	if err != nil {
     30 		return nil, &PathError{"GetFileType", file.name, err}
     31 	}
     32 	switch ft {
     33 	case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
     34 		return &fileStat{name: basename(file.name), filetype: ft}, nil
     35 	}
     36 
     37 	var d syscall.ByHandleFileInformation
     38 	err = file.pfd.GetFileInformationByHandle(&d)
     39 	if err != nil {
     40 		return nil, &PathError{"GetFileInformationByHandle", file.name, err}
     41 	}
     42 	return &fileStat{
     43 		name: basename(file.name),
     44 		sys: syscall.Win32FileAttributeData{
     45 			FileAttributes: d.FileAttributes,
     46 			CreationTime:   d.CreationTime,
     47 			LastAccessTime: d.LastAccessTime,
     48 			LastWriteTime:  d.LastWriteTime,
     49 			FileSizeHigh:   d.FileSizeHigh,
     50 			FileSizeLow:    d.FileSizeLow,
     51 		},
     52 		filetype: ft,
     53 		vol:      d.VolumeSerialNumber,
     54 		idxhi:    d.FileIndexHigh,
     55 		idxlo:    d.FileIndexLow,
     56 	}, nil
     57 }
     58 
     59 // statNolog implements Stat for Windows.
     60 func statNolog(name string) (FileInfo, error) {
     61 	if len(name) == 0 {
     62 		return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
     63 	}
     64 	if name == DevNull {
     65 		return &devNullStat, nil
     66 	}
     67 	namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
     68 	if err != nil {
     69 		return nil, &PathError{"Stat", name, err}
     70 	}
     71 	// Apparently (see https://golang.org/issues/19922#issuecomment-300031421)
     72 	// GetFileAttributesEx is fastest approach to get file info.
     73 	// It does not work for symlinks. But symlinks are rare,
     74 	// so try GetFileAttributesEx first.
     75 	var fs fileStat
     76 	err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
     77 	if err == nil && fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
     78 		fs.path = name
     79 		if !isAbs(fs.path) {
     80 			fs.path, err = syscall.FullPath(fs.path)
     81 			if err != nil {
     82 				return nil, &PathError{"FullPath", name, err}
     83 			}
     84 		}
     85 		fs.name = basename(name)
     86 		return &fs, nil
     87 	}
     88 	// Use Windows I/O manager to dereference the symbolic link, as per
     89 	// https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
     90 	h, err := syscall.CreateFile(namep, 0, 0, nil,
     91 		syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
     92 	if err != nil {
     93 		if err == windows.ERROR_SHARING_VIOLATION {
     94 			// try FindFirstFile now that CreateFile failed
     95 			return statWithFindFirstFile(name, namep)
     96 		}
     97 		return nil, &PathError{"CreateFile", name, err}
     98 	}
     99 	defer syscall.CloseHandle(h)
    100 
    101 	var d syscall.ByHandleFileInformation
    102 	err = syscall.GetFileInformationByHandle(h, &d)
    103 	if err != nil {
    104 		return nil, &PathError{"GetFileInformationByHandle", name, err}
    105 	}
    106 	return &fileStat{
    107 		name: basename(name),
    108 		sys: syscall.Win32FileAttributeData{
    109 			FileAttributes: d.FileAttributes,
    110 			CreationTime:   d.CreationTime,
    111 			LastAccessTime: d.LastAccessTime,
    112 			LastWriteTime:  d.LastWriteTime,
    113 			FileSizeHigh:   d.FileSizeHigh,
    114 			FileSizeLow:    d.FileSizeLow,
    115 		},
    116 		vol:   d.VolumeSerialNumber,
    117 		idxhi: d.FileIndexHigh,
    118 		idxlo: d.FileIndexLow,
    119 		// fileStat.path is used by os.SameFile to decide if it needs
    120 		// to fetch vol, idxhi and idxlo. But these are already set,
    121 		// so set fileStat.path to "" to prevent os.SameFile doing it again.
    122 		// Also do not set fileStat.filetype, because it is only used for
    123 		// console and stdin/stdout. But you cannot call os.Stat for these.
    124 	}, nil
    125 }
    126 
    127 // statWithFindFirstFile is used by Stat to handle special case of statting
    128 // c:\pagefile.sys. We might discover that other files need similar treatment.
    129 func statWithFindFirstFile(name string, namep *uint16) (FileInfo, error) {
    130 	var fd syscall.Win32finddata
    131 	h, err := syscall.FindFirstFile(namep, &fd)
    132 	if err != nil {
    133 		return nil, &PathError{"FindFirstFile", name, err}
    134 	}
    135 	syscall.FindClose(h)
    136 
    137 	fullpath := name
    138 	if !isAbs(fullpath) {
    139 		fullpath, err = syscall.FullPath(fullpath)
    140 		if err != nil {
    141 			return nil, &PathError{"FullPath", name, err}
    142 		}
    143 	}
    144 	return &fileStat{
    145 		name: basename(name),
    146 		path: fullpath,
    147 		sys: syscall.Win32FileAttributeData{
    148 			FileAttributes: fd.FileAttributes,
    149 			CreationTime:   fd.CreationTime,
    150 			LastAccessTime: fd.LastAccessTime,
    151 			LastWriteTime:  fd.LastWriteTime,
    152 			FileSizeHigh:   fd.FileSizeHigh,
    153 			FileSizeLow:    fd.FileSizeLow,
    154 		},
    155 	}, nil
    156 }
    157 
    158 // lstatNolog implements Lstat for Windows.
    159 func lstatNolog(name string) (FileInfo, error) {
    160 	if len(name) == 0 {
    161 		return nil, &PathError{"Lstat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
    162 	}
    163 	if name == DevNull {
    164 		return &devNullStat, nil
    165 	}
    166 	fs := &fileStat{name: basename(name)}
    167 	namep, e := syscall.UTF16PtrFromString(fixLongPath(name))
    168 	if e != nil {
    169 		return nil, &PathError{"Lstat", name, e}
    170 	}
    171 	e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
    172 	if e != nil {
    173 		if e != windows.ERROR_SHARING_VIOLATION {
    174 			return nil, &PathError{"GetFileAttributesEx", name, e}
    175 		}
    176 		// try FindFirstFile now that GetFileAttributesEx failed
    177 		return statWithFindFirstFile(name, namep)
    178 	}
    179 	fs.path = name
    180 	if !isAbs(fs.path) {
    181 		fs.path, e = syscall.FullPath(fs.path)
    182 		if e != nil {
    183 			return nil, &PathError{"FullPath", name, e}
    184 		}
    185 	}
    186 	return fs, nil
    187 }
    188