github.com/mymmsc/gox@v1.3.33/util/homedir/homedir.go (about)

     1  package homedir
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  // DisableCache will disable caching of the home directory. Caching is enabled
    16  // by default.
    17  var DisableCache bool
    18  
    19  var homedirCache string
    20  var cacheLock sync.RWMutex
    21  
    22  // Dir returns the home directory for the executing user.
    23  //
    24  // This uses an OS-specific method for discovering the home directory.
    25  // An error is returned if a home directory cannot be detected.
    26  func Dir() (string, error) {
    27  	if !DisableCache {
    28  		cacheLock.RLock()
    29  		cached := homedirCache
    30  		cacheLock.RUnlock()
    31  		if cached != "" {
    32  			return cached, nil
    33  		}
    34  	}
    35  
    36  	cacheLock.Lock()
    37  	defer cacheLock.Unlock()
    38  
    39  	var result string
    40  	var err error
    41  	if runtime.GOOS == "windows" {
    42  		result, err = dirWindows()
    43  	} else {
    44  		// Unix-like system, so just assume Unix
    45  		result, err = dirUnix()
    46  	}
    47  
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  	homedirCache = result
    52  	return result, nil
    53  }
    54  
    55  // Expand expands the path to include the home directory if the path
    56  // is prefixed with `~`. If it isn't prefixed with `~`, the path is
    57  // returned as-is.
    58  func Expand(path string) (string, error) {
    59  	if len(path) == 0 {
    60  		return path, nil
    61  	}
    62  
    63  	if path[0] != '~' {
    64  		return path, nil
    65  	}
    66  
    67  	if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
    68  		return "", errors.New("cannot expand user-specific home dir")
    69  	}
    70  
    71  	dir, err := Dir()
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  
    76  	return filepath.Join(dir, path[1:]), nil
    77  }
    78  
    79  // Reset clears the cache, forcing the next call to Dir to re-detect
    80  // the home directory. This generally never has to be called, but can be
    81  // useful in tests if you're modifying the home directory via the HOME
    82  // env var or something.
    83  func Reset() {
    84  	cacheLock.Lock()
    85  	defer cacheLock.Unlock()
    86  	homedirCache = ""
    87  }
    88  
    89  func dirUnix() (string, error) {
    90  	homeEnv := "HOME"
    91  	if runtime.GOOS == "plan9" {
    92  		// On plan9, env vars are lowercase.
    93  		homeEnv = "home"
    94  	}
    95  
    96  	// First prefer the HOME environmental variable
    97  	if home := os.Getenv(homeEnv); home != "" {
    98  		return home, nil
    99  	}
   100  
   101  	var stdout bytes.Buffer
   102  
   103  	// If that fails, try OS specific commands
   104  	if runtime.GOOS == "darwin" {
   105  		cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
   106  		cmd.Stdout = &stdout
   107  		if err := cmd.Run(); err == nil {
   108  			result := strings.TrimSpace(stdout.String())
   109  			if result != "" {
   110  				return result, nil
   111  			}
   112  		}
   113  	} else {
   114  		cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
   115  		cmd.Stdout = &stdout
   116  		if err := cmd.Run(); err != nil {
   117  			// If the error is ErrNotFound, we ignore it. Otherwise, return it.
   118  			if err != exec.ErrNotFound {
   119  				return "", err
   120  			}
   121  		} else {
   122  			if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
   123  				// username:password:uid:gid:gecos:home:shell
   124  				passwdParts := strings.SplitN(passwd, ":", 7)
   125  				if len(passwdParts) > 5 {
   126  					return passwdParts[5], nil
   127  				}
   128  			}
   129  		}
   130  	}
   131  
   132  	// If all else fails, try the shell
   133  	stdout.Reset()
   134  	cmd := exec.Command("sh", "-c", "cd && pwd")
   135  	cmd.Stdout = &stdout
   136  	if err := cmd.Run(); err != nil {
   137  		return "", err
   138  	}
   139  
   140  	result := strings.TrimSpace(stdout.String())
   141  	if result == "" {
   142  		return "", errors.New("blank output when reading home directory")
   143  	}
   144  
   145  	return result, nil
   146  }
   147  
   148  func dirWindows() (string, error) {
   149  	// First prefer the HOME environmental variable
   150  	if home := os.Getenv("HOME"); home != "" {
   151  		return home, nil
   152  	}
   153  
   154  	// Prefer standard environment variable USERPROFILE
   155  	if home := os.Getenv("USERPROFILE"); home != "" {
   156  		return home, nil
   157  	}
   158  
   159  	drive := os.Getenv("HOMEDRIVE")
   160  	path := os.Getenv("HOMEPATH")
   161  	home := drive + path
   162  	if drive == "" || path == "" {
   163  		return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
   164  	}
   165  
   166  	return home, nil
   167  }