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

280 lines
6.2 KiB
Go

package fat
import (
"fmt"
"strings"
"time"
"github.com/mitchellh/go-fs"
)
// Directory implements fs.Directory and is used to interface with
// a directory on a FAT filesystem.
type Directory struct {
device fs.BlockDevice
dirCluster *DirectoryCluster
fat *FAT
}
// DirectoryEntry implements fs.DirectoryEntry and represents a single
// file/folder within a directory in a FAT filesystem. Note that there may be
// more than one underlying directory entry data structure on the disk to
// account for long filenames.
type DirectoryEntry struct {
dir *Directory
lfnEntries []*DirectoryClusterEntry
entry *DirectoryClusterEntry
name string
}
// DecodeDirectoryEntry takes a list of entries, decodes the next full
// DirectoryEntry, and returns the newly created entry, the remaining
// entries, and an error, if there was one.
func DecodeDirectoryEntry(d *Directory, entries []*DirectoryClusterEntry) (*DirectoryEntry, []*DirectoryClusterEntry, error) {
var lfnEntries []*DirectoryClusterEntry
var entry *DirectoryClusterEntry
var name string
// Skip all the deleted entries
for len(entries) > 0 && entries[0].deleted {
entries = entries[1:]
}
// Skip the volume ID
if len(entries) > 0 && entries[0].IsVolumeId() {
entries = entries[1:]
}
if len(entries) == 0 {
return nil, entries, nil
}
// We have a long entry, so we have to traverse to the point where
// we're done. Also, calculate out the name and such.
if entries[0].IsLong() {
lfnEntries := make([]*DirectoryClusterEntry, 0, 3)
for entries[0].IsLong() {
lfnEntries = append(lfnEntries, entries[0])
entries = entries[1:]
}
var nameBytes []rune
nameBytes = make([]rune, 13*len(lfnEntries))
for i := len(lfnEntries) - 1; i >= 0; i-- {
for _, char := range lfnEntries[i].longName {
nameBytes = append(nameBytes, char)
}
}
name = string(nameBytes)
}
// Get the short entry
entry = entries[0]
entries = entries[1:]
// If the short entry is deleted, ignore everything
if entry.deleted {
return nil, entries, nil
}
if name == "" {
name = strings.TrimSpace(entry.name)
ext := strings.TrimSpace(entry.ext)
if ext != "" {
name = fmt.Sprintf("%s.%s", name, ext)
}
}
result := &DirectoryEntry{
dir: d,
lfnEntries: lfnEntries,
entry: entry,
name: name,
}
return result, entries, nil
}
func (d *DirectoryEntry) Dir() (fs.Directory, error) {
if !d.IsDir() {
panic("not a directory")
}
dirCluster, err := DecodeDirectoryCluster(
d.entry.cluster, d.dir.device, d.dir.fat)
if err != nil {
return nil, err
}
result := &Directory{
device: d.dir.device,
dirCluster: dirCluster,
fat: d.dir.fat,
}
return result, nil
}
func (d *DirectoryEntry) File() (fs.File, error) {
if d.IsDir() {
panic("not a file")
}
result := &File{
chain: &ClusterChain{
device: d.dir.device,
fat: d.dir.fat,
startCluster: d.entry.cluster,
},
dir: d.dir,
entry: d.entry,
}
return result, nil
}
func (d *DirectoryEntry) IsDir() bool {
return (d.entry.attr & AttrDirectory) == AttrDirectory
}
func (d *DirectoryEntry) Name() string {
return d.name
}
func (d *DirectoryEntry) ShortName() string {
if d.entry.name == "." || d.entry.name == ".." {
return d.entry.name
}
return fmt.Sprintf("%s.%s", d.entry.name, d.entry.ext)
}
func (d *Directory) AddDirectory(name string) (fs.DirectoryEntry, error) {
entry, err := d.addEntry(name, AttrDirectory)
if err != nil {
return nil, err
}
// Create the new directory cluster
newDirCluster := NewDirectoryCluster(
entry.entry.cluster, d.dirCluster.startCluster, entry.entry.createTime)
if err := newDirCluster.WriteToDevice(d.device, d.fat); err != nil {
return nil, err
}
return entry, nil
}
func (d *Directory) AddFile(name string) (fs.DirectoryEntry, error) {
entry, err := d.addEntry(name, DirectoryAttr(0))
if err != nil {
return nil, err
}
return entry, nil
}
func (d *Directory) Entries() []fs.DirectoryEntry {
entries := d.dirCluster.entries
result := make([]fs.DirectoryEntry, 0, len(entries)/2)
for len(entries) > 0 {
var entry *DirectoryEntry
entry, entries, _ = DecodeDirectoryEntry(d, entries)
if entry != nil {
result = append(result, entry)
}
}
return result
}
func (d *Directory) Entry(name string) fs.DirectoryEntry {
name = strings.ToUpper(name)
for _, entry := range d.Entries() {
if strings.ToUpper(entry.Name()) == name {
return entry
}
}
return nil
}
func (d *Directory) addEntry(name string, attr DirectoryAttr) (*DirectoryEntry, error) {
name = strings.TrimSpace(name)
entries := d.Entries()
usedNames := make([]string, 0, len(entries))
for _, entry := range entries {
if strings.ToUpper(entry.Name()) == strings.ToUpper(name) {
return nil, fmt.Errorf("name already exists: %s", name)
}
// Add it to the list of used names
dirEntry := entry.(*DirectoryEntry)
usedNames = append(usedNames, dirEntry.ShortName())
}
shortName, err := generateShortName(name, usedNames)
if err != nil {
return nil, err
}
var lfnEntries []*DirectoryClusterEntry
if shortName != strings.ToUpper(name) {
lfnEntries, err = NewLongDirectoryClusterEntry(name, shortName)
if err != nil {
return nil, err
}
}
// Allocate space for a cluster
startCluster, err := d.fat.AllocChain()
if err != nil {
return nil, err
}
createTime := time.Now()
// Create the entry for the short name
shortParts := strings.Split(shortName, ".")
if len(shortParts) == 1 {
shortParts = append(shortParts, "")
}
shortEntry := new(DirectoryClusterEntry)
shortEntry.attr = attr
shortEntry.name = shortParts[0]
shortEntry.ext = shortParts[1]
shortEntry.cluster = startCluster
shortEntry.accessTime = createTime
shortEntry.createTime = createTime
shortEntry.writeTime = createTime
// Write the new FAT out
if err := d.fat.WriteToDevice(d.device); err != nil {
return nil, err
}
// Write the entries out in this directory
if lfnEntries != nil {
d.dirCluster.entries = append(d.dirCluster.entries, lfnEntries...)
}
d.dirCluster.entries = append(d.dirCluster.entries, shortEntry)
if err := d.dirCluster.WriteToDevice(d.device, d.fat); err != nil {
return nil, err
}
newEntry := &DirectoryEntry{
dir: d,
lfnEntries: lfnEntries,
entry: shortEntry,
}
return newEntry, nil
}