2015-03-02 06:06:32 +03:00
|
|
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
2015-03-04 00:27:42 +03:00
|
|
|
//
|
|
|
|
// 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-03-02 06:06:32 +03:00
|
|
|
|
|
|
|
package memfs
|
|
|
|
|
2015-03-02 06:37:01 +03:00
|
|
|
import (
|
2015-03-02 08:01:01 +03:00
|
|
|
"fmt"
|
2015-03-05 20:56:56 +03:00
|
|
|
"io"
|
2015-03-02 08:01:01 +03:00
|
|
|
"os"
|
2015-03-05 22:04:51 +03:00
|
|
|
"time"
|
2015-03-02 08:01:01 +03:00
|
|
|
|
2015-03-24 08:20:32 +03:00
|
|
|
"github.com/jacobsa/fuse/fuseops"
|
2015-03-02 07:35:12 +03:00
|
|
|
"github.com/jacobsa/fuse/fuseutil"
|
2015-03-02 06:37:01 +03:00
|
|
|
)
|
|
|
|
|
2015-03-02 06:06:32 +03:00
|
|
|
// Common attributes for files and directories.
|
2015-06-25 14:35:09 +03:00
|
|
|
//
|
|
|
|
// External synchronization is required.
|
2015-03-02 06:06:32 +03:00
|
|
|
type inode struct {
|
2015-03-02 07:35:12 +03:00
|
|
|
/////////////////////////
|
|
|
|
// Mutable state
|
|
|
|
/////////////////////////
|
|
|
|
|
|
|
|
// The current attributes of this inode.
|
2015-03-02 06:06:32 +03:00
|
|
|
//
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
|
|
|
|
// INVARIANT: !(isDir() && isSymlink())
|
|
|
|
// INVARIANT: attrs.Size == len(contents)
|
2015-09-09 02:17:55 +03:00
|
|
|
attrs fuseops.InodeAttributes
|
2015-03-02 06:37:01 +03:00
|
|
|
|
2015-03-03 06:29:49 +03:00
|
|
|
// For directories, entries describing the children of the directory. Unused
|
|
|
|
// entries are of type DT_Unknown.
|
2015-03-02 07:35:12 +03:00
|
|
|
//
|
|
|
|
// This array can never be shortened, nor can its elements be moved, because
|
|
|
|
// we use its indices for Dirent.Offset, which is exposed to the user who
|
|
|
|
// might be calling readdir in a loop while concurrently modifying the
|
|
|
|
// directory. Unused entries can, however, be reused.
|
|
|
|
//
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: If !isDir(), len(entries) == 0
|
2015-03-02 07:35:12 +03:00
|
|
|
// INVARIANT: For each i, entries[i].Offset == i+1
|
2015-03-03 06:29:49 +03:00
|
|
|
// INVARIANT: Contains no duplicate names in used entries.
|
2015-09-09 02:17:55 +03:00
|
|
|
entries []fuseutil.Dirent
|
2015-03-02 07:18:23 +03:00
|
|
|
|
2015-03-02 07:35:12 +03:00
|
|
|
// For files, the current contents of the file.
|
|
|
|
//
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: If !isFile(), len(contents) == 0
|
2015-09-09 02:17:55 +03:00
|
|
|
contents []byte
|
2015-05-19 08:46:09 +03:00
|
|
|
|
|
|
|
// For symlinks, the target of the symlink.
|
|
|
|
//
|
|
|
|
// INVARIANT: If !isSymlink(), len(target) == 0
|
|
|
|
target string
|
2015-03-02 07:20:29 +03:00
|
|
|
}
|
2015-03-02 07:35:12 +03:00
|
|
|
|
2015-03-03 03:03:03 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Helpers
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-03-05 21:23:44 +03:00
|
|
|
// Create a new inode with the supplied attributes, which need not contain
|
|
|
|
// time-related information (the inode object will take care of that).
|
|
|
|
func newInode(
|
2015-03-24 08:20:32 +03:00
|
|
|
attrs fuseops.InodeAttributes) (in *inode) {
|
2015-03-05 21:23:44 +03:00
|
|
|
// Update time info.
|
2015-08-11 09:05:17 +03:00
|
|
|
now := time.Now()
|
2015-03-05 21:23:44 +03:00
|
|
|
attrs.Mtime = now
|
|
|
|
attrs.Crtime = now
|
|
|
|
|
|
|
|
// Create the object.
|
2015-03-02 07:54:56 +03:00
|
|
|
in = &inode{
|
2015-05-19 08:51:38 +03:00
|
|
|
attrs: attrs,
|
2015-03-02 07:54:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2015-03-02 07:35:12 +03:00
|
|
|
|
2015-06-25 14:35:09 +03:00
|
|
|
func (in *inode) CheckInvariants() {
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
|
|
|
|
if !(in.attrs.Mode&^(os.ModePerm|os.ModeDir|os.ModeSymlink) == 0) {
|
|
|
|
panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
|
2015-03-02 08:01:01 +03:00
|
|
|
}
|
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: !(isDir() && isSymlink())
|
|
|
|
if in.isDir() && in.isSymlink() {
|
|
|
|
panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
|
2015-03-02 08:01:01 +03:00
|
|
|
}
|
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: attrs.Size == len(contents)
|
2015-05-19 08:51:38 +03:00
|
|
|
if in.attrs.Size != uint64(len(in.contents)) {
|
2015-05-19 08:46:09 +03:00
|
|
|
panic(fmt.Sprintf(
|
|
|
|
"Size mismatch: %d vs. %d",
|
|
|
|
in.attrs.Size,
|
|
|
|
len(in.contents)))
|
|
|
|
}
|
2015-03-02 08:01:01 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: If !isDir(), len(entries) == 0
|
2015-05-19 08:51:38 +03:00
|
|
|
if !in.isDir() && len(in.entries) != 0 {
|
|
|
|
panic(fmt.Sprintf("Unexpected entries length: %d", len(in.entries)))
|
2015-05-19 08:46:09 +03:00
|
|
|
}
|
2015-03-02 08:12:54 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: For each i, entries[i].Offset == i+1
|
|
|
|
for i, e := range in.entries {
|
2015-05-19 08:51:38 +03:00
|
|
|
if !(e.Offset == fuseops.DirOffset(i+1)) {
|
2015-05-19 08:46:09 +03:00
|
|
|
panic(fmt.Sprintf("Unexpected offset for index %d: %d", i, e.Offset))
|
|
|
|
}
|
|
|
|
}
|
2015-03-02 08:12:54 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: Contains no duplicate names in used entries.
|
|
|
|
childNames := make(map[string]struct{})
|
2015-05-19 08:53:01 +03:00
|
|
|
for _, e := range in.entries {
|
2015-05-19 08:46:09 +03:00
|
|
|
if e.Type != fuseutil.DT_Unknown {
|
|
|
|
if _, ok := childNames[e.Name]; ok {
|
|
|
|
panic(fmt.Sprintf("Duplicate name: %s", e.Name))
|
2015-03-03 06:29:49 +03:00
|
|
|
}
|
2015-05-19 08:46:09 +03:00
|
|
|
|
|
|
|
childNames[e.Name] = struct{}{}
|
2015-03-02 08:01:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: If !isFile(), len(contents) == 0
|
|
|
|
if !in.isFile() && len(in.contents) != 0 {
|
|
|
|
panic(fmt.Sprintf("Unexpected length: %d", len(in.contents)))
|
2015-03-02 08:01:01 +03:00
|
|
|
}
|
2015-03-05 11:35:32 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
// INVARIANT: If !isSymlink(), len(target) == 0
|
|
|
|
if !in.isSymlink() && len(in.target) != 0 {
|
|
|
|
panic(fmt.Sprintf("Unexpected target length: %d", len(in.target)))
|
2015-03-05 11:35:32 +03:00
|
|
|
}
|
2015-05-19 08:46:09 +03:00
|
|
|
|
|
|
|
return
|
2015-03-02 08:01:01 +03:00
|
|
|
}
|
2015-03-02 07:50:34 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
func (in *inode) isDir() bool {
|
|
|
|
return in.attrs.Mode&os.ModeDir != 0
|
|
|
|
}
|
2015-03-02 07:54:56 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
func (in *inode) isSymlink() bool {
|
|
|
|
return in.attrs.Mode&os.ModeSymlink != 0
|
|
|
|
}
|
2015-03-02 07:55:26 +03:00
|
|
|
|
2015-05-19 08:46:09 +03:00
|
|
|
func (in *inode) isFile() bool {
|
|
|
|
return !(in.isDir() || in.isSymlink())
|
2015-03-02 07:54:56 +03:00
|
|
|
}
|
|
|
|
|
2015-05-19 08:52:28 +03:00
|
|
|
// Return the index of the child within in.entries, if it exists.
|
|
|
|
//
|
2015-06-25 14:35:09 +03:00
|
|
|
// REQUIRES: in.isDir()
|
2015-05-19 08:52:28 +03:00
|
|
|
func (in *inode) findChild(name string) (i int, ok bool) {
|
|
|
|
if !in.isDir() {
|
|
|
|
panic("findChild called on non-directory.")
|
|
|
|
}
|
|
|
|
|
|
|
|
var e fuseutil.Dirent
|
|
|
|
for i, e = range in.entries {
|
|
|
|
if e.Name == name {
|
|
|
|
ok = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-03-03 06:23:46 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Public methods
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2015-03-03 06:39:29 +03:00
|
|
|
// Return the number of children of the directory.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isDir()
|
|
|
|
func (in *inode) Len() (n int) {
|
|
|
|
for _, e := range in.entries {
|
2015-03-03 06:39:29 +03:00
|
|
|
if e.Type != fuseutil.DT_Unknown {
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-03-03 06:23:46 +03:00
|
|
|
// Find an entry for the given child name and return its inode ID.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isDir()
|
2015-06-25 09:54:49 +03:00
|
|
|
func (in *inode) LookUpChild(name string) (
|
|
|
|
id fuseops.InodeID,
|
|
|
|
typ fuseutil.DirentType,
|
|
|
|
ok bool) {
|
2015-05-19 08:48:17 +03:00
|
|
|
index, ok := in.findChild(name)
|
2015-03-03 06:23:46 +03:00
|
|
|
if ok {
|
2015-05-19 08:48:17 +03:00
|
|
|
id = in.entries[index].Inode
|
2015-06-25 09:54:49 +03:00
|
|
|
typ = in.entries[index].Type
|
2015-03-03 06:23:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-03-03 01:33:33 +03:00
|
|
|
// Add an entry for a child.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isDir()
|
2015-03-03 06:29:49 +03:00
|
|
|
// REQUIRES: dt != fuseutil.DT_Unknown
|
2015-05-19 08:48:17 +03:00
|
|
|
func (in *inode) AddChild(
|
2015-03-24 08:20:32 +03:00
|
|
|
id fuseops.InodeID,
|
2015-03-03 01:33:33 +03:00
|
|
|
name string,
|
2015-03-03 01:35:16 +03:00
|
|
|
dt fuseutil.DirentType) {
|
2015-03-03 06:45:39 +03:00
|
|
|
var index int
|
|
|
|
|
2015-03-05 21:33:17 +03:00
|
|
|
// Update the modification time.
|
2015-08-11 09:05:17 +03:00
|
|
|
in.attrs.Mtime = time.Now()
|
2015-03-05 21:33:17 +03:00
|
|
|
|
2015-03-03 06:45:39 +03:00
|
|
|
// No matter where we place the entry, make sure it has the correct Offset
|
|
|
|
// field.
|
|
|
|
defer func() {
|
2015-05-19 08:48:17 +03:00
|
|
|
in.entries[index].Offset = fuseops.DirOffset(index + 1)
|
2015-03-03 06:45:39 +03:00
|
|
|
}()
|
|
|
|
|
2015-03-03 06:31:15 +03:00
|
|
|
// Set up the entry.
|
2015-03-03 01:35:16 +03:00
|
|
|
e := fuseutil.Dirent{
|
2015-03-03 06:45:39 +03:00
|
|
|
Inode: id,
|
|
|
|
Name: name,
|
|
|
|
Type: dt,
|
2015-03-03 01:35:16 +03:00
|
|
|
}
|
|
|
|
|
2015-03-03 06:31:15 +03:00
|
|
|
// Look for a gap in which we can insert it.
|
2015-05-19 08:48:17 +03:00
|
|
|
for index = range in.entries {
|
|
|
|
if in.entries[index].Type == fuseutil.DT_Unknown {
|
|
|
|
in.entries[index] = e
|
2015-03-03 06:31:15 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2015-03-03 06:29:49 +03:00
|
|
|
|
2015-03-03 06:31:15 +03:00
|
|
|
// Append it to the end.
|
2015-05-19 08:48:17 +03:00
|
|
|
index = len(in.entries)
|
|
|
|
in.entries = append(in.entries, e)
|
2015-03-03 01:35:16 +03:00
|
|
|
}
|
2015-03-03 01:33:33 +03:00
|
|
|
|
2015-03-03 03:28:41 +03:00
|
|
|
// Remove an entry for a child.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isDir()
|
2015-03-03 03:28:41 +03:00
|
|
|
// REQUIRES: An entry for the given name exists.
|
2015-05-19 08:48:17 +03:00
|
|
|
func (in *inode) RemoveChild(name string) {
|
2015-03-05 21:33:17 +03:00
|
|
|
// Update the modification time.
|
2015-08-11 09:05:17 +03:00
|
|
|
in.attrs.Mtime = time.Now()
|
2015-03-05 21:33:17 +03:00
|
|
|
|
2015-03-03 06:29:49 +03:00
|
|
|
// Find the entry.
|
2015-05-19 08:48:17 +03:00
|
|
|
i, ok := in.findChild(name)
|
2015-03-03 06:29:49 +03:00
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("Unknown child: %s", name))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark it as unused.
|
2015-05-19 08:48:17 +03:00
|
|
|
in.entries[i] = fuseutil.Dirent{
|
2015-03-03 06:30:14 +03:00
|
|
|
Type: fuseutil.DT_Unknown,
|
2015-03-24 08:24:43 +03:00
|
|
|
Offset: fuseops.DirOffset(i + 1),
|
2015-03-03 06:29:49 +03:00
|
|
|
}
|
|
|
|
}
|
2015-03-03 03:28:41 +03:00
|
|
|
|
2015-03-02 07:54:56 +03:00
|
|
|
// Serve a ReadDir request.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isDir()
|
2015-07-29 03:12:11 +03:00
|
|
|
func (in *inode) ReadDir(p []byte, offset int) (n int) {
|
2015-05-19 08:53:01 +03:00
|
|
|
if !in.isDir() {
|
2015-03-02 07:54:56 +03:00
|
|
|
panic("ReadDir called on non-directory.")
|
|
|
|
}
|
|
|
|
|
2015-05-19 08:48:17 +03:00
|
|
|
for i := offset; i < len(in.entries); i++ {
|
|
|
|
e := in.entries[i]
|
2015-03-03 06:29:49 +03:00
|
|
|
|
|
|
|
// Skip unused entries.
|
|
|
|
if e.Type == fuseutil.DT_Unknown {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-07-29 03:12:11 +03:00
|
|
|
tmp := fuseutil.WriteDirent(p[n:], in.entries[i])
|
2015-07-29 03:12:59 +03:00
|
|
|
if tmp == 0 {
|
2015-03-02 07:54:56 +03:00
|
|
|
break
|
|
|
|
}
|
2015-07-29 03:12:11 +03:00
|
|
|
|
|
|
|
n += tmp
|
2015-03-02 07:54:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2015-03-05 11:33:10 +03:00
|
|
|
|
2015-03-05 20:56:56 +03:00
|
|
|
// Read from the file's contents. See documentation for ioutil.ReaderAt.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isFile()
|
|
|
|
func (in *inode) ReadAt(p []byte, off int64) (n int, err error) {
|
2015-05-19 08:53:01 +03:00
|
|
|
if !in.isFile() {
|
|
|
|
panic("ReadAt called on non-file.")
|
2015-03-05 20:56:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the offset is in range.
|
2015-05-19 08:48:17 +03:00
|
|
|
if off > int64(len(in.contents)) {
|
2015-03-05 20:56:56 +03:00
|
|
|
err = io.EOF
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read what we can.
|
2015-05-19 08:48:17 +03:00
|
|
|
n = copy(p, in.contents[off:])
|
2015-03-05 20:56:56 +03:00
|
|
|
if n < len(p) {
|
|
|
|
err = io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-03-05 11:33:10 +03:00
|
|
|
// Write to the file's contents. See documentation for ioutil.WriterAt.
|
|
|
|
//
|
2015-05-19 08:48:17 +03:00
|
|
|
// REQUIRES: in.isFile()
|
|
|
|
func (in *inode) WriteAt(p []byte, off int64) (n int, err error) {
|
2015-05-19 08:53:01 +03:00
|
|
|
if !in.isFile() {
|
|
|
|
panic("WriteAt called on non-file.")
|
2015-03-05 11:33:10 +03:00
|
|
|
}
|
|
|
|
|
2015-03-05 21:27:04 +03:00
|
|
|
// Update the modification time.
|
2015-08-11 09:05:17 +03:00
|
|
|
in.attrs.Mtime = time.Now()
|
2015-03-05 21:27:04 +03:00
|
|
|
|
2015-03-05 11:33:10 +03:00
|
|
|
// Ensure that the contents slice is long enough.
|
|
|
|
newLen := int(off) + len(p)
|
2015-05-19 08:48:17 +03:00
|
|
|
if len(in.contents) < newLen {
|
|
|
|
padding := make([]byte, newLen-len(in.contents))
|
|
|
|
in.contents = append(in.contents, padding...)
|
2015-05-19 08:51:38 +03:00
|
|
|
in.attrs.Size = uint64(newLen)
|
2015-03-05 11:33:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy in the data.
|
2015-05-19 08:48:17 +03:00
|
|
|
n = copy(in.contents[off:], p)
|
2015-03-05 11:33:10 +03:00
|
|
|
|
|
|
|
// Sanity check.
|
|
|
|
if n != len(p) {
|
|
|
|
panic(fmt.Sprintf("Unexpected short copy: %v", n))
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2015-03-05 21:50:58 +03:00
|
|
|
|
|
|
|
// Update attributes from non-nil parameters.
|
2015-05-19 08:48:17 +03:00
|
|
|
func (in *inode) SetAttributes(
|
2015-03-05 22:04:51 +03:00
|
|
|
size *uint64,
|
|
|
|
mode *os.FileMode,
|
|
|
|
mtime *time.Time) {
|
2015-03-05 21:50:58 +03:00
|
|
|
// Update the modification time.
|
2015-08-11 09:05:17 +03:00
|
|
|
in.attrs.Mtime = time.Now()
|
2015-03-05 21:50:58 +03:00
|
|
|
|
2015-03-05 21:56:12 +03:00
|
|
|
// Truncate?
|
2015-03-05 21:50:58 +03:00
|
|
|
if size != nil {
|
|
|
|
intSize := int(*size)
|
2015-03-05 21:56:12 +03:00
|
|
|
|
|
|
|
// Update contents.
|
2015-05-19 08:48:17 +03:00
|
|
|
if intSize <= len(in.contents) {
|
|
|
|
in.contents = in.contents[:intSize]
|
2015-03-05 21:50:58 +03:00
|
|
|
} else {
|
2015-05-19 08:48:17 +03:00
|
|
|
padding := make([]byte, intSize-len(in.contents))
|
|
|
|
in.contents = append(in.contents, padding...)
|
2015-03-05 21:50:58 +03:00
|
|
|
}
|
2015-03-05 21:56:12 +03:00
|
|
|
|
|
|
|
// Update attributes.
|
2015-05-19 08:51:38 +03:00
|
|
|
in.attrs.Size = *size
|
2015-03-05 21:50:58 +03:00
|
|
|
}
|
2015-03-05 22:00:55 +03:00
|
|
|
|
|
|
|
// Change mode?
|
|
|
|
if mode != nil {
|
2015-05-19 08:51:38 +03:00
|
|
|
in.attrs.Mode = *mode
|
2015-03-05 22:00:55 +03:00
|
|
|
}
|
2015-03-05 22:04:51 +03:00
|
|
|
|
|
|
|
// Change mtime?
|
|
|
|
if mtime != nil {
|
2015-05-19 08:51:38 +03:00
|
|
|
in.attrs.Mtime = *mtime
|
2015-03-05 22:04:51 +03:00
|
|
|
}
|
2015-03-05 21:50:58 +03:00
|
|
|
}
|