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_mod_time int64
var stat_size 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 { if err != nil {
return nil, fmt.Errorf("can not get rpm header: %v", err) 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 { if err != nil {
return nil, fmt.Errorf("error while checksum calculation: %v", err) 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 return pkg, nil
} }
@@ -65,7 +69,7 @@ func rpm_get_checksum(file_path string, checksum_type RpmChecksumType, cache_dir
return checksum, nil 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 f *os.File
var err error var err error
var rpm *rpmutils.Rpm 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) fda, err = rpm.Header.GetUint64(rpmutils.FILEDIGESTALGO)
if err != nil { 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)) pkg.RpmChecksumType = rpmutils.GetFileAlgoName(int(fda))
@@ -226,7 +231,7 @@ func rpm_package_from_rpm_base(full_path string, change_log_limit int) (*RpmPack
pf = RpmPackageFile{} pf = RpmPackageFile{}
pf.FullPath = file_info.Name() pf.FullPath = file_info.Name()
pf.Digest = file_info.Digest() pf.Digest = file_info.Digest()
if file_info.Mode()&^07777 == cpio.S_ISDIR { if file_info.Mode() & ^07777 == cpio.S_ISDIR {
pf.Type = "dir" pf.Type = "dir"
} else if file_info.Flags() & rpmutils.RPMFILE_GHOST != 0 { } else if file_info.Flags() & rpmutils.RPMFILE_GHOST != 0 {
pf.Type = "ghost" pf.Type = "ghost"

View File

@@ -1,6 +1,7 @@
package repokit package repokit
import "encoding/xml" import "encoding/xml"
import "errors"
import "fmt" import "fmt"
import "io" import "io"
import "io/fs" import "io/fs"
@@ -11,6 +12,7 @@ import "sort"
import "strconv" import "strconv"
import "strings" import "strings"
import "sync" import "sync"
import "syscall"
import "time" import "time"
type RpmLockMode int 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_dir string
var in_repo string var in_repo string
var out_dir string var out_dir string
var out_repo string var out_repo string
var tmp_out_dir string var tmp_out_dir string
var lock_dir string
var out_dir_exist bool var out_dir_exist bool
var err error var err error
var pool_tasks []*rpm_pool_task 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 data rpm_user_data
var compression_type RpmCompressionType var compression_type RpmCompressionType
var lock_mode RpmLockMode var lock_mode RpmLockMode
var lock_handle *os.File
in_dir = rpm_normalize_dir_path(dir) in_dir = rpm_normalize_dir_path(dir)
in_repo = path.Join(in_dir, "repodata") in_repo = path.Join(in_dir, "repodata")
out_dir = options.OutputDir out_dir = options.OutputDir
out_repo = "" out_repo = ""
tmp_out_dir = "" tmp_out_dir = ""
lock_dir = ""
if out_dir != "" { if out_dir != "" {
out_dir_exist, err = rpm_path_exists(out_dir) out_dir_exist, err = rpm_path_exists(out_dir)
@@ -164,7 +165,7 @@ func rpm_create_rpm_repo(dir string, options RpmRepoOptions) error {
} }
total_pkg_num = len(pool_tasks) total_pkg_num = len(pool_tasks)
lock_mode = options.LockMode lock_mode = options.LockMode
if lock_mode == RpmLockUnset { if lock_mode == RpmLockUnset {
if options.IgnoreLock { if options.IgnoreLock {
lock_mode = RpmLockNone lock_mode = RpmLockNone
@@ -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 { if err != nil {
return fmt.Errorf("error to lock repo: %v", err) 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) jobs = make(chan *rpm_repo_job, 100)
results = make(chan error, 100) results = make(chan error, 100)
@@ -297,7 +308,7 @@ lock_mode = options.LockMode
} }
if failed_pkg_num > 0 { if failed_pkg_num > 0 {
return fmt.Errorf("finished with error: %d packages failed, %d packages succeeded", failed_pkg_num, total_pkg_num-failed_pkg_num) return fmt.Errorf("finished with error: %d packages failed, %d packages succeeded", failed_pkg_num, total_pkg_num - failed_pkg_num)
} }
return nil return nil
} }
@@ -504,45 +515,61 @@ func rpm_exit_cleanup(lock_dir string) {
_ = os.RemoveAll(lock_dir) _ = os.RemoveAll(lock_dir)
} }
func rpm_lock_repo(repo_dir string, lock_mode RpmLockMode) (string, string, error) { func rpm_lock_repo(repo_dir string, lock_mode RpmLockMode) (*os.File, string, error) {
var lock_dir string var lock_handle *os.File
var tmp_repodata_dir string var tmp_repodata_dir string
var err error var err error
var sleep_duration time.Duration var flock_flags int
lock_dir = filepath.Join(repo_dir, ".repodata") if lock_mode != RpmLockNone {
if lock_mode == RpmLockNone { lock_handle, err = os.Open(repo_dir)
tmp_repodata_dir = filepath.Join(repo_dir, rpm_append_pid_and_datetime(".repodata", ""))
err = os.Mkdir(tmp_repodata_dir, 0775)
if err != nil { if err != nil {
return "", "", fmt.Errorf("cannot create %s: %w", tmp_repodata_dir, err) return nil, "", fmt.Errorf("cannot open repo dir %s for locking: %w", repo_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)
} }
flock_flags = syscall.LOCK_EX
if lock_mode == RpmLockFail { 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 { err = syscall.Flock(int(lock_handle.Fd()), flock_flags)
time.Sleep(sleep_duration) if err != nil {
continue 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 { 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) { func TestRpmLockRepoModes(t *testing.T) {
var dir string var dir string
var lock_dir string var lock_handle *os.File
var tmp_dir string var tmp_dir string
var err error var err error
var wait_done chan error var wait_done chan error
var remove_err error
var exists bool var exists bool
var wait_err error var wait_err error
var lock_err error
dir = t.TempDir() dir = t.TempDir()
lock_dir = filepath.Join(dir, ".repodata") lock_handle, tmp_dir, err = rpm_lock_repo(dir, RpmLockWait)
err = os.Mkdir(lock_dir, 0775)
if err != nil { 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) _, _, err = rpm_lock_repo(dir, RpmLockFail)
if err == nil { if err == nil {
@@ -187,23 +187,25 @@ func TestRpmLockRepoModes(t *testing.T) {
wait_done = make(chan error, 1) wait_done = make(chan error, 1)
go func() { go func() {
var tmp_lock string var tmp_lock *os.File
var tmp_out string var tmp_out string
var inner_err error var inner_err error
tmp_lock, tmp_out, inner_err = rpm_lock_repo(dir, RpmLockWait) tmp_lock, tmp_out, inner_err = rpm_lock_repo(dir, RpmLockWait)
if inner_err == nil { if inner_err == nil {
if tmp_lock == "" || tmp_out == "" { if tmp_lock == nil || tmp_out == "" {
inner_err = fmt.Errorf("RpmLockWait returned empty paths") inner_err = fmt.Errorf("RpmLockWait returned empty paths")
} }
_ = rpm_unlock_repo(tmp_lock)
rpm_exit_cleanup(tmp_out)
} }
wait_done <- inner_err wait_done <- inner_err
}() }()
time.Sleep(150 * time.Millisecond) time.Sleep(150 * time.Millisecond)
remove_err = os.RemoveAll(lock_dir) lock_err = rpm_unlock_repo(lock_handle)
if remove_err != nil { if lock_err != nil {
t.Fatalf("remove lock dir: %v", remove_err) t.Fatalf("unlock lock handle: %v", lock_err)
} }
wait_err = <-wait_done wait_err = <-wait_done
@@ -211,12 +213,12 @@ func TestRpmLockRepoModes(t *testing.T) {
t.Fatalf("RpmLockWait error: %v", wait_err) 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 { if err != nil {
t.Fatalf("RpmLockNone error: %v", err) t.Fatalf("RpmLockNone error: %v", err)
} }
if lock_dir != "" { if lock_handle != nil {
t.Fatalf("RpmLockNone should not return lock dir, got %q", lock_dir) t.Fatalf("RpmLockNone should not return lock handle")
} }
exists, err = rpm_path_exists(tmp_dir) exists, err = rpm_path_exists(tmp_dir)
if err != nil { if err != nil {
@@ -225,4 +227,46 @@ func TestRpmLockRepoModes(t *testing.T) {
if !exists { if !exists {
t.Fatalf("RpmLockNone tmp dir missing: %q", tmp_dir) 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())
}
}
} }