Skip to content

Commit 09b235a

Browse files
committed
Detect the user's home directory without user.Current()
user.Current() fails on Mac OS for binaries that have been cross-compiled on another OS than Mac with the message: "Error: user: Current not implemented on darwin/amd64" Since this is, for some reason unclear to me, intended behaviour by go (see: golang/go#6376) I will use Mitchell Hachimoto's go-homedir library for determining the current users home directory path. I also fixed two failing unit tests that are unrelated to this feature. Fixes issue /issues/1
1 parent cb31f6a commit 09b235a

File tree

8 files changed

+287
-6
lines changed

8 files changed

+287
-6
lines changed

src/allmark.io/modules/common/config/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import (
1313
"net"
1414
"os"
1515
"os/exec"
16-
"os/user"
1716
"path/filepath"
1817

1918
"allmark.io/modules/common/certificates"
2019
"allmark.io/modules/common/logger/loglevel"
2120
"allmark.io/modules/common/ports"
2221
"allmark.io/modules/common/util/fsutil"
2322
"github.com/abbot/go-http-auth"
23+
"github.com/mitchellh/go-homedir"
2424
)
2525

2626
// Global configuration constants.
@@ -53,6 +53,7 @@ const (
5353
DefaultUserStoreFileName = "users.htpasswd"
5454
)
5555

56+
// homeDirectory returns the current users home directory path.
5657
var homeDirectory func() string
5758

5859
// A flag indicating whether the DOCX conversion tool is available
@@ -62,13 +63,13 @@ var conversionEndpointBinding *TCPBinding
6263

6364
func init() {
6465

65-
usr, err := user.Current()
66+
homeDirPath, err := homedir.Dir()
6667
if err != nil {
6768
panic(fmt.Sprintf("Cannot determine the current users home direcotry. Error: %s", err))
6869
}
6970

7071
homeDirectory = func() string {
71-
return filepath.Clean(usr.HomeDir)
72+
return filepath.Clean(homeDirPath)
7273
}
7374

7475
// check if pandoc is available in the path

src/allmark.io/modules/services/converter/markdowntohtml/postprocessor/image_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
"allmark.io/modules/common/route"
1212
"allmark.io/modules/model"
13-
"allmark.io/modules/services/converter/markdowntohtml/common"
13+
"allmark.io/modules/services/converter/markdowntohtml/imageprovider"
1414
"allmark.io/modules/services/thumbnail"
1515
)
1616

@@ -25,7 +25,7 @@ func Test_Convert(t *testing.T) {
2525
baseRoute := route.New()
2626
files := []*model.File{}
2727
thumbnailIndex := thumbnail.EmptyIndex()
28-
imageProvider := common.NewImageProvider(pathProvider, thumbnailIndex)
28+
imageProvider := imageprovider.NewImageProvider(pathProvider, thumbnailIndex)
2929

3030
postprocessor := newImagePostprocessor(pathProvider, baseRoute, files, imageProvider)
3131

src/allmark.io/modules/web/webpaths/relative_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func Test_RelativeWebPathProvider_RootAsBasePath_Path_ReturnsPathWithLeadingSlas
1919
routesProvider := dummyRoutesProvider{routes}
2020
pathProvider := newRelativeWebPathProvider(routesProvider, baseRoute)
2121
inputPath := "ya/da/ya/da"
22-
expected := "/ya/da/ya/da"
22+
expected := "ya/da/ya/da"
2323

2424
// act
2525
result := pathProvider.Path(inputPath)

src/code.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ allmark relies on a number of third-party libraries:
2020
- [github.com/abbot/go-http-auth](github.com/abbot/go-http-auth)
2121
- [github.com/spf13/afero](github.com/spf13/afero)
2222
- [github.com/kyokomi/emoji](github.com/kyokomi/emoji)
23+
- [github.com/mitchellh/go-homedir](github.com/mitchellh/go-homedir)
2324

2425
These dependencies are not covered by the allmark copyright/license. See the respective projects for their copyright & licensing details.
2526

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2013 Mitchell Hashimoto
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# go-homedir
2+
3+
This is a Go library for detecting the user's home directory without
4+
the use of cgo, so the library can be used in cross-compilation environments.
5+
6+
Usage is incredibly simple, just call `homedir.Dir()` to get the home directory
7+
for a user, and `homedir.Expand()` to expand the `~` in a path to the home
8+
directory.
9+
10+
**Why not just use `os/user`?** The built-in `os/user` package requires
11+
cgo on Darwin systems. This means that any Go code that uses that package
12+
cannot cross compile. But 99% of the time the use for `os/user` is just to
13+
retrieve the home directory, which we can do for the current user without
14+
cgo. This library does that, enabling cross-compilation.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
func dirUnix() (string, error) {
80+
// First prefer the HOME environmental variable
81+
if home := os.Getenv("HOME"); home != "" {
82+
return home, nil
83+
}
84+
85+
// If that fails, try getent
86+
var stdout bytes.Buffer
87+
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
88+
cmd.Stdout = &stdout
89+
if err := cmd.Run(); err != nil {
90+
// If "getent" is missing, ignore it
91+
if err != exec.ErrNotFound {
92+
return "", err
93+
}
94+
} else {
95+
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
96+
// username:password:uid:gid:gecos:home:shell
97+
passwdParts := strings.SplitN(passwd, ":", 7)
98+
if len(passwdParts) > 5 {
99+
return passwdParts[5], nil
100+
}
101+
}
102+
}
103+
104+
// If all else fails, try the shell
105+
stdout.Reset()
106+
cmd = exec.Command("sh", "-c", "cd && pwd")
107+
cmd.Stdout = &stdout
108+
if err := cmd.Run(); err != nil {
109+
return "", err
110+
}
111+
112+
result := strings.TrimSpace(stdout.String())
113+
if result == "" {
114+
return "", errors.New("blank output when reading home directory")
115+
}
116+
117+
return result, nil
118+
}
119+
120+
func dirWindows() (string, error) {
121+
drive := os.Getenv("HOMEDRIVE")
122+
path := os.Getenv("HOMEPATH")
123+
home := drive + path
124+
if drive == "" || path == "" {
125+
home = os.Getenv("USERPROFILE")
126+
}
127+
if home == "" {
128+
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
129+
}
130+
131+
return home, nil
132+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package homedir
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/user"
7+
"testing"
8+
)
9+
10+
func patchEnv(key, value string) func() {
11+
bck := os.Getenv(key)
12+
deferFunc := func() {
13+
os.Setenv(key, bck)
14+
}
15+
16+
os.Setenv(key, value)
17+
return deferFunc
18+
}
19+
20+
func BenchmarkDir(b *testing.B) {
21+
// We do this for any "warmups"
22+
for i := 0; i < 10; i++ {
23+
Dir()
24+
}
25+
26+
b.ResetTimer()
27+
for i := 0; i < b.N; i++ {
28+
Dir()
29+
}
30+
}
31+
32+
func TestDir(t *testing.T) {
33+
u, err := user.Current()
34+
if err != nil {
35+
t.Fatalf("err: %s", err)
36+
}
37+
38+
dir, err := Dir()
39+
if err != nil {
40+
t.Fatalf("err: %s", err)
41+
}
42+
43+
if u.HomeDir != dir {
44+
t.Fatalf("%#v != %#v", u.HomeDir, dir)
45+
}
46+
}
47+
48+
func TestExpand(t *testing.T) {
49+
u, err := user.Current()
50+
if err != nil {
51+
t.Fatalf("err: %s", err)
52+
}
53+
54+
cases := []struct {
55+
Input string
56+
Output string
57+
Err bool
58+
}{
59+
{
60+
"/foo",
61+
"/foo",
62+
false,
63+
},
64+
65+
{
66+
"~/foo",
67+
fmt.Sprintf("%s/foo", u.HomeDir),
68+
false,
69+
},
70+
71+
{
72+
"",
73+
"",
74+
false,
75+
},
76+
77+
{
78+
"~",
79+
u.HomeDir,
80+
false,
81+
},
82+
83+
{
84+
"~foo/foo",
85+
"",
86+
true,
87+
},
88+
}
89+
90+
for _, tc := range cases {
91+
actual, err := Expand(tc.Input)
92+
if (err != nil) != tc.Err {
93+
t.Fatalf("Input: %#v\n\nErr: %s", tc.Input, err)
94+
}
95+
96+
if actual != tc.Output {
97+
t.Fatalf("Input: %#v\n\nOutput: %#v", tc.Input, actual)
98+
}
99+
}
100+
101+
DisableCache = true
102+
defer func() { DisableCache = false }()
103+
defer patchEnv("HOME", "/custom/path/")()
104+
expected := "/custom/path/foo/bar"
105+
actual, err := Expand("~/foo/bar")
106+
107+
if err != nil {
108+
t.Errorf("No error is expected, got: %v", err)
109+
} else if actual != "/custom/path/foo/bar" {
110+
t.Errorf("Expected: %v; actual: %v", expected, actual)
111+
}
112+
}

0 commit comments

Comments
 (0)