1// Copyright 2016 Keybase Inc. All rights reserved.
2// Use of this source code is governed by a BSD
3// license that can be found in the LICENSE file.
4
5package libdokan
6
7import (
8	"strings"
9	"time"
10
11	"github.com/keybase/client/go/kbfs/data"
12	"github.com/keybase/client/go/kbfs/dokan"
13	"github.com/keybase/client/go/kbfs/idutil"
14	"github.com/keybase/client/go/kbfs/kbfsmd"
15	"golang.org/x/net/context"
16)
17
18const (
19	// PublicName is the name of the parent of all public top-level folders.
20	PublicName = "public"
21
22	// PrivateName is the name of the parent of all private top-level folders.
23	PrivateName = "private"
24
25	// TeamName is the name of the parent of all team top-level folders.
26	TeamName = "team"
27
28	// CtxOpID is the display name for the unique operation Dokan ID tag.
29	CtxOpID = "DID"
30
31	// WrongUserErrorFileName is the name of error directory for other users.
32	WrongUserErrorFileName = `kbfs.access.denied.for.other.windows.users.txt`
33
34	// WrongUserErrorContents is the contents of the file.
35	WrongUserErrorContents = `Access to KBFS is limited to the windows user (sid) running KBFS.`
36)
37
38// CtxTagKey is the type used for unique context tags
39type CtxTagKey int
40
41const (
42	// CtxIDKey is the type of the tag for unique operation IDs.
43	CtxIDKey CtxTagKey = iota
44)
45
46// eiToStat converts from a libkbfs.EntryInfo and error to a *dokan.Stat and error.
47// Note that handling symlinks to directories requires extra processing not done here.
48func eiToStat(ei data.EntryInfo, err error) (*dokan.Stat, error) {
49	if err != nil {
50		return nil, errToDokan(err)
51	}
52	st := &dokan.Stat{}
53	fillStat(st, &ei)
54	return st, nil
55}
56
57// fillStat fill a dokan.Stat from a libkbfs.DirEntry.
58// Note that handling symlinks to directories requires extra processing not done here.
59func fillStat(a *dokan.Stat, de *data.EntryInfo) {
60	a.FileSize = int64(de.Size)
61	a.LastWrite = time.Unix(0, de.Mtime)
62	a.LastAccess = a.LastWrite
63	a.Creation = time.Unix(0, de.Ctime)
64	switch de.Type {
65	case data.File, data.Exec:
66		a.FileAttributes = dokan.FileAttributeNormal
67	case data.Dir:
68		a.FileAttributes = dokan.FileAttributeDirectory
69	case data.Sym:
70		a.FileAttributes = dokan.FileAttributeReparsePoint
71		a.ReparsePointTag = dokan.IOReparseTagSymlink
72	}
73}
74
75// addFileAttribute adds a file attribute to the stat struct.
76func addFileAttribute(a *dokan.Stat, fa dokan.FileAttribute) {
77	// FileAttributeNormal is valid only if no other attribute is set.
78	// Thus clear the normal flag (if set) from the attributes and or
79	// the new flag.
80	a.FileAttributes = (a.FileAttributes &^ dokan.FileAttributeNormal) | fa
81}
82
83// errToDokan makes some libkbfs errors easier to digest in dokan. Not needed in most places.
84func errToDokan(err error) error {
85	switch err.(type) {
86	case idutil.NoSuchNameError:
87		return dokan.ErrObjectNameNotFound
88	case idutil.NoSuchUserError:
89		return dokan.ErrObjectNameNotFound
90	case kbfsmd.ServerErrorUnauthorized:
91		return dokan.ErrAccessDenied
92	case nil:
93		return nil
94	}
95	return err
96}
97
98// defaultDirectoryInformation returns default directory information.
99func defaultDirectoryInformation() (*dokan.Stat, error) {
100	var st dokan.Stat
101	st.FileAttributes = dokan.FileAttributeDirectory
102	return &st, nil
103}
104
105// defaultFileInformation returns default file information.
106func defaultFileInformation() (*dokan.Stat, error) {
107	var st dokan.Stat
108	st.FileAttributes = dokan.FileAttributeNormal
109	return &st, nil
110}
111
112// defaultSymlinkFileInformation returns default symlink to file information.
113func defaultSymlinkFileInformation() (*dokan.Stat, error) {
114	var st dokan.Stat
115	st.FileAttributes = dokan.FileAttributeReparsePoint
116	st.ReparsePointTag = dokan.IOReparseTagSymlink
117	return &st, nil
118}
119
120// defaultSymlinkDirInformation returns default symlink to directory information.
121func defaultSymlinkDirInformation() (*dokan.Stat, error) {
122	var st dokan.Stat
123	st.FileAttributes = dokan.FileAttributeReparsePoint | dokan.FileAttributeDirectory
124	st.ReparsePointTag = dokan.IOReparseTagSymlink
125	return &st, nil
126}
127
128// lowerTranslateCandidate returns whether a path components
129// has a (different) lowercase translation.
130func lowerTranslateCandidate(oc *openContext, s string) string {
131	if !oc.isUppercasePath {
132		return ""
133	}
134	c := strings.ToLower(s)
135	if c == s {
136		return ""
137	}
138	return c
139}
140
141func stringReadFile(contents string) dokan.File {
142	return &stringFile{data: contents}
143}
144
145type stringFile struct {
146	emptyFile
147	data string
148}
149
150// GetFileInformation does stats for dokan.
151func (s *stringFile) GetFileInformation(ctx context.Context, fi *dokan.FileInfo) (*dokan.Stat, error) {
152	a, err := defaultFileInformation()
153	if err != nil {
154		return nil, err
155	}
156	a.FileAttributes |= dokan.FileAttributeReadonly
157	a.FileSize = int64(len(s.data))
158	t := time.Now()
159	a.LastWrite = t
160	a.LastAccess = t
161	a.Creation = t
162	return a, nil
163}
164
165// ReadFile does reads for dokan.
166func (s *stringFile) ReadFile(ctx context.Context, fi *dokan.FileInfo, bs []byte, offset int64) (int, error) {
167	data := s.data
168	if offset >= int64(len(data)) {
169		return 0, nil
170	}
171
172	data = data[int(offset):]
173
174	return copy(bs, data), nil
175}
176