okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
f6da61d
main
1package storage 2 3import ( 4"albedo/cli/internal/daemon" 5"bytes" 6"context" 7"encoding/json" 8"errors" 9"fmt" 10"os" 11"os/exec" 12"path/filepath" 13"strings" 14"time" 15) 16 17func (s * Service )command (ctx context.Context ,args ... string ) (* exec.Cmd ,error ) { 18if s .Command == nil { 19return nil ,errors .New ("offline storage requires a local daemon executable; set ALBEDO_DAEMON or ALBEDO_ROOT, or query a running daemon without --offline" ) 20 } 21return s .Command (ctx ,args ... ) 22} 23 24func (s * Service )sqliteStorage (ctx context.Context ,path string ) (daemon.StorageDatabase ,error ) { 25db := daemon.StorageDatabase {Sessions : []daemon.StorageSession {}} 26if err := ctx .Err ();err != nil { 27return db ,err 28 } 29scratch ,err := os .MkdirTemp ("" ,"albedo-storage-" ) 30if err != nil { 31return db ,fmt .Errorf ("create private offline storage snapshot: %w" ,err ) 32 } 33defer os .RemoveAll (scratch ) 34command ,err := s .command (ctx ,"storage" ,"inspect" ,path ,scratch ) 35if err != nil { 36return db ,err 37 } 38var stderr bytes.Buffer 39command .Stderr = & stderr 40out ,err := command .Output () 41if ctx .Err ()!= nil { 42return db ,ctx .Err () 43 } 44if err != nil { 45return db ,fmt .Errorf ("read disk usage from the database: %w: %s" ,err ,strings .TrimSpace (stderr .String ())) 46 } 47if err := json .Unmarshal (out ,& db );err != nil { 48return db ,fmt .Errorf ("decode the database disk usage report: %w" ,err ) 49 } 50return db ,nil 51} 52 53func regularSize (path string ) (int64 ,error ) { 54info ,err := os .Lstat (path ) 55if errors .Is (err ,os .ErrNotExist ) { 56return 0 ,nil 57 } 58if err != nil { 59return 0 ,err 60 } 61if ! info .Mode ().IsRegular () { 62return 0 ,fmt .Errorf ("expected a file, but found a different kind of entry: %s" ,path ) 63 } 64return info .Size (),nil 65} 66 67func storageFiles (ctx context.Context ,dir string ,eligible func (string , os.FileInfo )bool ) (int64 , []daemon.StorageFile ,error ) { 68stat ,err := os .Lstat (dir ) 69if errors .Is (err ,os .ErrNotExist ) { 70return 0 , []daemon.StorageFile {},nil 71 } 72if err != nil { 73return 0 ,nil ,err 74 } 75if ! stat .IsDir () { 76return 0 ,nil ,fmt .Errorf ("expected a directory for storage: %s" ,dir ) 77 } 78entries ,err := os .ReadDir (dir ) 79if err != nil { 80return 0 ,nil ,err 81 } 82var total int64 83candidates := []daemon.StorageFile {} 84for _ ,entry := range entries { 85if err := ctx .Err ();err != nil { 86return 0 ,nil ,err 87 } 88info ,err := entry .Info () 89if err != nil { 90return 0 ,nil ,err 91 } 92if ! info .Mode ().IsRegular () { 93continue 94 } 95total += info .Size () 96if eligible (entry .Name (),info ) { 97candidates = append (candidates , daemon.StorageFile {Path :filepath .Join (dir ,entry .Name ()),Bytes :info .Size ()}) 98 } 99 } 100return total ,candidates ,nil 101} 102 103func (s * Service )OfflineReport (ctx context.Context ) (daemon.StorageReport ,error ) { 104home ,now := s .Home ,s .Now () 105if err := ctx .Err ();err != nil { 106return daemon.StorageReport {},err 107 } 108p := daemon.StorageReport {OldKernels : []daemon.StorageFile {},OldBackups : []daemon.StorageFile {},DB : daemon.StorageDatabase {Sessions : []daemon.StorageSession {}}} 109root ,err := os .Lstat (home ) 110if errors .Is (err ,os .ErrNotExist ) { 111return p ,nil 112 } 113if err != nil { 114return p ,err 115 } 116if ! root .IsDir () { 117return p ,fmt .Errorf ("ALBEDO_HOME must be a directory: %s" ,home ) 118 } 119dbPath := filepath .Join (home ,"albedo.sqlite" ) 120databaseInfo ,err := os .Lstat (dbPath ) 121databaseExists := err == nil 122if err != nil && ! errors .Is (err ,os .ErrNotExist ) { 123return p ,err 124 } 125if databaseExists { 126if ! databaseInfo .Mode ().IsRegular () { 127return p ,fmt .Errorf ("expected a file, but found a different kind of entry: %s" ,dbPath ) 128 } 129p .Database = databaseInfo .Size () 130 } 131for _ ,suffix := range []string {"-wal" ,"-shm" } { 132size ,sizeErr := regularSize (dbPath + suffix ) 133if sizeErr != nil { 134return p ,sizeErr 135 } 136p .WAL += size 137 } 138if databaseExists { 139if p .DB ,err = s .sqliteStorage (ctx ,dbPath );err != nil { 140return p ,err 141 } 142 } 143known := make (map [string ]bool ) 144for _ ,session := range p .DB .Sessions { 145known [session .ID ]= true 146 } 147cutoff := now .Add (- 30 * 24 * time .Hour ) 148p .Kernels ,p .OldKernels ,err = storageFiles (ctx ,filepath .Join (home ,"kernels" ),func (name string ,info os.FileInfo )bool { 149return strings .HasSuffix (name ,".state" )&& ! known [strings .TrimSuffix (name ,".state" )]&& info .ModTime ().Before (cutoff ) 150 }) 151if err != nil { 152return p ,err 153 } 154p .Backups ,p .OldBackups ,err = storageFiles (ctx ,filepath .Join (home ,"backups" ),func (name string ,info os.FileInfo )bool { 155if ! strings .HasPrefix (name ,"albedo-before-image-store-" )|| ! strings .HasSuffix (name ,".sqlite" ) { 156return false 157 } 158if info .ModTime ().Before (cutoff ) { 159return true 160 } 161p .RecentBackups += info .Size () 162p .RecentBackupCount ++ 163return false 164 }) 165if err != nil { 166return p ,err 167 } 168entries ,err := os .ReadDir (home ) 169if err != nil { 170return p ,err 171 } 172for _ ,entry := range entries { 173if err := ctx .Err ();err != nil { 174return p ,err 175 } 176if entry .Name ()== "kernels" || entry .Name ()== "backups" || entry .Name ()== "albedo.sqlite" || entry .Name ()== "albedo.sqlite-wal" || entry .Name ()== "albedo.sqlite-shm" { 177continue 178 } 179info ,err := entry .Info () 180if err != nil { 181return p ,err 182 } 183if info .Mode ().IsRegular () { 184p .Other += info .Size () 185 } 186 } 187return p ,nil 188}