1// Copyright 2016 the Go-FUSE 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
5package nodefs
6
7import (
8	"log"
9	"sync"
10	"unsafe"
11
12	"github.com/hanwen/go-fuse/v2/fuse"
13)
14
15// openedFile stores either an open dir or an open file.
16type openedFile struct {
17	handled
18
19	WithFlags
20
21	dir *connectorDir
22}
23
24type fileSystemMount struct {
25	// Node that we were mounted on.
26	mountInode *Inode
27
28	// Parent to the mountInode.
29	parentInode *Inode
30
31	// Options for the mount.
32	options *Options
33
34	// Protects the "children" and "parents" hashmaps of the inodes
35	// within the mount.
36	// treeLock should be acquired before openFilesLock.
37	//
38	// If multiple treeLocks must be acquired, the treeLocks
39	// closer to the root must be acquired first.
40	treeLock sync.RWMutex
41
42	// Manage filehandles of open files.
43	openFiles handleMap
44
45	Debug bool
46
47	connector *FileSystemConnector
48}
49
50// Must called with lock for parent held.
51func (m *fileSystemMount) mountName() string {
52	for k, v := range m.parentInode.children {
53		if m.mountInode == v {
54			return k
55		}
56	}
57	panic("not found")
58}
59
60func (m *fileSystemMount) setOwner(attr *fuse.Attr) {
61	if m.options.Owner != nil {
62		attr.Owner = *m.options.Owner
63	}
64}
65
66func (m *fileSystemMount) fillEntry(out *fuse.EntryOut) {
67	out.SetEntryTimeout(m.options.EntryTimeout)
68	out.SetAttrTimeout(m.options.AttrTimeout)
69	m.setOwner(&out.Attr)
70	if out.Mode&fuse.S_IFDIR == 0 && out.Nlink == 0 {
71		out.Nlink = 1
72	}
73}
74
75func (m *fileSystemMount) fillAttr(out *fuse.AttrOut, nodeId uint64) {
76	out.SetTimeout(m.options.AttrTimeout)
77	m.setOwner(&out.Attr)
78	if out.Ino == 0 {
79		out.Ino = nodeId
80	}
81}
82
83func (m *fileSystemMount) getOpenedFile(h uint64) *openedFile {
84	var b *openedFile
85	if h != 0 {
86		b = (*openedFile)(unsafe.Pointer(m.openFiles.Decode(h)))
87	}
88
89	if b != nil && m.connector.debug && b.WithFlags.Description != "" {
90		log.Printf("File %d = %q", h, b.WithFlags.Description)
91	}
92	return b
93}
94
95func (m *fileSystemMount) unregisterFileHandle(handle uint64, node *Inode) *openedFile {
96	_, obj := m.openFiles.Forget(handle, 1)
97	opened := (*openedFile)(unsafe.Pointer(obj))
98	node.openFilesMutex.Lock()
99	idx := -1
100	for i, v := range node.openFiles {
101		if v == opened {
102			idx = i
103			break
104		}
105	}
106
107	l := len(node.openFiles)
108	if idx == l-1 {
109		node.openFiles[idx] = nil
110	} else {
111		node.openFiles[idx] = node.openFiles[l-1]
112	}
113	node.openFiles = node.openFiles[:l-1]
114	node.openFilesMutex.Unlock()
115
116	return opened
117}
118
119// registerFileHandle registers f or dir to have a handle.
120//
121// The handle is then used as file-handle in communications with kernel.
122//
123// If dir != nil the handle is registered for OpenDir and the inner file (see
124// below) must be nil. If dir = nil the handle is registered for regular open &
125// friends.
126//
127// f can be nil, or a WithFlags that leads to File=nil. For !OpenDir, if that
128// is the case, returned handle will be 0 to indicate a handleless open, and
129// the filesystem operations on the opened file will be routed to be served by
130// the node.
131//
132// other arguments:
133//
134//	node  - Inode for which f or dir were opened,
135//	flags - file open flags, like O_RDWR.
136func (m *fileSystemMount) registerFileHandle(node *Inode, dir *connectorDir, f File, flags uint32) (handle uint64, opened *openedFile) {
137	b := &openedFile{
138		dir: dir,
139		WithFlags: WithFlags{
140			File:      f,
141			OpenFlags: flags,
142		},
143	}
144
145	for {
146		withFlags, ok := f.(*WithFlags)
147		if !ok {
148			break
149		}
150
151		b.WithFlags.File = withFlags.File
152		b.WithFlags.FuseFlags |= withFlags.FuseFlags
153		b.WithFlags.Description += withFlags.Description
154		f = withFlags.File
155	}
156
157	// don't allow both dir and file
158	if dir != nil && b.WithFlags.File != nil {
159		panic("registerFileHandle: both dir and file are set.")
160	}
161
162	if b.WithFlags.File == nil && dir == nil {
163		// it was just WithFlags{...}, but the file itself is nil
164		return 0, b
165	}
166
167	if b.WithFlags.File != nil {
168		b.WithFlags.File.SetInode(node)
169	}
170
171	node.openFilesMutex.Lock()
172	node.openFiles = append(node.openFiles, b)
173	handle, _ = m.openFiles.Register(&b.handled)
174	node.openFilesMutex.Unlock()
175	return handle, b
176}
177
178// Creates a return entry for a non-existent path.
179func (m *fileSystemMount) negativeEntry(out *fuse.EntryOut) bool {
180	if m.options.NegativeTimeout > 0.0 {
181		out.NodeId = 0
182		out.SetEntryTimeout(m.options.NegativeTimeout)
183		return true
184	}
185	return false
186}
187