Source file src/os/getwd.go

     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  	"runtime"
     9  	"sync"
    10  	"syscall"
    11  )
    12  
    13  var getwdCache struct {
    14  	sync.Mutex
    15  	dir string
    16  }
    17  
    18  // Getwd returns an absolute path name corresponding to the
    19  // current directory. If the current directory can be
    20  // reached via multiple paths (due to symbolic links),
    21  // Getwd may return any one of them.
    22  //
    23  // On Unix platforms, if the environment variable PWD
    24  // provides an absolute name, and it is a name of the
    25  // current directory, it is returned.
    26  func Getwd() (dir string, err error) {
    27  	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
    28  		// Use syscall.Getwd directly for
    29  		//   - plan9: see reasons in CL 89575;
    30  		//   - windows: syscall implementation is sufficient,
    31  		//     and we should not rely on $PWD.
    32  		dir, err = syscall.Getwd()
    33  		return dir, NewSyscallError("getwd", err)
    34  	}
    35  
    36  	// Clumsy but widespread kludge:
    37  	// if $PWD is set and matches ".", use it.
    38  	var dot FileInfo
    39  	dir = Getenv("PWD")
    40  	if len(dir) > 0 && dir[0] == '/' {
    41  		dot, err = statNolog(".")
    42  		if err != nil {
    43  			return "", err
    44  		}
    45  		d, err := statNolog(dir)
    46  		if err == nil && SameFile(dot, d) {
    47  			return dir, nil
    48  		}
    49  		// If err is ENAMETOOLONG here, the syscall.Getwd below will
    50  		// fail with the same error, too, but let's give it a try
    51  		// anyway as the fallback code is much slower.
    52  	}
    53  
    54  	// If the operating system provides a Getwd call, use it.
    55  	if syscall.ImplementsGetwd {
    56  		for {
    57  			dir, err = syscall.Getwd()
    58  			if err != syscall.EINTR {
    59  				break
    60  			}
    61  		}
    62  		// Linux returns ENAMETOOLONG if the result is too long.
    63  		// Some BSD systems appear to return EINVAL.
    64  		// FreeBSD systems appear to use ENOMEM
    65  		// Solaris appears to use ERANGE.
    66  		if err != syscall.ENAMETOOLONG && err != syscall.EINVAL && err != errERANGE && err != errENOMEM {
    67  			return dir, NewSyscallError("getwd", err)
    68  		}
    69  	}
    70  
    71  	// We're trying to find our way back to ".".
    72  	if dot == nil {
    73  		dot, err = statNolog(".")
    74  		if err != nil {
    75  			return "", err
    76  		}
    77  	}
    78  	// Apply same kludge but to cached dir instead of $PWD.
    79  	getwdCache.Lock()
    80  	dir = getwdCache.dir
    81  	getwdCache.Unlock()
    82  	if len(dir) > 0 {
    83  		d, err := statNolog(dir)
    84  		if err == nil && SameFile(dot, d) {
    85  			return dir, nil
    86  		}
    87  	}
    88  
    89  	// Root is a special case because it has no parent
    90  	// and ends in a slash.
    91  	root, err := statNolog("/")
    92  	if err != nil {
    93  		// Can't stat root - no hope.
    94  		return "", err
    95  	}
    96  	if SameFile(root, dot) {
    97  		return "/", nil
    98  	}
    99  
   100  	// General algorithm: find name in parent
   101  	// and then find name of parent. Each iteration
   102  	// adds /name to the beginning of dir.
   103  	dir = ""
   104  	for parent := ".."; ; parent = "../" + parent {
   105  		if len(parent) >= 1024 { // Sanity check
   106  			return "", NewSyscallError("getwd", syscall.ENAMETOOLONG)
   107  		}
   108  		fd, err := openDirNolog(parent)
   109  		if err != nil {
   110  			return "", err
   111  		}
   112  
   113  		for {
   114  			names, err := fd.Readdirnames(100)
   115  			if err != nil {
   116  				fd.Close()
   117  				// Readdirnames can return io.EOF or other error.
   118  				// In any case, we're here because syscall.Getwd
   119  				// is not implemented or failed with ENAMETOOLONG,
   120  				// so return the most sensible error.
   121  				if syscall.ImplementsGetwd {
   122  					return "", NewSyscallError("getwd", syscall.ENAMETOOLONG)
   123  				}
   124  				return "", NewSyscallError("getwd", errENOSYS)
   125  			}
   126  			for _, name := range names {
   127  				d, _ := lstatNolog(parent + "/" + name)
   128  				if SameFile(d, dot) {
   129  					dir = "/" + name + dir
   130  					goto Found
   131  				}
   132  			}
   133  		}
   134  
   135  	Found:
   136  		pd, err := fd.Stat()
   137  		fd.Close()
   138  		if err != nil {
   139  			return "", err
   140  		}
   141  		if SameFile(pd, root) {
   142  			break
   143  		}
   144  		// Set up for next round.
   145  		dot = pd
   146  	}
   147  
   148  	// Save answer as hint to avoid the expensive path next time.
   149  	getwdCache.Lock()
   150  	getwdCache.dir = dir
   151  	getwdCache.Unlock()
   152  
   153  	return dir, nil
   154  }
   155  

View as plain text