| // Copyright © 2015 Steve Francia <spf@spf13.com>. |
| // Copyright 2013 tsuru authors. 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. |
| |
| package mem |
| |
| import ( |
| "bytes" |
| "errors" |
| "io" |
| "os" |
| "path/filepath" |
| "sync" |
| "sync/atomic" |
| ) |
| |
| import "time" |
| |
| const FilePathSeparator = string(filepath.Separator) |
| |
| type File struct { |
| // atomic requires 64-bit alignment for struct field access |
| at int64 |
| readDirCount int64 |
| closed bool |
| readOnly bool |
| fileData *FileData |
| } |
| |
| func NewFileHandle(data *FileData) *File { |
| return &File{fileData: data} |
| } |
| |
| func NewReadOnlyFileHandle(data *FileData) *File { |
| return &File{fileData: data, readOnly: true} |
| } |
| |
| func (f File) Data() *FileData { |
| return f.fileData |
| } |
| |
| type FileData struct { |
| sync.Mutex |
| name string |
| data []byte |
| memDir Dir |
| dir bool |
| mode os.FileMode |
| modtime time.Time |
| } |
| |
| func (d FileData) Name() string { |
| return d.name |
| } |
| |
| func CreateFile(name string) *FileData { |
| return &FileData{name: name, mode: os.ModeTemporary, modtime: time.Now()} |
| } |
| |
| func CreateDir(name string) *FileData { |
| return &FileData{name: name, memDir: &DirMap{}, dir: true} |
| } |
| |
| func ChangeFileName(f *FileData, newname string) { |
| f.name = newname |
| } |
| |
| func SetMode(f *FileData, mode os.FileMode) { |
| f.mode = mode |
| } |
| |
| func SetModTime(f *FileData, mtime time.Time) { |
| f.modtime = mtime |
| } |
| |
| func GetFileInfo(f *FileData) *FileInfo { |
| return &FileInfo{f} |
| } |
| |
| func (f *File) Open() error { |
| atomic.StoreInt64(&f.at, 0) |
| atomic.StoreInt64(&f.readDirCount, 0) |
| f.fileData.Lock() |
| f.closed = false |
| f.fileData.Unlock() |
| return nil |
| } |
| |
| func (f *File) Close() error { |
| f.fileData.Lock() |
| f.closed = true |
| if !f.readOnly { |
| SetModTime(f.fileData, time.Now()) |
| } |
| f.fileData.Unlock() |
| return nil |
| } |
| |
| func (f *File) Name() string { |
| return f.fileData.name |
| } |
| |
| func (f *File) Stat() (os.FileInfo, error) { |
| return &FileInfo{f.fileData}, nil |
| } |
| |
| func (f *File) Sync() error { |
| return nil |
| } |
| |
| func (f *File) Readdir(count int) (res []os.FileInfo, err error) { |
| var outLength int64 |
| |
| f.fileData.Lock() |
| files := f.fileData.memDir.Files()[f.readDirCount:] |
| if count > 0 { |
| if len(files) < count { |
| outLength = int64(len(files)) |
| } else { |
| outLength = int64(count) |
| } |
| if len(files) == 0 { |
| err = io.EOF |
| } |
| } else { |
| outLength = int64(len(files)) |
| } |
| f.readDirCount += outLength |
| f.fileData.Unlock() |
| |
| res = make([]os.FileInfo, outLength) |
| for i := range res { |
| res[i] = &FileInfo{files[i]} |
| } |
| |
| return res, err |
| } |
| |
| func (f *File) Readdirnames(n int) (names []string, err error) { |
| fi, err := f.Readdir(n) |
| names = make([]string, len(fi)) |
| for i, f := range fi { |
| _, names[i] = filepath.Split(f.Name()) |
| } |
| return names, err |
| } |
| |
| func (f *File) Read(b []byte) (n int, err error) { |
| f.fileData.Lock() |
| defer f.fileData.Unlock() |
| if f.closed == true { |
| return 0, ErrFileClosed |
| } |
| if len(b) > 0 && int(f.at) == len(f.fileData.data) { |
| return 0, io.EOF |
| } |
| if len(f.fileData.data)-int(f.at) >= len(b) { |
| n = len(b) |
| } else { |
| n = len(f.fileData.data) - int(f.at) |
| } |
| copy(b, f.fileData.data[f.at:f.at+int64(n)]) |
| atomic.AddInt64(&f.at, int64(n)) |
| return |
| } |
| |
| func (f *File) ReadAt(b []byte, off int64) (n int, err error) { |
| atomic.StoreInt64(&f.at, off) |
| return f.Read(b) |
| } |
| |
| func (f *File) Truncate(size int64) error { |
| if f.closed == true { |
| return ErrFileClosed |
| } |
| if f.readOnly { |
| return &os.PathError{"truncate", f.fileData.name, errors.New("file handle is read only")} |
| } |
| if size < 0 { |
| return ErrOutOfRange |
| } |
| if size > int64(len(f.fileData.data)) { |
| diff := size - int64(len(f.fileData.data)) |
| f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{00}, int(diff))...) |
| } else { |
| f.fileData.data = f.fileData.data[0:size] |
| } |
| SetModTime(f.fileData, time.Now()) |
| return nil |
| } |
| |
| func (f *File) Seek(offset int64, whence int) (int64, error) { |
| if f.closed == true { |
| return 0, ErrFileClosed |
| } |
| switch whence { |
| case 0: |
| atomic.StoreInt64(&f.at, offset) |
| case 1: |
| atomic.AddInt64(&f.at, int64(offset)) |
| case 2: |
| atomic.StoreInt64(&f.at, int64(len(f.fileData.data))+offset) |
| } |
| return f.at, nil |
| } |
| |
| func (f *File) Write(b []byte) (n int, err error) { |
| if f.readOnly { |
| return 0, &os.PathError{"write", f.fileData.name, errors.New("file handle is read only")} |
| } |
| n = len(b) |
| cur := atomic.LoadInt64(&f.at) |
| f.fileData.Lock() |
| defer f.fileData.Unlock() |
| diff := cur - int64(len(f.fileData.data)) |
| var tail []byte |
| if n+int(cur) < len(f.fileData.data) { |
| tail = f.fileData.data[n+int(cur):] |
| } |
| if diff > 0 { |
| f.fileData.data = append(bytes.Repeat([]byte{00}, int(diff)), b...) |
| f.fileData.data = append(f.fileData.data, tail...) |
| } else { |
| f.fileData.data = append(f.fileData.data[:cur], b...) |
| f.fileData.data = append(f.fileData.data, tail...) |
| } |
| SetModTime(f.fileData, time.Now()) |
| |
| atomic.StoreInt64(&f.at, int64(len(f.fileData.data))) |
| return |
| } |
| |
| func (f *File) WriteAt(b []byte, off int64) (n int, err error) { |
| atomic.StoreInt64(&f.at, off) |
| return f.Write(b) |
| } |
| |
| func (f *File) WriteString(s string) (ret int, err error) { |
| return f.Write([]byte(s)) |
| } |
| |
| func (f *File) Info() *FileInfo { |
| return &FileInfo{f.fileData} |
| } |
| |
| type FileInfo struct { |
| *FileData |
| } |
| |
| // Implements os.FileInfo |
| func (s *FileInfo) Name() string { |
| _, name := filepath.Split(s.name) |
| return name |
| } |
| func (s *FileInfo) Mode() os.FileMode { return s.mode } |
| func (s *FileInfo) ModTime() time.Time { return s.modtime } |
| func (s *FileInfo) IsDir() bool { return s.dir } |
| func (s *FileInfo) Sys() interface{} { return nil } |
| func (s *FileInfo) Size() int64 { |
| if s.IsDir() { |
| return int64(42) |
| } |
| return int64(len(s.data)) |
| } |
| |
| var ( |
| ErrFileClosed = errors.New("File is closed") |
| ErrOutOfRange = errors.New("Out of range") |
| ErrTooLarge = errors.New("Too large") |
| ErrFileNotFound = os.ErrNotExist |
| ErrFileExists = os.ErrExist |
| ErrDestinationExists = os.ErrExist |
| ) |