Skip to content

Commit

Permalink
Merge pull request #61 from marcosps/micro-support
Browse files Browse the repository at this point in the history
Add Micro support
  • Loading branch information
fgyanz authored Jan 17, 2025
2 parents cd7b2e1 + 7cb4518 commit 5e4b234
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 80 deletions.
85 changes: 55 additions & 30 deletions klpbuild/codestream.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

class Codestream:
__slots__ = ("data_path", "lp_path", "lp_name", "sle", "sp", "update", "rt",
"ktype", "needs_ibt", "project", "kernel", "archs", "files",
"modules", "repo")
"ktype", "needs_ibt", "is_micro", "project", "patchid", "kernel",
"archs", "files", "modules", "repo")

def __init__(self, data_path, lp_path, sle, sp, update, rt, project,
kernel, archs, files, modules):
patchid, kernel, archs, files, modules):
self.data_path = data_path
self.lp_path = lp_path
self.lp_name = PurePath(lp_path).name
Expand All @@ -23,41 +23,48 @@ def __init__(self, data_path, lp_path, sle, sp, update, rt, project,
self.update = update
self.rt = rt
self.ktype = "-rt" if rt else "-default"
self.needs_ibt = sle > 15 or (sle == 15 and sp >= 6)
self.is_micro = sle == 6
self.needs_ibt = self.is_micro or sle > 15 or (sle == 15 and sp >= 6)
self.project = project
self.patchid = patchid
self.kernel = kernel
self.archs = archs
self.files = files
self.modules = modules
self.repo = self.get_repo()


@classmethod
def from_codestream(cls, data_path, lp_path, cs, proj, kernel):
def from_codestream(cls, data_path, lp_path, cs, proj, patchid, kernel):
# Parse SLE15-SP2_Update_25 to 15.2u25
rt = "rt" if "-RT" in cs else ""
sp = "0"
u = "0"

sle, _, u = cs.replace("SLE", "").replace("-RT", "").split("_")
if "-SP" in sle:
sle, sp = sle.split("-SP")
else:
sp = "0"
# SLE12-SP5_Update_51
if "SLE" in cs:
sle, _, u = cs.replace("SLE", "").replace("-RT", "").split("_")
if "-SP" in sle:
sle, sp = sle.split("-SP")

# MICRO-6-0_Update_2
elif "MICRO" in cs:
sle, sp, u = cs.replace("MICRO-", "").replace("-RT", "").replace("_Update_", "-").split("-")

return cls(data_path, lp_path, int(sle), int(sp), int(u), rt, proj, kernel, [], {}, {})
return cls(data_path, lp_path, int(sle), int(sp), int(u), rt, proj, patchid, kernel, [], {}, {})


@classmethod
def from_cs(cls, cs):
match = re.search(r"(\d+)\.(\d+)(rt)?u(\d+)", cs)
return cls("", "", int(match.group(1)), int(match.group(2)),
int(match.group(4)), match.group(3), "", "", [], {}, {})
int(match.group(4)), match.group(3), "", "", "", [], {}, {})


@classmethod
def from_data(cls, data):
return cls(data["data_path"], data["lp_path"], data["sle"], data["sp"],
data["update"], data["rt"], data["project"], data["kernel"],
data["archs"], data["files"], data["modules"])
data["update"], data["rt"], data["project"], data["patchid"],
data["kernel"], data["archs"], data["files"], data["modules"])


def __eq__(self, cs):
Expand Down Expand Up @@ -86,14 +93,17 @@ def get_odir(self):
def get_ipa_file(self, fname):
return Path(self.get_odir(), f"{fname}.000i.ipa-clones")


def get_boot_file(self, file, arch=ARCH):
assert file in ["vmlinux", "config", "symvers"]
return Path(self.get_data_dir(arch), "boot", f"{file}-{self.kname()}")
assert file.startswith("vmlinux") or file.startswith("config") or file.startswith("symvers")
if self.is_micro:
return Path(self.get_mod_path(arch), file)

# Strip the suffix from the filename so we can add the kernel version in the middle
fname = f"{Path(file).stem}-{self.kname()}{Path(file).suffix}"
return Path(self.get_data_dir(arch), "boot", fname)

def get_repo(self):
if self.update == 0:
if self.update == 0 or self.is_micro:
return "standard"

repo = f"SUSE_SLE-{self.sle}"
Expand All @@ -108,10 +118,18 @@ def get_repo(self):

return f"{repo}_Products_SLERT_Update"

def set_archs(self):
# RT is supported only on x86_64 at the moment
if self.rt:
self.archs = ["x86_64"]

def set_archs(self, archs):
self.archs = archs
# MICRO 6.0 doest support ppc64le
elif "6.0" in self.name():
self.archs = ["x86_64", "s390x"]

# We support all architecture for all other codestreams
else:
self.archs = ["x86_64", "s390x", "ppc64le"]

def set_files(self, files):
self.files = files
Expand Down Expand Up @@ -146,13 +164,15 @@ def name_cs(self):
return f"{self.sle}.{self.sp}rt"
return f"{self.sle}.{self.sp}"


# Parse 15.2u25 to SLE15-SP2_Update_25
def name_full(self):
buf = f"SLE{self.sle}"

if int(self.sp) > 0:
buf = f"{buf}-SP{self.sp}"
# Parse 15.2u25 to SLE15-SP2_Update_25
# Parse 6.0u2 to MICRO
if self.is_micro:
buf = f"MICRO-{self.sle}-{self.sp}"
else:
buf = f"SLE{self.sle}"
if int(self.sp) > 0:
buf = f"{buf}-SP{self.sp}"

if self.rt:
buf = f"{buf}-RT"
Expand All @@ -163,12 +183,16 @@ def name_full(self):
# 15.4 onwards we don't have module_mutex, so template generates
# different code
def is_mod_mutex(self):
return self.sle < 15 or (self.sle == 15 and self.sp < 4)

return not self.is_micro and (self.sle < 15 or (self.sle == 15 and self.sp < 4))

def get_mod_path(self, arch):
return Path(self.get_data_dir(arch), "lib", "modules", f"{self.kname()}")
# Micro already has support for usrmerge
if self.is_micro:
mod_path = Path("usr", "lib")
else:
mod_path = Path("lib")

return Path(self.get_data_dir(arch), mod_path, "modules", f"{self.kname()}")

# A codestream can be patching multiple objects, so get the path related to
# the module that we are interested
Expand Down Expand Up @@ -325,6 +349,7 @@ def data(self):
"update" : self.update,
"rt" : self.rt,
"project" : self.project,
"patchid": self.patchid,
"kernel" : self.kernel,
"archs" : self.archs,
"files" : self.files,
Expand Down
2 changes: 1 addition & 1 deletion klpbuild/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def cmd_args(self, cs, fname, out_dir, fdata, cmd):
]:
cmd = cmd.replace(opt, "")

if cs.sle >= 15 and cs.sp >= 4:
if cs.is_micro or (cs.sle >= 15 and cs.sp >= 4):
cmd += " -D__has_attribute(x)=0"

ccp_args.extend(cmd.split(" "))
Expand Down
83 changes: 46 additions & 37 deletions klpbuild/ibs.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,32 +142,41 @@ def download_cs_data(self, cs_list):
rpms = []
i = 1

# The packages that we search for are:
# kernel-source
# kernel-devel
# kernel-(default|rt)
# kernel-(default|rt)-devel
# kernel-(default|rt)-livepatch-devel (for SLE15+)
# kernel-default-kgraft (for SLE12)
# kernel-default-kgraft-devel (for SLE12)
cs_data = {
"kernel-default": r"(kernel-(default|rt)\-((livepatch|kgraft)?\-?devel)?\-?[\d\.\-]+.(s390x|x86_64|ppc64le).rpm)",
"kernel-source": r"(kernel-(source|devel)(\-rt)?\-?[\d\.\-]+.noarch.rpm)",
}

dest = Path(self.data, "kernel-rpms")
dest.mkdir(exist_ok=True, parents=True)

logging.info("Getting list of files...")
for cs in cs_list:
prj = cs.project
repo = cs.repo

path_dest = Path(self.data, "kernel-rpms")
path_dest.mkdir(exist_ok=True, parents=True)

for arch in cs.archs:
for pkg, regex in cs_data.items():
if cs.is_micro:
# For MICRO, we use the patchid to find the list of binaries
pkg = cs.patchid

elif cs.rt:
# RT kernels have different package names
if cs.rt:
if pkg == "kernel-default":
pkg = "kernel-rt"
elif pkg == "kernel-source":
pkg = "kernel-source-rt"

if repo != "standard":
pkg = f"{pkg}.{repo}"
if cs.repo != "standard":
pkg = f"{pkg}.{cs.repo}"

ret = self.osc.build.get_binary_list(prj, repo, arch, pkg)
ret = self.osc.build.get_binary_list(cs.project, cs.repo, arch, pkg)
for file in re.findall(regex, str(etree.tostring(ret))):
# FIXME: adjust the regex to only deal with strings
if isinstance(file, str):
Expand All @@ -187,7 +196,7 @@ def download_cs_data(self, cs_list):
if arch != ARCH:
continue

rpms.append((i, cs, prj, repo, arch, pkg, rpm, path_dest))
rpms.append((i, cs, cs.project, cs.repo, arch, pkg, rpm, dest))
i += 1

logging.info(f"Downloading {len(rpms)} rpms...")
Expand All @@ -199,15 +208,15 @@ def download_cs_data(self, cs_list):
for cs in cs_list:
for arch in cs.archs:
# Extract modules and vmlinux files that are compressed
mod_path = Path(cs.get_data_dir(arch), "lib", "modules", cs.kname())
mod_path = cs.get_mod_path(arch)
for fext, ecmd in [("zst", "unzstd -f -d"), ("xz", "xz --quiet -d -k")]:
cmd = rf'find {mod_path} -name "*ko.{fext}" -exec {ecmd} --quiet {{}} \;'
cmd = rf'find {mod_path} -name "*.{fext}" -exec {ecmd} --quiet {{}} \;'
subprocess.check_output(cmd, shell=True)

# Extract gzipped files per arch
files = ["vmlinux", "symvers"]
for f in files:
f_path = Path(cs.get_data_dir(arch), "boot", f"{f}-{cs.kname()}.gz")
f_path = cs.get_boot_file(f"{f}.gz")
# ppc64le doesn't gzips vmlinux
if f_path.exists():
subprocess.check_output(rf'gzip -k -d -f {f_path}', shell=True)
Expand Down Expand Up @@ -260,28 +269,18 @@ def find_missing_symbols(self, cs, arch, lp_mod_path):
return missing_syms

def validate_livepatch_module(self, cs, arch, rpm_dir, rpm):
match = re.search(r"(livepatch)-.*(default|rt)\-(\d+)\-(\d+)\.(\d+)\.(\d+)\.", rpm)
if match:
dir_path = match.group(1)
ktype = match.group(2)
lp_file = f"livepatch-{match.group(3)}-{match.group(4)}_{match.group(5)}_{match.group(6)}.ko"
else:
ktype = "default"
match = re.search(r"(kgraft)\-patch\-.*default\-(\d+)\-(\d+)\.(\d+)\.", rpm)
if match:
dir_path = match.group(1)
lp_file = f"kgraft-patch-{match.group(2)}-{match.group(3)}_{match.group(4)}.ko"

fdest = Path(rpm_dir, rpm)
# Extract the livepatch module for later inspection
cmd = f"rpm2cpio {fdest} | cpio --quiet -uidm"
subprocess.check_output(cmd, shell=True, cwd=rpm_dir)
subprocess.check_output(f"rpm2cpio {fdest} | cpio --quiet -uidm",
shell=True, cwd=rpm_dir)

# There should be only one .ko file extracted
lp_mod_path = sorted(rpm_dir.glob("**/*.ko"))[0]
elffile = get_elf_object(lp_mod_path)

# Check depends field
# At this point we found that our livepatch module depends on
# exported functions from other modules. List the modules here.
lp_mod_path = Path(rpm_dir, "lib", "modules", f"{cs.kernel}-{ktype}", dir_path, lp_file)
elffile = get_elf_object(lp_mod_path)
deps = get_elf_modinfo_entry(elffile, "depends")
if len(deps):
logging.warning(f"{cs.name()}:{arch} has dependencies: {deps}.")
Expand Down Expand Up @@ -396,7 +395,7 @@ def download(self):

archs = result.xpath("repository/arch")
for arch in archs:
ret = self.osc.build.get_binary_list(prj, "devbuild", arch, "klp")
ret = self.osc.build.get_binary_list(prj, "standard", arch, "klp")
rpm_name = f"{arch}.rpm"
for rpm in ret.xpath("binary/@filename"):
if not rpm.endswith(rpm_name):
Expand All @@ -409,7 +408,7 @@ def download(self):
dest = Path(cs.dir(), str(arch), "rpm")
dest.mkdir(exist_ok=True, parents=True)

rpms.append((i, cs, prj, "devbuild", arch, "klp", rpm, dest))
rpms.append((i, cs, prj, "standard", arch, "klp", rpm, dest))
i += 1

logging.info(f"Downloading {len(rpms)} packages...")
Expand Down Expand Up @@ -450,7 +449,7 @@ def status(self, wait=False):
# codestreams built without issues
if not finished:
states = set(archs.values())
if len(states) == 1 and states.pop() == "succeeded":
if len(states) == 1 and states.pop() in ["succeeded", "excluded"]:
finished = True

if finished:
Expand Down Expand Up @@ -488,7 +487,7 @@ def create_prj_meta(self, cs):
"<project name=''><title></title><description></description>"
"<build><enable/></build><publish><disable/></publish>"
"<debuginfo><disable/></debuginfo>"
'<repository name="devbuild">'
'<repository name="standard">'
f"<path project=\"{cs.project}\" repository=\"{cs.repo}\"/>"
"</repository>"
"</project>"
Expand Down Expand Up @@ -526,7 +525,7 @@ def create_lp_package(self, i, cs):

except Exception as e:
logging.error(e, e.response.content)
raise RuntimeError("")
raise RuntimeError("") from e

# Remove previously created directories
prj_path = Path(cs.dir(), "checkout")
Expand All @@ -551,9 +550,19 @@ def create_lp_package(self, i, cs):
stderr=subprocess.STDOUT,
)

# Add remote with all codestreams, because the clone above will set the remote origin
# to the local directory, so it can't find the remote codestreams
subprocess.check_output(["/usr/bin/git", "remote", "add", "kgr",
"[email protected]:kernel/kgraft-patches.git"],
stderr=subprocess.STDOUT, cwd=code_path)

# Fetch all remote codestreams so we can rebase in the next step
subprocess.check_output(["/usr/bin/git", "fetch", "kgr", str(base_branch)],
stderr=subprocess.STDOUT, cwd=code_path)

# Get the new bsc commit on top of the codestream branch (should be the last commit on the specific branch)
subprocess.check_output(
["/usr/bin/git", "rebase", f"origin/{base_branch}"],
["/usr/bin/git", "rebase", f"kgr/{base_branch}"],
stderr=subprocess.STDOUT, cwd=code_path
)

Expand Down Expand Up @@ -587,7 +596,7 @@ def create_lp_package(self, i, cs):
logging.info(f"({i}/{self.total}) {cs.name()} done")

def log(self, cs, arch):
logging.info(self.osc.build.get_log(self.cs_to_project(cs), "devbuild", arch, "klp"))
logging.info(self.osc.build.get_log(self.cs_to_project(cs), "standard", arch, "klp"))

def push(self, wait=False):
cs_list = filter_cs(self.lp_filter, "", self.codestreams)
Expand Down
Loading

0 comments on commit 5e4b234

Please sign in to comment.