packer-cn/vendor/github.com/mitchellh/go-fs/fat/directory_cluster.go

441 lines
11 KiB
Go

package fat
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"strings"
"time"
"unicode/utf16"
"github.com/mitchellh/go-fs"
)
type DirectoryAttr uint8
const (
AttrReadOnly DirectoryAttr = 0x01
AttrHidden = 0x02
AttrSystem = 0x04
AttrVolumeId = 0x08
AttrDirectory = 0x10
AttrArchive = 0x20
AttrLongName = AttrReadOnly | AttrHidden | AttrSystem | AttrVolumeId
)
// The size in bytes of a single directory entry.
const DirectoryEntrySize = 32
// Mask applied to the ord of the last long entry.
const LastLongEntryMask = 0x40
// DirectoryCluster represents a cluster on the disk that contains
// entries/contents.
type DirectoryCluster struct {
entries []*DirectoryClusterEntry
fat16Root bool
startCluster uint32
}
// DirectoryClusterEntry is a single 32-byte entry that is part of the
// chain of entries in a directory cluster.
type DirectoryClusterEntry struct {
name string
ext string
attr DirectoryAttr
createTime time.Time
accessTime time.Time
writeTime time.Time
cluster uint32
fileSize uint32
deleted bool
longOrd uint8
longName string
longChecksum uint8
}
func DecodeDirectoryCluster(startCluster uint32, device fs.BlockDevice, fat *FAT) (*DirectoryCluster, error) {
bs := fat.bs
chain := fat.Chain(startCluster)
data := make([]byte, uint32(len(chain))*bs.BytesPerCluster())
for i, clusterNumber := range chain {
dataOffset := uint32(i) * bs.BytesPerCluster()
devOffset := int64(bs.ClusterOffset(int(clusterNumber)))
chainData := data[dataOffset : dataOffset+bs.BytesPerCluster()]
if _, err := device.ReadAt(chainData, devOffset); err != nil {
return nil, err
}
}
result, err := decodeDirectoryCluster(data, bs)
if err != nil {
return nil, err
}
result.startCluster = startCluster
return result, nil
}
// DecodeFAT16RootDirectory decodes the FAT16 root directory structure
// from the device.
func DecodeFAT16RootDirectoryCluster(device fs.BlockDevice, bs *BootSectorCommon) (*DirectoryCluster, error) {
data := make([]byte, DirectoryEntrySize*bs.RootEntryCount)
if _, err := device.ReadAt(data, int64(bs.RootDirOffset())); err != nil {
return nil, err
}
result, err := decodeDirectoryCluster(data, bs)
if err != nil {
return nil, err
}
result.fat16Root = true
return result, nil
}
func decodeDirectoryCluster(data []byte, bs *BootSectorCommon) (*DirectoryCluster, error) {
entries := make([]*DirectoryClusterEntry, 0, bs.RootEntryCount)
for i := uint16(0); i < uint16(len(data)/DirectoryEntrySize); i++ {
offset := i * DirectoryEntrySize
entryData := data[offset : offset+DirectoryEntrySize]
if entryData[0] == 0 {
break
}
entry, err := DecodeDirectoryClusterEntry(entryData)
if err != nil {
return nil, err
}
entries = append(entries, entry)
}
result := &DirectoryCluster{
entries: entries,
}
return result, nil
}
func NewDirectoryCluster(start uint32, parent uint32, t time.Time) *DirectoryCluster {
cluster := new(DirectoryCluster)
cluster.startCluster = start
// Create the "." and ".." entries
cluster.entries = []*DirectoryClusterEntry{
{
accessTime: t,
attr: AttrDirectory,
cluster: start,
createTime: t,
name: ".",
writeTime: t,
},
{
accessTime: t,
attr: AttrDirectory,
cluster: parent,
createTime: t,
name: "..",
writeTime: t,
},
}
return cluster
}
// NewFat16RootDirectory creates a new DirectoryCluster that is meant only
// to be the root directory of a FAT12/FAT16 filesystem.
func NewFat16RootDirectoryCluster(bs *BootSectorCommon, label string) (*DirectoryCluster, error) {
if bs.RootEntryCount == 0 {
return nil, errors.New("root entry count is 0 in boot sector")
}
result := &DirectoryCluster{
entries: make([]*DirectoryClusterEntry, 1, bs.RootEntryCount),
}
// Create the volume ID entry
result.entries[0] = &DirectoryClusterEntry{
attr: AttrVolumeId,
name: label,
cluster: 0,
}
return result, nil
}
// Bytes returns the on-disk byte data for this directory structure.
func (d *DirectoryCluster) Bytes() []byte {
result := make([]byte, cap(d.entries)*DirectoryEntrySize)
for i, entry := range d.entries {
offset := i * DirectoryEntrySize
entryBytes := entry.Bytes()
copy(result[offset:offset+DirectoryEntrySize], entryBytes)
}
return result
}
// WriteToDevice writes the cluster to the device.
func (d *DirectoryCluster) WriteToDevice(device fs.BlockDevice, fat *FAT) error {
if d.fat16Root {
// Write the cluster to the FAT16 root directory location
offset := int64(fat.bs.RootDirOffset())
if _, err := device.WriteAt(d.Bytes(), offset); err != nil {
return err
}
} else {
chain := &ClusterChain{
device: device,
fat: fat,
startCluster: d.startCluster,
}
if _, err := chain.Write(d.Bytes()); err != nil {
return err
}
}
return nil
}
// Bytes returns the on-disk byte data for this directory entry.
func (d *DirectoryClusterEntry) Bytes() []byte {
var result [DirectoryEntrySize]byte
if d.longName != "" {
runes := bytes.Runes([]byte(d.longName))
// The name must be zero-terminated then padded with 0xFF
// up to 13 characters
if len(runes) < 13 {
runes = append(runes, 0)
for len(runes) < 13 {
runes = append(runes, 0xFFFF)
}
}
// LDIR_Ord
result[0] = d.longOrd
// LDIR_Name1
for i := 0; i < int(math.Min(float64(len(runes)), 5)); i++ {
offset := 1 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i]))
}
// LDIR_Attr
result[11] = byte(AttrLongName)
// LDIR_Type
result[12] = 0
// LDIR_Chksum
result[13] = d.longChecksum
// LDIR_Name2
for i := 0; i < 6; i++ {
offset := 14 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i+5]))
}
// LDIR_FstClusLO
result[26] = 0
result[27] = 0
// LDIR_Name3
for i := 0; i < 2; i++ {
offset := 28 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i+11]))
}
} else {
// DIR_Name
var simpleName string
if d.name == "." || d.name == ".." {
simpleName = d.name
} else {
simpleName = fmt.Sprintf("%s.%s", d.name, d.ext)
}
copy(result[0:11], shortNameEntryValue(simpleName))
// DIR_Attr
result[11] = byte(d.attr)
// DIR_CrtTime
crtDate, crtTime, crtTenths := encodeDOSTime(d.createTime)
result[13] = crtTenths
binary.LittleEndian.PutUint16(result[14:16], crtTime)
binary.LittleEndian.PutUint16(result[16:18], crtDate)
// DIR_LstAccDate
accDate, _, _ := encodeDOSTime(d.accessTime)
binary.LittleEndian.PutUint16(result[18:20], accDate)
// DIR_FstClusHI
binary.LittleEndian.PutUint16(result[20:22], uint16(d.cluster>>16))
// DIR_WrtTime and DIR_WrtDate
wrtDate, wrtTime, _ := encodeDOSTime(d.writeTime)
binary.LittleEndian.PutUint16(result[22:24], wrtTime)
binary.LittleEndian.PutUint16(result[24:26], wrtDate)
// DIR_FstClusLO
binary.LittleEndian.PutUint16(result[26:28], uint16(d.cluster&0xFFFF))
// DIR_FileSize
binary.LittleEndian.PutUint32(result[28:32], d.fileSize)
}
return result[:]
}
// IsLong returns true if this is a long entry.
func (d *DirectoryClusterEntry) IsLong() bool {
return (d.attr & AttrLongName) == AttrLongName
}
func (d *DirectoryClusterEntry) IsVolumeId() bool {
return (d.attr & AttrVolumeId) == AttrVolumeId
}
// DecodeDirectoryClusterEntry decodes a single directory entry in the
// Directory structure.
func DecodeDirectoryClusterEntry(data []byte) (*DirectoryClusterEntry, error) {
var result DirectoryClusterEntry
// Do the attributes so we can determine if we're dealing with long names
result.attr = DirectoryAttr(data[11])
if (result.attr & AttrLongName) == AttrLongName {
result.longOrd = data[0]
chars := make([]uint16, 13)
for i := 0; i < 5; i++ {
offset := 1 + (i * 2)
chars[i] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
for i := 0; i < 6; i++ {
offset := 14 + (i * 2)
chars[i+5] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
for i := 0; i < 2; i++ {
offset := 28 + (i * 2)
chars[i+11] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
result.longName = string(utf16.Decode(chars))
result.longChecksum = data[13]
} else {
result.deleted = data[0] == 0xE5
// Basic attributes
if data[0] == 0x05 {
data[0] = 0xE5
}
result.name = strings.TrimRight(string(data[0:8]), " ")
result.ext = strings.TrimRight(string(data[8:11]), " ")
// Creation time
createTimeTenths := data[13]
createTimeWord := binary.LittleEndian.Uint16(data[14:16])
createDateWord := binary.LittleEndian.Uint16(data[16:18])
result.createTime = decodeDOSTime(createDateWord, createTimeWord, createTimeTenths)
// Access time
accessDateWord := binary.LittleEndian.Uint16(data[18:20])
result.accessTime = decodeDOSTime(accessDateWord, 0, 0)
// Write time
writeTimeWord := binary.LittleEndian.Uint16(data[22:24])
writeDateWord := binary.LittleEndian.Uint16(data[24:26])
result.writeTime = decodeDOSTime(writeDateWord, writeTimeWord, 0)
// Cluster
result.cluster = uint32(binary.LittleEndian.Uint16(data[20:22]))
result.cluster <<= 4
result.cluster |= uint32(binary.LittleEndian.Uint16(data[26:28]))
// File size
result.fileSize = binary.LittleEndian.Uint32(data[28:32])
}
return &result, nil
}
// NewLongDirectoryClusterEntry returns the series of directory cluster
// entries that need to be written for a long directory entry. This list
// of entries does NOT contain the short name entry.
func NewLongDirectoryClusterEntry(name string, shortName string) ([]*DirectoryClusterEntry, error) {
// Split up the shortName properly
checksum := checksumShortName(shortNameEntryValue(shortName))
// Calcualte the number of entries we'll actually need to store
// the long name.
numLongEntries := len(name) / 13
if len(name)%13 != 0 {
numLongEntries++
}
entries := make([]*DirectoryClusterEntry, numLongEntries)
for i := 0; i < numLongEntries; i++ {
entries[i] = new(DirectoryClusterEntry)
entry := entries[i]
entry.attr = AttrLongName
entry.longOrd = uint8(numLongEntries - i)
if i == 0 {
entry.longOrd |= LastLongEntryMask
}
// Calculate the offsets of the string for this entry
j := (numLongEntries - i - 1) * 13
k := j + 13
if k > len(name) {
k = len(name)
}
entry.longChecksum = checksum
entry.longName = name[j:k]
}
return entries, nil
}
func decodeDOSTime(date, dosTime uint16, tenths uint8) time.Time {
return time.Date(
1980+int(date>>9),
time.Month((date>>5)&0x0F),
int(date&0x1F),
int(dosTime>>11),
int((dosTime>>5)&0x3F),
int((dosTime&0x1F)*2),
int(tenths)*10*int(time.Millisecond),
time.Local)
}
func encodeDOSTime(t time.Time) (uint16, uint16, uint8) {
var date uint16 = uint16((t.Year() - 1980) << 9)
date |= uint16(t.Month()) << 5
date += uint16(t.Day() & 0xFF)
var time uint16 = uint16(t.Hour() << 11)
time |= uint16(t.Minute() << 5)
time += uint16(t.Second() / 2)
var tenths uint8
// TODO(mitchellh): Do tenths
return date, time, tenths
}