694 lines
17 KiB
Go
694 lines
17 KiB
Go
package repokit
|
||
|
||
import "compress/gzip"
|
||
import "encoding/xml"
|
||
import "fmt"
|
||
import "github.com/klauspost/compress/zstd"
|
||
import "github.com/sassoftware/go-rpmutils"
|
||
import "io"
|
||
import "os"
|
||
import "path/filepath"
|
||
import "regexp"
|
||
import "strconv"
|
||
import "strings"
|
||
import "syscall"
|
||
|
||
func rpm_normalize_dir_path(path_value string) string {
|
||
var normalized string
|
||
|
||
if path_value == "" {
|
||
return "./"
|
||
}
|
||
|
||
normalized = filepath.Clean(path_value)
|
||
if !strings.HasSuffix(normalized, string(filepath.Separator)) && normalized != "/" {
|
||
normalized = normalized + string(filepath.Separator)
|
||
}
|
||
return normalized
|
||
}
|
||
|
||
func rpm_path_exists(path_value string) (bool, error) {
|
||
var err error
|
||
|
||
_, err = os.Stat(path_value)
|
||
if err == nil {
|
||
return true, nil
|
||
}
|
||
if os.IsNotExist(err) {
|
||
return false, nil
|
||
}
|
||
return false, err
|
||
}
|
||
|
||
func rpm_copy_dir(src string, dst string) error {
|
||
var src_info os.FileInfo
|
||
var err error
|
||
var entries []os.DirEntry
|
||
var entry os.DirEntry
|
||
var src_path string
|
||
var dst_path string
|
||
|
||
src_info, err = os.Stat(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
err = os.MkdirAll(dst, src_info.Mode())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
entries, err = os.ReadDir(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, entry = range entries {
|
||
src_path = filepath.Join(src, entry.Name())
|
||
dst_path = filepath.Join(dst, entry.Name())
|
||
if entry.IsDir() {
|
||
err = rpm_copy_dir(src_path, dst_path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
err = rpm_copy_file(src_path, dst_path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func rpm_copy_file(src string, dst string) error {
|
||
var source_file *os.File
|
||
var dest_file *os.File
|
||
var err error
|
||
|
||
source_file, err = os.Open(src)
|
||
if err != nil {
|
||
return fmt.Errorf("could not open source file: %v", err)
|
||
}
|
||
defer source_file.Close()
|
||
|
||
dest_file, err = os.Create(dst)
|
||
if err != nil {
|
||
return fmt.Errorf("could not create destination file: %v", err)
|
||
}
|
||
defer dest_file.Close()
|
||
|
||
_, err = io.Copy(dest_file, source_file)
|
||
if err != nil {
|
||
return fmt.Errorf("could not copy file contents: %v", err)
|
||
}
|
||
|
||
err = dest_file.Close()
|
||
if err != nil {
|
||
return fmt.Errorf("could not close destination file: %v", err)
|
||
}
|
||
|
||
err = rpm_copy_metadata_and_ownership(src, dst)
|
||
if err != nil {
|
||
return fmt.Errorf("could not copy metadata: %v", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func rpm_copy_metadata_and_ownership(src string, dst string) error {
|
||
var src_info os.FileInfo
|
||
var err error
|
||
var src_sys *syscall.Stat_t
|
||
|
||
src_info, err = os.Stat(src)
|
||
if err != nil {
|
||
return fmt.Errorf("could not get source file info: %v", err)
|
||
}
|
||
|
||
err = os.Chmod(dst, src_info.Mode())
|
||
if err != nil {
|
||
return fmt.Errorf("could not set file permissions: %v", err)
|
||
}
|
||
|
||
err = os.Chtimes(dst, src_info.ModTime(), src_info.ModTime())
|
||
if err != nil {
|
||
return fmt.Errorf("could not set file timestamps: %v", err)
|
||
}
|
||
|
||
src_sys = src_info.Sys().(*syscall.Stat_t)
|
||
err = os.Chown(dst, int(src_sys.Uid), int(src_sys.Gid))
|
||
if err != nil {
|
||
return fmt.Errorf("could not change file ownership: %v", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func rpm_contains(slice []string, elem string) bool {
|
||
var v string
|
||
|
||
for _, v = range slice {
|
||
if v == elem {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func rpm_flag_to_str(flags uint64) string {
|
||
var result string
|
||
|
||
flags = flags & 0xf
|
||
switch flags {
|
||
case 0:
|
||
result = ""
|
||
case 2:
|
||
result = "LT"
|
||
case 4:
|
||
result = "GT"
|
||
case 8:
|
||
result = "EQ"
|
||
case 10:
|
||
result = "LE"
|
||
case 12:
|
||
result = "GE"
|
||
default:
|
||
result = ""
|
||
}
|
||
return result
|
||
}
|
||
|
||
func rpm_is_primary_file(file_path string) bool {
|
||
// it mirrors the historical createrepo/createrepo_c definition of “primary” files. In RPM repodata,
|
||
// primary.xml only lists a subset of files (to keep metadata smaller). The heuristic is:
|
||
//
|
||
// - /etc/* - configuration files are important for dependency and discovery.
|
||
// - /usr/lib/sendmail - special legacy binary that historically needed to be listed even though it
|
||
// doesn’t live in /bin.
|
||
// - any path containing bin/ - executables are treated as primary.
|
||
|
||
if strings.HasPrefix(file_path, "/etc/") {
|
||
return true
|
||
}
|
||
if file_path == "/usr/lib/sendmail" {
|
||
return true
|
||
}
|
||
if strings.Contains(file_path, "bin/") {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func rpm_compare_dependency(dep1 string, dep2 string) int {
|
||
|
||
// NOTE: The function assume first parts must be same!
|
||
// libc.so.6() < libc.so.6(GLIBC_2.3.4)(64 bit) < libc.so.6(GLIBC_2.4)
|
||
// Returns -1 if first < second, 1 if first > second, and 0 if first == second.
|
||
var regex *regexp.Regexp
|
||
var matches1 []string
|
||
var matches2 []string
|
||
|
||
regex = regexp.MustCompile(`\((?i:glibc)_([^)]+)\)`)
|
||
matches1 = regex.FindStringSubmatch(dep1)
|
||
matches2 = regex.FindStringSubmatch(dep2)
|
||
|
||
if len(matches1) == 0 && len(matches2) == 0 { return 0 }
|
||
if len(matches1) == 0 { return -1 }
|
||
if len(matches2) == 0 { return 1 }
|
||
|
||
return rpmutils.Vercmp(matches1[1], matches2[1])
|
||
}
|
||
|
||
func rpm_package_nevra(pkg *RpmPackage) string {
|
||
var epoch string
|
||
|
||
epoch = pkg.Epoch
|
||
if epoch == "" {
|
||
epoch = "0"
|
||
}
|
||
|
||
return fmt.Sprintf("%s-%s:%s-%s.%s", pkg.Name, epoch, pkg.Version, pkg.Release, pkg.Arch)
|
||
}
|
||
|
||
func rpm_str_to_evr(input string) *RpmEVR {
|
||
var evr *RpmEVR
|
||
var epoch_end int
|
||
var epoch_str string
|
||
var err error
|
||
var bad_epoch bool
|
||
var version_release []string
|
||
|
||
evr = &RpmEVR{}
|
||
if input == "" {
|
||
return evr
|
||
}
|
||
|
||
bad_epoch = false
|
||
epoch_end = strings.Index(input, ":")
|
||
if epoch_end != -1 {
|
||
epoch_str = input[:epoch_end]
|
||
_, err = strconv.Atoi(epoch_str)
|
||
if err != nil {
|
||
bad_epoch = true
|
||
} else {
|
||
evr.Epoch = epoch_str
|
||
}
|
||
input = input[epoch_end+1:]
|
||
}
|
||
|
||
if evr.Epoch == "" && !bad_epoch {
|
||
evr.Epoch = "0"
|
||
}
|
||
|
||
version_release = strings.SplitN(input, "-", 2)
|
||
if len(version_release) > 0 {
|
||
evr.Version = version_release[0]
|
||
if len(version_release) == 2 {
|
||
evr.Release = version_release[1]
|
||
}
|
||
}
|
||
|
||
return evr
|
||
}
|
||
|
||
func rpm_has_control_chars(value string) bool {
|
||
var i int
|
||
|
||
for i = 0; i < len(value); i++ {
|
||
if value[i] < 32 && value[i] != 9 && value[i] != 10 && value[i] != 13 {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func rpm_dependency_list_contains_forbidden_control_chars(deps []RpmDependency) bool {
|
||
var ret bool
|
||
var dep RpmDependency
|
||
|
||
ret = false
|
||
for _, dep = range deps {
|
||
if dep.Name != "" && rpm_has_control_chars(dep.Name) {
|
||
ret = true
|
||
break
|
||
}
|
||
if dep.Epoch != "" && rpm_has_control_chars(dep.Epoch) {
|
||
ret = true
|
||
break
|
||
}
|
||
if dep.Version != "" && rpm_has_control_chars(dep.Version) {
|
||
ret = true
|
||
break
|
||
}
|
||
if dep.Release != "" && rpm_has_control_chars(dep.Release) {
|
||
ret = true
|
||
break
|
||
}
|
||
}
|
||
return ret
|
||
}
|
||
|
||
func rpm_package_contains_forbidden_control_chars(pkg *RpmPackage) bool {
|
||
var ret bool
|
||
var file_item RpmPackageFile
|
||
var change_log RpmChangelogEntry
|
||
|
||
ret = false
|
||
if pkg.Name != "" && rpm_has_control_chars(pkg.Name) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Epoch != "" && rpm_has_control_chars(pkg.Epoch) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Version != "" && rpm_has_control_chars(pkg.Version) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Release != "" && rpm_has_control_chars(pkg.Release) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Arch != "" && rpm_has_control_chars(pkg.Arch) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Summary != "" && rpm_has_control_chars(pkg.Summary) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Description != "" && rpm_has_control_chars(pkg.Description) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.Url != "" && rpm_has_control_chars(pkg.Url) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmLicense != "" && rpm_has_control_chars(pkg.RpmLicense) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmVendor != "" && rpm_has_control_chars(pkg.RpmVendor) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmGroup != "" && rpm_has_control_chars(pkg.RpmGroup) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmBuildHost != "" && rpm_has_control_chars(pkg.RpmBuildHost) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmSourceRpm != "" && rpm_has_control_chars(pkg.RpmSourceRpm) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.RpmPackager != "" && rpm_has_control_chars(pkg.RpmPackager) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.LocationHref != "" && rpm_has_control_chars(pkg.LocationHref) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if pkg.LocationBase != "" && rpm_has_control_chars(pkg.LocationBase) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Requires) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Provides) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Conflicts) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Obsoletes) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Suggests) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Enhances) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Recommends) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if rpm_dependency_list_contains_forbidden_control_chars(pkg.Supplements) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
|
||
for _, file_item = range pkg.Files {
|
||
if file_item.FullPath != "" && rpm_has_control_chars(file_item.FullPath) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
}
|
||
|
||
for _, change_log = range pkg.Changelogs {
|
||
if change_log.Author != "" && rpm_has_control_chars(change_log.Author) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
if change_log.Changelog != "" && rpm_has_control_chars(change_log.Changelog) {
|
||
ret = true
|
||
goto done
|
||
}
|
||
}
|
||
|
||
done:
|
||
return ret
|
||
}
|
||
|
||
func rpm_get_primary_package(pkg *RpmPackage, task_id int64) *RpmPrimaryPackage {
|
||
var primary_pkg *RpmPrimaryPackage
|
||
|
||
primary_pkg = &RpmPrimaryPackage{}
|
||
primary_pkg.ID = task_id
|
||
primary_pkg.Type = "rpm"
|
||
primary_pkg.Name = pkg.Name
|
||
primary_pkg.Arch = pkg.Arch
|
||
primary_pkg.Version = RpmVersion{Epoch: pkg.Epoch, Ver: pkg.Version, Rel: pkg.Release}
|
||
primary_pkg.Checksum = RpmChecksum{Type: pkg.RpmChecksumType, PkgID: "YES", Value: pkg.PkgId}
|
||
primary_pkg.Summary = pkg.Summary
|
||
primary_pkg.Description = pkg.Description
|
||
primary_pkg.Packager = pkg.RpmPackager
|
||
primary_pkg.URL = pkg.Url
|
||
primary_pkg.Time = RpmTime{File: fmt.Sprintf("%d", pkg.TimeFile), Build: fmt.Sprintf("%d", pkg.TimeBuild)}
|
||
primary_pkg.Size = RpmSize{Package: fmt.Sprintf("%d", pkg.SizePackage), Installed: fmt.Sprintf("%d", pkg.SizeInstalled), Archive: fmt.Sprintf("%d", pkg.SizeArchive)}
|
||
primary_pkg.Location = RpmLocation{Href: pkg.LocationHref, XMLBase: rpm_prepend_protocol(pkg.LocationBase)}
|
||
primary_pkg.Format = RpmFormat{}
|
||
primary_pkg.Format.License = pkg.RpmLicense
|
||
primary_pkg.Format.Vendor = pkg.RpmVendor
|
||
primary_pkg.Format.Group = pkg.RpmGroup
|
||
primary_pkg.Format.BuildHost = pkg.RpmBuildHost
|
||
primary_pkg.Format.SourceRPM = pkg.RpmSourceRpm
|
||
primary_pkg.Format.HeaderRange = RpmHeaderRange{Start: fmt.Sprintf("%d", pkg.RpmHeaderStart), End: fmt.Sprintf("%d", pkg.RpmHeaderEnd)}
|
||
primary_pkg.Format.Provides = rpm_xml_dump_primary_dump_pcor(pkg.Provides, RPM_DEP_PROVIDES)
|
||
primary_pkg.Format.Conflicts = rpm_xml_dump_primary_dump_pcor(pkg.Conflicts, RPM_DEP_CONFLICTS)
|
||
primary_pkg.Format.Obsoletes = rpm_xml_dump_primary_dump_pcor(pkg.Obsoletes, RPM_DEP_OBSOLETES)
|
||
primary_pkg.Format.Requires = rpm_xml_dump_primary_dump_pcor(pkg.Requires, RPM_DEP_REQUIRES)
|
||
primary_pkg.Format.Suggests = rpm_xml_dump_primary_dump_pcor(pkg.Suggests, RPM_DEP_SUGGESTS)
|
||
primary_pkg.Format.Enhances = rpm_xml_dump_primary_dump_pcor(pkg.Enhances, RPM_DEP_ENHANCES)
|
||
primary_pkg.Format.Recommends = rpm_xml_dump_primary_dump_pcor(pkg.Recommends, RPM_DEP_RECOMMENDS)
|
||
primary_pkg.Format.Supplements = rpm_xml_dump_primary_dump_pcor(pkg.Supplements, RPM_DEP_SUPPLEMENTS)
|
||
primary_pkg.Format.Files = rpm_xml_dump_files(pkg.Files, true, false)
|
||
|
||
return primary_pkg
|
||
}
|
||
|
||
func rpm_get_filelists_package(pkg *RpmPackage, is_filelists_ext bool, task_id int64) *RpmFilelistsPackage {
|
||
var filelists_pkg *RpmFilelistsPackage
|
||
|
||
filelists_pkg = &RpmFilelistsPackage{}
|
||
filelists_pkg.ID = task_id
|
||
filelists_pkg.PkgID = pkg.PkgId
|
||
filelists_pkg.Name = pkg.Name
|
||
filelists_pkg.Arch = pkg.Arch
|
||
filelists_pkg.Version = RpmVersion{Epoch: pkg.Epoch, Ver: pkg.Version, Rel: pkg.Release}
|
||
filelists_pkg.File = rpm_xml_dump_files(pkg.Files, false, is_filelists_ext)
|
||
if is_filelists_ext {
|
||
filelists_pkg.Checksum = &RpmFilelistsChecksum{Type: pkg.RpmChecksumType}
|
||
}
|
||
return filelists_pkg
|
||
}
|
||
|
||
func rpm_get_other_package(pkg *RpmPackage, task_id int64) *RpmOtherPackage {
|
||
var other_pkg *RpmOtherPackage
|
||
var change_log_list []*RpmChangelog
|
||
var c_log RpmChangelogEntry
|
||
var change_log *RpmChangelog
|
||
|
||
other_pkg = &RpmOtherPackage{}
|
||
other_pkg.ID = task_id
|
||
other_pkg.PkgID = pkg.PkgId
|
||
other_pkg.Name = pkg.Name
|
||
other_pkg.Arch = pkg.Arch
|
||
other_pkg.Version = RpmVersion{Epoch: pkg.Epoch, Ver: pkg.Version, Rel: pkg.Release}
|
||
|
||
change_log_list = nil
|
||
for _, c_log = range pkg.Changelogs {
|
||
change_log = &RpmChangelog{Content: c_log.Changelog, Author: c_log.Author, Date: fmt.Sprintf("%d", c_log.Date)}
|
||
change_log_list = append(change_log_list, change_log)
|
||
}
|
||
if change_log_list != nil {
|
||
other_pkg.Changelog = change_log_list
|
||
}
|
||
return other_pkg
|
||
}
|
||
|
||
func rpm_xml_dump_files(files []RpmPackageFile, is_primary bool, is_filelists_ext bool) []*RpmFile {
|
||
var xml_files []*RpmFile
|
||
var file_item RpmPackageFile
|
||
var file_value *RpmFile
|
||
|
||
xml_files = nil
|
||
for _, file_item = range files {
|
||
if file_item.FullPath == "" {
|
||
continue
|
||
}
|
||
if is_primary && !rpm_is_primary_file(file_item.FullPath) {
|
||
continue
|
||
}
|
||
file_value = &RpmFile{}
|
||
file_value.Value = file_item.FullPath
|
||
if file_item.Type != "" && file_item.Type != "file" {
|
||
file_value.Type = file_item.Type
|
||
}
|
||
if is_filelists_ext && file_item.Digest != "" {
|
||
file_value.Hash = file_item.Digest
|
||
}
|
||
xml_files = append(xml_files, file_value)
|
||
}
|
||
return xml_files
|
||
}
|
||
|
||
func rpm_xml_dump_primary_dump_pcor(dependencies []RpmDependency, dep_type RpmDepType) *RpmDepEntryList {
|
||
var dep_entry_list *RpmDepEntryList
|
||
var dep RpmDependency
|
||
var entry RpmDepEntry
|
||
|
||
dep_entry_list = nil
|
||
if len(dependencies) > 0 {
|
||
dep_entry_list = &RpmDepEntryList{}
|
||
for _, dep = range dependencies {
|
||
if dep.Name == "" {
|
||
continue
|
||
}
|
||
entry = RpmDepEntry{Name: dep.Name}
|
||
if dep.Flags != "" {
|
||
entry.Flags = dep.Flags
|
||
if dep.Epoch != "" {
|
||
entry.Epoch = dep.Epoch
|
||
}
|
||
if dep.Version != "" {
|
||
entry.Ver = dep.Version
|
||
}
|
||
if dep.Release != "" {
|
||
entry.Rel = dep.Release
|
||
}
|
||
}
|
||
if dep_type == RPM_DEP_REQUIRES && dep.Pre {
|
||
entry.Pre = "1"
|
||
}
|
||
dep_entry_list.Entries = append(dep_entry_list.Entries, entry)
|
||
}
|
||
}
|
||
return dep_entry_list
|
||
}
|
||
|
||
func rpm_prepend_protocol(url_value string) string {
|
||
if strings.HasPrefix(url_value, "/") {
|
||
return "file://" + url_value
|
||
}
|
||
return url_value
|
||
}
|
||
|
||
func rpm_dump_xml(obj any) ([]byte, error) {
|
||
var xml_data []byte
|
||
var err error
|
||
var combined []byte
|
||
|
||
xml_data, err = xml.MarshalIndent(obj, "", " ")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
combined = []byte(xml.Header + string(xml_data))
|
||
return combined, nil
|
||
}
|
||
|
||
func rpm_compress_xml(xml_data []byte, output_path string, compression_type RpmCompressionType) error {
|
||
var output_file *os.File
|
||
var err error
|
||
var encoder *zstd.Encoder
|
||
var gzip_writer *gzip.Writer
|
||
var writer io.Writer
|
||
|
||
output_file, err = os.Create(output_path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer output_file.Close()
|
||
|
||
if compression_type == RPM_COMPRESSION_NONE {
|
||
_, err = output_file.Write(xml_data)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
if compression_type == RPM_COMPRESSION_GZ {
|
||
gzip_writer = gzip.NewWriter(output_file)
|
||
defer gzip_writer.Close()
|
||
writer = gzip_writer
|
||
} else if compression_type == RPM_COMPRESSION_ZSTD {
|
||
encoder, err = zstd.NewWriter(output_file, zstd.WithEncoderLevel(zstd.EncoderLevelFromZstd(7)))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer encoder.Close()
|
||
writer = encoder
|
||
} else {
|
||
return fmt.Errorf("unsupported compression type")
|
||
}
|
||
|
||
_, err = writer.Write(xml_data)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func rpm_write_xml(obj any, path_value string) error {
|
||
var xml_data []byte
|
||
var err error
|
||
var file *os.File
|
||
var combined []byte
|
||
|
||
xml_data, err = xml.MarshalIndent(obj, "", " ")
|
||
if err != nil {
|
||
return fmt.Errorf("error marshalling to XML: %v", err)
|
||
}
|
||
combined = []byte(xml.Header + string(xml_data))
|
||
|
||
file, err = os.Create(path_value)
|
||
if err != nil {
|
||
return fmt.Errorf("error creating XML file %s: %v", path_value, err)
|
||
}
|
||
defer file.Close()
|
||
|
||
_, err = file.Write(combined)
|
||
if err != nil {
|
||
return fmt.Errorf("error writing XML data to file %s: %v", path_value, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func rpm_stat_file(full_path string) (int64, int64, error) {
|
||
var stat_buf os.FileInfo
|
||
var err error
|
||
var path_err *os.PathError
|
||
var ok bool
|
||
|
||
stat_buf, err = os.Stat(full_path)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return 0, 0, fmt.Errorf("file does not exist: %s", full_path)
|
||
}
|
||
path_err, ok = err.(*os.PathError)
|
||
if ok && path_err.Err == syscall.ENOENT {
|
||
return 0, 0, fmt.Errorf("stat(%s) failed: %s", full_path, path_err.Err)
|
||
}
|
||
return 0, 0, fmt.Errorf("stat(%s) failed: %v", full_path, err)
|
||
}
|
||
return stat_buf.ModTime().Unix(), stat_buf.Size(), nil
|
||
}
|
||
|
||
func rpm_reverse_array[T any](slice []T) {
|
||
var i int
|
||
var j int
|
||
|
||
j = len(slice) - 1
|
||
for i = 0; i < j; i, j = i+1, j-1 {
|
||
slice[i], slice[j] = slice[j], slice[i]
|
||
}
|
||
}
|