123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 |
- /*
- * Rufs - Remote Union File System
- *
- * Copyright 2017 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the MIT
- * License, If a copy of the MIT License was not distributed with this
- * file, You can obtain one at https://opensource.org/licenses/MIT.
- */
- /*
- Package rufs contains the main API to Rufs.
- Rufs is organized as a collection of branches. Each branch represents a physical
- file system structure which can be queried and updated by an authorized client.
- On the client side one or several branches are organized into a tree. The
- single branches can overlay each other. For example:
- Branch A
- /foo/A
- /foo/B
- /bar/C
- Branch B
- /foo/C
- /test/D
- Tree 1
- /myspace => Branch A, Branch B
- Accessing tree with:
- /myspace/foo/A gets file /foo/A from Branch A while
- /myspace/foo/C gets file /foo/C from Branch B
- Write operations go only to branches which are mapped as writing branches
- and who accept them (i.e. are not set to readonly on the side of the branch).
- */
- package rufs
- import (
- "bytes"
- "crypto/tls"
- "encoding/gob"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/fileutil"
- "devt.de/krotik/common/pools"
- "devt.de/krotik/common/stringutil"
- "devt.de/krotik/rufs/config"
- "devt.de/krotik/rufs/node"
- )
- func init() {
- // Make sure we can use the relevant types in a gob operation
- gob.Register([][]os.FileInfo{})
- gob.Register(&FileInfo{})
- }
- /*
- Branch models a single exported branch in Rufs.
- */
- type Branch struct {
- rootPath string // Local directory (absolute path) modeling the branch root
- node *node.RufsNode // Local RPC node
- readonly bool // Flag if this branch is readonly
- }
- /*
- NewBranch returns a new exported branch.
- */
- func NewBranch(cfg map[string]interface{}, cert *tls.Certificate) (*Branch, error) {
- var err error
- var b *Branch
- // Make sure the given config is ok
- if err = config.CheckBranchExportConfig(cfg); err == nil {
- // Create RPC server
- addr := fmt.Sprintf("%v:%v", fileutil.ConfStr(cfg, config.RPCHost),
- fileutil.ConfStr(cfg, config.RPCPort))
- rn := node.NewNode(addr, fileutil.ConfStr(cfg, config.BranchName),
- fileutil.ConfStr(cfg, config.BranchSecret), cert, nil)
- // Start the rpc server
- if err = rn.Start(cert); err == nil {
- var rootPath string
- // Construct root path
- if rootPath, err = filepath.Abs(fileutil.ConfStr(cfg, config.LocalFolder)); err == nil {
- b = &Branch{rootPath, rn, fileutil.ConfBool(cfg, config.EnableReadOnly)}
- rn.DataHandler = b.requestHandler
- }
- }
- }
- return b, err
- }
- /*
- Name returns the name of the branch.
- */
- func (b *Branch) Name() string {
- return b.node.Name()
- }
- /*
- SSLFingerprint returns the SSL fingerprint of the branch.
- */
- func (b *Branch) SSLFingerprint() string {
- return b.node.SSLFingerprint()
- }
- /*
- Shutdown shuts the branch down.
- */
- func (b *Branch) Shutdown() error {
- return b.node.Shutdown()
- }
- /*
- IsReadOnly returns if this branch is read-only.
- */
- func (b *Branch) IsReadOnly() bool {
- return b.readonly
- }
- /*
- checkReadOnly returns an error if this branch is read-only.
- */
- func (b *Branch) checkReadOnly() error {
- var err error
- if b.IsReadOnly() {
- err = fmt.Errorf("Branch %v is read-only", b.Name())
- }
- return err
- }
- // Branch API
- // ==========
- /*
- Dir returns file listings matching a given pattern of one or more directories.
- The contents of the given path is returned along with checksums if the checksum
- flag is specified. Optionally, also the contents of all subdirectories can be
- returned if the recursive flag is set. The return values is a list of traversed
- directories (platform-agnostic) and their corresponding contents.
- */
- func (b *Branch) Dir(spath string, pattern string, recursive bool, checksums bool) ([]string, [][]os.FileInfo, error) {
- var fis []os.FileInfo
- // Compile pattern
- re, err := regexp.Compile(pattern)
- if err != nil {
- return nil, nil, err
- }
- createRufsFileInfos := func(dirname string, afis []os.FileInfo) []os.FileInfo {
- var fis []os.FileInfo
- fis = make([]os.FileInfo, 0, len(afis))
- for _, fi := range afis {
- // Append if it matches the pattern
- if re.MatchString(fi.Name()) {
- fis = append(fis, fi)
- }
- }
- // Wrap normal file infos and calculate checksum if necessary
- ret := WrapFileInfos(dirname, fis)
- if checksums {
- for _, fi := range fis {
- if !fi.IsDir() {
- // The sum is either there or not ... - access errors should
- // be caught when trying to read the file
- sum, _ := fileutil.CheckSumFileFast(filepath.Join(dirname, fi.Name()))
- fi.(*FileInfo).FiChecksum = sum
- }
- }
- }
- return ret
- }
- subPath, err := b.constructSubPath(spath)
- if err == nil {
- if !recursive {
- if fis, err = ioutil.ReadDir(subPath); err == nil {
- return []string{spath},
- [][]os.FileInfo{createRufsFileInfos(subPath, fis)}, nil
- }
- } else {
- var rpaths []string
- var rfis [][]os.FileInfo
- var addSubDir func(string, string) error
- // Recursive function to walk directories and symlinks
- // in a platform-agnostic way
- addSubDir = func(p string, rp string) error {
- fis, err = ioutil.ReadDir(p)
- if err == nil {
- rpaths = append(rpaths, rp)
- rfis = append(rfis, createRufsFileInfos(p, fis))
- for _, fi := range fis {
- if err == nil && fi.IsDir() {
- err = addSubDir(filepath.Join(p, fi.Name()),
- path.Join(rp, fi.Name()))
- }
- }
- }
- return err
- }
- if err = addSubDir(subPath, spath); err == nil {
- return rpaths, rfis, nil
- }
- }
- }
- // Ignore any not exists errors
- if os.IsNotExist(err) {
- err = nil
- }
- return nil, nil, err
- }
- /*
- ReadFileToBuffer reads a complete file into a given buffer which implements
- io.Writer.
- */
- func (b *Branch) ReadFileToBuffer(spath string, buf io.Writer) error {
- var n int
- var err error
- var offset int64
- readBuf := make([]byte, DefaultReadBufferSize)
- for err == nil {
- n, err = b.ReadFile(spath, readBuf, offset)
- if err == nil {
- _, err = buf.Write(readBuf[:n])
- offset += int64(n)
- } else if IsEOF(err) {
- // We reached the end of the file
- err = nil
- break
- }
- }
- return err
- }
- /*
- ReadFile reads up to len(p) bytes into p from the given offset. It
- returns the number of bytes read (0 <= n <= len(p)) and any error
- encountered.
- */
- func (b *Branch) ReadFile(spath string, p []byte, offset int64) (int, error) {
- var n int
- subPath, err := b.constructSubPath(spath)
- if err == nil {
- var fi os.FileInfo
- if fi, err = os.Stat(subPath); err == nil {
- if fi.IsDir() {
- err = fmt.Errorf("read /%v: is a directory", spath)
- } else if err == nil {
- var f *os.File
- if f, err = os.Open(subPath); err == nil {
- defer f.Close()
- sr := io.NewSectionReader(f, 0, fi.Size())
- if _, err = sr.Seek(offset, io.SeekStart); err == nil {
- n, err = sr.Read(p)
- }
- }
- }
- }
- }
- return n, err
- }
- /*
- WriteFileFromBuffer writes a complete file from a given buffer which implements
- io.Reader.
- */
- func (b *Branch) WriteFileFromBuffer(spath string, buf io.Reader) error {
- var err error
- var offset int64
- if err = b.checkReadOnly(); err == nil {
- writeBuf := make([]byte, DefaultReadBufferSize)
- for err == nil {
- var n int
- if n, err = buf.Read(writeBuf); err == nil {
- _, err = b.WriteFile(spath, writeBuf[:n], offset)
- offset += int64(n)
- } else if IsEOF(err) {
- // We reached the end of the file
- b.WriteFile(spath, []byte{}, offset)
- err = nil
- break
- }
- }
- }
- return err
- }
- /*
- WriteFile writes p into the given file from the given offset. It
- returns the number of written bytes and any error encountered.
- */
- func (b *Branch) WriteFile(spath string, p []byte, offset int64) (int, error) {
- var n int
- var m int64
- if err := b.checkReadOnly(); err != nil {
- return 0, err
- }
- buf := byteSlicePool.Get().([]byte)
- defer func() {
- byteSlicePool.Put(buf)
- }()
- growFile := func(f *os.File, n int64) {
- var err error
- toWrite := n
- for err == nil && toWrite > 0 {
- if toWrite > int64(DefaultReadBufferSize) {
- _, err = f.Write(buf[:DefaultReadBufferSize])
- toWrite -= int64(DefaultReadBufferSize)
- } else {
- _, err = f.Write(buf[:toWrite])
- toWrite = 0
- }
- }
- }
- subPath, err := b.constructSubPath(spath)
- if err == nil {
- var fi os.FileInfo
- var f *os.File
- if fi, err = os.Stat(subPath); os.IsNotExist(err) {
- // Ensure path exists
- dir, _ := filepath.Split(subPath)
- if err = os.MkdirAll(dir, 0755); err == nil {
- // Create the file newly
- if f, err = os.OpenFile(subPath, os.O_RDWR|os.O_CREATE, 0644); err == nil {
- defer f.Close()
- if offset > 0 {
- growFile(f, offset)
- }
- m, err = io.Copy(f, bytes.NewBuffer(p))
- n += int(m)
- }
- }
- } else if err == nil {
- // File does exist
- if f, err := os.OpenFile(subPath, os.O_RDWR, 0644); err == nil {
- defer f.Close()
- if fi.Size() < offset {
- f.Seek(fi.Size(), io.SeekStart)
- growFile(f, offset-fi.Size())
- } else {
- f.Seek(offset, io.SeekStart)
- }
- m, err = io.Copy(f, bytes.NewBuffer(p))
- errorutil.AssertOk(err)
- n += int(m)
- }
- }
- }
- return n, err
- }
- /*
- ItemOp parameter
- */
- const (
- ItemOpAction = "itemop_action" // ItemOp action
- ItemOpName = "itemop_name" // Item name
- ItemOpNewName = "itemop_newname" // New item name
- )
- /*
- ItemOp actions
- */
- const (
- ItemOpActRename = "rename" // Rename a file or directory
- ItemOpActDelete = "delete" // Delete a file or directory
- ItemOpActMkDir = "mkdir" // Create a directory
- )
- /*
- ItemOp executes a file or directory specific operation which can either
- succeed or fail (e.g. rename or delete). Actions and parameters should
- be given in the opdata map.
- */
- func (b *Branch) ItemOp(spath string, opdata map[string]string) (bool, error) {
- res := false
- if err := b.checkReadOnly(); err != nil {
- return false, err
- }
- subPath, err := b.constructSubPath(spath)
- if err == nil {
- action := opdata[ItemOpAction]
- fileFromOpData := func(key string) (string, error) {
- // Make sure we are only dealing with files
- _, name := filepath.Split(opdata[key])
- if name == "" {
- return "", fmt.Errorf("This operation requires a specific file or directory")
- }
- // Build the relative paths
- return filepath.Join(filepath.FromSlash(subPath), name), nil
- }
- if action == ItemOpActMkDir {
- var name string
- // Make directory action
- if name, err = fileFromOpData(ItemOpName); err == nil {
- err = os.MkdirAll(name, 0755)
- }
- } else if action == ItemOpActRename {
- var name, newname string
- // Rename action
- if name, err = fileFromOpData(ItemOpName); err == nil {
- if newname, err = fileFromOpData(ItemOpNewName); err == nil {
- err = os.Rename(name, newname)
- }
- }
- } else if action == ItemOpActDelete {
- var name string
- // Delete action
- if name, err = fileFromOpData(ItemOpName); err == nil {
- del := func(name string) error {
- var err error
- if ok, _ := fileutil.PathExists(name); ok {
- err = os.RemoveAll(name)
- } else {
- err = os.ErrNotExist
- }
- return err
- }
- if strings.Contains(name, "*") {
- var rex string
- // We have a wildcard
- rootdir, glob := filepath.Split(name)
- // Create a regex from the given glob expression
- if rex, err = stringutil.GlobToRegex(glob); err == nil {
- var dirs []string
- var fis [][]os.FileInfo
- if dirs, fis, err = b.Dir(spath, rex, true, false); err == nil {
- for i, dir := range dirs {
- // Remove all files and dirs according to the wildcard
- for _, fi := range fis[i] {
- os.RemoveAll(filepath.Join(rootdir,
- filepath.FromSlash(dir), fi.Name()))
- }
- }
- }
- }
- } else {
- err = del(name)
- }
- }
- }
- // Determine if we succeeded
- res = err == nil || os.IsNotExist(err)
- }
- return res, err
- }
- // Request handling functions
- // ==========================
- /*
- DefaultReadBufferSize is the default size for file reading.
- */
- var DefaultReadBufferSize = 1024 * 16
- /*
- bufferPool holds buffers which are used to marshal objects.
- */
- var bufferPool = pools.NewByteBufferPool()
- /*
- byteSlicePool holds buffers which are used to read files
- */
- var byteSlicePool = pools.NewByteSlicePool(DefaultReadBufferSize)
- /*
- Meta parameter
- */
- const (
- ParamAction = "a" // Requested action
- ParamPath = "p" // Path string
- ParamPattern = "x" // Pattern string
- ParamRecursive = "r" // Recursive flag
- ParamChecksums = "c" // Checksum flag
- ParamOffset = "o" // Offset parameter
- ParamSize = "s" // Size parameter
- )
- /*
- Possible actions
- */
- const (
- OpDir = "dir" // Read the contents of a path
- OpRead = "read" // Read the contents of a file
- OpWrite = "write" // Read the contents of a file
- OpItemOp = "itemop" // File or directory operation
- )
- /*
- requestHandler handles incoming requests from other branches or trees.
- */
- func (b *Branch) requestHandler(ctrl map[string]string, data []byte) ([]byte, error) {
- var err error
- var res interface{}
- var ret []byte
- action := ctrl[ParamAction]
- // Handle operation requests
- if action == OpDir {
- var dirs []string
- var fis [][]os.FileInfo
- dir := ctrl[ParamPath]
- pattern := ctrl[ParamPattern]
- rec := strings.ToLower(ctrl[ParamRecursive]) == "true"
- sum := strings.ToLower(ctrl[ParamChecksums]) == "true"
- if dirs, fis, err = b.Dir(dir, pattern, rec, sum); err == nil {
- res = []interface{}{dirs, fis}
- }
- } else if action == OpItemOp {
- res, err = b.ItemOp(ctrl[ParamPath], ctrl)
- } else if action == OpRead {
- var size, n int
- var offset int64
- spath := ctrl[ParamPath]
- if size, err = strconv.Atoi(ctrl[ParamSize]); err == nil {
- if offset, err = strconv.ParseInt(ctrl[ParamOffset], 10, 64); err == nil {
- buf := byteSlicePool.Get().([]byte)
- defer func() {
- byteSlicePool.Put(buf)
- }()
- if len(buf) < size {
- // Constantly requesting bigger buffers will
- // eventually replace all default sized buffers
- buf = make([]byte, size)
- }
- if n, err = b.ReadFile(spath, buf[:size], offset); err == nil {
- res = []interface{}{n, buf[:size]}
- }
- }
- }
- } else if action == OpWrite {
- var offset int64
- spath := ctrl[ParamPath]
- if offset, err = strconv.ParseInt(ctrl[ParamOffset], 10, 64); err == nil {
- res, err = b.WriteFile(spath, data, offset)
- }
- }
- // Send the response
- if err == nil {
- // Allocate a new encoding buffer - no need to lock as
- // it is based on sync.Pool
- // Pooled encoding buffers are used to keep expensive buffer
- // reallocations to a minimum. It is better to allocate the
- // actual response buffer once the response size is known.
- bb := bufferPool.Get().(*bytes.Buffer)
- if err = gob.NewEncoder(bb).Encode(res); err == nil {
- toSend := bb.Bytes()
- // Allocate the response array
- ret = make([]byte, len(toSend))
- // Copy encoded result into the response array
- copy(ret, toSend)
- }
- // Return the encoding buffer back to the pool
- go func() {
- bb.Reset()
- bufferPool.Put(bb)
- }()
- }
- if err != nil {
- // Ensure we don't leak local paths - this might not work in
- // all situations and depends on the underlying os. In this
- // error messages might include information on the full local
- // path in error messages.
- absRoot, _ := filepath.Abs(b.rootPath)
- err = fmt.Errorf("%v", strings.Replace(err.Error(), absRoot, "", -1))
- }
- return ret, err
- }
- // Util functions
- // ==============
- func (b *Branch) constructSubPath(rpath string) (string, error) {
- // Produce the actual subpath - this should also produce windows
- // paths correctly (i.e. foo/bar -> C:\root\foo\bar)
- subPath := filepath.Join(b.rootPath, filepath.FromSlash(rpath))
- // Check that the new sub path is under the root path
- absSubPath, err := filepath.Abs(subPath)
- if err == nil {
- if strings.HasPrefix(absSubPath, b.rootPath) {
- return subPath, nil
- }
- err = fmt.Errorf("Requested path %v is outside of the branch", rpath)
- }
- return "", err
- }
|