Compare commits

...

2 Commits

Author SHA1 Message Date
9415866801 fixed the checksum name issue 2026-02-20 19:30:06 +09:00
9ce17287a5 changed the rpm repo locking mechanism 2026-02-20 10:41:08 +09:00
3 changed files with 128 additions and 52 deletions

View File

@@ -28,7 +28,7 @@ func rpm_load_rpm(full_path string, checksum_type RpmChecksumType, location_href
var stat_mod_time int64
var stat_size int64
pkg, err = rpm_package_from_rpm_base(full_path, change_log_limit)
pkg, err = RpmPackageFromRpmBase(full_path, change_log_limit)
if err != nil {
return nil, fmt.Errorf("can not get rpm header: %v", err)
}
@@ -47,6 +47,10 @@ func rpm_load_rpm(full_path string, checksum_type RpmChecksumType, location_href
if err != nil {
return nil, fmt.Errorf("error while checksum calculation: %v", err)
}
// since we did calcualte the checksum with our own type,
// we need to override the chekcsum type name
pkg.RpmChecksumType = RpmChecksumName(checksum_type)
return pkg, nil
}
@@ -65,7 +69,7 @@ func rpm_get_checksum(file_path string, checksum_type RpmChecksumType, cache_dir
return checksum, nil
}
func rpm_package_from_rpm_base(full_path string, change_log_limit int) (*RpmPackage, error) {
func RpmPackageFromRpmBase(full_path string, change_log_limit int) (*RpmPackage, error) {
var f *os.File
var err error
var rpm *rpmutils.Rpm
@@ -207,7 +211,8 @@ func rpm_package_from_rpm_base(full_path string, change_log_limit int) (*RpmPack
fda, err = rpm.Header.GetUint64(rpmutils.FILEDIGESTALGO)
if err != nil {
return nil, err
// default to md5. i assume the rpm file uses md5 when this field is missing
fda = rpmutils.PGPHASHALGO_MD5
}
pkg.RpmChecksumType = rpmutils.GetFileAlgoName(int(fda))

View File

@@ -1,6 +1,7 @@
package repokit
import "encoding/xml"
import "errors"
import "fmt"
import "io"
import "io/fs"
@@ -11,6 +12,7 @@ import "sort"
import "strconv"
import "strings"
import "sync"
import "syscall"
import "time"
type RpmLockMode int
@@ -97,13 +99,12 @@ func RpmCreateRepoWithOptions(dir string, options RpmRepoOptions) error {
}
func rpm_create_rpm_repo(dir string, options RpmRepoOptions) error {
func rpm_create_rpm_repo(dir string, options RpmRepoOptions) (ret_err error) {
var in_dir string
var in_repo string
var out_dir string
var out_repo string
var tmp_out_dir string
var lock_dir string
var out_dir_exist bool
var err error
var pool_tasks []*rpm_pool_task
@@ -135,13 +136,13 @@ func rpm_create_rpm_repo(dir string, options RpmRepoOptions) error {
var data rpm_user_data
var compression_type RpmCompressionType
var lock_mode RpmLockMode
var lock_handle *os.File
in_dir = rpm_normalize_dir_path(dir)
in_repo = path.Join(in_dir, "repodata")
out_dir = options.OutputDir
out_repo = ""
tmp_out_dir = ""
lock_dir = ""
if out_dir != "" {
out_dir_exist, err = rpm_path_exists(out_dir)
@@ -173,11 +174,21 @@ lock_mode = options.LockMode
}
}
lock_dir, tmp_out_dir, err = rpm_lock_repo(out_dir, lock_mode)
lock_handle, tmp_out_dir, err = rpm_lock_repo(out_dir, lock_mode)
if err != nil {
return fmt.Errorf("error to lock repo: %v", err)
}
defer rpm_exit_cleanup(lock_dir)
defer func() {
var unlock_err error
rpm_exit_cleanup(tmp_out_dir)
unlock_err = rpm_unlock_repo(lock_handle)
if ret_err == nil && unlock_err != nil {
// i can't use the return statement because it is
// set in a deferred function.
ret_err = fmt.Errorf("unlock repo lock failed: %w", unlock_err)
}
}()
jobs = make(chan *rpm_repo_job, 100)
results = make(chan error, 100)
@@ -504,45 +515,61 @@ func rpm_exit_cleanup(lock_dir string) {
_ = os.RemoveAll(lock_dir)
}
func rpm_lock_repo(repo_dir string, lock_mode RpmLockMode) (string, string, error) {
var lock_dir string
func rpm_lock_repo(repo_dir string, lock_mode RpmLockMode) (*os.File, string, error) {
var lock_handle *os.File
var tmp_repodata_dir string
var err error
var sleep_duration time.Duration
var flock_flags int
lock_dir = filepath.Join(repo_dir, ".repodata")
if lock_mode == RpmLockNone {
tmp_repodata_dir = filepath.Join(repo_dir, rpm_append_pid_and_datetime(".repodata", ""))
err = os.Mkdir(tmp_repodata_dir, 0775)
if lock_mode != RpmLockNone {
lock_handle, err = os.Open(repo_dir)
if err != nil {
return "", "", fmt.Errorf("cannot create %s: %w", tmp_repodata_dir, err)
}
return "", tmp_repodata_dir, nil
}
sleep_duration = 500 * time.Millisecond
for {
err = os.Mkdir(lock_dir, 0775)
if err == nil {
tmp_repodata_dir = lock_dir
return lock_dir, tmp_repodata_dir, nil
}
if !os.IsExist(err) {
return "", "", fmt.Errorf("error while creating temporary repodata directory: %s: %w", lock_dir, err)
return nil, "", fmt.Errorf("cannot open repo dir %s for locking: %w", repo_dir, err)
}
flock_flags = syscall.LOCK_EX
if lock_mode == RpmLockFail {
return "", "", fmt.Errorf("repodata lock exists: %s", lock_dir)
flock_flags = flock_flags | syscall.LOCK_NB
} else if lock_mode != RpmLockWait {
lock_handle.Close()
return nil, "", fmt.Errorf("unknown lock mode")
}
if lock_mode == RpmLockWait {
time.Sleep(sleep_duration)
continue
err = syscall.Flock(int(lock_handle.Fd()), flock_flags)
if err != nil {
lock_handle.Close()
if errors.Is(err, syscall.EWOULDBLOCK) || errors.Is(err, syscall.EAGAIN) {
return nil, "", fmt.Errorf("repo dir lock exists: %s", repo_dir)
}
return nil, "", fmt.Errorf("cannot lock repo dir %s: %w", repo_dir, err)
}
}
return "", "", fmt.Errorf("unknown lock mode")
tmp_repodata_dir = filepath.Join(repo_dir, rpm_append_pid_and_datetime(".repodata", ""))
err = os.Mkdir(tmp_repodata_dir, os.ModePerm)
if err != nil {
rpm_unlock_repo(lock_handle)
return nil, "", fmt.Errorf("cannot create %s: %w", tmp_repodata_dir, err)
}
return lock_handle, tmp_repodata_dir, nil
}
func rpm_unlock_repo(lock_handle *os.File) error {
var err error
if lock_handle == nil {
// in case rpm_lock_repo() didn't really lock a repo or did fail.
return nil
}
err = syscall.Flock(int(lock_handle.Fd()), syscall.LOCK_UN)
if err != nil {
lock_handle.Close()
return err
}
return lock_handle.Close()
}
func rpm_append_pid_and_datetime(prefix string, suffix string) string {

View File

@@ -164,21 +164,21 @@ func TestRpmNormalizeDirPathWindowsDrive(t *testing.T) {
func TestRpmLockRepoModes(t *testing.T) {
var dir string
var lock_dir string
var lock_handle *os.File
var tmp_dir string
var err error
var wait_done chan error
var remove_err error
var exists bool
var wait_err error
var lock_err error
dir = t.TempDir()
lock_dir = filepath.Join(dir, ".repodata")
err = os.Mkdir(lock_dir, 0775)
lock_handle, tmp_dir, err = rpm_lock_repo(dir, RpmLockWait)
if err != nil {
t.Fatalf("mkdir lock dir: %v", err)
t.Fatalf("initial lock: %v", err)
}
defer rpm_exit_cleanup(tmp_dir)
_, _, err = rpm_lock_repo(dir, RpmLockFail)
if err == nil {
@@ -187,23 +187,25 @@ func TestRpmLockRepoModes(t *testing.T) {
wait_done = make(chan error, 1)
go func() {
var tmp_lock string
var tmp_lock *os.File
var tmp_out string
var inner_err error
tmp_lock, tmp_out, inner_err = rpm_lock_repo(dir, RpmLockWait)
if inner_err == nil {
if tmp_lock == "" || tmp_out == "" {
if tmp_lock == nil || tmp_out == "" {
inner_err = fmt.Errorf("RpmLockWait returned empty paths")
}
_ = rpm_unlock_repo(tmp_lock)
rpm_exit_cleanup(tmp_out)
}
wait_done <- inner_err
}()
time.Sleep(150 * time.Millisecond)
remove_err = os.RemoveAll(lock_dir)
if remove_err != nil {
t.Fatalf("remove lock dir: %v", remove_err)
lock_err = rpm_unlock_repo(lock_handle)
if lock_err != nil {
t.Fatalf("unlock lock handle: %v", lock_err)
}
wait_err = <-wait_done
@@ -211,12 +213,12 @@ func TestRpmLockRepoModes(t *testing.T) {
t.Fatalf("RpmLockWait error: %v", wait_err)
}
lock_dir, tmp_dir, err = rpm_lock_repo(dir, RpmLockNone)
lock_handle, tmp_dir, err = rpm_lock_repo(dir, RpmLockNone)
if err != nil {
t.Fatalf("RpmLockNone error: %v", err)
}
if lock_dir != "" {
t.Fatalf("RpmLockNone should not return lock dir, got %q", lock_dir)
if lock_handle != nil {
t.Fatalf("RpmLockNone should not return lock handle")
}
exists, err = rpm_path_exists(tmp_dir)
if err != nil {
@@ -225,4 +227,46 @@ func TestRpmLockRepoModes(t *testing.T) {
if !exists {
t.Fatalf("RpmLockNone tmp dir missing: %q", tmp_dir)
}
rpm_exit_cleanup(tmp_dir)
}
func TestRpmCreateRepoWithOptionsLockNone(t *testing.T) {
var dir string
var options RpmRepoOptions
var err error
var repodata_dir string
var repomd_path string
var exists bool
var entries []os.DirEntry
var entry os.DirEntry
dir = t.TempDir()
options = RpmDefaultRepoOptions()
options.LockMode = RpmLockNone
options.AllowMissingRepomd = true
err = RpmCreateRepoWithOptions(dir, options)
if err != nil {
t.Fatalf("RpmCreateRepoWithOptions lock none: %v", err)
}
repodata_dir = filepath.Join(dir, "repodata")
repomd_path = filepath.Join(repodata_dir, "repomd.xml")
exists, err = rpm_path_exists(repomd_path)
if err != nil {
t.Fatalf("repomd exists check: %v", err)
}
if !exists {
t.Fatalf("repomd not found at %q", repomd_path)
}
entries, err = os.ReadDir(dir)
if err != nil {
t.Fatalf("readdir repo dir: %v", err)
}
for _, entry = range entries {
if strings.HasPrefix(entry.Name(), ".repodata.") {
t.Fatalf("temporary repodata dir leaked: %q", entry.Name())
}
}
}