DroidFS/app/libgocryptfs/main.go

958 lines
28 KiB
Go

package main
import (
"C"
"crypto/cipher"
"crypto/aes"
"syscall"
"strings"
"bytes"
"unsafe"
"os"
"io"
"fmt"
"path/filepath"
"golang.org/x/sys/unix"
"./gocryptfs_internal/cryptocore"
"./gocryptfs_internal/stupidgcm"
"./gocryptfs_internal/eme"
"./gocryptfs_internal/nametransform"
"./rewrites/syscallcompat"
"./rewrites/configfile"
"./rewrites/contentenc"
)
const (
file_mode = uint32(0660)
folder_mode = uint32(0770)
)
type Directory struct {
fd int
iv []byte
}
type File struct {
fd *os.File
path string
}
type SessionVars struct {
root_cipher_dir string
nameTransform *nametransform.NameTransform
cryptoCore *cryptocore.CryptoCore
contentEnc *contentenc.ContentEnc
dirCache map[string]Directory
file_handles map[int]File
fileIDs map[int][]byte
}
var sessions map[int]SessionVars
func err_to_bool(e error) bool {
if e == nil {
return true
}
return false
}
func wipe(d []byte){
for i := range d {
d[i] = 0
}
d = nil
}
func clear_dirCache(sessionID int) {
for k, _ := range sessions[sessionID].dirCache {
delete(sessions[sessionID].dirCache, k)
}
}
func openBackingDir(sessionID int, relPath string) (dirfd int, cName string, err error) {
dirRelPath := nametransform.Dir(relPath)
dir, ok := sessions[sessionID].dirCache[dirRelPath]
if ok {
// If relPath is empty, cName is ".".
if relPath == "" {
cache_dirfd, err := syscall.Dup(dir.fd)
if err != nil {
return -1, "", err
}
return cache_dirfd, ".", nil
}
name := filepath.Base(relPath)
cName, err = sessions[sessionID].nameTransform.EncryptAndHashName(name, dir.iv)
if err != nil {
syscall.Close(dir.fd)
return -1, "", err
}
cache_dirfd, err := syscall.Dup(dir.fd)
if err != nil {
return -1, "", err
}
return cache_dirfd, cName, nil
}
// Open cipherdir (following symlinks)
dirfd, err = syscall.Open(sessions[sessionID].root_cipher_dir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", err
}
// If relPath is empty, cName is ".".
if relPath == "" {
return dirfd, ".", nil
}
// Walk the directory tree
parts := strings.Split(relPath, "/")
for i, name := range parts {
iv, err := nametransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
cName, err = sessions[sessionID].nameTransform.EncryptAndHashName(name, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
// Last part? We are done.
if i == len(parts)-1 {
cache_dirfd, err := syscall.Dup(dirfd)
if err == nil {
var dirRelPathCopy strings.Builder
dirRelPathCopy.WriteString(dirRelPath)
sessions[sessionID].dirCache[dirRelPathCopy.String()] = Directory{cache_dirfd, iv}
}
break
}
// Not the last part? Descend into next directory.
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
syscall.Close(dirfd)
if err != nil {
return -1, "", err
}
dirfd = dirfd2
}
return dirfd, cName, nil
}
func mkdirWithIv(dirfd int, cName string, mode uint32) error {
err := syscallcompat.Mkdirat(dirfd, cName, mode)
if err != nil {
return err
}
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0)
if err == nil {
// Create gocryptfs.diriv
err = nametransform.WriteDirIVAt(dirfd2)
syscall.Close(dirfd2)
}
if err != nil {
// Delete inconsistent directory (missing gocryptfs.diriv!)
err2 := syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR)
if err2 != nil {
return err2
}
}
return err
}
func mangleOpenFlags(flags uint32) (newFlags int) {
newFlags = int(flags)
// Convert WRONLY to RDWR. We always need read access to do read-modify-write cycles.
if (newFlags & syscall.O_ACCMODE) == syscall.O_WRONLY {
newFlags = newFlags ^ os.O_WRONLY | os.O_RDWR
}
// We also cannot open the file in append mode, we need to seek back for RMW
newFlags = newFlags &^ os.O_APPEND
// O_DIRECT accesses must be aligned in both offset and length. Due to our
// crypto header, alignment will be off, even if userspace makes aligned
// accesses. Running xfstests generic/013 on ext4 used to trigger lots of
// EINVAL errors due to missing alignment. Just fall back to buffered IO.
newFlags = newFlags &^ syscallcompat.O_DIRECT
// Create and Open are two separate FUSE operations, so O_CREAT should not
// be part of the open flags.
newFlags = newFlags &^ syscall.O_CREAT
// We always want O_NOFOLLOW to be safe against symlink races
newFlags |= syscall.O_NOFOLLOW
return newFlags
}
func register_file_handle(sessionID int, file File) int {
handleID := -1
c := 0
for handleID == -1 {
_, ok := sessions[sessionID].file_handles[c]
if !ok {
handleID = c
}
c++
}
sessions[sessionID].file_handles[handleID] = file
return handleID
}
func readFileID(fd *os.File) ([]byte, error) {
// We read +1 byte to determine if the file has actual content
// and not only the header. A header-only file will be considered empty.
// This makes File ID poisoning more difficult.
readLen := contentenc.HeaderLen + 1
buf := make([]byte, readLen)
_, err := fd.ReadAt(buf, 0)
if err != nil {
return nil, err
}
buf = buf[:contentenc.HeaderLen]
h, err := contentenc.ParseHeader(buf)
if err != nil {
return nil, err
}
return h.ID, nil
}
func createHeader(fd *os.File) (fileID []byte, err error) {
h := contentenc.RandomHeader()
buf := h.Pack()
// Prevent partially written (=corrupt) header by preallocating the space beforehand
//NoPrealloc
err = syscallcompat.EnospcPrealloc(int(fd.Fd()), 0, contentenc.HeaderLen)
if err != nil {
return nil, err
}
// Actually write header
_, err = fd.WriteAt(buf, 0)
if err != nil {
return nil, err
}
return h.ID, err
}
func doRead(sessionID, handleID int, dst_buff []byte, offset uint64, length uint64) ([]byte, bool) {
f, ok := sessions[sessionID].file_handles[handleID]
if !ok {
return nil, false
}
fd := f.fd
var fileID []byte
test_fileID, ok := sessions[sessionID].fileIDs[handleID]
if ok {
fileID = test_fileID
} else {
var err error
fileID, err = readFileID(fd)
if err != nil || fileID == nil {
return nil, false
}
sessions[sessionID].fileIDs[handleID] = fileID
}
// Read the backing ciphertext in one go
blocks := sessions[sessionID].contentEnc.ExplodePlainRange(offset, length)
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
skip := blocks[0].Skip
ciphertext := sessions[sessionID].contentEnc.CReqPool.Get()
ciphertext = ciphertext[:int(alignedLength)]
n, err := fd.ReadAt(ciphertext, int64(alignedOffset))
if err != nil && err != io.EOF {
return nil, false
}
// The ReadAt came back empty. We can skip all the decryption and return early.
if n == 0 {
sessions[sessionID].contentEnc.CReqPool.Put(ciphertext)
return dst_buff, true
}
// Truncate ciphertext buffer down to actually read bytes
ciphertext = ciphertext[0:n]
firstBlockNo := blocks[0].BlockNo
// Decrypt it
plaintext, err := sessions[sessionID].contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
sessions[sessionID].contentEnc.CReqPool.Put(ciphertext)
if err != nil {
return nil, false
}
// Crop down to the relevant part
var out []byte
lenHave := len(plaintext)
lenWant := int(skip + length)
if lenHave > lenWant {
out = plaintext[skip:lenWant]
} else if lenHave > int(skip) {
out = plaintext[skip:lenHave]
}
// else: out stays empty, file was smaller than the requested offset
out = append(dst_buff, out...)
sessions[sessionID].contentEnc.PReqPool.Put(plaintext)
return out, true
}
func doWrite(sessionID, handleID int, data []byte, offset uint64) (uint32, bool){
fileWasEmpty := false
f, ok := sessions[sessionID].file_handles[handleID]
if !ok {
return 0, false
}
fd := f.fd
var err error
var fileID []byte
test_fileID, ok := sessions[sessionID].fileIDs[handleID]
if ok {
fileID = test_fileID
} else {
fileID, err = readFileID(fd)
// Write a new file header if the file is empty
if err == io.EOF {
fileID, err = createHeader(fd)
fileWasEmpty = true
}
if err != nil {
return 0, false
}
sessions[sessionID].fileIDs[handleID] = fileID
}
// Handle payload data
dataBuf := bytes.NewBuffer(data)
blocks := sessions[sessionID].contentEnc.ExplodePlainRange(offset, uint64(len(data)))
toEncrypt := make([][]byte, len(blocks))
for i, b := range blocks {
blockData := dataBuf.Next(int(b.Length))
// Incomplete block -> Read-Modify-Write
if b.IsPartial() {
// Read
oldData, success := doRead(sessionID, handleID, nil, b.BlockPlainOff(), sessions[sessionID].contentEnc.PlainBS())
if !success {
return 0, false
}
// Modify
blockData = sessions[sessionID].contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
}
// Write into the to-encrypt list
toEncrypt[i] = blockData
}
// Encrypt all blocks
ciphertext := sessions[sessionID].contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, fileID)
// Preallocate so we cannot run out of space in the middle of the write.
// This prevents partially written (=corrupt) blocks.
cOff := int64(blocks[0].BlockCipherOff())
//NoPrealloc
err = syscallcompat.EnospcPrealloc(int(fd.Fd()), cOff, int64(len(ciphertext)))
if err != nil {
if fileWasEmpty {
syscall.Ftruncate(int(fd.Fd()), 0)
// Kill the file header again
gcf_close_file(sessionID, handleID) //f.fileTableEntry.ID = nil
}
return 0, false
}
// Write
_, err = fd.WriteAt(ciphertext, cOff)
// Return memory to CReqPool
sessions[sessionID].contentEnc.CReqPool.Put(ciphertext)
if err != nil {
return 0, false
}
return uint32(len(data)), true
}
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
// if the file is already block-aligned.
func zeroPad(sessionID, handleID int, plainSize uint64) bool {
lastBlockLen := plainSize % sessions[sessionID].contentEnc.PlainBS()
if lastBlockLen == 0 {
// Already block-aligned
return true
}
missing := sessions[sessionID].contentEnc.PlainBS() - lastBlockLen
pad := make([]byte, missing)
_, success := doWrite(sessionID, handleID, pad, plainSize)
return success
}
// truncateGrowFile extends a file using seeking or ftruncate performing RMW on
// the first and last block as necessary. New blocks in the middle become
// file holes unless they have been fallocate()'d beforehand.
func truncateGrowFile(sessionID, handleID int, oldPlainSz uint64, newPlainSz uint64) bool {
if newPlainSz <= oldPlainSz {
return false
}
newEOFOffset := newPlainSz - 1
if oldPlainSz > 0 {
n1 := sessions[sessionID].contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
n2 := sessions[sessionID].contentEnc.PlainOffToBlockNo(newEOFOffset)
// The file is grown within one block, no need to pad anything.
// Write a single zero to the last byte and let doWrite figure out the RMW.
if n1 == n2 {
buf := make([]byte, 1)
_, success := doWrite(sessionID, handleID, buf, newEOFOffset)
return success
}
}
// The truncate creates at least one new block.
//
// Make sure the old last block is padded to the block boundary. This call
// is a no-op if it is already block-aligned.
success := zeroPad(sessionID, handleID, oldPlainSz)
if !success {
return false
}
// The new size is block-aligned. In this case we can do everything ourselves
// and avoid the call to doWrite.
if newPlainSz%sessions[sessionID].contentEnc.PlainBS() == 0 {
// The file was empty, so it did not have a header. Create one.
if oldPlainSz == 0 {
id, err := createHeader(sessions[sessionID].file_handles[handleID].fd)
if err != nil {
return false
}
sessions[sessionID].fileIDs[handleID] = id
}
cSz := int64(sessions[sessionID].contentEnc.PlainSizeToCipherSize(newPlainSz))
return err_to_bool(syscall.Ftruncate(int(sessions[sessionID].file_handles[handleID].fd.Fd()), cSz))
}
// The new size is NOT aligned, so we need to write a partial block.
// Write a single zero to the last byte and let doWrite figure it out.
buf := make([]byte, 1)
_, success = doWrite(sessionID, handleID, buf, newEOFOffset)
return success
}
func truncate(sessionID, handleID int, newSize uint64) bool {
fileFD := int(sessions[sessionID].file_handles[handleID].fd.Fd())
/*// Common case first: Truncate to zero
if newSize == 0 {
err = syscall.Ftruncate(fileFD, 0)
if err != nil {
return false
}
// Truncate to zero kills the file header
f.fileTableEntry.ID = nil
return true
}*/
// We need the old file size to determine if we are growing or shrinking
// the file
oldSize, _, success := gcf_get_attrs(sessionID, sessions[sessionID].file_handles[handleID].path)
if !success {
return false
}
// File size stays the same - nothing to do
if newSize == oldSize {
return true
}
// File grows
if newSize > oldSize {
return truncateGrowFile(sessionID, handleID, oldSize, newSize)
}
// File shrinks
blockNo := sessions[sessionID].contentEnc.PlainOffToBlockNo(newSize)
cipherOff := sessions[sessionID].contentEnc.BlockNoToCipherOff(blockNo)
plainOff := sessions[sessionID].contentEnc.BlockNoToPlainOff(blockNo)
lastBlockLen := newSize - plainOff
var data []byte
if lastBlockLen > 0 {
data, success = doRead(sessionID, handleID, nil, plainOff, lastBlockLen)
if !success {
return false
}
}
// Truncate down to the last complete block
err := syscall.Ftruncate(fileFD, int64(cipherOff))
if err != nil {
return false
}
// Append partial block
if lastBlockLen > 0 {
_, success := doWrite(sessionID, handleID, data, plainOff)
return success
}
return true
}
func init_new_session(root_cipher_dir string, masterkey []byte) int {
// Initialize EME for filename encryption.
var emeCipher *eme.EMECipher
var err error
var emeBlockCipher cipher.Block
emeKey := cryptocore.HkdfDerive(masterkey, cryptocore.HkdfInfoEMENames, cryptocore.KeyLen)
emeBlockCipher, err = aes.NewCipher(emeKey)
for i := range emeKey {
emeKey[i] = 0
}
if err == nil {
var new_session SessionVars
emeCipher = eme.New(emeBlockCipher)
new_session.nameTransform = nametransform.New(emeCipher, true, true)
// Initialize contentEnc
cryptoBackend := cryptocore.BackendGoGCM
if stupidgcm.PreferOpenSSL() {
cryptoBackend = cryptocore.BackendOpenSSL
}
forcedecode := false
new_session.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode)
new_session.contentEnc = contentenc.New(new_session.cryptoCore, contentenc.DefaultBS, forcedecode)
//copying root_cipher_dir
var grcd strings.Builder
grcd.WriteString(root_cipher_dir)
new_session.root_cipher_dir = grcd.String()
// New empty caches
new_session.dirCache = make(map[string]Directory)
new_session.file_handles = make(map[int]File)
new_session.fileIDs = make(map[int][]byte)
//find unused sessionID
sessionID := -1
c := 0
for sessionID == -1 {
_, ok := sessions[c]
if !ok {
sessionID = c
}
c++
}
if sessions == nil {
sessions = make(map[int]SessionVars)
}
sessions[sessionID] = new_session;
return sessionID
}
return -1
}
//export gcf_init
func gcf_init(root_cipher_dir string, password, givenScryptHash, returnedScryptHashBuff []byte) int {
sessionID := -1
cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName))
if err == nil {
masterkey := cf.GetMasterkey(password, givenScryptHash, returnedScryptHashBuff)
if masterkey != nil {
sessionID = init_new_session(root_cipher_dir, masterkey)
wipe(masterkey)
}
}
return sessionID
}
//export gcf_close
func gcf_close(sessionID int){
sessions[sessionID].cryptoCore.Wipe()
for handleID, _ := range sessions[sessionID].file_handles {
gcf_close_file(sessionID, handleID)
}
clear_dirCache(sessionID)
delete(sessions, sessionID)
}
//export gcf_is_closed
func gcf_is_closed(sessionID int) bool {
_, ok := sessions[sessionID]
return !ok
}
//export gcf_create_volume
func gcf_create_volume(root_cipher_dir string, password []byte, logN int, creator string) bool {
err := configfile.Create(filepath.Join(root_cipher_dir, configfile.ConfDefaultName), password, false, logN, creator, false, false)
if err == nil {
dirfd, err := syscall.Open(root_cipher_dir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err == nil {
err = nametransform.WriteDirIVAt(dirfd)
syscall.Close(dirfd)
return err_to_bool(err)
}
}
return false
}
//export gcf_change_password
func gcf_change_password(root_cipher_dir string, old_password, givenScryptHash, new_password, returnedScryptHashBuff []byte) bool {
success := false
cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName))
if err == nil {
masterkey := cf.GetMasterkey(old_password, givenScryptHash, nil)
if masterkey != nil {
logN := cf.ScryptObject.LogN()
scryptHash := cf.EncryptKey(masterkey, new_password, logN, len(returnedScryptHashBuff)>0)
wipe(masterkey)
for i := range scryptHash {
returnedScryptHashBuff[i] = scryptHash[i]
scryptHash[i] = 0
}
success = err_to_bool(cf.WriteFile())
}
}
return success
}
//export gcf_list_dir
func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
parentDirFd, cDirName, err := openBackingDir(sessionID, dirName)
if err != nil {
return nil, nil, 0
}
defer syscall.Close(parentDirFd)
// Read ciphertext directory
var cipherEntries []syscallcompat.DirEntry
fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
return nil, nil, 0
}
defer syscall.Close(fd)
cipherEntries, err = syscallcompat.Getdents(fd)
if err != nil {
return nil, nil, 0
}
// Get DirIV (stays nil if PlaintextNames is used)
var cachedIV []byte
// Read the DirIV from disk
cachedIV, err = nametransform.ReadDirIVAt(fd)
if err != nil {
return nil, nil, 0
}
// Decrypted directory entries
var plain strings.Builder
var modes []uint32
// Filter and decrypt filenames
for i := range cipherEntries {
cName := cipherEntries[i].Name
if dirName == "" && cName == configfile.ConfDefaultName {
// silently ignore "gocryptfs.conf" in the top level dir
continue
}
if cName == nametransform.DirIVFilename {
// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
continue
}
// Handle long file name
isLong := nametransform.NameType(cName)
if isLong == nametransform.LongNameContent {
cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
if err != nil {
continue
}
cName = cNameLong
} else if isLong == nametransform.LongNameFilename {
// ignore "gocryptfs.longname.*.name"
continue
}
name, err := sessions[sessionID].nameTransform.DecryptName(cName, cachedIV)
if err != nil {
continue
}
// Override the ciphertext name with the plaintext name but reuse the rest
// of the structure
cipherEntries[i].Name = name
plain.WriteString(cipherEntries[i].Name+"\x00")
modes = append(modes, cipherEntries[i].Mode)
}
p := C.malloc(C.ulong(C.sizeof_int*len(modes)))
for i := 0; i < len(modes); i++ {
offset := C.sizeof_int*uintptr(i)
*(*C.int)(unsafe.Pointer(uintptr(p)+offset)) = (C.int)(modes[i])
}
return C.CString(plain.String()), (*C.int)(p), (C.int)(len(modes))
}
//export gcf_mkdir
func gcf_mkdir(sessionID int, newPath string) bool {
dirfd, cName, err := openBackingDir(sessionID, newPath)
if err != nil {
return false
}
defer syscall.Close(dirfd)
// We need write and execute permissions to create gocryptfs.diriv.
// Also, we need read permissions to open the directory (to avoid
// race-conditions between getting and setting the mode).
origMode := folder_mode
mode := folder_mode | 0700
// Handle long file name
if nametransform.IsLongContent(cName) {
// Create ".name"
err = sessions[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, newPath)
if err != nil {
return false
}
// Create directory
err = mkdirWithIv(dirfd, cName, mode)
if err != nil {
nametransform.DeleteLongNameAt(dirfd, cName)
return false
}
} else {
err = mkdirWithIv(dirfd, cName, mode)
if err != nil {
return false
}
}
// Set mode
if origMode != mode {
dirfd2, err := syscallcompat.Openat(dirfd, cName,
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
return false
}
defer syscall.Close(dirfd2)
var st syscall.Stat_t
err = syscall.Fstat(dirfd2, &st)
if err != nil {
return false
}
// Preserve SGID bit if it was set due to inheritance.
origMode = uint32(st.Mode&^0777) | origMode
err = syscall.Fchmod(dirfd2, origMode)
if err != nil {
return false
}
}
return true
}
//export gcf_rmdir
func gcf_rmdir(sessionID int, relPath string) bool {
defer clear_dirCache(sessionID)
parentDirFd, cName, err := openBackingDir(sessionID, relPath)
if err != nil {
return false
}
defer syscall.Close(parentDirFd)
dirfd, err := syscallcompat.Openat(parentDirFd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
return false
}
defer syscall.Close(dirfd)
// Check directory contents
children, err := syscallcompat.Getdents(dirfd)
if err == io.EOF {
// The directory is empty
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
return err_to_bool(err)
}
if err != nil {
return false
}
// If the directory is not empty besides gocryptfs.diriv, do not even
// attempt the dance around gocryptfs.diriv.
if len(children) > 1 {
return false
}
// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
tmpName := fmt.Sprintf("%s.rmdir.%d", nametransform.DirIVFilename, cryptocore.RandUint64())
err = syscallcompat.Renameat(dirfd, nametransform.DirIVFilename, parentDirFd, tmpName)
if err != nil {
return false
}
// Actual Rmdir
err = syscallcompat.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
if err != nil {
// This can happen if another file in the directory was created in the
// meantime, undo the rename
err2 := syscallcompat.Renameat(parentDirFd, tmpName, dirfd, nametransform.DirIVFilename)
return err_to_bool(err2)
}
// Delete "gocryptfs.diriv.rmdir.XYZ"
err = syscallcompat.Unlinkat(parentDirFd, tmpName, 0)
// Delete .name file
if nametransform.IsLongContent(cName) {
nametransform.DeleteLongNameAt(parentDirFd, cName)
}
return true
}
//export gcf_open_read_mode
func gcf_open_read_mode(sessionID int, path string) int {
newFlags := mangleOpenFlags(0)
dirfd, cName, err := openBackingDir(sessionID, path)
if err != nil {
return -1
}
defer syscall.Close(dirfd)
fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0)
if err != nil {
return -1
}
return register_file_handle(sessionID, File{os.NewFile(uintptr(fd), cName), path})
}
//export gcf_open_write_mode
func gcf_open_write_mode(sessionID int, path string) int {
newFlags := mangleOpenFlags(syscall.O_RDWR)
dirfd, cName, err := openBackingDir(sessionID, path)
if err != nil {
return -1
}
defer syscall.Close(dirfd)
fd := -1
// Handle long file name
if nametransform.IsLongContent(cName) {
// Create ".name"
err = sessions[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path)
if err != nil {
return -1
}
// Create content
fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, file_mode)
if err != nil {
nametransform.DeleteLongNameAt(dirfd, cName)
}
} else {
// Create content, normal (short) file name
fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, file_mode)
}
if err != nil {
// xfstests generic/488 triggers this
if err == syscall.EMFILE {
var lim syscall.Rlimit
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
}
return -1
}
return register_file_handle(sessionID, File{os.NewFile(uintptr(fd), cName), path})
}
//export gcf_truncate
func gcf_truncate(sessionID int, path string, offset uint64) bool {
handleID := gcf_open_write_mode(sessionID, path)
if handleID != -1 {
success := truncate(sessionID, handleID, offset)
gcf_close_file(sessionID, handleID)
return success
}
return false
}
//export gcf_close_file
func gcf_close_file(sessionID, handleID int){
f, ok := sessions[sessionID].file_handles[handleID]
if ok {
f.fd.Close()
delete(sessions[sessionID].file_handles, handleID)
_, ok := sessions[sessionID].fileIDs[handleID]
if ok {
delete(sessions[sessionID].fileIDs, handleID)
}
}
}
//export gcf_read_file
func gcf_read_file(sessionID, handleID int, offset uint64, dst_buff []byte) uint32 {
length := uint64(len(dst_buff))
if length > contentenc.MAX_KERNEL_WRITE {
return 0;
}
out, _ := doRead(sessionID, handleID, dst_buff[:0], offset, length)
return uint32(len(out))
}
//export gcf_write_file
func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32 {
length := uint64(len(data))
if length > contentenc.MAX_KERNEL_WRITE {
return 0;
}
written, _ := doWrite(sessionID, handleID, data, offset)
return written
}
//export gcf_get_attrs
func gcf_get_attrs(sessionID int, relPath string) (uint64, int64, bool) {
dirfd, cName, err := openBackingDir(sessionID, relPath)
if err != nil {
return 0, 0, false
}
var st unix.Stat_t
err = syscallcompat.Fstatat(dirfd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
syscall.Close(dirfd)
if err != nil {
return 0, 0, false
}
return sessions[sessionID].contentEnc.CipherSizeToPlainSize(uint64(st.Size)), st.Mtim.Sec, true
}
//export gcf_rename
func gcf_rename(sessionID int, oldPath string, newPath string) bool {
defer clear_dirCache(sessionID)
oldDirfd, oldCName, err := openBackingDir(sessionID, oldPath)
if err != nil {
return false
}
defer syscall.Close(oldDirfd)
newDirfd, newCName, err := openBackingDir(sessionID, newPath)
if err != nil {
return false
}
defer syscall.Close(newDirfd)
// Long destination file name: create .name file
nameFileAlreadyThere := false
if nametransform.IsLongContent(newCName) {
err = sessions[sessionID].nameTransform.WriteLongNameAt(newDirfd, newCName, newPath)
// Failure to write the .name file is expected when the target path already
// exists. Since hashes are pretty unique, there is no need to modify the
// .name file in this case, and we ignore the error.
if err == syscall.EEXIST {
nameFileAlreadyThere = true
} else if err != nil {
return false
}
}
// Actual rename
err = syscallcompat.Renameat(oldDirfd, oldCName, newDirfd, newCName)
if err == syscall.ENOTEMPTY || err == syscall.EEXIST {
// If an empty directory is overwritten we will always get an error as
// the "empty" directory will still contain gocryptfs.diriv.
// Interestingly, ext4 returns ENOTEMPTY while xfs returns EEXIST.
// We handle that by trying to fs.Rmdir() the target directory and trying
// again.
if gcf_rmdir(sessionID, newPath) {
err = syscallcompat.Renameat(oldDirfd, oldCName, newDirfd, newCName)
}
}
if err != nil {
if nametransform.IsLongContent(newCName) && nameFileAlreadyThere == false {
// Roll back .name creation unless the .name file was already there
nametransform.DeleteLongNameAt(newDirfd, newCName)
}
return false
}
if nametransform.IsLongContent(oldCName) {
nametransform.DeleteLongNameAt(oldDirfd, oldCName)
}
return true
}
//export gcf_remove_file
func gcf_remove_file(sessionID int, path string) bool {
dirfd, cName, err := openBackingDir(sessionID, path)
if err != nil {
return false
}
defer syscall.Close(dirfd)
// Delete content
err = syscallcompat.Unlinkat(dirfd, cName, 0)
if err != nil {
return false
}
// Delete ".name" file
if nametransform.IsLongContent(cName) {
err = nametransform.DeleteLongNameAt(dirfd, cName)
}
return err_to_bool(err)
}
func main(){}