402 lines
9.5 KiB
Go
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
|
|
}
|