// Copyright 2018 Google Inc.
//
// 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 device defines reserved virtual kernel devices and structures
// for managing them.
//
// Saving and restoring devices is not necessary if the devices are initialized
// as package global variables. Package initialization happens in a single goroutine
// and in a deterministic order, so minor device numbers will be assigned in the
// same order as packages are loaded.
package device

import (
	"bytes"
	"fmt"
	"sync"
	"sync/atomic"

	"gvisor.googlesource.com/gvisor/pkg/abi/linux"
)

// ID identifies a device.
type ID struct {
	Major uint64
	Minor uint64
}

// DeviceID formats a major and minor device number into a standard device number.
func (i *ID) DeviceID() uint64 {
	return uint64(linux.MakeDeviceID(uint16(i.Major), uint32(i.Minor)))
}

// nextAnonDeviceMinor is the next minor number for a new anonymous device.
// Must be accessed atomically.
var nextAnonDeviceMinor uint64

// NewAnonDevice creates a new anonymous device. Packages that require an anonymous
// device should initialize the device in a global variable in a file called device.go:
//
// var myDevice = device.NewAnonDevice()
func NewAnonDevice() *Device {
	return &Device{
		ID: newAnonID(),
	}
}

// NewAnonMultiDevice creates a new multi-keyed anonymous device. Packages that require
// a multi-key anonymous device should initialize the device in a global variable in a
// file called device.go:
//
// var myDevice = device.NewAnonMultiDevice()
func NewAnonMultiDevice() *MultiDevice {
	return &MultiDevice{
		ID: newAnonID(),
	}
}

// newAnonID assigns a major and minor number to an anonymous device ID.
func newAnonID() ID {
	return ID{
		// Anon devices always have a major number of 0.
		Major: 0,
		// Use the next minor number.
		Minor: atomic.AddUint64(&nextAnonDeviceMinor, 1),
	}
}

// Device is a simple virtual kernel device.
type Device struct {
	ID

	// last is the last generated inode.
	last uint64
}

// NextIno generates a new inode number
func (d *Device) NextIno() uint64 {
	return atomic.AddUint64(&d.last, 1)
}

// MultiDeviceKey provides a hashable key for a MultiDevice. The key consists
// of a raw device and inode for a resource, which must consistently identify
// the unique resource.  It may optionally include a secondary device if
// appropriate.
//
// Note that using the path is not enough, because filesystems may rename a file
// to a different backing resource, at which point the path points to a different
// entity.  Using only the inode is also not enough because the inode is assumed
// to be unique only within the device on which the resource exists.
type MultiDeviceKey struct {
	Device          uint64
	SecondaryDevice string
	Inode           uint64
}

// String stringifies the key.
func (m MultiDeviceKey) String() string {
	return fmt.Sprintf("key{device: %d, sdevice: %s, inode: %d}", m.Device, m.SecondaryDevice, m.Inode)
}

// MultiDevice allows for remapping resources that come from a variety of raw
// devices into a single device.  The device ID should be one of the static
// Device IDs above and cannot be reused.
type MultiDevice struct {
	ID

	mu     sync.Mutex
	last   uint64
	cache  map[MultiDeviceKey]uint64
	rcache map[uint64]MultiDeviceKey
}

// String stringifies MultiDevice.
func (m *MultiDevice) String() string {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("cache{")
	for k, v := range m.cache {
		buf.WriteString(fmt.Sprintf("%s -> %d, ", k, v))
	}
	buf.WriteString("}")
	return buf.String()
}

// Map maps a raw device and inode into the inode space of MultiDevice,
// returning a virtualized inode.  Raw devices and inodes can be reused;
// in this case, the same virtual inode will be returned.
func (m *MultiDevice) Map(key MultiDeviceKey) uint64 {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.cache == nil {
		m.cache = make(map[MultiDeviceKey]uint64)
		m.rcache = make(map[uint64]MultiDeviceKey)
	}

	id, ok := m.cache[key]
	if ok {
		return id
	}
	// Step over reserved entries that may have been loaded.
	idx := m.last + 1
	for {
		if _, ok := m.rcache[idx]; !ok {
			break
		}
		idx++
	}
	// We found a non-reserved entry, use it.
	m.last = idx
	m.cache[key] = m.last
	m.rcache[m.last] = key
	return m.last
}

// Load loads a raw device and inode into MultiDevice inode mappings
// with value as the virtual inode.
//
// By design, inodes start from 1 and continue until max uint64.  This means
// that the zero value, which is often the uninitialized value, can be rejected
// as invalid.
func (m *MultiDevice) Load(key MultiDeviceKey, value uint64) bool {
	// Reject the uninitialized value; see comment above.
	if value == 0 {
		return false
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	if m.cache == nil {
		m.cache = make(map[MultiDeviceKey]uint64)
		m.rcache = make(map[uint64]MultiDeviceKey)
	}

	if val, exists := m.cache[key]; exists && val != value {
		return false
	}
	if k, exists := m.rcache[value]; exists && k != key {
		// Should never happen.
		panic("MultiDevice's caches are inconsistent")
	}

	// Cache value at key.
	m.cache[key] = value

	// Prevent value from being used by new inode mappings.
	m.rcache[value] = key

	return true
}