2015-03-24 06:24:00 +03:00
|
|
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2015-07-27 07:21:36 +03:00
|
|
|
package fuse
|
2015-03-24 06:24:00 +03:00
|
|
|
|
2015-03-24 06:36:44 +03:00
|
|
|
import (
|
2015-07-24 01:02:49 +03:00
|
|
|
"bytes"
|
2015-07-24 03:04:12 +03:00
|
|
|
"errors"
|
2015-07-27 08:55:14 +03:00
|
|
|
"fmt"
|
2015-07-24 03:54:09 +03:00
|
|
|
"os"
|
2015-07-29 04:02:29 +03:00
|
|
|
"reflect"
|
2015-07-24 03:54:09 +03:00
|
|
|
"syscall"
|
2015-03-24 07:00:05 +03:00
|
|
|
"time"
|
2015-07-24 00:53:16 +03:00
|
|
|
"unsafe"
|
2015-03-24 07:00:05 +03:00
|
|
|
|
2015-07-27 07:30:34 +03:00
|
|
|
"github.com/jacobsa/fuse/fuseops"
|
2015-07-24 07:33:27 +03:00
|
|
|
"github.com/jacobsa/fuse/internal/buffer"
|
2015-07-23 09:22:32 +03:00
|
|
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
2015-03-24 06:36:44 +03:00
|
|
|
)
|
2015-03-24 06:24:00 +03:00
|
|
|
|
2015-07-27 08:55:14 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Incoming messages
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-07-27 08:43:41 +03:00
|
|
|
// Convert a kernel message to an appropriate op. If the op is unknown, a
|
|
|
|
// special unexported type will be used.
|
2015-05-05 03:00:18 +03:00
|
|
|
//
|
2015-07-27 07:30:34 +03:00
|
|
|
// The caller is responsible for arranging for the message to be destroyed.
|
|
|
|
func convertInMessage(
|
2015-07-29 04:02:29 +03:00
|
|
|
inMsg *buffer.InMessage,
|
|
|
|
outMsg *buffer.OutMessage,
|
2015-07-27 08:43:41 +03:00
|
|
|
protocol fusekernel.Protocol) (o interface{}, err error) {
|
2015-07-29 04:02:29 +03:00
|
|
|
switch inMsg.Header().Opcode {
|
2015-07-23 09:40:27 +03:00
|
|
|
case fusekernel.OpLookup:
|
2015-07-29 04:02:29 +03:00
|
|
|
buf := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-23 09:40:27 +03:00
|
|
|
n := len(buf)
|
|
|
|
if n == 0 || buf[n-1] != '\x00' {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpLookup")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-23 09:40:27 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.LookUpInodeOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:33:19 +03:00
|
|
|
Name: string(buf[:n-1]),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-23 09:41:34 +03:00
|
|
|
case fusekernel.OpGetattr:
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.GetInodeAttributesOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 00:53:16 +03:00
|
|
|
case fusekernel.OpSetattr:
|
2015-07-24 07:36:59 +03:00
|
|
|
type input fusekernel.SetattrIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:36:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpSetattr")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 00:53:16 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:32:29 +03:00
|
|
|
to := &fuseops.SetInodeAttributesOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-07-27 07:35:26 +03:00
|
|
|
o = to
|
2015-03-24 08:32:12 +03:00
|
|
|
|
2015-07-24 00:53:16 +03:00
|
|
|
valid := fusekernel.SetattrValid(in.Valid)
|
|
|
|
if valid&fusekernel.SetattrSize != 0 {
|
|
|
|
to.Size = &in.Size
|
2015-03-24 08:32:12 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 00:53:16 +03:00
|
|
|
if valid&fusekernel.SetattrMode != 0 {
|
2015-07-24 08:23:11 +03:00
|
|
|
mode := convertFileMode(in.Mode)
|
2015-07-24 00:53:16 +03:00
|
|
|
to.Mode = &mode
|
2015-03-24 08:32:12 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 00:53:16 +03:00
|
|
|
if valid&fusekernel.SetattrAtime != 0 {
|
|
|
|
t := time.Unix(int64(in.Atime), int64(in.AtimeNsec))
|
|
|
|
to.Atime = &t
|
2015-03-24 08:32:12 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 00:53:16 +03:00
|
|
|
if valid&fusekernel.SetattrMtime != 0 {
|
|
|
|
t := time.Unix(int64(in.Mtime), int64(in.MtimeNsec))
|
|
|
|
to.Mtime = &t
|
2015-03-24 08:32:12 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 00:57:31 +03:00
|
|
|
case fusekernel.OpForget:
|
2015-07-24 07:36:59 +03:00
|
|
|
type input fusekernel.ForgetIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:36:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpForget")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 00:57:31 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.ForgetInodeOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 00:57:31 +03:00
|
|
|
N: in.Nlookup,
|
2015-03-31 01:58:43 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:02:49 +03:00
|
|
|
case fusekernel.OpMkdir:
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol)))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpMkdir")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:02:49 +03:00
|
|
|
}
|
2015-07-24 07:40:43 +03:00
|
|
|
|
2015-07-29 04:02:29 +03:00
|
|
|
name := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:02:49 +03:00
|
|
|
i := bytes.IndexByte(name, '\x00')
|
|
|
|
if i < 0 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpMkdir")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:02:49 +03:00
|
|
|
}
|
|
|
|
name = name[:i]
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.MkDirOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:33:19 +03:00
|
|
|
Name: string(name),
|
2015-07-24 06:23:39 +03:00
|
|
|
|
|
|
|
// On Linux, vfs_mkdir calls through to the inode with at most
|
|
|
|
// permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
|
|
|
|
// passes that on directly (cf. https://goo.gl/f31aMo). In other words,
|
|
|
|
// the fact that this is a directory is implicit in the fact that the
|
|
|
|
// opcode is mkdir. But we want the correct mode to go through, so ensure
|
|
|
|
// that os.ModeDir is set.
|
2015-07-24 08:23:11 +03:00
|
|
|
Mode: convertFileMode(in.Mode) | os.ModeDir,
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-07-24 06:23:39 +03:00
|
|
|
|
2015-07-24 01:04:03 +03:00
|
|
|
case fusekernel.OpCreate:
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*fusekernel.CreateIn)(inMsg.Consume(fusekernel.CreateInSize(protocol)))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 08:13:26 +03:00
|
|
|
err = errors.New("Corrupt OpCreate")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:04:03 +03:00
|
|
|
}
|
2015-07-24 07:40:43 +03:00
|
|
|
|
2015-07-29 04:02:29 +03:00
|
|
|
name := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:04:03 +03:00
|
|
|
i := bytes.IndexByte(name, '\x00')
|
|
|
|
if i < 0 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpCreate")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:04:03 +03:00
|
|
|
}
|
|
|
|
name = name[:i]
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.CreateFileOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:33:19 +03:00
|
|
|
Name: string(name),
|
|
|
|
Mode: convertFileMode(in.Mode),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:04:59 +03:00
|
|
|
case fusekernel.OpSymlink:
|
2015-07-24 07:40:43 +03:00
|
|
|
// The message is "newName\0target\0".
|
2015-07-29 04:02:29 +03:00
|
|
|
names := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:04:59 +03:00
|
|
|
if len(names) == 0 || names[len(names)-1] != 0 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpSymlink")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:04:59 +03:00
|
|
|
}
|
|
|
|
i := bytes.IndexByte(names, '\x00')
|
|
|
|
if i < 0 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpSymlink")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:04:59 +03:00
|
|
|
}
|
|
|
|
newName, target := names[0:i], names[i+1:len(names)-1]
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.CreateSymlinkOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:33:19 +03:00
|
|
|
Name: string(newName),
|
|
|
|
Target: string(target),
|
2015-05-19 08:34:14 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:07:22 +03:00
|
|
|
case fusekernel.OpRename:
|
2015-07-24 07:40:43 +03:00
|
|
|
type input fusekernel.RenameIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRename")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:07:22 +03:00
|
|
|
}
|
2015-07-24 07:40:43 +03:00
|
|
|
|
2015-07-29 04:02:29 +03:00
|
|
|
names := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:07:22 +03:00
|
|
|
// names should be "old\x00new\x00"
|
|
|
|
if len(names) < 4 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRename")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:07:22 +03:00
|
|
|
}
|
|
|
|
if names[len(names)-1] != '\x00' {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRename")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:07:22 +03:00
|
|
|
}
|
|
|
|
i := bytes.IndexByte(names, '\x00')
|
|
|
|
if i < 0 {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRename")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:07:22 +03:00
|
|
|
}
|
|
|
|
oldName, newName := names[:i], names[i+1:len(names)-1]
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.RenameOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
OldParent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 01:07:22 +03:00
|
|
|
OldName: string(oldName),
|
2015-07-27 07:33:19 +03:00
|
|
|
NewParent: fuseops.InodeID(in.Newdir),
|
2015-07-24 01:07:22 +03:00
|
|
|
NewName: string(newName),
|
2015-06-25 08:37:04 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:08:41 +03:00
|
|
|
case fusekernel.OpUnlink:
|
2015-07-29 04:02:29 +03:00
|
|
|
buf := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:08:41 +03:00
|
|
|
n := len(buf)
|
|
|
|
if n == 0 || buf[n-1] != '\x00' {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpUnlink")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:08:41 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.UnlinkOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 01:08:41 +03:00
|
|
|
Name: string(buf[:n-1]),
|
|
|
|
}
|
|
|
|
|
2015-07-24 01:09:07 +03:00
|
|
|
case fusekernel.OpRmdir:
|
2015-07-29 04:02:29 +03:00
|
|
|
buf := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:09:07 +03:00
|
|
|
n := len(buf)
|
|
|
|
if n == 0 || buf[n-1] != '\x00' {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRmdir")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:09:07 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.RmDirOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 01:09:07 +03:00
|
|
|
Name: string(buf[:n-1]),
|
2015-03-24 06:51:07 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:12:07 +03:00
|
|
|
case fusekernel.OpOpen:
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.OpenFileOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 01:12:07 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:12:29 +03:00
|
|
|
case fusekernel.OpOpendir:
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.OpenDirOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-03-24 06:56:41 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:10:45 +03:00
|
|
|
case fusekernel.OpRead:
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRead")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:10:45 +03:00
|
|
|
}
|
|
|
|
|
2015-07-29 04:02:29 +03:00
|
|
|
to := &fuseops.ReadFileOp{
|
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:35:26 +03:00
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-07-24 01:10:45 +03:00
|
|
|
Offset: int64(in.Offset),
|
|
|
|
}
|
2015-07-29 04:02:29 +03:00
|
|
|
o = to
|
|
|
|
|
|
|
|
readSize := int(in.Size)
|
|
|
|
p := outMsg.GrowNoZero(uintptr(readSize))
|
|
|
|
if p == nil {
|
|
|
|
err = fmt.Errorf("Can't grow for %d-byte read", readSize)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
|
|
|
|
sh.Data = uintptr(p)
|
|
|
|
sh.Len = readSize
|
|
|
|
sh.Cap = readSize
|
2015-07-24 01:10:45 +03:00
|
|
|
|
2015-07-24 01:11:24 +03:00
|
|
|
case fusekernel.OpReaddir:
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpReaddir")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-03-24 06:56:41 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-29 04:03:27 +03:00
|
|
|
to := &fuseops.ReadDirOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:35:26 +03:00
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
|
|
|
Offset: fuseops.DirOffset(in.Offset),
|
2015-07-24 01:11:24 +03:00
|
|
|
}
|
2015-07-29 04:03:27 +03:00
|
|
|
o = to
|
|
|
|
|
|
|
|
readSize := int(in.Size)
|
|
|
|
p := outMsg.GrowNoZero(uintptr(readSize))
|
|
|
|
if p == nil {
|
|
|
|
err = fmt.Errorf("Can't grow for %d-byte read", readSize)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
|
|
|
|
sh.Data = uintptr(p)
|
|
|
|
sh.Len = readSize
|
|
|
|
sh.Cap = readSize
|
2015-07-24 01:11:24 +03:00
|
|
|
|
2015-07-24 01:13:36 +03:00
|
|
|
case fusekernel.OpRelease:
|
2015-07-24 07:40:43 +03:00
|
|
|
type input fusekernel.ReleaseIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpRelease")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:13:36 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.ReleaseFileHandleOp{
|
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-07-24 01:13:36 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:14:02 +03:00
|
|
|
case fusekernel.OpReleasedir:
|
2015-07-24 07:40:43 +03:00
|
|
|
type input fusekernel.ReleaseIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:40:43 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpReleasedir")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-03-24 06:56:41 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.ReleaseDirHandleOp{
|
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-07-24 01:14:02 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 01:15:52 +03:00
|
|
|
case fusekernel.OpWrite:
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*fusekernel.WriteIn)(inMsg.Consume(fusekernel.WriteInSize(protocol)))
|
2015-07-24 07:41:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpWrite")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:15:52 +03:00
|
|
|
}
|
|
|
|
|
2015-07-29 04:02:29 +03:00
|
|
|
buf := inMsg.ConsumeBytes(inMsg.Len())
|
2015-07-24 01:15:52 +03:00
|
|
|
if len(buf) < int(in.Size) {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpWrite")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:15:52 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.WriteFileOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:35:26 +03:00
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-07-24 01:15:52 +03:00
|
|
|
Data: buf,
|
|
|
|
Offset: int64(in.Offset),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:16:46 +03:00
|
|
|
case fusekernel.OpFsync:
|
2015-07-24 07:41:59 +03:00
|
|
|
type input fusekernel.FsyncIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:41:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpFsync")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:16:46 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.SyncFileOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:35:26 +03:00
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:19:04 +03:00
|
|
|
case fusekernel.OpFlush:
|
2015-07-24 07:41:59 +03:00
|
|
|
type input fusekernel.FlushIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:41:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpFlush")
|
2015-07-24 03:04:12 +03:00
|
|
|
return
|
2015-07-24 01:19:04 +03:00
|
|
|
}
|
|
|
|
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.FlushFileOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-27 07:35:26 +03:00
|
|
|
Handle: fuseops.HandleID(in.Fh),
|
2015-03-24 06:46:31 +03:00
|
|
|
}
|
2015-03-24 06:40:58 +03:00
|
|
|
|
2015-07-24 01:19:32 +03:00
|
|
|
case fusekernel.OpReadlink:
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &fuseops.ReadSymlinkOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-05-19 09:05:54 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 03:30:46 +03:00
|
|
|
case fusekernel.OpStatfs:
|
2015-07-27 08:52:13 +03:00
|
|
|
o = &statFSOp{}
|
2015-07-24 03:30:46 +03:00
|
|
|
|
|
|
|
case fusekernel.OpInterrupt:
|
2015-07-24 07:41:59 +03:00
|
|
|
type input fusekernel.InterruptIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 07:41:59 +03:00
|
|
|
if in == nil {
|
2015-07-24 03:31:07 +03:00
|
|
|
err = errors.New("Corrupt OpInterrupt")
|
2015-07-24 03:30:46 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-07-27 08:52:13 +03:00
|
|
|
o = &interruptOp{
|
2015-07-24 03:30:46 +03:00
|
|
|
FuseID: in.Unique,
|
|
|
|
}
|
|
|
|
|
2015-07-24 09:35:14 +03:00
|
|
|
case fusekernel.OpInit:
|
|
|
|
type input fusekernel.InitIn
|
2015-07-29 04:02:29 +03:00
|
|
|
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
2015-07-24 09:35:14 +03:00
|
|
|
if in == nil {
|
|
|
|
err = errors.New("Corrupt OpInit")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-07-27 08:52:13 +03:00
|
|
|
o = &initOp{
|
2015-07-24 09:35:14 +03:00
|
|
|
Kernel: fusekernel.Protocol{in.Major, in.Minor},
|
|
|
|
MaxReadahead: in.MaxReadahead,
|
|
|
|
Flags: fusekernel.InitFlags(in.Flags),
|
|
|
|
}
|
|
|
|
|
2015-03-24 06:36:44 +03:00
|
|
|
default:
|
2015-07-27 07:35:26 +03:00
|
|
|
o = &unknownOp{
|
2015-07-29 04:02:29 +03:00
|
|
|
opCode: inMsg.Header().Opcode,
|
|
|
|
inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
2015-07-24 02:58:42 +03:00
|
|
|
}
|
2015-03-24 06:36:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-07-27 08:55:14 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Outgoing messages
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-07-29 03:39:37 +03:00
|
|
|
// Fill in the response that should be sent to the kernel, or set noResponse if
|
|
|
|
// the op requires no response.
|
2015-07-28 09:13:18 +03:00
|
|
|
func (c *Connection) kernelResponse(
|
2015-07-29 03:39:37 +03:00
|
|
|
m *buffer.OutMessage,
|
2015-07-27 08:55:14 +03:00
|
|
|
fuseID uint64,
|
|
|
|
op interface{},
|
2015-07-29 03:39:37 +03:00
|
|
|
opErr error) (noResponse bool) {
|
|
|
|
h := m.OutHeader()
|
|
|
|
h.Unique = fuseID
|
|
|
|
|
|
|
|
// Did the user return an error? Otherwise, fill in the rest of the response.
|
2015-07-27 08:55:14 +03:00
|
|
|
if opErr != nil {
|
|
|
|
if errno, ok := opErr.(syscall.Errno); ok {
|
2015-07-28 09:13:18 +03:00
|
|
|
m.OutHeader().Error = -int32(errno)
|
2015-07-27 08:55:14 +03:00
|
|
|
} else {
|
2015-07-28 09:13:18 +03:00
|
|
|
m.OutHeader().Error = -int32(syscall.EIO)
|
2015-07-27 08:55:14 +03:00
|
|
|
}
|
|
|
|
} else {
|
2015-07-29 03:39:37 +03:00
|
|
|
noResponse = c.kernelResponseForOp(m, op)
|
2015-07-27 08:55:14 +03:00
|
|
|
}
|
|
|
|
|
2015-07-29 03:39:37 +03:00
|
|
|
h.Len = uint32(m.Len())
|
2015-07-27 08:55:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Like kernelResponse, but assumes the user replied with a nil error to the
|
2015-07-29 03:39:37 +03:00
|
|
|
// op.
|
2015-07-28 09:17:06 +03:00
|
|
|
func (c *Connection) kernelResponseForOp(
|
2015-07-29 03:39:37 +03:00
|
|
|
m *buffer.OutMessage,
|
|
|
|
op interface{}) (noResponse bool) {
|
2015-07-27 08:55:14 +03:00
|
|
|
// Create the appropriate output message
|
|
|
|
switch o := op.(type) {
|
|
|
|
case *fuseops.LookUpInodeOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
size := fusekernel.EntryOutSize(c.protocol)
|
|
|
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
2015-07-27 08:55:14 +03:00
|
|
|
convertChildInodeEntry(&o.Entry, out)
|
|
|
|
|
|
|
|
case *fuseops.GetInodeAttributesOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
size := fusekernel.AttrOutSize(c.protocol)
|
|
|
|
out := (*fusekernel.AttrOut)(m.Grow(size))
|
2015-07-27 08:55:14 +03:00
|
|
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
|
|
|
|
o.AttributesExpiration)
|
|
|
|
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
|
|
|
|
|
|
|
case *fuseops.SetInodeAttributesOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
size := fusekernel.AttrOutSize(c.protocol)
|
|
|
|
out := (*fusekernel.AttrOut)(m.Grow(size))
|
2015-07-27 08:55:14 +03:00
|
|
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
|
|
|
|
o.AttributesExpiration)
|
|
|
|
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
|
|
|
|
|
|
|
case *fuseops.ForgetInodeOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
noResponse = true
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.MkDirOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
size := fusekernel.EntryOutSize(c.protocol)
|
|
|
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
2015-07-27 08:55:14 +03:00
|
|
|
convertChildInodeEntry(&o.Entry, out)
|
|
|
|
|
|
|
|
case *fuseops.CreateFileOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
eSize := fusekernel.EntryOutSize(c.protocol)
|
2015-07-27 08:55:14 +03:00
|
|
|
|
2015-07-28 09:17:06 +03:00
|
|
|
e := (*fusekernel.EntryOut)(m.Grow(eSize))
|
2015-07-27 08:55:14 +03:00
|
|
|
convertChildInodeEntry(&o.Entry, e)
|
|
|
|
|
2015-07-28 09:17:06 +03:00
|
|
|
oo := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
2015-07-27 08:55:14 +03:00
|
|
|
oo.Fh = uint64(o.Handle)
|
|
|
|
|
|
|
|
case *fuseops.CreateSymlinkOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
size := fusekernel.EntryOutSize(c.protocol)
|
|
|
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
2015-07-27 08:55:14 +03:00
|
|
|
convertChildInodeEntry(&o.Entry, out)
|
|
|
|
|
|
|
|
case *fuseops.RenameOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.RmDirOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.UnlinkOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.OpenDirOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
out := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
2015-07-27 08:55:14 +03:00
|
|
|
out.Fh = uint64(o.Handle)
|
|
|
|
|
|
|
|
case *fuseops.ReadDirOp:
|
2015-07-29 04:03:27 +03:00
|
|
|
// convertInMessage already set up the destination buffer to be at the end
|
|
|
|
// of the out message. We need only shrink to the right size based on how
|
|
|
|
// much the user read.
|
|
|
|
m.Shrink(uintptr(m.Len() - (int(buffer.OutMessageInitialSize) + o.BytesRead)))
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.ReleaseDirHandleOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.OpenFileOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
out := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
2015-07-27 08:55:14 +03:00
|
|
|
out.Fh = uint64(o.Handle)
|
|
|
|
|
|
|
|
case *fuseops.ReadFileOp:
|
2015-07-29 04:02:29 +03:00
|
|
|
// convertInMessage already set up the destination buffer to be at the end
|
|
|
|
// of the out message. We need only shrink to the right size based on how
|
|
|
|
// much the user read.
|
|
|
|
m.Shrink(uintptr(m.Len() - (int(buffer.OutMessageInitialSize) + o.BytesRead)))
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.WriteFileOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
out := (*fusekernel.WriteOut)(m.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
|
2015-07-27 08:55:14 +03:00
|
|
|
out.Size = uint32(len(o.Data))
|
|
|
|
|
|
|
|
case *fuseops.SyncFileOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.FlushFileOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.ReleaseFileHandleOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
// Empty response
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *fuseops.ReadSymlinkOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
m.AppendString(o.Target)
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *statFSOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
m.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *interruptOp:
|
2015-07-29 03:39:37 +03:00
|
|
|
noResponse = true
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
case *initOp:
|
2015-07-28 09:17:06 +03:00
|
|
|
out := (*fusekernel.InitOut)(m.Grow(unsafe.Sizeof(fusekernel.InitOut{})))
|
2015-07-27 08:55:14 +03:00
|
|
|
|
|
|
|
out.Major = o.Library.Major
|
|
|
|
out.Minor = o.Library.Minor
|
|
|
|
out.MaxReadahead = o.MaxReadahead
|
|
|
|
out.Flags = uint32(o.Flags)
|
|
|
|
out.MaxWrite = o.MaxWrite
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown op: %#v", op))
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// General conversions
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-07-24 04:08:24 +03:00
|
|
|
func convertTime(t time.Time) (secs uint64, nsec uint32) {
|
|
|
|
totalNano := t.UnixNano()
|
|
|
|
secs = uint64(totalNano / 1e9)
|
|
|
|
nsec = uint32(totalNano % 1e9)
|
|
|
|
return
|
|
|
|
}
|
2015-07-24 03:54:09 +03:00
|
|
|
|
2015-06-05 08:24:22 +03:00
|
|
|
func convertAttributes(
|
2015-07-27 07:33:19 +03:00
|
|
|
inodeID fuseops.InodeID,
|
2015-07-27 07:38:18 +03:00
|
|
|
in *fuseops.InodeAttributes,
|
2015-07-24 03:54:09 +03:00
|
|
|
out *fusekernel.Attr) {
|
|
|
|
out.Ino = uint64(inodeID)
|
|
|
|
out.Size = in.Size
|
|
|
|
out.Atime, out.AtimeNsec = convertTime(in.Atime)
|
|
|
|
out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
|
|
|
|
out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
|
|
|
|
out.SetCrtime(convertTime(in.Crtime))
|
2015-07-27 06:29:29 +03:00
|
|
|
out.Nlink = in.Nlink
|
2015-07-24 03:54:09 +03:00
|
|
|
out.Uid = in.Uid
|
|
|
|
out.Gid = in.Gid
|
|
|
|
|
|
|
|
// Set the mode.
|
2015-07-24 04:49:37 +03:00
|
|
|
out.Mode = uint32(in.Mode) & 0777
|
2015-07-24 03:54:09 +03:00
|
|
|
switch {
|
|
|
|
default:
|
|
|
|
out.Mode |= syscall.S_IFREG
|
|
|
|
case in.Mode&os.ModeDir != 0:
|
|
|
|
out.Mode |= syscall.S_IFDIR
|
|
|
|
case in.Mode&os.ModeDevice != 0:
|
|
|
|
if in.Mode&os.ModeCharDevice != 0 {
|
|
|
|
out.Mode |= syscall.S_IFCHR
|
|
|
|
} else {
|
|
|
|
out.Mode |= syscall.S_IFBLK
|
|
|
|
}
|
|
|
|
case in.Mode&os.ModeNamedPipe != 0:
|
|
|
|
out.Mode |= syscall.S_IFIFO
|
|
|
|
case in.Mode&os.ModeSymlink != 0:
|
|
|
|
out.Mode |= syscall.S_IFLNK
|
|
|
|
case in.Mode&os.ModeSocket != 0:
|
|
|
|
out.Mode |= syscall.S_IFSOCK
|
2015-03-24 07:00:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert an absolute cache expiration time to a relative time from now for
|
2015-07-24 03:54:09 +03:00
|
|
|
// consumption by the fuse kernel module.
|
|
|
|
func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
|
2015-03-24 07:00:05 +03:00
|
|
|
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
|
2015-07-24 03:54:09 +03:00
|
|
|
// counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
|
|
|
|
// are right out. There is no need to cap the positive magnitude, because
|
|
|
|
// 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
|
|
|
|
d := t.Sub(time.Now())
|
|
|
|
if d > 0 {
|
|
|
|
secs = uint64(d / time.Second)
|
|
|
|
nsecs = uint32((d % time.Second) / time.Nanosecond)
|
2015-03-24 07:00:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertChildInodeEntry(
|
2015-07-27 07:38:18 +03:00
|
|
|
in *fuseops.ChildInodeEntry,
|
2015-07-24 03:41:29 +03:00
|
|
|
out *fusekernel.EntryOut) {
|
2015-07-24 03:54:09 +03:00
|
|
|
out.Nodeid = uint64(in.Child)
|
|
|
|
out.Generation = uint64(in.Generation)
|
|
|
|
out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
|
|
|
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration)
|
|
|
|
|
|
|
|
convertAttributes(in.Child, &in.Attributes, &out.Attr)
|
2015-03-24 07:00:05 +03:00
|
|
|
}
|
2015-07-24 08:23:11 +03:00
|
|
|
|
|
|
|
func convertFileMode(unixMode uint32) os.FileMode {
|
|
|
|
mode := os.FileMode(unixMode & 0777)
|
|
|
|
switch unixMode & syscall.S_IFMT {
|
|
|
|
case syscall.S_IFREG:
|
|
|
|
// nothing
|
|
|
|
case syscall.S_IFDIR:
|
|
|
|
mode |= os.ModeDir
|
|
|
|
case syscall.S_IFCHR:
|
|
|
|
mode |= os.ModeCharDevice | os.ModeDevice
|
|
|
|
case syscall.S_IFBLK:
|
|
|
|
mode |= os.ModeDevice
|
|
|
|
case syscall.S_IFIFO:
|
|
|
|
mode |= os.ModeNamedPipe
|
|
|
|
case syscall.S_IFLNK:
|
|
|
|
mode |= os.ModeSymlink
|
|
|
|
case syscall.S_IFSOCK:
|
|
|
|
mode |= os.ModeSocket
|
|
|
|
default:
|
|
|
|
// no idea
|
|
|
|
mode |= os.ModeDevice
|
|
|
|
}
|
|
|
|
if unixMode&syscall.S_ISUID != 0 {
|
|
|
|
mode |= os.ModeSetuid
|
|
|
|
}
|
|
|
|
if unixMode&syscall.S_ISGID != 0 {
|
|
|
|
mode |= os.ModeSetgid
|
|
|
|
}
|
|
|
|
return mode
|
|
|
|
}
|