Files
lineageos-ota-server/processFiles.go
T

194 lines
4.6 KiB
Go
Raw Normal View History

package main
import (
2026-07-04 10:03:18 -06:00
"archive/zip"
"bufio"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io"
"io/fs"
"log"
"os"
2026-07-04 10:03:18 -06:00
"strconv"
"strings"
)
2026-07-04 10:03:18 -06:00
type ZipMetadata struct {
Timestamp int
SDKLevel int
PatchLevel string
OTAPropertyFiles string
}
// Searches the build toolchain output directory for new LineageOS builds.
// Any new builds are moved into the public http server directory.
// Returns true if new builds were moved
func moveBuildArtifacts() bool {
f, err := os.Open(buildOutDirectory)
if err != nil {
log.Printf("failed to open ROM directory: %v", err)
return false
}
defer f.Close()
files, err := f.ReadDir(0)
if err != nil {
log.Printf("failed to read files in directory: %v", err)
return false
}
var newROMs bool
for _, v := range files {
2023-06-30 20:14:34 -06:00
if isLineageROM, _ := parseROMFileName(v); !isLineageROM { // skip files that aren't LineageOS ROMs
continue
}
newROMs = true
log.Printf("new build found - moving file %s", v.Name())
romCache.Lock() // RW lock to prevent multiple concurrent goroutines moving the same file
2023-06-30 19:56:25 -06:00
err := copyThenDeleteFile(fmt.Sprintf("%s/%s", buildOutDirectory, v.Name()), fmt.Sprintf("%s/%s", romDirectory, v.Name()))
romCache.Unlock()
if err != nil {
log.Printf("failed to move file '%s' from out to rom directory: %v", v.Name(), err)
continue
}
}
return newROMs
}
// Returns a sha256 hash of a file located at the path provided
func hashFile(filename string) (string, error) {
2026-07-04 10:03:18 -06:00
_, sha256Val, err := hashFileSha1AndSha256(filename)
return sha256Val, err
}
// Returns sha1 and sha256 hashes of a file located at the path provided
func hashFileSha1AndSha256(filename string) (string, string, error) {
f, err := os.Open(filename)
if err != nil {
2026-07-04 10:03:18 -06:00
return "", "", fmt.Errorf("failed to open file '%s': %v", filename, err)
}
defer f.Close()
2026-07-04 10:03:18 -06:00
h1 := sha1.New()
h256 := sha256.New()
buf := make([]byte, 32768)
for {
n, err := f.Read(buf)
if n > 0 {
h1.Write(buf[:n])
h256.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
return "", "", fmt.Errorf("failed to read file '%s': %v", filename, err)
}
}
return fmt.Sprintf("%x", h1.Sum(nil)), fmt.Sprintf("%x", h256.Sum(nil)), nil
}
// Reads metadata from META-INF/com/android/metadata inside the zip file
func readZipMetadata(zipPath string) (ZipMetadata, error) {
var meta ZipMetadata
r, err := zip.OpenReader(zipPath)
if err != nil {
return meta, err
}
defer r.Close()
for _, f := range r.File {
if f.Name == "META-INF/com/android/metadata" {
rc, err := f.Open()
if err != nil {
return meta, err
}
defer rc.Close()
scanner := bufio.NewScanner(rc)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
switch key {
case "post-timestamp":
t, _ := strconv.Atoi(val)
meta.Timestamp = t
case "post-sdk-level":
s, _ := strconv.Atoi(val)
meta.SDKLevel = s
case "security-patch-level":
meta.PatchLevel = val
case "ota-property-files":
meta.OTAPropertyFiles = val
}
}
}
return meta, scanner.Err()
}
}
2026-07-04 10:03:18 -06:00
return meta, fmt.Errorf("metadata file not found in zip")
}
// false if a file is not a LineageOS ROM .zip file
// no formal validation is performed - only file naming convention is checked
// also returns a lineage ROM's filename sliced and delimited by -'s
// Example filename: lineage-20.0-20230604-UNOFFICIAL-sunfish.zip
2023-06-30 20:14:34 -06:00
func parseROMFileName(v fs.DirEntry) (bool, []string) {
// skip directories, non .zip files and files that don't begin with lineage-
if v.Type().IsDir() || !strings.HasSuffix(v.Name(), ".zip") || !strings.HasPrefix(v.Name(), "lineage-") {
return false, nil
}
splitName := strings.Split(v.Name(), "-")
// expect 5 dashes
return len(splitName) == 5, splitName
}
// A custom "move file" function because in docker container the mounted folders are different overlay filesystems
// Instead of os.Rename, we must copy and delete
2023-06-30 19:56:25 -06:00
func copyThenDeleteFile(src, dst string) error {
sourceFileStat, err := os.Stat(src)
if err != nil {
return err
}
if !sourceFileStat.Mode().IsRegular() {
return fmt.Errorf("%s is not a regular file", src)
}
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return fmt.Errorf("failed to copy file: %v", err)
}
err = os.Remove(src)
if err != nil {
return fmt.Errorf("failed to delete source file after copy: %v", err)
}
return nil
}