// Copyright 2018 The gVisor Authors.
//
// 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 fs

import (
	"fmt"
	"strings"

	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/sentry/memmap"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/syserror"
	"gvisor.dev/gvisor/pkg/usermem"
)

// The virtual filesystem implements an overlay configuration. For a high-level
// description, see README.md.
//
// Note on whiteouts:
//
// This implementation does not use the "Docker-style" whiteouts (symlinks with
// ".wh." prefix). Instead upper filesystem directories support a set of extended
// attributes to encode whiteouts: "trusted.overlay.whiteout.<filename>". This
// gives flexibility to persist whiteouts independently of the filesystem layout
// while additionally preventing name conflicts with files prefixed with ".wh.".
//
// Known deficiencies:
//
// - The device number of two files under the same overlay mount point may be
//   different. This can happen if a file is found in the lower filesystem (takes
//   the lower filesystem device) and another file is created in the upper
//   filesystem (takes the upper filesystem device). This may appear odd but
//   should not break applications.
//
// - Registered events on files (i.e. for notification of read/write readiness)
//   are not copied across copy up. This is fine in the common case of files that
//   do not block. For files that do block, like pipes and sockets, copy up is not
//   supported.
//
// - Hardlinks in a lower filesystem are broken by copy up. For this reason, no
//   attempt is made to preserve link count across copy up.
//
// - The maximum length of an extended attribute name is the same as the maximum
//   length of a file path in Linux (XATTR_NAME_MAX == NAME_MAX). This means that
//   whiteout attributes, if set directly on the host, are limited additionally by
//   the extra whiteout prefix length (file paths must be strictly shorter than
//   NAME_MAX). This is not a problem for in-memory filesystems which don't enforce
//   XATTR_NAME_MAX.

const (
	// XattrOverlayPrefix is the prefix for extended attributes that affect
	// the behavior of an overlay.
	XattrOverlayPrefix = "trusted.overlay."

	// XattrOverlayWhiteoutPrefix is the prefix for extended attributes
	// that indicate that a whiteout exists.
	XattrOverlayWhiteoutPrefix = XattrOverlayPrefix + "whiteout."
)

// XattrOverlayWhiteout returns an extended attribute that indicates a
// whiteout exists for name. It is supported by directories that wish to
// mask the existence of name.
func XattrOverlayWhiteout(name string) string {
	return XattrOverlayWhiteoutPrefix + name
}

// isXattrOverlay returns whether the given extended attribute configures the
// overlay.
func isXattrOverlay(name string) bool {
	return strings.HasPrefix(name, XattrOverlayPrefix)
}

// NewOverlayRoot produces the root of an overlay.
//
// Preconditions:
//
// - upper and lower must be non-nil.
// - upper must not be an overlay.
// - lower should not expose character devices, pipes, or sockets, because
//   copying up these types of files is not supported.
// - lower must not require that file objects be revalidated.
// - lower must not have dynamic file/directory content.
func NewOverlayRoot(ctx context.Context, upper *Inode, lower *Inode, flags MountSourceFlags) (*Inode, error) {
	if !IsDir(upper.StableAttr) {
		return nil, fmt.Errorf("upper Inode is a %v, not a directory", upper.StableAttr.Type)
	}
	if !IsDir(lower.StableAttr) {
		return nil, fmt.Errorf("lower Inode is a %v, not a directory", lower.StableAttr.Type)
	}
	if upper.overlay != nil {
		return nil, fmt.Errorf("cannot nest overlay in upper file of another overlay")
	}

	msrc := newOverlayMountSource(ctx, upper.MountSource, lower.MountSource, flags)
	overlay, err := newOverlayEntry(ctx, upper, lower, true)
	if err != nil {
		msrc.DecRef()
		return nil, err
	}

	return newOverlayInode(ctx, overlay, msrc), nil
}

// NewOverlayRootFile produces the root of an overlay that points to a file.
//
// Preconditions:
//
// - lower must be non-nil.
// - lower should not expose character devices, pipes, or sockets, because
//   copying up these types of files is not supported. Neither it can be a dir.
// - lower must not require that file objects be revalidated.
// - lower must not have dynamic file/directory content.
func NewOverlayRootFile(ctx context.Context, upperMS *MountSource, lower *Inode, flags MountSourceFlags) (*Inode, error) {
	if !IsRegular(lower.StableAttr) {
		return nil, fmt.Errorf("lower Inode is not a regular file")
	}
	msrc := newOverlayMountSource(ctx, upperMS, lower.MountSource, flags)
	overlay, err := newOverlayEntry(ctx, nil, lower, true)
	if err != nil {
		msrc.DecRef()
		return nil, err
	}
	return newOverlayInode(ctx, overlay, msrc), nil
}

// newOverlayInode creates a new Inode for an overlay.
func newOverlayInode(ctx context.Context, o *overlayEntry, msrc *MountSource) *Inode {
	var inode *Inode
	if o.upper != nil {
		inode = NewInode(ctx, nil, msrc, o.upper.StableAttr)
	} else {
		inode = NewInode(ctx, nil, msrc, o.lower.StableAttr)
	}
	inode.overlay = o
	return inode
}

// overlayEntry is the overlay metadata of an Inode. It implements Mappable.
//
// +stateify savable
type overlayEntry struct {
	// lowerExists is true if an Inode exists for this file in the lower
	// filesystem. If lowerExists is true, then the overlay must create
	// a whiteout entry when renaming and removing this entry to mask the
	// lower Inode.
	//
	// Note that this is distinct from actually holding onto a non-nil
	// lower Inode (below). The overlay does not need to keep a lower Inode
	// around unless it needs to operate on it, but it always needs to know
	// whether the lower Inode exists to correctly execute a rename or
	// remove operation.
	lowerExists bool

	// lower is an Inode from a lower filesystem. Modifications are
	// never made on this Inode.
	lower *Inode

	// copyMu serializes copy-up for operations above
	// mm.MemoryManager.mappingMu in the lock order.
	copyMu sync.RWMutex `state:"nosave"`

	// mapsMu serializes copy-up for operations between
	// mm.MemoryManager.mappingMu and mm.MemoryManager.activeMu in the lock
	// order.
	mapsMu sync.Mutex `state:"nosave"`

	// mappings tracks memory mappings of this Mappable so they can be removed
	// from the lower filesystem Mappable and added to the upper filesystem
	// Mappable when copy up occurs. It is strictly unnecessary after copy-up.
	//
	// mappings is protected by mapsMu.
	mappings memmap.MappingSet

	// dataMu serializes copy-up for operations below mm.MemoryManager.activeMu
	// in the lock order.
	dataMu sync.RWMutex `state:"nosave"`

	// upper is an Inode from an upper filesystem. It is non-nil if
	// the file exists in the upper filesystem. It becomes non-nil
	// when the Inode that owns this overlayEntry is modified.
	//
	// upper is protected by all of copyMu, mapsMu, and dataMu. Holding any of
	// these locks is sufficient to read upper; holding all three for writing
	// is required to mutate it.
	upper *Inode

	// dirCacheMu protects dirCache.
	dirCacheMu sync.RWMutex `state:"nosave"`

	// dirCache is cache of DentAttrs from upper and lower Inodes.
	dirCache *SortedDentryMap
}

// newOverlayEntry returns a new overlayEntry.
func newOverlayEntry(ctx context.Context, upper *Inode, lower *Inode, lowerExists bool) (*overlayEntry, error) {
	if upper == nil && lower == nil {
		panic("invalid overlayEntry, needs at least one Inode")
	}
	if upper != nil && upper.overlay != nil {
		panic("nested writable layers are not supported")
	}
	// Check for supported lower filesystem types.
	if lower != nil {
		switch lower.StableAttr.Type {
		case RegularFile, Directory, Symlink, Socket:
		default:
			// We don't support copying up from character devices,
			// named pipes, or anything weird (like proc files).
			log.Warningf("%s not supported in lower filesytem", lower.StableAttr.Type)
			return nil, syserror.EINVAL
		}
	}
	return &overlayEntry{
		lowerExists: lowerExists,
		lower:       lower,
		upper:       upper,
	}, nil
}

func (o *overlayEntry) release() {
	// We drop a reference on upper and lower file system Inodes
	// rather than releasing them, because in-memory filesystems
	// may hold an extra reference to these Inodes so that they
	// stay in memory.
	if o.upper != nil {
		o.upper.DecRef()
	}
	if o.lower != nil {
		o.lower.DecRef()
	}
}

// overlayUpperMountSource gives the upper mount of an overlay mount.
//
// The caller may not use this MountSource past the lifetime of overlayMountSource and may
// not call DecRef on it.
func overlayUpperMountSource(overlayMountSource *MountSource) *MountSource {
	return overlayMountSource.MountSourceOperations.(*overlayMountSourceOperations).upper
}

// Preconditions: At least one of o.copyMu, o.mapsMu, or o.dataMu must be locked.
func (o *overlayEntry) inodeLocked() *Inode {
	if o.upper != nil {
		return o.upper
	}
	return o.lower
}

// Preconditions: At least one of o.copyMu, o.mapsMu, or o.dataMu must be locked.
func (o *overlayEntry) isMappableLocked() bool {
	return o.inodeLocked().Mappable() != nil
}

// markDirectoryDirty marks any cached data dirty for this directory. This is
// necessary in order to ensure that this node does not retain stale state
// throughout its lifetime across multiple open directory handles.
//
// Currently this means invalidating any readdir caches.
func (o *overlayEntry) markDirectoryDirty() {
	o.dirCacheMu.Lock()
	o.dirCache = nil
	o.dirCacheMu.Unlock()
}

// AddMapping implements memmap.Mappable.AddMapping.
func (o *overlayEntry) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) error {
	o.mapsMu.Lock()
	defer o.mapsMu.Unlock()
	if err := o.inodeLocked().Mappable().AddMapping(ctx, ms, ar, offset, writable); err != nil {
		return err
	}
	o.mappings.AddMapping(ms, ar, offset, writable)
	return nil
}

// RemoveMapping implements memmap.Mappable.RemoveMapping.
func (o *overlayEntry) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) {
	o.mapsMu.Lock()
	defer o.mapsMu.Unlock()
	o.inodeLocked().Mappable().RemoveMapping(ctx, ms, ar, offset, writable)
	o.mappings.RemoveMapping(ms, ar, offset, writable)
}

// CopyMapping implements memmap.Mappable.CopyMapping.
func (o *overlayEntry) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR usermem.AddrRange, offset uint64, writable bool) error {
	o.mapsMu.Lock()
	defer o.mapsMu.Unlock()
	if err := o.inodeLocked().Mappable().CopyMapping(ctx, ms, srcAR, dstAR, offset, writable); err != nil {
		return err
	}
	o.mappings.AddMapping(ms, dstAR, offset, writable)
	return nil
}

// Translate implements memmap.Mappable.Translate.
func (o *overlayEntry) Translate(ctx context.Context, required, optional memmap.MappableRange, at usermem.AccessType) ([]memmap.Translation, error) {
	o.dataMu.RLock()
	defer o.dataMu.RUnlock()
	return o.inodeLocked().Mappable().Translate(ctx, required, optional, at)
}

// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
func (o *overlayEntry) InvalidateUnsavable(ctx context.Context) error {
	o.mapsMu.Lock()
	defer o.mapsMu.Unlock()
	return o.inodeLocked().Mappable().InvalidateUnsavable(ctx)
}