This repository has been archived on 2023-07-12. You can view files and clone it, but cannot push or open issues or pull requests.
2022-04-28 20:01:08 +02:00

402 lines
9.5 KiB
Go

package info
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
)
// file structure extracted from a debian buster docker container including:
// - curl 7.64.0
// - net-tools
// - OpenSSH_7.9p1
// - python3.7.3
// - wget 1.20.1
// - and all dependencies of the installed packages
const (
TypeDirectory = 4
TypeCharacterDevice = 2
TypeBlockDevice = 6
TypeRegularFile = 10
TypeFifo = 1
TypeSymbolicLink = 12
TypeSocketFile = 14
)
type BasicFile interface {
// This returns the directory in which the file is located.
// Be careful to not mix it up with Directory.Files
Directory() (Directory, bool)
RawDirectory() map[string]interface{}
// If the file a dir
IsDir() bool
// Removes the file and all sub files if the file is a directory
Remove()
}
type Directory struct {
BasicFile
File
Files []File
}
func (dir Directory) Directory() (Directory, bool) {return dir.File.Directory()}
func (dir Directory) RawDirectory() map[string]interface{} {
return dir.dir
}
func (dir Directory) IsDir() bool {
return true
}
func (dir Directory) Remove() {
dir.File.Remove()
}
type File struct {
BasicFile
fs *FileSystem
dir map[string]interface{}
location string
Type uint8
Size int64
Links int
Permissions os.FileMode
Name string
CreationTimestamp int64
UserID uint16
GroupID uint16
}
func (f File) Directory() (Directory, bool) {
if dir, ok, _ := f.fs.getOrCreateFile(filepath.Dir(f.location), false, false, false); !ok {
return Directory{}, false
} else {
return dir.(Directory), true
}
}
func (f File) RawDirectory() map[string]interface{} {return f.dir}
func (f File) IsDir() bool {
return false
}
func (f File) Remove() {
parent, _ := filepath.Split(f.location)
dir, _ := f.fs.GetExplicitDirectory(parent, false)
delete(dir.dir["files"].(map[string]interface{}), strings.TrimSuffix(f.Name, string(os.PathSeparator)))
}
func (f File) Location() string {
return f.location
}
type FileSystem struct {
fs map[string]interface{}
info []interface{}
currentDir string
rawCurrentDir map[string]interface{}
Manipulate bool
root bool
fromFile string
}
func (fs *FileSystem) Close() error {
if fs.Manipulate && fs.fromFile != "" {
file, err := os.Open(fs.fromFile)
if err != nil {
return err
}
return json.NewEncoder(file).Encode(fs.fs)
}
return nil
}
func (fs *FileSystem) CreateDirectory(path string) (Directory, error) {
if fs.Manipulate {
dir, exist, created := fs.getOrCreateFile(path, false, true, false)
if exist {
return Directory{}, os.ErrExist
} else if !created {
return Directory{}, os.ErrPermission
} else {
return dir.(Directory), nil
}
} else {
return Directory{}, os.ErrPermission
}
}
func (fs *FileSystem) CreateFile(path string) (File, error) {
if fs.Manipulate {
file, exist, created := fs.getOrCreateFile(path, false, true, false)
if exist {
return File{}, os.ErrExist
} else if !created {
return File{}, os.ErrPermission
} else {
return file.(File), nil
}
} else {
return File{}, os.ErrPermission
}
}
func (fs *FileSystem) Cwd() Directory {
dir, _ := fs.GetFile("", false)
return dir.(Directory)
}
func (fs *FileSystem) Exists(path string) bool {
_, exists, _ := fs.getOrCreateFile(path, false, false, false)
return exists
}
func (fs *FileSystem) GetFile(path string, follow bool) (BasicFile, bool) {
file, ok, _ := fs.getOrCreateFile(path, follow, false, false)
return file, ok
}
func (fs *FileSystem) GetExplicitDirectory(path string, follow bool) (Directory, bool) {
if file, ok, _ := fs.getOrCreateFile(path, follow, false, false); ok {
f, ok := file.(Directory)
return f, ok
} else {
return Directory{}, false
}
}
func (fs *FileSystem) GetExplicitFile(path string) (File, bool) {
if file, ok, _ := fs.getOrCreateFile(path, false, false, false); ok {
f, ok := file.(File)
return f, ok
} else {
return File{}, false
}
}
func (fs *FileSystem) Walk(path string, walkFunc func(path string, f BasicFile)) bool {
if dir, ok := fs.GetExplicitDirectory(path, false); ok {
resultDir := dir.RawDirectory()["files"].(map[string]interface{})
for name, details := range resultDir {
switch details.(type) {
case []interface{}:
walkFunc(path, fs.generateFileInfo(resultDir, path, name, details.([]interface{})))
case map[string]interface{}:
resultDir = details.(map[string]interface{})["files"].(map[string]interface{})
resultInfo := details.(map[string]interface{})["info"].([]interface{})
walkFunc(path, Directory{
File: fs.generateFileInfo(resultDir, path, name, resultInfo),
Files: fs.directoryFiles(resultDir, path),
})
}
}
return true
} else {
return false
}
}
func (fs *FileSystem) generateFileInfo(resultDir map[string]interface{}, location string, name string, rawInfos []interface{}) File {
permissions := os.FileMode(uint32(rawInfos[3].(float64)))
if uint8(rawInfos[0].(float64)) == 4 {
permissions |= os.ModeDir
}
if permissions.IsDir() && !strings.HasSuffix(name, "/") {
name += "/"
}
if strings.Contains(name, " ") && !strings.HasPrefix(name, "'") && !strings.HasSuffix(name, "'") {
name = "'" + name + "'"
}
return File{
fs: fs,
dir: resultDir,
location: location,
Type: uint8(rawInfos[0].(float64)),
Size: int64(rawInfos[1].(float64)),
Links: int(rawInfos[2].(float64)),
Permissions: permissions,
Name: name,
CreationTimestamp: int64(rawInfos[4].(float64)),
UserID: uint16(rawInfos[5].(float64)),
GroupID: uint16(rawInfos[6].(float64)),
}
}
func (fs *FileSystem) getOrCreateFile(path string, follow bool, create bool, createDir bool) (file BasicFile, exists bool, created bool) {
var isFile bool
rawDirectory := fs.rawCurrentDir
resultDir := fs.rawCurrentDir["files"].(map[string]interface{})
var resultFile []interface{}
if !strings.HasPrefix(path, "/") {
path = filepath.Join(fs.currentDir, path)
}
location, name := filepath.Split(path)
splitPath := strings.Split(path, string(os.PathSeparator))[1:]
if path == "/" {
resultDir = fs.fs
resultFile = fs.info
location = ""
name = "/"
splitPath = make([]string, 0)
}
for i, s := range splitPath {
if s == "" {
splitPath = append(splitPath[:i], splitPath[i+1:]...)
}
}
for _, file := range splitPath {
if dir, ok := resultDir[file]; ok {
if preCurrentFile, ok := dir.(map[string]interface{}); ok {
rawDirectory = preCurrentFile
resultDir = preCurrentFile["files"].(map[string]interface{})
resultFile = preCurrentFile["info"].([]interface{})
} else if preCurrentFile, ok := dir.([]interface{}); ok {
isFile = true
resultFile = preCurrentFile
break
}
} else {
return nil, false, false
}
}
if resultFile == nil {
if create {
timestamp := time.Now().Unix()
file := File{
fs: fs,
dir: resultDir,
location: location,
Type: TypeRegularFile,
Size: 0,
// i dont really know why its 1, linux logic i think
Links: 1,
Permissions: os.ModeType,
Name: name,
CreationTimestamp: timestamp,
UserID: 1000,
GroupID: 1000,
}
if fs.root {
file.UserID = 0
file.GroupID = 0
}
if createDir {
file.Type = TypeDirectory
// i dont really know why its 2, linux logic i think
file.Links = 2
file.Permissions = os.ModeDir
resultDir[name] = map[string]interface{} {"files": make([]string, 0),
"info": []interface{} {file.Type, file.Size, file.Links, file.Permissions, file.CreationTimestamp, file.UserID, file.GroupID}}
return Directory{
BasicFile: file,
Files: make([]File, 0),
}, false, true
} else {
resultDir[name] = []interface{} {file.Type, file.Size, file.Links, file.Permissions, file.CreationTimestamp, file.UserID, file.GroupID}
}
if follow && strings.HasPrefix(path, "/") {
fs.currentDir = path
} else {
fs.currentDir = filepath.Join(fs.currentDir, path)
}
return file, false, true
} else {
return nil, false, false
}
}
file = fs.generateFileInfo(rawDirectory, location, name, resultFile)
if !isFile {
var files []File
for fname, info := range resultDir {
switch info.(type) {
// case normal file
case []interface{}:
info := info.([]interface{})
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
// case dir
case map[string]interface{}:
info := info.(map[string]interface{})["info"].([]interface{})
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
}
}
if follow {
fs.currentDir = path
}
return Directory{
File: file.(File),
Files: fs.directoryFiles(resultDir, location),
}, true, false
} else {
return file, true, false
}
}
func (fs *FileSystem) directoryFiles(rawDirectory map[string]interface{}, location string) []File {
var files []File
for fname, info := range rawDirectory {
switch info.(type) {
// case normal file
case []interface{}:
info := info.([]interface{})
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
// case dir
case map[string]interface{}:
info := info.(map[string]interface{})["info"].([]interface{})
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
}
}
return files
}
func LoadFSFromJson(fsFile string) (*FileSystem, error) {
file, err := os.Open(fsFile)
if err != nil {
return nil, err
}
fs := make(map[string]interface{})
if err := json.NewDecoder(file).Decode(&fs); err != nil {
return nil, err
}
return &FileSystem{
fs: fs["files"].(map[string]interface{}),
info: fs["info"].([]interface{}),
rawCurrentDir: fs,
currentDir: "/",
fromFile: fsFile,
}, nil
}