280 lines
6.2 KiB
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
|
|
}
|