From 862d5a7de0acfe17cd01a87f2d1284438299065c Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 28 May 2025 11:26:43 +0800 Subject: [PATCH 1/9] upatch-diff: sync from 25.03 to 24.03-LTS-SP2 Signed-off-by: Jvle --- upatch-diff/create-diff-object.c | 28 +++++++++++++++++++++- upatch-diff/elf-common.c | 41 +++++++++++++++++++++++++++++--- upatch-diff/elf-common.h | 2 ++ upatch-diff/elf-correlate.c | 6 +++++ upatch-diff/elf-insn.c | 8 +++++++ upatch-diff/elf-insn.h | 5 ++++ upatch-diff/upatch-elf.c | 10 ++++++++ upatch-diff/upatch-elf.h | 1 + 8 files changed, 97 insertions(+), 4 deletions(-) diff --git a/upatch-diff/create-diff-object.c b/upatch-diff/create-diff-object.c index 9e4504e9..886d8b21 100644 --- a/upatch-diff/create-diff-object.c +++ b/upatch-diff/create-diff-object.c @@ -434,7 +434,8 @@ static void replace_section_syms(struct upatch_elf *uelf) !is_text_section(sym->sec) && (rela->type == R_X86_64_32S || rela->type == R_X86_64_32 || - rela->type == R_AARCH64_ABS64) && + rela->type == R_AARCH64_ABS64 || + rela->type == R_RISCV_64) && rela->addend == (long)sym->sec->sh.sh_size && end == (long)sym->sec->sh.sh_size) { ERROR("Relocation refer end of data sections."); @@ -590,6 +591,12 @@ static void include_symbol(struct symbol *sym) if ((sym->status != SAME) || (sym->type == STT_SECTION)) { include_section(sym->sec); } +#ifdef __riscv + /* .L symbols not exist in EXE. If they are included, so are their sections. */ + else if (sym->sec && !sym->sec->include && !strncmp(sym->name, ".L", 2)) { + include_section(sym->sec); + } +#endif } static void include_section(struct section *sec) @@ -749,6 +756,23 @@ static void verify_patchability(struct upatch_elf *uelf) } } +/* + * These types are for linker optimization and memory layout. + * They have no associated symbols and their names are empty + * string which would mismatch running-elf symbols in later + * lookup_relf(). Drop these useless items now. + */ +static void rv_drop_useless_rela(struct section *relasec) +{ + struct rela *rela, *saferela; + list_for_each_entry_safe(rela, saferela, &relasec->relas, list) + if (rela->type == R_RISCV_RELAX || rela->type == R_RISCV_ALIGN) { + list_del(&rela->list); + memset(rela, 0, sizeof(*rela)); + free(rela); + } +} + static void migrate_included_elements(struct upatch_elf *uelf_patched, struct upatch_elf *uelf_out) { @@ -776,6 +800,8 @@ static void migrate_included_elements(struct upatch_elf *uelf_patched, if (sec->sym && !sec->sym->include) { sec->sym = NULL; // break link to non-included section symbol } + } else if (uelf_patched->arch == RISCV64) { + rv_drop_useless_rela(sec); } } diff --git a/upatch-diff/elf-common.c b/upatch-diff/elf-common.c index c5d8f619..22d12ca9 100644 --- a/upatch-diff/elf-common.c +++ b/upatch-diff/elf-common.c @@ -26,6 +26,35 @@ #include "elf-common.h" +#ifdef __riscv +/* + * .L local symbols are named as ".L" + "class prefix" + "number". + * The numbers are volatile due to code change. + * Compare class prefix(composed of letters) only. + */ +static int mangled_strcmp_dot_L(char *str1, char *str2) +{ + if (!*str2 || strncmp(str2, ".L", 2)) { + return 1; + } + + /* RISCV_FAKE_LABEL_NAME matched exactly */ + if (!strcmp(str1, ".L0 ") || !strcmp(str2, ".L0 ")) { + return strcmp(str1, str2); + } + + char *p = str1 + 2; + char *q = str2 + 2; + while (*p < '0' || *p > '9') p++; + while (*q < '0' || *q > '9') q++; + if ((p - str1 != q - str2) || strncmp(str1, str2, (size_t)(p - str1))) { + return 1; + } + + return 0; +} +#endif + static bool is_dynamic_debug_symbol(struct symbol *sym) { static const char *SEC_NAMES[] = { @@ -122,6 +151,12 @@ int mangled_strcmp(char *str1, char *str2) return strcmp(str1, str2); } +#ifdef __riscv + if (!strncmp(str1, ".L", 2)) { + return mangled_strcmp_dot_L(str1, str2); + } +#endif + while (*str1 == *str2) { if (!*str2) { return 0; @@ -180,7 +215,6 @@ int offset_of_string(struct list_head *list, char *name) ALLOC_LINK(string, list); string->name = name; - return index; } @@ -192,16 +226,17 @@ bool is_gcc6_localentry_bundled_sym(struct upatch_elf *uelf) return false; case X86_64: return false; + case RISCV64: + return false; default: ERROR("unsupported arch"); } - return false; } bool is_mapping_symbol(struct upatch_elf *uelf, struct symbol *sym) { - if (uelf->arch != AARCH64) { + if ((uelf->arch != AARCH64) && (uelf->arch != RISCV64)) { return false; } if (sym->name && sym->name[0] == '$' && diff --git a/upatch-diff/elf-common.h b/upatch-diff/elf-common.h index 3cebd32c..f252cb95 100644 --- a/upatch-diff/elf-common.h +++ b/upatch-diff/elf-common.h @@ -278,6 +278,8 @@ static inline unsigned int absolute_rela_type(struct upatch_elf *uelf) return R_AARCH64_ABS64; case X86_64: return R_X86_64_64; + case RISCV64: + return R_RISCV_64; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-correlate.c b/upatch-diff/elf-correlate.c index dcf3cfa5..8e2f7659 100644 --- a/upatch-diff/elf-correlate.c +++ b/upatch-diff/elf-correlate.c @@ -83,6 +83,12 @@ void upatch_correlate_symbols(struct upatch_elf *uelf_source, continue; } + /* on RISC-V: .L symbols should not change section */ + if (uelf_source->arch == RISCV64 && !strncmp(sym_orig->name, ".L", 2) && + sym_orig->sec && sym_orig->sec->twin != sym_patched->sec) { + continue; + } + if (is_mapping_symbol(uelf_source, sym_orig)) { continue; } diff --git a/upatch-diff/elf-insn.c b/upatch-diff/elf-insn.c index 6fabd887..466a00c9 100644 --- a/upatch-diff/elf-insn.c +++ b/upatch-diff/elf-insn.c @@ -67,6 +67,8 @@ long rela_target_offset(struct upatch_elf *uelf, struct section *relasec, struct long add_off; switch (uelf->arch) { + case RISCV64: + /* fall through */ case AARCH64: add_off = 0; break; @@ -106,6 +108,12 @@ unsigned int insn_length(struct upatch_elf *uelf, void *addr) insn_init(&decoded_insn, addr, 1); insn_get_length(&decoded_insn); return decoded_insn.length; + case RISCV64: + /* LSB 2 bits distinguish insn size. Now only RV32, RVC supported. */ + if ((*(char *)addr & 0x3) == 0x3) { + return RISCV64_INSN_LEN_4; + } + return RISCV64_INSN_LEN_2; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-insn.h b/upatch-diff/elf-insn.h index ac48ad03..fdbc2e34 100644 --- a/upatch-diff/elf-insn.h +++ b/upatch-diff/elf-insn.h @@ -32,8 +32,13 @@ struct section; struct rela; struct insn; +// arm #define ARM64_INSTR_LEN 4 +// riscv +#define RISCV64_INSN_LEN_4 4 +#define RISCV64_INSN_LEN_2 2 + void rela_insn(const struct section *sec, const struct rela *rela, struct insn *insn); /* diff --git a/upatch-diff/upatch-elf.c b/upatch-diff/upatch-elf.c index c21b6ecc..331504bb 100644 --- a/upatch-diff/upatch-elf.c +++ b/upatch-diff/upatch-elf.c @@ -321,6 +321,16 @@ void uelf_open(struct upatch_elf *uelf, const char *name) case EM_X86_64: uelf->arch = X86_64; break; + case EM_RISCV: + /* + | Val | Macros | Description | + | 1 | ELFCLASS32 | riscv32 | + | 2 | ELFCLASS64 | riscv64 | + */ + if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + uelf->arch = RISCV64; + } + break; default: ERROR("Unsupported architecture"); } diff --git a/upatch-diff/upatch-elf.h b/upatch-diff/upatch-elf.h index 858cfa92..b34c1af6 100644 --- a/upatch-diff/upatch-elf.h +++ b/upatch-diff/upatch-elf.h @@ -41,6 +41,7 @@ struct symbol; enum architecture { X86_64 = 0x1 << 0, AARCH64 = 0x1 << 1, + RISCV64 = 0x1 << 2, }; enum data_source { -- Gitee From 34c1da2b5f42716dbbd222ca4dcb1416f91cc3a3 Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 28 May 2025 11:27:10 +0800 Subject: [PATCH 2/9] upatch-manage: sync from 25.03 to 24.03-LTS-SP2 Signed-off-by: Jvle --- upatch-manage/arch/riscv64/insn.h | 123 ++++++++++++ upatch-manage/arch/riscv64/process.h | 28 +++ upatch-manage/arch/riscv64/ptrace.c | 219 +++++++++++++++++++++ upatch-manage/arch/riscv64/relocation.c | 246 ++++++++++++++++++++++++ upatch-manage/arch/riscv64/resolve.c | 154 +++++++++++++++ upatch-manage/upatch-patch.c | 22 +++ upatch-manage/upatch-ptrace.c | 4 + upatch-manage/upatch-ptrace.h | 8 + 8 files changed, 804 insertions(+) create mode 100644 upatch-manage/arch/riscv64/insn.h create mode 100644 upatch-manage/arch/riscv64/process.h create mode 100644 upatch-manage/arch/riscv64/ptrace.c create mode 100644 upatch-manage/arch/riscv64/relocation.c create mode 100644 upatch-manage/arch/riscv64/resolve.c diff --git a/upatch-manage/arch/riscv64/insn.h b/upatch-manage/arch/riscv64/insn.h new file mode 100644 index 00000000..8ab4daf7 --- /dev/null +++ b/upatch-manage/arch/riscv64/insn.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 laokz + */ + +#ifndef _ARCH_RISCV64_INSN_H +#define _ARCH_RISCV64_INSN_H + +static inline unsigned set_utype_imm(unsigned ins, unsigned long imm) +{ + /* + imm[11] to counteract lo12 sign extension in next instruction */ + unsigned long temp_imm = imm; + temp_imm += (temp_imm & 0x800) << 1; + return (temp_imm & 0xfffff000) | (ins & 0xfff); +} + +static inline unsigned set_itype_imm(unsigned ins, unsigned long imm) +{ + return ((imm & 0xfff) << 20) | (ins & 0xfffff); +} + +static inline unsigned set_stype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[11-8,7-5] 1,1111, 1111,1 111, imm[4-1,0] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 11-----5 4---0 + 1111,111 1,1111 + imm mask fe0 1f + + ==>imm bit no. 31----25 11--7 + */ + return (ins & 0x1fff07f) | + ((imm & 0xfe0) << (31 - 11)) | ((imm & 0x1f) << (11 - 4)); +} + +static inline unsigned set_jtype_imm(unsigned ins, unsigned long imm) +{ + /* + imm bit no. 20 19------12 11 10---------1 + 1, 1111,1111, 1 111,1111,111 0 + mask 100000 ff000 800 7fe + + ==>imm bit no. 31 19------12 20 30---------21 + */ + return (ins & 0xfff) | + ((imm & 0x100000) << (31 - 20)) | (imm & 0xff000) | + ((imm & 0x800) << (20 - 11)) | ((imm & 0x7fe) << (30 - 10)); +} + +static inline unsigned set_btype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[12 10-8,7-5] 1,1111, 1111,1 111, imm[4-1,11] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 12 11 10----5 4--1 + 1, 1 111,111 1,111 0 + imm mask 1000 800 7e0 1e + + ==>imm bit no. 31 7 30---25 11-8 + */ + return (ins & 0x01fff07f) | + ((imm & 0x1000) << (31 - 12)) | ((imm & 0x800) >> (11 - 7)) | + ((imm & 0x7e0) << (30 - 10)) | ((imm & 0x1e) << (11 - 4)); +} + +static inline unsigned short set_cjtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm opcode + ins: 111 offset[11,4 9 8 10, 6 7 3 2, 1 5] 11 + ins mask e 0 0 3 + + imm bit no. 11 10 9-8 7 6 5 4 3-1 + 1 1 11, 1 1 1 1, 111 0 + imm mask 800 400 300 80 40 20 10 e + + ==>imm bit no. 12 8 10-9 6 7 2 11 5-3 + */ + return (ins & 0xe003) | + ((imm & 0x800) << (12 - 11)) | ((imm & 0x400) >> (10 - 8)) | + ((imm & 0x300) << (10 - 9)) | ((imm & 0x80) >> (7 - 6)) | + ((imm & 0x40) << (7 - 6)) | ((imm & 0x20) >> (5 - 2)) | + ((imm & 0x10) << (11 - 4)) | ((imm & 0xe) << (5 - 3)); +} + +/* only support C.LUI */ +static inline unsigned short set_citype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[17] rd imm[16:12] opcode + ins: 111 imm[17], 1111,1 imm[16-14,13-12] 11 + ins mask e f 8 3 + + imm bit no. 17 16--12 + 1 1,1111 + imm mask 20000 1f000 + + ==>imm bit no. 12 6---2 + */ + return (ins & 0xef83) | + ((imm & 0x20000) >> (17 - 12)) | ((imm & 0x1f000) >> (16 - 6)); +} + +/* only support C.BEQZ C.BNEZ */ +static inline unsigned short set_cbtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[8 4 3] rs imm[7 6 2 1 5] opcode + ins: 111 0,0 0 11,1 0 0 0,0 0 11 + ins mask e 3 8 3 + + imm bit no. 8 , 7 6 5 4,3 2 1 0 + imm mask 100 c0 20 18 6 + + ==>imm bit no. 12 6-5 2 11-10 4-3 + */ + return (ins & 0xe383) | + ((imm & 0x100) << (12 - 8)) | ((imm & 0xc0) >> (7 - 6)) | + ((imm & 0x20) >> (5 - 2)) | ((imm & 0x18) << (11 - 4)) | + ((imm & 0x6) << (4 - 2)); +} + +#endif diff --git a/upatch-manage/arch/riscv64/process.h b/upatch-manage/arch/riscv64/process.h new file mode 100644 index 00000000..91926fa2 --- /dev/null +++ b/upatch-manage/arch/riscv64/process.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __PROCESS__ +#define __PROCESS__ + +#ifndef MAX_DISTANCE +#define MAX_DISTANCE 0x80000000 +#endif + +#endif diff --git a/upatch-manage/arch/riscv64/ptrace.c b/upatch-manage/arch/riscv64/ptrace.c new file mode 100644 index 00000000..2ccdc9ef --- /dev/null +++ b/upatch-manage/arch/riscv64/ptrace.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#include "upatch-ptrace.h" + +int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) +{ + struct iovec regs_iov; + struct user_regs_struct regs; + + regs_iov.iov_base = ®s; + regs_iov.iov_len = sizeof(regs); + + if (ptrace(PTRACE_GETREGSET, pid, + (void *)NT_PRSTATUS, (void *)®s_iov) < 0) { + log_error("Cannot get regs from %d\n", pid); + return -1; + } + *sp = (unsigned long)regs.sp; + *pc = (unsigned long)regs.pc; + return 0; +} + +static long read_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_GETREGSET)"); + return -1; + } + return 0; +} + +static long write_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_SETREGSET)"); + return -1; + } + return 0; +} + +long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, + unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6, + unsigned long *res) +{ + struct user_regs_struct regs; + unsigned char syscall[] = { + 0x73, 0x00, 0x00, 0x00, // ecall + 0x73, 0x00, 0x10, 0x00, // ebreak + }; + long ret; + + log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); + regs.a7 = (unsigned long)nr; + regs.a0 = arg1; + regs.a1 = arg2; + regs.a2 = arg3; + regs.a3 = arg4; + regs.a4 = arg5; + regs.a5 = arg6; + + ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); + if (ret == 0) + *res = regs.a0; + + return ret; +} + +long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, + const unsigned char *code, size_t codelen, + struct user_regs_struct *pregs, + int (*func)(struct upatch_ptrace_ctx *pctx, + const void *data), + const void *data) +{ + struct user_regs_struct orig_regs, regs; + if (!codelen) { + log_error("Invalid codelen\n"); + return -1; + } + unsigned char *orig_code = (unsigned char *)malloc(sizeof(*orig_code) * codelen); + if (orig_code == NULL) { + log_error("Malloc orig_code failed\n"); + return -1; + } + long ret; + struct upatch_process *proc = pctx->proc; + unsigned long libc_base = proc->libc_base; + + ret = read_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + free(orig_code); + return -1; + } + ret = upatch_process_mem_read(proc, libc_base, + (unsigned long *)orig_code, codelen); + if (ret < 0) { + log_error("can't peek original code - %d\n", pctx->pid); + free(orig_code); + return -1; + } + ret = upatch_process_mem_write(proc, (const unsigned long *)code, libc_base, + codelen); + if (ret < 0) { + log_error("can't poke syscall code - %d\n", pctx->pid); + goto poke_back; + } + + regs = orig_regs; + regs.pc = libc_base; + + copy_regs(®s, pregs); + + ret = write_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = func(pctx, data); + if (ret < 0) { + log_error("failed call to func\n"); + goto poke_back; + } + + ret = read_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = write_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + goto poke_back; + } + + *pregs = regs; + +poke_back: + upatch_process_mem_write(proc, (unsigned long *)orig_code, libc_base, + codelen); + free(orig_code); + return ret; +} + +void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) +{ +#define COPY_REG(x) dst->x = src->x + COPY_REG(a0); + COPY_REG(a1); + COPY_REG(a2); + COPY_REG(a3); + COPY_REG(a4); + COPY_REG(a5); + COPY_REG(a6); + COPY_REG(a7); +#undef COPY_REG +} + +#define UPATCH_INSN_LEN 8 +#define UPATCH_ADDR_LEN 8 +#define ORIGIN_INSN_LEN (UPATCH_INSN_LEN + UPATCH_ADDR_LEN) + +size_t get_origin_insn_len() +{ + return ORIGIN_INSN_LEN; +} + +size_t get_upatch_insn_len() +{ + return UPATCH_INSN_LEN; +} + +size_t get_upatch_addr_len() +{ + return UPATCH_ADDR_LEN; +} + +/* + * On RISC-V, there must be 3 instructors(12 bytes) to jump to + * arbitrary address. The core upatch-manage limit jump instructor + * to one long(8 bytes), for us is +-2G range. + */ +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr) +{ + unsigned long offset; + unsigned int insn0, insn4; + + offset = new_addr - old_addr; + offset += (offset & 0x800) << 1; + insn0 = 0xf97 | (offset & 0xfffff000); // auipc t6, off[20] + insn4 = 0xf8067 | ((offset & 0xfff) << 20); // jalr zero, off[12](t6) + return (unsigned long)(insn0 | ((unsigned long)insn4 << 32)); +} diff --git a/upatch-manage/arch/riscv64/relocation.c b/upatch-manage/arch/riscv64/relocation.c new file mode 100644 index 00000000..ba825992 --- /dev/null +++ b/upatch-manage/arch/riscv64/relocation.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "insn.h" +#include "upatch-relocation.h" +#include "upatch-resolve.h" + +/* + * In PCREL_LO12 relocation entity, its corresponding symbol's value + * points to the ..._HI20 instruction, where the LO12 part of the + * immediate is part of the ..._HI20 symbol value. + */ +static unsigned long find_pcrel_hi_value(GElf_Rela *r, int idx, GElf_Sym *st, unsigned long v) +{ + int i = idx; + r--; + for (; i > 0; i--, r--) { + if ((r->r_offset == v) && + ((GELF_R_TYPE(r->r_info) == R_RISCV_PCREL_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GOT_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GD_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_GOT_HI20))) + return st[GELF_R_SYM(r->r_info)].st_value; + } + + /* should never happen */ + log_error("Not found no. %d rela's corresponding HI20\n", idx); + return 0; +} + +/* + * The patch is a .o file, has only static relocations, all symbols + * have been resolved with our jump table act as got/plt. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, + unsigned int relsec) +{ + unsigned int i; + GElf_Sym *sym, *symtab; + char const *sym_name; + unsigned long uloc_sec; + void *loc; + void *uloc; + u64 val; + GElf_Shdr *shdrs = (void *)uelf->info.shdrs; + GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; + + symtab = (GElf_Sym *)shdrs[symindex].sh_addr; + for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { + /* loc corresponds to P in the kernel space */ + loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + + /* uloc corresponds P in user space */ + uloc_sec = shdrs[shdrs[relsec].sh_info].sh_addralign; + uloc = (void *)uloc_sec + rel[i].r_offset; + + /* sym is the ELF symbol we're referring to */ + sym = symtab + GELF_R_SYM(rel[i].r_info); + if (GELF_ST_TYPE(sym->st_info) == STT_SECTION && + sym->st_shndx < uelf->info.hdr->e_shnum) + sym_name = uelf->info.shstrtab + + shdrs[sym->st_shndx].sh_name; + else + sym_name = uelf->strtab + sym->st_name; + + /* val corresponds to (S + A) */ + val = (s64)(sym->st_value + rel[i].r_addend); + log_debug( + "upatch: reloc symbol, name=%s, k_addr=0x%lx, u_addr=0x%lx, " + "r_offset=0x%lx, st_value=0x%lx, r_addend=0x%lx \n", + sym_name, shdrs[shdrs[relsec].sh_info].sh_addr, + uloc_sec, rel[i].r_offset, sym->st_value, rel[i].r_addend); + + /* Perform the static relocation. */ + switch (GELF_R_TYPE(rel[i].r_info)) { + case R_RISCV_NONE: + case R_RISCV_TPREL_ADD: + break; + + case R_RISCV_64: + *(unsigned long *)loc = val; + break; + + /* seems no need to recalculate as it should confined in the same func */ + case R_RISCV_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 4096 || (signed)val < -4096) + goto overflow; + *(unsigned *)loc = set_btype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_JAL: + val -= (unsigned long)uloc; + if ((signed)val >= (1 << 20) || (signed)val < -(1 << 20)) + goto overflow; + *(unsigned *)loc = set_jtype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_CALL: + case R_RISCV_CALL_PLT: + // in our jump table, must not overflow + val -= (unsigned long)uloc; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + *(unsigned *)(loc + 4) = set_itype_imm(*(unsigned *)(loc + 4), val); + break; + + case R_RISCV_GOT_HI20: + case R_RISCV_TLS_GOT_HI20: + case R_RISCV_TLS_GD_HI20: + case R_RISCV_PCREL_HI20: + val -= (unsigned long)uloc; // fall through + case R_RISCV_HI20: + case R_RISCV_TPREL_HI20: + if ((long)val != (long)(int)val) + goto overflow; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_I: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_I: + case R_RISCV_TPREL_LO12_I: + *(unsigned *)loc = set_itype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_S: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_S: + case R_RISCV_TPREL_LO12_S: + *(unsigned *)loc = set_stype_imm(*(unsigned *)loc, val); + break; + + /* inner function label calculation, must not overflow */ + case R_RISCV_ADD8: + *(char *)loc += val; + break; + case R_RISCV_ADD16: + *(short *)loc += val; + break; + case R_RISCV_ADD32: + *(int *)loc += val; + break; + case R_RISCV_ADD64: + *(long *)loc += val; + break; + + case R_RISCV_SUB8: + *(char *)loc -= val; + break; + case R_RISCV_SUB16: + *(short *)loc -= val; + break; + case R_RISCV_SUB32: + *(int *)loc -= val; + break; + case R_RISCV_SUB64: + *(long *)loc -= val; + break; + + case R_RISCV_RVC_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 256 || (signed)val < -256) + goto overflow; + *(unsigned short *)loc = set_cbtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_JUMP: + val -= (unsigned long)uloc; + if ((signed)val >= 2048 || (signed)val < -2048) + goto overflow; + *(unsigned short *)loc = set_cjtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_LUI: + if ((signed)val >= (1 << 17) || (signed)val < -(1 << 17) || (val & 0x3f000) == 0) + goto overflow; + *(unsigned short *)loc = set_citype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_SET8: + *(char *)loc = val; + break; + case R_RISCV_SET16: + *(short *)loc = val; + break; + case R_RISCV_32_PCREL: + case R_RISCV_PLT32: + val -= (unsigned long)uloc; // fall through + case R_RISCV_32: + case R_RISCV_SET32: + if ((long)val != (long)(int)val) + goto overflow; + *(int *)loc = val; + break; + + case R_RISCV_SUB6: + char w6 = (*(char *)loc - (char)val) & 0x3f; + *(char *)loc = (*(char *)loc & 0xc0) | w6; + break; + case R_RISCV_SET6: + *(char *)loc = (*(char *)loc & 0xc0) | (val & 0x3f); + break; + + default: + log_error("upatch: unsupported RELA relocation: %lu\n", + GELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + return 0; + +overflow: + log_error("upatch: overflow in relocation type %d val %lx\n", + (int)GELF_R_TYPE(rel[i].r_info), val); + return -ENOEXEC; +} +#pragma GCC diagnostic pop diff --git a/upatch-manage/arch/riscv64/resolve.c b/upatch-manage/arch/riscv64/resolve.c new file mode 100644 index 00000000..0b4aa8ba --- /dev/null +++ b/upatch-manage/arch/riscv64/resolve.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include "log.h" +#include "upatch-ptrace.h" +#include "upatch-resolve.h" + +/* + * auipc t6,0x0 + * ld t6,16(t6) # addr + * jr t6 + * undefined + */ +#define RISCV64_JMP_TABLE_JUMP0 0x010fbf8300000f97 +#define RISCV64_JMP_TABLE_JUMP1 0x000f8067 + +struct upatch_jmp_table_entry { + unsigned long inst[2]; + unsigned long addr[2]; +}; + +unsigned int get_jmp_table_entry() +{ + return sizeof(struct upatch_jmp_table_entry); +} + +static unsigned long setup_jmp_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long origin_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + if (index >= uelf->jmp_max_entry) { + log_error("jmp table overflow\n"); + return 0; + } + + table[index].inst[0] = RISCV64_JMP_TABLE_JUMP0; + table[index].inst[1] = RISCV64_JMP_TABLE_JUMP1; + table[index].addr[0] = jmp_addr; + table[index].addr[1] = origin_addr; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +unsigned long setup_got_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long tls_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + + if (index >= uelf->jmp_max_entry) { + log_error("got table overflow\n"); + return 0; + } + + table[index].inst[0] = jmp_addr; + table[index].inst[1] = tls_addr; + table[index].addr[0] = 0xffffffff; + table[index].addr[1] = 0xffffffff; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type __attribute__((unused)), unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_jmp_table(uelf, jmp_addr, (unsigned long)addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} + +unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type, unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + /* + * Addr with this type means the symbol is a dynamic TLS variable. + * Addr points to a GOT entry(16 bytes) having type + * + * typedef struct { + * unsigned long int ti_module; + * unsigned long int ti_offset; + * } tls_index; + * + * We also need copy ti_offset to our jump table. + * + * The corresponding symbol will associate with TLS_GD_HI20 + * relocation type, using this tls_index as argument to call + * `void *__tls_get_addr (tls_index *ti)` to resolve the real address. + */ + if (r_type == R_RISCV_TLS_DTPMOD64 && + upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), + &tls_addr, sizeof(tls_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} diff --git a/upatch-manage/upatch-patch.c b/upatch-manage/upatch-patch.c index 4b1b1be9..fd2c5f22 100644 --- a/upatch-manage/upatch-patch.c +++ b/upatch-manage/upatch-patch.c @@ -475,6 +475,23 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, upatch_func->addr = upatch_funcs_addr[i].addr; upatch_func->addr.old_addr += uelf->relf->load_bias; + +#ifdef __riscv +#define RISCV_MAX_JUMP_RANGE (1L << 31) + /* + * On RISC-V, to jump to arbitrary address, there must be + * at least 12 bytes to hold 3 instructors. Struct upatch_info + * new_insn field is only 8 bytes. We can only jump into + * +-2G ranges. Here do the check. + */ + long riscv_offset = (long)(upatch_func->addr.new_addr - upatch_func->addr.old_addr); + if (riscv_offset >= RISCV_MAX_JUMP_RANGE || riscv_offset < -RISCV_MAX_JUMP_RANGE) { + log_error("new_addr=%lx old_addr=%lx exceed +-2G range\n", + upatch_func->addr.new_addr, upatch_func->addr.old_addr); + goto out; + } +#endif + ret = upatch_process_mem_read(obj->proc, upatch_func->addr.old_addr, &upatch_func->old_insn, get_origin_insn_len()); if (ret) { @@ -483,7 +500,12 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, goto out; } +#ifdef __riscv + upatch_func->new_insn = get_new_insn( + upatch_func->addr.old_addr, upatch_func->addr.new_addr); +#else upatch_func->new_insn = get_new_insn(); +#endif log_debug("\taddr: 0x%lx -> 0x%lx, insn: 0x%lx -> 0x%lx, name: '%s'\n", upatch_func->addr.old_addr, upatch_func->addr.new_addr, upatch_func->old_insn[0], upatch_func->new_insn, diff --git a/upatch-manage/upatch-ptrace.c b/upatch-manage/upatch-ptrace.c index ab21891d..4ebe0380 100644 --- a/upatch-manage/upatch-ptrace.c +++ b/upatch-manage/upatch-ptrace.c @@ -25,6 +25,10 @@ #include #include +#ifdef __riscv +/* user_regs_struct defined here */ +#include +#endif #include #include diff --git a/upatch-manage/upatch-ptrace.h b/upatch-manage/upatch-ptrace.h index 8f36565d..f2503cfd 100644 --- a/upatch-manage/upatch-ptrace.h +++ b/upatch-manage/upatch-ptrace.h @@ -22,6 +22,9 @@ #define __UPATCH_PTRACE__ #include +#ifdef __riscv +#include +#endif #include "upatch-process.h" #include "list.h" @@ -79,6 +82,11 @@ long upatch_execute_remote(struct upatch_ptrace_ctx *, size_t get_origin_insn_len(void); size_t get_upatch_insn_len(void); size_t get_upatch_addr_len(void); + +#ifdef __riscv +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr); +#else unsigned long get_new_insn(void); +#endif #endif -- Gitee From b19aea7c92e1196a5a20226dfd92a41cb3654dac Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 23 Apr 2025 17:08:09 +0800 Subject: [PATCH 3/9] CMake: fix -Werror=cast-align for riscv64 Signed-off-by: Jvle --- CMakeLists.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8b3051..95a917f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,11 +62,23 @@ list(APPEND PROJECT_C_BUILD_FLAGS -DBUILD_VERSION="${PROJECT_BUILD_VERSION}" -D_FORTIFY_SOURCE=2 -Wtrampolines -Wformat=2 -Wstrict-prototypes -Wdate-time -Wstack-usage=8192 -Wfloat-equal -Wswitch-default - -Wshadow -Wconversion -Wcast-qual -Wcast-align -Wunused -Wundef + -Wshadow -Wconversion -Wcast-qual -Wunused -Wundef -funsigned-char -fstack-protector-all -fpic -fpie -ftrapv -fstack-check -freg-struct-return -fno-canonical-system-headers -fno-common -pipe -fdebug-prefix-map=old=new ) + +# The -Werror=cast-align compiler flag causes issues on riscv64 GCC, +# while the same operations do not error on aarch64. This appears to be +# a compiler-specific problem. Temporarily disable this option as a +# workaround since applying fixes would require intrusive code changes +# across multiple files. +if(NOT ARCH STREQUAL "riscv64") + list(APPEND PROJECT_C_BUILD_FLAGS + -Wcast-align + ) +endif() + list(APPEND PROJECT_RUST_FLAGS --cfg unsound_local_offset -D warnings -- Gitee From fa2a0fda9441b7e467e8a17ab8dad0b193a910a7 Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 12 Jun 2025 18:45:00 +0800 Subject: [PATCH 4/9] upatch-build: fix compile failure for lower rust version Signed-off-by: renoseven --- upatch-build/src/dwarf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upatch-build/src/dwarf.rs b/upatch-build/src/dwarf.rs index 4934a7a4..fe0821c6 100644 --- a/upatch-build/src/dwarf.rs +++ b/upatch-build/src/dwarf.rs @@ -112,7 +112,7 @@ impl ProducerParser { let section_name = section_id.name(); let section = match file.section_by_name(section_name) { Some(section) => section, - None => return Ok(EndianRcSlice::new(Rc::default(), endian)), + None => return Ok(EndianRcSlice::new(Rc::new([]), endian)), }; let mut section_data = section -- Gitee From b53cc2fdefd9a32059db5db7bab00e60fef5bb28 Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 29 May 2025 18:12:32 +0800 Subject: [PATCH 5/9] common: update component 1. rewrite common::os 2. rewrite common::process 3. rewrite common::util::digest 4. remove common::io 5. move fs::fs_impl::xattr to fs::xattr 6. impl fs::flock::flock_exists() 7. prevent file creation in fs::mmap() 8. support kernel module management Signed-off-by: renoseven --- syscare-common/Cargo.toml | 3 +- syscare-common/src/fs/flock.rs | 130 ++++-- syscare-common/src/fs/fs_impl.rs | 97 +---- syscare-common/src/fs/glob.rs | 4 +- syscare-common/src/fs/mmap.rs | 87 ++-- syscare-common/src/fs/mod.rs | 2 + syscare-common/src/fs/xattr.rs | 97 +++++ syscare-common/src/io/mod.rs | 19 - syscare-common/src/io/os_lines.rs | 78 ---- syscare-common/src/io/select.rs | 77 ---- syscare-common/src/lib.rs | 1 - syscare-common/src/os/cpu.rs | 46 +-- syscare-common/src/os/kernel.rs | 161 +++++++- syscare-common/src/os/mod.rs | 2 - syscare-common/src/os/platform.rs | 82 ++-- syscare-common/src/os/proc_maps.rs | 90 ---- syscare-common/src/os/proc_mounts.rs | 160 -------- syscare-common/src/os/process.rs | 68 +--- syscare-common/src/os/selinux.rs | 331 +++++++++++---- syscare-common/src/os/umask.rs | 80 ++-- syscare-common/src/os/user.rs | 122 +++--- syscare-common/src/process/args.rs | 55 --- syscare-common/src/process/child.rs | 68 ++-- syscare-common/src/process/command.rs | 133 ------ syscare-common/src/process/envs.rs | 68 ---- syscare-common/src/process/mod.rs | 563 +++++++++++++++++++------- syscare-common/src/process/output.rs | 179 ++++++++ syscare-common/src/process/stdio.rs | 179 -------- syscare-common/src/util/digest.rs | 216 +++++++++- 29 files changed, 1702 insertions(+), 1496 deletions(-) create mode 100644 syscare-common/src/fs/xattr.rs delete mode 100644 syscare-common/src/io/mod.rs delete mode 100644 syscare-common/src/io/os_lines.rs delete mode 100644 syscare-common/src/io/select.rs delete mode 100644 syscare-common/src/os/proc_maps.rs delete mode 100644 syscare-common/src/os/proc_mounts.rs delete mode 100644 syscare-common/src/process/args.rs delete mode 100644 syscare-common/src/process/command.rs delete mode 100644 syscare-common/src/process/envs.rs create mode 100644 syscare-common/src/process/output.rs delete mode 100644 syscare-common/src/process/stdio.rs diff --git a/syscare-common/Cargo.toml b/syscare-common/Cargo.toml index c559631d..f0b6446c 100644 --- a/syscare-common/Cargo.toml +++ b/syscare-common/Cargo.toml @@ -10,10 +10,11 @@ build = "build.rs" [dependencies] anyhow = { version = "1.0" } -lazy_static = { version = "1.4" } log = { version = "0.4" } memmap2 = { version = "0.9" } nix = { version = "0.26" } +num_cpus = { version = "1.14" } +object = { version = "0.29" } regex = { version = "1.7" } sha2 = { version = "0.10" } serde = { version = "1.0", features = ["derive"] } diff --git a/syscare-common/src/fs/flock.rs b/syscare-common/src/fs/flock.rs index a1ca6ca7..931a9c75 100644 --- a/syscare-common/src/fs/flock.rs +++ b/syscare-common/src/fs/flock.rs @@ -16,7 +16,7 @@ use std::{ fs::File, io::Result, ops::{Deref, DerefMut}, - os::unix::io::AsRawFd, + os::{fd::RawFd, unix::io::AsRawFd}, path::Path, }; @@ -36,23 +36,15 @@ pub struct FileLock { } impl FileLock { - fn new>(file_path: P, kind: FileLockType) -> Result { - let file_path = file_path.as_ref(); - let flock = Self { - file: if file_path.exists() { - File::open(file_path)? - } else { - File::create(file_path)? - }, - }; - flock.acquire(kind)?; + fn new(file_path: &Path, kind: FileLockType) -> Result { + let file = File::open(file_path)?; + Self::acquire_flock(file.as_raw_fd(), kind)?; - Ok(flock) + Ok(Self { file }) } #[inline] - fn acquire(&self, kind: FileLockType) -> Result<()> { - let fd = self.file.as_raw_fd(); + fn acquire_flock(fd: RawFd, kind: FileLockType) -> Result<()> { let arg = match kind { FileLockType::Shared => fcntl::FlockArg::LockShared, FileLockType::Exclusive => fcntl::FlockArg::LockExclusive, @@ -65,10 +57,8 @@ impl FileLock { } #[inline] - fn release(&self) { - let fd = self.file.as_raw_fd(); - let arg = fcntl::FlockArg::Unlock; - fcntl::flock(fd, arg).expect("Failed to release file lock"); + fn release_flock(fd: RawFd) { + fcntl::flock(fd, fcntl::FlockArg::Unlock).expect("Failed to release file lock"); } } @@ -88,48 +78,110 @@ impl DerefMut for FileLock { impl Drop for FileLock { fn drop(&mut self) { - self.release(); + Self::release_flock(self.file.as_raw_fd()); } } +pub fn flock_exists>(file_path: P, kind: FileLockType) -> Result { + FileLock::new(file_path.as_ref(), kind) +} + pub fn flock>(file_path: P, kind: FileLockType) -> Result { - FileLock::new(file_path, kind) + let file_path = file_path.as_ref(); + if !file_path.exists() { + File::create(file_path)?; + } + self::flock_exists(file_path, kind) } #[test] fn test() -> anyhow::Result<()> { - use anyhow::{ensure, Context}; - + use anyhow::{anyhow, ensure}; use std::fs; let file_path = std::env::temp_dir().join("flock_test"); - fs::remove_file(&file_path)?; - - println!("Testing shared flock on {}...", file_path.display()); - let shared_lock = self::flock(&file_path, FileLockType::SharedNonBlock) - .context("Failed to create shared flock")?; - let shared_lock1 = self::flock(&file_path, FileLockType::SharedNonBlock) - .context("Failed to create shared flock")?; + let non_exist_file = std::env::temp_dir().join("flock_test_non_exist"); + + fs::remove_file(&file_path).ok(); + fs::remove_file(&non_exist_file).ok(); + + fs::write(&file_path, "flock_test")?; + + println!("Testing fs::flock_exists()..."); + println!("- Shared flock '{}'...", file_path.display()); + let shared_lock = + self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; + let shared_lock1 = + self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; ensure!( - self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), - "Exclusive flock should be failed" + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + file_path.display() ); drop(shared_lock); drop(shared_lock1); - println!("Testing exclusive flock on {}...", file_path.display()); - let exclusive_lock = self::flock(&file_path, FileLockType::ExclusiveNonBlock) - .context("Failed to create exclusive flock")?; + println!("- Exclusive flock '{}'...", file_path.display()); + let exclusive_lock = + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).map_err(|e| { + anyhow!( + "Failed to create exclusive flock '{}', {}", + file_path.display(), + e + ) + })?; ensure!( - self::flock(&file_path, FileLockType::SharedNonBlock).is_err(), - "Shared flock should be failed" + self::flock_exists(&file_path, FileLockType::SharedNonBlock).is_err(), + "Shared flock '{}' should be failed", + file_path.display() ); ensure!( - self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), - "Exclusive flock should be failed" + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + file_path.display() ); - drop(exclusive_lock); + println!("- Non-exist flock '{}'...", non_exist_file.display()); + ensure!( + self::flock_exists(&non_exist_file, FileLockType::SharedNonBlock).is_err(), + "Shared flock '{}' should be failed", + non_exist_file.display() + ); + ensure!( + self::flock_exists(&non_exist_file, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + non_exist_file.display() + ); + + println!("Testing fs::flock()..."); + println!("- Non-exist flock '{}'...", non_exist_file.display()); + let _ = self::flock(&non_exist_file, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; + let _ = self::flock(&non_exist_file, FileLockType::ExclusiveNonBlock).map_err(|e| { + anyhow!( + "Failed to create exclusive flock '{}', {}", + file_path.display(), + e + ) + })?; + Ok(()) } diff --git a/syscare-common/src/fs/fs_impl.rs b/syscare-common/src/fs/fs_impl.rs index f5393f8d..a0488041 100644 --- a/syscare-common/src/fs/fs_impl.rs +++ b/syscare-common/src/fs/fs_impl.rs @@ -14,15 +14,14 @@ use std::{ env, - ffi::{CStr, OsStr, OsString}, + ffi::{OsStr, OsString}, fs::{File, FileType, Metadata, Permissions, ReadDir}, io, - os::{raw::c_void, unix::fs::PermissionsExt}, + os::unix::fs::PermissionsExt, path::{Component, Path, PathBuf}, - ptr::null_mut, }; -use crate::ffi::{CStrExt, OsStrExt}; +use crate::ffi::OsStrExt; trait RewriteError { fn rewrite_err(self, err_msg: String) -> Self; @@ -196,96 +195,6 @@ pub fn file_ext>(path: P) -> OsString { .unwrap_or_default() } -pub fn getxattr(path: P, name: S) -> std::io::Result -where - P: AsRef, - S: AsRef, -{ - let file_path = path.as_ref().to_cstring()?; - let xattr_name = name.as_ref().to_cstring()?; - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - let mut ret = - unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), null_mut(), 0) }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot get path {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy() - )); - } - - let mut buf = vec![0; ret.unsigned_abs()]; - let value_ptr = buf.as_mut_ptr().cast::(); - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - ret = unsafe { - nix::libc::getxattr( - file_path.as_ptr(), - xattr_name.as_ptr(), - value_ptr, - buf.len(), - ) - }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot get path {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy(), - )); - } - - let value = CStr::from_bytes_with_nul(&buf[0..ret.unsigned_abs()]) - .unwrap_or_default() - .to_os_string(); - - Ok(value) -} - -pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> -where - P: AsRef, - S: AsRef, - T: AsRef, -{ - let file_path = path.as_ref().to_cstring()?; - let xattr_name = name.as_ref().to_cstring()?; - let xattr_value = value.as_ref().to_cstring()?; - let size = xattr_value.to_bytes_with_nul().len(); - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - let ret = unsafe { - nix::libc::setxattr( - file_path.as_ptr(), - xattr_name.as_ptr(), - xattr_value.as_ptr().cast::(), - size, - 0, - ) - }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot set {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy() - )); - } - - Ok(()) -} - pub fn normalize>(path: P) -> io::Result { let mut new_path = PathBuf::new(); diff --git a/syscare-common/src/fs/glob.rs b/syscare-common/src/fs/glob.rs index 8165343b..9c115f42 100644 --- a/syscare-common/src/fs/glob.rs +++ b/syscare-common/src/fs/glob.rs @@ -69,7 +69,7 @@ impl Glob { } fn match_component(&mut self, path: PathBuf, index: usize) -> Result> { - let last_index = self.components.len() - 1; + let last_index = self.components.len().saturating_sub(1); for dir_entry in fs::read_dir(&path)? { let next_path = dir_entry?.path(); @@ -126,7 +126,7 @@ impl Iterator for Glob { fn next(&mut self) -> Option { while let Some((mut curr_path, mut index)) = self.stack.pop() { - let last_index = self.components.len() - 1; + let last_index = self.components.len().saturating_sub(1); // iterate all of components over matching path while index <= last_index { diff --git a/syscare-common/src/fs/mmap.rs b/syscare-common/src/fs/mmap.rs index b65f170b..07c9a17d 100644 --- a/syscare-common/src/fs/mmap.rs +++ b/syscare-common/src/fs/mmap.rs @@ -12,36 +12,18 @@ * See the Mulan PSL v2 for more details. */ -use std::{io::Result, ops::Deref, os::fd::AsRawFd, path::Path}; +use std::{ops::Deref, os::unix::io::AsRawFd, path::Path}; use memmap2::{Advice, Mmap, MmapOptions}; -use super::flock::*; +use super::flock; #[derive(Debug)] pub struct FileMmap { - _lock: FileLock, + _lock: flock::FileLock, mmap: Mmap, } -impl FileMmap { - fn new>(file_path: P) -> Result { - /* - * SAFETY: - * All file-backed memory map constructors are marked unsafe because of the - * potential for Undefined Behavior (UB) using the map if the underlying file - * is subsequently modified, in or out of process. - * Our implementation uses shared file lock to avoid cross-process file access. - * This mapped area would be safe. - */ - let lock = flock(file_path, FileLockType::Shared)?; - let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; - mmap.advise(Advice::Random)?; - - Ok(Self { _lock: lock, mmap }) - } -} - impl Deref for FileMmap { type Target = [u8]; @@ -51,27 +33,58 @@ impl Deref for FileMmap { } pub fn mmap>(file_path: P) -> std::io::Result { - FileMmap::new(file_path) + /* + * SAFETY: + * All file-backed memory map constructors are marked unsafe because of the + * potential for Undefined Behavior (UB) using the map if the underlying file + * is subsequently modified, in or out of process. + * Our implementation uses shared file lock to avoid cross-process file access. + * This mapped area would be safe. + */ + let lock = flock::flock_exists(file_path, flock::FileLockType::Shared)?; + let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; + mmap.advise(Advice::Random)?; + + Ok(FileMmap { _lock: lock, mmap }) } -#[test] -fn test() -> anyhow::Result<()> { - use anyhow::Context; +#[cfg(test)] +mod test { + use std::fs; + + use super::*; - const FILE_PATH: &str = "/etc/os-release"; - const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; - const PROC_FS_PATH: &str = "/proc/version"; + #[test] + fn mmap_file() -> std::io::Result<()> { + let file_path = std::env::temp_dir().join("mmap_test"); + fs::remove_file(&file_path).ok(); + fs::write(&file_path, "mmap_test")?; - println!("Testing FileMmap..."); - let orig_file = - std::fs::read(FILE_PATH).with_context(|| format!("Failed to open file {}", FILE_PATH))?; - let map_file = - self::mmap(FILE_PATH).with_context(|| format!("Failed to mmap file {}", FILE_PATH))?; + let orig_file = fs::read(&file_path)?; + let map_file = self::mmap(&file_path)?; + assert_eq!(orig_file, map_file.as_ref()); - let _ = self::mmap(SYS_FS_PATH).expect_err("Sysfs cannot not be mapped"); - let _ = self::mmap(PROC_FS_PATH).expect_err("Procfs cannot not be mapped"); + fs::remove_file(&file_path)?; + Ok(()) + } - assert_eq!(orig_file, map_file.as_ref()); + #[test] + fn mmap_non_exists_file() { + const NON_EXISTS_FILE: &str = "/non_exists_file"; + fs::remove_file(NON_EXISTS_FILE).ok(); - Ok(()) + assert!(self::mmap(NON_EXISTS_FILE).is_err(),); + } + + #[test] + fn mmap_proc_file() { + const PROC_FS_PATH: &str = "/proc/version"; + assert!(self::mmap(PROC_FS_PATH).is_err(),); + } + + #[test] + fn mmap_sys_file() { + const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; + assert!(self::mmap(SYS_FS_PATH).is_err(),); + } } diff --git a/syscare-common/src/fs/mod.rs b/syscare-common/src/fs/mod.rs index 35269150..b6d2d049 100644 --- a/syscare-common/src/fs/mod.rs +++ b/syscare-common/src/fs/mod.rs @@ -16,8 +16,10 @@ mod flock; mod fs_impl; mod glob; mod mmap; +mod xattr; pub use flock::*; pub use fs_impl::*; pub use glob::*; pub use mmap::*; +pub use xattr::*; diff --git a/syscare-common/src/fs/xattr.rs b/syscare-common/src/fs/xattr.rs new file mode 100644 index 00000000..237c70b9 --- /dev/null +++ b/syscare-common/src/fs/xattr.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + ffi::{c_void, CString, OsStr, OsString}, + os::unix::ffi::{OsStrExt, OsStringExt}, + path::Path, + ptr, +}; + +pub fn getxattr(path: P, name: S) -> std::io::Result +where + P: AsRef, + S: AsRef, +{ + let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; + let xattr_name = CString::new(name.as_ref().as_bytes())?; + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let buf_size = + unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), ptr::null_mut(), 0) }; + if buf_size == -1 { + return Err(std::io::Error::last_os_error()); + } + + let mut buf = vec![0; buf_size.unsigned_abs()]; + let value_ptr = buf.as_mut_ptr().cast::(); + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let bytes_read = unsafe { + nix::libc::getxattr( + file_path.as_ptr(), + xattr_name.as_ptr(), + value_ptr, + buf.len(), + ) + }; + if bytes_read == -1 { + return Err(std::io::Error::last_os_error()); + } + if buf.last() == Some(&0) { + buf.pop(); + } + + Ok(OsString::from_vec(buf)) +} + +pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> +where + P: AsRef, + S: AsRef, + T: AsRef, +{ + let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; + let xattr_name = CString::new(name.as_ref().as_bytes())?; + let xattr_value = CString::new(value.as_ref().as_bytes())?; + let size = xattr_value.to_bytes_with_nul().len(); + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let ret = unsafe { + nix::libc::setxattr( + file_path.as_ptr(), + xattr_name.as_ptr(), + xattr_value.as_ptr().cast::(), + size, + 0, + ) + }; + if ret == -1 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} diff --git a/syscare-common/src/io/mod.rs b/syscare-common/src/io/mod.rs deleted file mode 100644 index a36f750c..00000000 --- a/syscare-common/src/io/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -mod os_lines; -mod select; - -pub use os_lines::*; -pub use select::*; diff --git a/syscare-common/src/io/os_lines.rs b/syscare-common/src/io/os_lines.rs deleted file mode 100644 index 08ae0cef..00000000 --- a/syscare-common/src/io/os_lines.rs +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ffi::OsString, io::BufRead, os::unix::prelude::OsStringExt}; - -pub struct OsLines { - buf: R, -} - -impl Iterator for OsLines { - type Item = std::io::Result; - - fn next(&mut self) -> Option { - const CHAR_LF: [u8; 1] = [b'\n']; - const CHAR_CR: [u8; 1] = [b'\r']; - - let mut buf = Vec::new(); - match self.buf.read_until(CHAR_LF[0], &mut buf) { - Ok(0) => None, - Ok(_) => { - // Drop "\n" or "\r\n" on the buf tail - if buf.ends_with(&CHAR_LF) { - buf.pop(); - if buf.ends_with(&CHAR_CR) { - buf.pop(); - } - } - buf.shrink_to_fit(); - Some(Ok(OsString::from_vec(buf))) - } - Err(_) => Some(self.buf.read_to_end(&mut buf).map(|_| { - buf.shrink_to_fit(); - OsString::from_vec(buf) - })), - } - } -} - -pub trait BufReadOsLines: BufRead + Sized { - fn os_lines(self) -> OsLines { - OsLines { buf: self } - } -} - -impl BufReadOsLines for R {} - -#[test] -fn test() { - use crate::fs; - use std::io::BufReader; - - let buf_reader = - BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); - let lines = buf_reader.lines(); - for str in lines.flatten() { - println!("{}", str); - assert!(!str.is_empty()); - } - - let buf_reader = - BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); - let os_lines = buf_reader.os_lines(); - for str in os_lines.flatten() { - println!("{}", str.to_string_lossy()); - assert!(!str.is_empty()); - } -} diff --git a/syscare-common/src/io/select.rs b/syscare-common/src/io/select.rs deleted file mode 100644 index 5d9400f4..00000000 --- a/syscare-common/src/io/select.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{ - os::unix::io::{AsRawFd, RawFd}, - time::Duration, -}; - -use anyhow::Result; -use nix::sys::{ - select::{select, FdSet}, - time::TimeVal, -}; - -pub enum SelectResult { - Readable(RawFd), - Writable(RawFd), - Error(RawFd), -} - -pub struct Select { - fd_set: FdSet, - readfds: FdSet, - writefds: FdSet, - errorfds: FdSet, - timeout: Option, -} - -impl Select { - pub fn new(fds: I) -> Self - where - I: IntoIterator, - F: AsRawFd, - { - Self::with_timeout(fds, None) - } - - pub fn with_timeout(fds: I, duration: Option) -> Self - where - I: IntoIterator, - F: AsRawFd, - { - let mut fd_set = FdSet::new(); - for fd in fds { - fd_set.insert(fd.as_raw_fd()); - } - let readfds = FdSet::new(); - let writefds = FdSet::new(); - let errorfds = FdSet::new(); - let timeout = duration.map(|t| TimeVal::new(t.as_secs() as i64, t.subsec_micros() as i64)); - - Self { - fd_set, - readfds, - writefds, - errorfds, - timeout, - } - } - - pub fn select(&mut self) -> Result + '_> { - self.readfds = self.fd_set; - self.writefds = self.fd_set; - self.errorfds = self.fd_set; - - select( - None, - &mut self.readfds, - &mut self.writefds, - &mut self.errorfds, - &mut self.timeout, - )?; - - let rd_fds = self.readfds.fds(None).map(SelectResult::Readable); - let wr_fds = self.writefds.fds(None).map(SelectResult::Writable); - let err_fds = self.errorfds.fds(None).map(SelectResult::Error); - - Ok(rd_fds.chain(wr_fds).chain(err_fds)) - } -} diff --git a/syscare-common/src/lib.rs b/syscare-common/src/lib.rs index a3188e6d..b568a2cc 100644 --- a/syscare-common/src/lib.rs +++ b/syscare-common/src/lib.rs @@ -14,7 +14,6 @@ pub mod ffi; pub mod fs; -pub mod io; mod macros; pub mod os; pub mod os_str; diff --git a/syscare-common/src/os/cpu.rs b/syscare-common/src/os/cpu.rs index ead7eee0..9d39bca2 100644 --- a/syscare-common/src/os/cpu.rs +++ b/syscare-common/src/os/cpu.rs @@ -12,39 +12,35 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +use std::ffi::OsString; -use lazy_static::lazy_static; - -use nix::{ - sched::{sched_getaffinity, CpuSet}, - unistd::getpid, -}; +use num_cpus; use super::platform; -pub fn arch() -> &'static OsStr { +pub fn arch() -> OsString { platform::arch() } pub fn num() -> usize { - lazy_static! { - static ref CPU_NUM: usize = { - let cpu_set = sched_getaffinity(getpid()).unwrap_or_default(); - let mut cpu_count = 0; - for i in 0..CpuSet::count() { - if cpu_set.is_set(i).unwrap_or_default() { - cpu_count += 1; - } - } - cpu_count - }; - } - *CPU_NUM + num_cpus::get() } -#[test] -fn test() { - println!("arch: {}", arch().to_string_lossy()); - println!("num: {}", num()) +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_arch() { + let arch = self::arch(); + println!("cpu arch: {}", arch.to_string_lossy()); + assert!(!arch.is_empty()); + } + + #[test] + fn test_num() { + let num = self::num(); + println!("cpu num: {}", num); + assert_ne!(num, 0); + } } diff --git a/syscare-common/src/os/kernel.rs b/syscare-common/src/os/kernel.rs index a29e663d..c3551679 100644 --- a/syscare-common/src/os/kernel.rs +++ b/syscare-common/src/os/kernel.rs @@ -12,10 +12,165 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + collections::HashSet, + ffi::{CStr, CString, OsStr, OsString}, + fs::File, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, +}; + +use log::{error, trace}; +use nix::{errno::Errno, kmod}; +use object::{Object, ObjectSection}; + +use super::{platform, selinux}; +use crate::fs; + +const SYS_MODULE_DIR: &str = "/sys/module"; + +pub type KernelModuleInfo = ModuleInfo; +pub type KernelModuleGuard = ModuleGuard; -use super::platform; +#[derive(Debug)] +pub struct ModuleInfo { + pub name: OsString, + pub depends: HashSet, + pub module_path: PathBuf, +} + +#[derive(Debug)] +pub struct ModuleGuard(ModuleInfo); + +impl Drop for ModuleGuard { + fn drop(&mut self) { + if !self.0.module_path.exists() { + return; + } + if let Err(e) = self::remove_module(&self.0.name) { + error!( + "Failed to remove kernel module '{}', {}", + self.0.name.to_string_lossy(), + e.to_string().to_lowercase() + ); + } + } +} -pub fn version() -> &'static OsStr { +pub fn version() -> OsString { platform::release() } + +pub fn list_modules() -> std::io::Result> { + const LIST_OPTIONS: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; + + let modules = fs::list_dirs(SYS_MODULE_DIR, LIST_OPTIONS)? + .into_iter() + .filter_map(|path| path.file_name().map(OsStr::to_os_string)) + .collect(); + Ok(modules) +} + +pub fn relable_module_file>(file_path: P) -> std::io::Result<()> { + const KMOD_FILE_TYPE: &str = "modules_object_t"; + + let file_path = file_path.as_ref(); + let mut context = selinux::get_security_context(file_path)?; + if context.get_type() == KMOD_FILE_TYPE { + return Ok(()); + } + + context.set_type(KMOD_FILE_TYPE)?; + selinux::set_security_context(file_path, &context)?; + + Ok(()) +} + +pub fn module_info>(file_path: P) -> std::io::Result { + const MODINFO_SECTION_NAME: &str = ".modinfo"; + const MODINFO_NAME_PREFIX: &[u8] = b"name="; + const MODINFO_DEPENDS_PREFIX: &[u8] = b"depends="; + + let file_path = file_path.as_ref().to_path_buf(); + let mmap = fs::mmap(&file_path)?; + let file = object::File::parse(&*mmap).map_err(|_| Errno::ENOEXEC)?; + if file.format() != object::BinaryFormat::Elf { + return Err(std::io::Error::from(Errno::ENOEXEC)); + } + + let data = file + .section_by_name(MODINFO_SECTION_NAME) + .and_then(|section| section.data().ok()) + .ok_or(Errno::ENOEXEC)?; + let name = data + .split(|b| *b == b'\0') + .find_map(|entry| entry.strip_prefix(MODINFO_NAME_PREFIX)) + .map(|slice| OsStr::from_bytes(slice).to_os_string()) + .ok_or(Errno::ENOEXEC)?; + let depends = data + .split(|b| *b == b'\0') + .find_map(|entry| entry.strip_prefix(MODINFO_DEPENDS_PREFIX)) + .unwrap_or_default() + .split(|b| *b == b',') + .filter(|b| !b.is_empty()) + .map(|b| OsStr::from_bytes(b).to_os_string()) + .collect(); + let module_path = Path::new(SYS_MODULE_DIR).join(&name); + + let kmod = ModuleInfo { + name, + depends, + module_path, + }; + Ok(kmod) +} + +pub fn insert_module>(file_path: P) -> std::io::Result<()> { + const PARAM_VALUES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; + + let file_path = file_path.as_ref(); + trace!("Inserting kernel module '{}'...", file_path.display()); + + let file = File::open(file_path)?; + kmod::finit_module(&file, PARAM_VALUES, kmod::ModuleInitFlags::empty())?; + + Ok(()) +} + +pub fn insert_module_guarded>(file_path: P) -> std::io::Result { + let file_path = file_path.as_ref(); + + let modinfo = self::module_info(file_path)?; + if !modinfo.module_path.exists() { + self::insert_module(file_path)?; + } + + Ok(ModuleGuard(modinfo)) +} + +pub fn remove_module>(module_name: S) -> std::io::Result<()> { + let module_name = module_name.as_ref(); + trace!( + "Removing kernel module '{}'...", + module_name.to_string_lossy() + ); + + let name = CString::new(module_name.as_bytes())?; + kmod::delete_module(&name, kmod::DeleteModuleFlags::O_NONBLOCK)?; + + Ok(()) +} diff --git a/syscare-common/src/os/mod.rs b/syscare-common/src/os/mod.rs index 6a93a20f..9d330101 100644 --- a/syscare-common/src/os/mod.rs +++ b/syscare-common/src/os/mod.rs @@ -15,8 +15,6 @@ pub mod cpu; pub mod kernel; pub mod platform; -pub mod proc_maps; -pub mod proc_mounts; pub mod process; pub mod selinux; pub mod umask; diff --git a/syscare-common/src/os/platform.rs b/syscare-common/src/os/platform.rs index 8d38b44f..178ecd4e 100644 --- a/syscare-common/src/os/platform.rs +++ b/syscare-common/src/os/platform.rs @@ -12,59 +12,71 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +use std::ffi::OsString; -use lazy_static::lazy_static; -use nix::sys::utsname::{uname, UtsName}; +use nix::sys::utsname; #[inline(always)] -fn info() -> &'static UtsName { - lazy_static! { - static ref PLATFORM_INFO: UtsName = uname().expect("Failed to get uname"); - } - &PLATFORM_INFO +fn sysinfo() -> utsname::UtsName { + utsname::uname().expect("Failed to get system infomation") } -pub fn hostname() -> &'static OsStr { - info().nodename() +pub fn sysname() -> OsString { + self::sysinfo().sysname().to_os_string() } -pub fn sysname() -> &'static OsStr { - info().sysname() +pub fn hostname() -> OsString { + self::sysinfo().nodename().to_os_string() } -pub fn release() -> &'static OsStr { - info().release() +pub fn release() -> OsString { + self::sysinfo().release().to_os_string() } -pub fn version() -> &'static OsStr { - info().version() +pub fn version() -> OsString { + self::sysinfo().version().to_os_string() } -pub fn arch() -> &'static OsStr { - info().machine() +pub fn arch() -> OsString { + self::sysinfo().machine().to_os_string() } -#[test] -fn test() { - let sysname = sysname(); - let hostname = hostname(); - let release = release(); - let version = version(); - let arch = arch(); +#[cfg(test)] +mod test { + use super::*; - println!("sysname: {}", sysname.to_string_lossy()); - assert!(!sysname.is_empty()); + #[test] + fn test_sysname() { + let sysname = self::sysname(); + println!("sysname: {}", sysname.to_string_lossy()); + assert!(!sysname.is_empty()); + } - println!("hostname: {}", hostname.to_string_lossy()); - assert!(!hostname.is_empty()); + #[test] + fn test_hostname() { + let hostname = self::hostname(); + println!("hostname: {}", hostname.to_string_lossy()); + assert!(!hostname.is_empty()); + } - println!("release: {}", release.to_string_lossy()); - assert!(!release.is_empty()); + #[test] + fn test_release() { + let release = self::release(); + println!("release: {}", release.to_string_lossy()); + assert!(!release.is_empty()); + } - println!("version: {}", version.to_string_lossy()); - assert!(!version.is_empty()); + #[test] + fn test_version() { + let version = self::version(); + println!("version: {}", version.to_string_lossy()); + assert!(!version.is_empty()); + } - println!("arch: {}", arch.to_string_lossy()); - assert!(!arch.is_empty()); + #[test] + fn test_arch() { + let arch = self::arch(); + println!("arch: {}", arch.to_string_lossy()); + assert!(!arch.is_empty()); + } } diff --git a/syscare-common/src/os/proc_maps.rs b/syscare-common/src/os/proc_maps.rs deleted file mode 100644 index 2855a4ae..00000000 --- a/syscare-common/src/os/proc_maps.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{convert::TryFrom, ffi::OsString, fs::File, io::BufReader}; - -use anyhow::{ensure, Result}; - -use crate::{ - ffi::OsStrExt, - fs, - io::{BufReadOsLines, OsLines}, -}; - -#[derive(Debug)] -pub struct ProcMap { - pub address: OsString, - pub permission: OsString, - pub offset: OsString, - pub dev: OsString, - pub inode: OsString, - pub path_name: OsString, -} - -impl TryFrom for ProcMap { - type Error = anyhow::Error; - - fn try_from(value: OsString) -> std::result::Result { - const MAP_FIELD_NUM: usize = 6; - - let fields = value.split_whitespace().collect::>(); - ensure!( - fields.len() == MAP_FIELD_NUM, - "Failed to parse process mapping" - ); - - Ok(Self { - address: fields[0].to_owned(), - permission: fields[1].to_owned(), - offset: fields[2].to_owned(), - dev: fields[3].to_owned(), - inode: fields[4].to_owned(), - path_name: fields[5].to_owned(), - }) - } -} - -pub struct ProcMaps { - lines: OsLines>, -} - -impl ProcMaps { - pub fn new(pid: i32) -> Result { - let file_path = format!("/proc/{}/maps", pid); - let lines = BufReader::new(fs::open_file(file_path)?).os_lines(); - - Ok(Self { lines }) - } -} - -impl Iterator for ProcMaps { - type Item = ProcMap; - - fn next(&mut self) -> Option { - self.lines - .next() - .and_then(Result::ok) - .map(ProcMap::try_from) - .and_then(Result::ok) - } -} - -#[test] -fn test() { - use super::process; - - for map in ProcMaps::new(process::id()).expect("Failed to read proc maps") { - println!("{:#?}", map); - } -} diff --git a/syscare-common/src/os/proc_mounts.rs b/syscare-common/src/os/proc_mounts.rs deleted file mode 100644 index 1e720bfc..00000000 --- a/syscare-common/src/os/proc_mounts.rs +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::{OsStr, OsString}; -use std::fs::File; -use std::io::BufReader; -use std::os::unix::ffi::{OsStrExt as StdOsStrExt, OsStringExt}; -use std::path::PathBuf; - -use anyhow::Result; - -use crate::{ - ffi::OsStrExt, - fs, - io::{BufReadOsLines, OsLines}, -}; - -#[derive(Debug)] -pub struct MountInfo { - pub mount_id: u32, - pub parent_id: u32, - pub device_id: OsString, - pub root: PathBuf, - pub mount_point: PathBuf, - pub mount_opts: Vec, - pub optional: Vec, - pub filesystem: OsString, - pub mount_source: PathBuf, - pub super_opts: Vec, -} - -struct MountInfoParser<'a> { - data: &'a [u8], - num: usize, - pos: usize, -} - -impl<'a> Iterator for MountInfoParser<'a> { - type Item = &'a OsStr; - - fn next(&mut self) -> Option { - const OPTION_INDEX: usize = 6; - const NORMAL_SPLITTER: char = ' '; - const OPTION_SPLITTER: char = '-'; - - let data = &self.data[self.pos..]; - let new_str = OsStr::from_bytes(data); - if new_str.is_empty() { - return None; - } - - for (index, char) in new_str.char_indices() { - let pattern; - let skip_len; - - match self.num { - OPTION_INDEX => { - pattern = OPTION_SPLITTER; - skip_len = 2; - } - _ => { - pattern = NORMAL_SPLITTER; - skip_len = 1; - } - }; - if char == pattern { - self.num += 1; - self.pos += index + skip_len; - - return Some(OsStr::from_bytes(&data[..index])); - } - } - - self.pos = self.data.len() - 1; - Some(OsStr::from_bytes(data)) - } -} - -pub struct Mounts { - lines: OsLines>, -} - -impl Mounts { - pub fn new() -> Result { - const MOUNTINFO_PATH: &str = "/proc/self/mountinfo"; - - Ok(Self { - lines: BufReader::new(fs::open_file(MOUNTINFO_PATH)?).os_lines(), - }) - } -} - -impl Mounts { - fn parse_line(str: OsString) -> Option { - const VALUE_SPLITTER: char = ','; - - let str_bytes = str.into_vec(); - let mut iter = MountInfoParser { - data: &str_bytes, - num: 0, - pos: 0, - }; - - Some(MountInfo { - mount_id: iter.next()?.to_string_lossy().parse::().ok()?, - parent_id: iter.next()?.to_string_lossy().parse::().ok()?, - device_id: iter.next()?.to_os_string(), - root: PathBuf::from(iter.next()?), - mount_point: PathBuf::from(iter.next()?), - mount_opts: iter - .next()? - .split(VALUE_SPLITTER) - .map(OsStrExt::trim) - .map(OsString::from) - .collect::>(), - optional: iter - .next()? - .split_whitespace() - .map(OsString::from) - .collect::>(), - filesystem: iter.next()?.to_os_string(), - mount_source: PathBuf::from(iter.next()?), - super_opts: iter - .next()? - .split(VALUE_SPLITTER) - .map(OsStrExt::trim) - .map(OsString::from) - .collect::>(), - }) - } -} - -impl Iterator for Mounts { - type Item = MountInfo; - - fn next(&mut self) -> Option { - self.lines.next()?.ok().and_then(Self::parse_line) - } -} - -#[test] -fn test() { - let mount_info = Mounts::new().expect("Failed to read mount info"); - for mount in mount_info { - println!(); - println!("{:#?}", mount); - assert!(mount.mount_point.exists()) - } -} diff --git a/syscare-common/src/os/process.rs b/syscare-common/src/os/process.rs index 45695878..fc8c6cd0 100644 --- a/syscare-common/src/os/process.rs +++ b/syscare-common/src/os/process.rs @@ -13,72 +13,48 @@ */ use std::ffi::{OsStr, OsString}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -use lazy_static::lazy_static; -use nix::unistd::getpid; - -use crate::fs; +use nix::errno::Errno; +use nix::unistd; pub fn id() -> i32 { - lazy_static! { - static ref PROCESS_ID: i32 = getpid().as_raw(); - } - *PROCESS_ID + unistd::getpid().as_raw() } -pub fn path() -> &'static Path { - lazy_static! { - static ref PROCESS_PATH: PathBuf = - std::env::current_exe().unwrap_or_else(|_| PathBuf::from("/")); - } - PROCESS_PATH.as_path() +pub fn path() -> std::io::Result { + std::env::current_exe() } -pub fn name() -> &'static OsStr { - lazy_static! { - static ref PROCESS_NAME: OsString = fs::file_name(path()); - } - PROCESS_NAME.as_os_str() +pub fn name() -> std::io::Result { + self::path()? + .file_name() + .map(OsStr::to_os_string) + .ok_or(std::io::Error::from(Errno::EINVAL)) } #[cfg(test)] -mod tests_process { - use crate::os::process::{id, name, path}; - use std::process::Command; - use std::{println, string::ToString}; - - fn build_commd(s: &str) -> String { - let mut cmd = "ps -ef |grep ".to_string(); - cmd = cmd + s + "|grep -v grep"; - let output = Command::new("bash").arg("-c").arg(cmd).output().unwrap(); - String::from_utf8(output.stdout).unwrap() - } +mod test { + use super::*; #[test] fn test_id() { - let process_id = id().to_string(); - println!("This process id is {}", process_id); - - let sys_proc = build_commd(&process_id); - assert!(!sys_proc.is_empty()); + let pid = self::id(); + println!("pid: {}", pid); + assert!(pid > 1) } #[test] fn test_path() { - let process_path = path().display().to_string(); - println!("This path is {:#?}", process_path); - - let sys_path = build_commd(&process_path); - assert!(!sys_path.is_empty()); + let path = self::path().expect("Failed to get executable path"); + println!("path: {}", path.display()); + assert!(path.exists()); } #[test] fn test_name() { - let process_name = name().to_string_lossy(); - println!("This name is {:#?}", process_name); - - let sys_name = build_commd(&process_name); - assert!(!sys_name.is_empty()); + let name = self::name().expect("Failed to get executable name"); + println!("name: {}", name.to_string_lossy()); + assert!(!name.is_empty()); } } diff --git a/syscare-common/src/os/selinux.rs b/syscare-common/src/os/selinux.rs index bb9864ca..e1aa73ea 100644 --- a/syscare-common/src/os/selinux.rs +++ b/syscare-common/src/os/selinux.rs @@ -12,119 +12,304 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; -use std::os::unix::ffi::OsStringExt as UnixOsStringExt; -use std::path::Path; +use std::{ + ffi::{OsStr, OsString}, + fmt::Debug, + os::unix::ffi::{OsStrExt, OsStringExt}, + path::Path, +}; -use anyhow::{bail, ensure, Context, Result}; +use nix::errno::Errno; -use crate::{concat_os, ffi::OsStrExt, fs}; +use crate::fs; const SELINUX_SYS_FILE: &str = "/sys/fs/selinux/enforce"; -const SELINUX_XATTR_NAME: &str = "security.selinux"; -const SELINUX_XATTR_SPLITTER: &str = ":"; -const SECURITY_XATTR_LEN: usize = 4; +const SELINUX_STATUS_PERMISSIVE: &str = "0"; +const SELINUX_STATUS_ENFORCING: &str = "1"; + +const SECURITY_CONTEXT_XATTR_NAME: &str = "security.selinux"; +const SECURITY_CONTEXT_SPLITER: u8 = b':'; +const SECURITY_CONTEXT_SPLITER_COUNT: usize = 3; +const SECURITY_CONTEXT_ATTR_COUNT: usize = SECURITY_CONTEXT_SPLITER_COUNT + 1; + +const SECURITY_CONTEXT_USER_INDEX: usize = 0; +const SECURITY_CONTEXT_ROLE_INDEX: usize = 1; +const SECURITY_CONTEXT_TYPE_INDEX: usize = 2; +const SECURITY_CONTEXT_LEVEL_INDEX: usize = 3; + +pub type SELinuxStatus = Status; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { + Disabled, Permissive, Enforcing, - Disabled, } impl std::fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{:?}", self)) + write!(f, "{:?}", self) } } -pub fn get_status() -> Result { - if !Path::new(SELINUX_SYS_FILE).is_file() { - return Ok(Status::Disabled); +pub fn get_status() -> Status { + let value = fs::read_to_string(SELINUX_SYS_FILE).unwrap_or_default(); + match value.as_str() { + SELINUX_STATUS_PERMISSIVE => Status::Permissive, + SELINUX_STATUS_ENFORCING => Status::Enforcing, + _ => Status::Disabled, } +} - let value = OsString::from_vec(fs::read(SELINUX_SYS_FILE)?) - .to_string_lossy() - .parse::() - .context("Failed to parse selinux status")?; +pub fn set_status(value: Status) -> std::io::Result<()> { + let contents = match value { + Status::Permissive => SELINUX_STATUS_PERMISSIVE, + Status::Enforcing => SELINUX_STATUS_ENFORCING, + _ => return Err(std::io::Error::from(Errno::EINVAL)), + }; + fs::write(SELINUX_SYS_FILE, contents)?; - Ok(match value { - 0 => Status::Permissive, - 1 => Status::Enforcing, - _ => Status::Disabled, - }) + Ok(()) } -pub fn set_status(value: Status) -> Result<()> { - if (value != Status::Permissive) && (value != Status::Enforcing) { - bail!("Status {} is invalid", value); +#[derive(Clone, PartialEq, Eq)] +pub struct SecurityContext(OsString); + +impl SecurityContext { + fn get_attribute(&self, index: usize) -> &OsStr { + self.0 + .as_bytes() + .splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { + b == SECURITY_CONTEXT_SPLITER + }) + .nth(index) + .map(OsStr::from_bytes) + .expect("Unexpected security context format") } - fs::write( - SELINUX_SYS_FILE, - match value { - Status::Enforcing => "1", - _ => "0", - }, - )?; - Ok(()) + fn set_attribute>(&mut self, index: usize, value: S) -> std::io::Result<()> { + let value = value.as_ref().as_bytes(); + + if value.is_empty() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + if (index != SECURITY_CONTEXT_LEVEL_INDEX) && (value.contains(&SECURITY_CONTEXT_SPLITER)) { + return Err(std::io::Error::from(Errno::EINVAL)); + } + let attrs = self.0.as_bytes().splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { + b == SECURITY_CONTEXT_SPLITER + }); + + let mut new_context = Vec::new(); + for (i, attr) in attrs.enumerate() { + new_context.extend_from_slice(if i != index { attr } else { value }); + new_context.push(SECURITY_CONTEXT_SPLITER); + } + new_context.pop(); + + self.0 = OsString::from_vec(new_context); + Ok(()) + } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SecurityContext { - pub user: OsString, - pub role: OsString, - pub kind: OsString, - pub level: OsString, +impl SecurityContext { + pub fn parse>(value: S) -> std::io::Result { + let context = value.as_ref(); + if context.is_empty() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + let spliter_count = context + .as_bytes() + .iter() + .filter(|&b| *b == SECURITY_CONTEXT_SPLITER) + .count(); + if spliter_count < SECURITY_CONTEXT_SPLITER_COUNT { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + Ok(Self(context.to_os_string())) + } + + pub fn get_user(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_USER_INDEX) + } + + pub fn get_role(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_ROLE_INDEX) + } + + pub fn get_type(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_TYPE_INDEX) + } + + pub fn get_level(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_LEVEL_INDEX) + } + + pub fn set_user>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_USER_INDEX, value) + } + + pub fn set_role>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_ROLE_INDEX, value) + } + + pub fn set_type>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_TYPE_INDEX, value) + } + + pub fn set_level>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_LEVEL_INDEX, value) + } + + pub fn as_os_str(&self) -> &OsStr { + &self.0 + } } -impl AsRef for SecurityContext { - fn as_ref(&self) -> &SecurityContext { - self +impl Debug for SecurityContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) } } -pub fn get_security_context

(file_path: P) -> Result +impl AsRef for SecurityContext { + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +pub fn get_security_context

(file_path: P) -> std::io::Result where P: AsRef, { - let value = fs::getxattr(file_path, SELINUX_XATTR_NAME)?; - let data = value.split(SELINUX_XATTR_SPLITTER).collect::>(); - ensure!( - data.len() == SECURITY_XATTR_LEN, - "Failed to parse selinux security context" - ); - - Ok(SecurityContext { - user: data[0].to_os_string(), - role: data[1].to_os_string(), - kind: data[2].to_os_string(), - level: data[3].to_os_string(), - }) + SecurityContext::parse(fs::getxattr(file_path, SECURITY_CONTEXT_XATTR_NAME)?) } -pub fn set_security_context(file_path: P, value: S) -> Result<()> +pub fn set_security_context

(file_path: P, value: &SecurityContext) -> std::io::Result<()> where P: AsRef, - S: AsRef, { - let old_value = get_security_context(&file_path)?; - let new_value = value.as_ref(); + fs::setxattr(file_path, SECURITY_CONTEXT_XATTR_NAME, value) +} + +#[cfg(test)] +mod test { + use std::{fs, path::Path}; - if &old_value == new_value { - return Ok(()); + use super::*; + + #[test] + fn test_selinux_status() { + let status = self::get_status(); + println!("SELinux status: {}", status); + + let sys_file = Path::new(self::SELINUX_SYS_FILE); + if sys_file.exists() { + assert!(self::get_status() == Status::Disabled); + } else { + assert!(self::get_status() == Status::Disabled); + } + assert!(self::set_status(Status::Disabled).is_err()); } - let new_context = concat_os!( - &new_value.user, - SELINUX_XATTR_SPLITTER, - &new_value.role, - SELINUX_XATTR_SPLITTER, - &new_value.kind, - SELINUX_XATTR_SPLITTER, - &new_value.level, - ); - fs::setxattr(&file_path, SELINUX_XATTR_NAME, new_context)?; + #[test] + fn test_security_context_parse() { + const TEST_CASES: &[&str] = &[ + "system_u:object_r:bin_t:s0", + "user_u:role_r:type_t:s0:c1,c2", + "user.dom:role-1:type_x:s0:c1.c2", + "a:b:c:d.e-f,g", + "a_:b_:c_:d_", + ]; + for str in TEST_CASES { + let result = SecurityContext::parse(str).is_ok(); + assert!(result, "Failed to parse security context '{}'", str); + } + } - Ok(()) + #[test] + fn test_security_context_get() { + const TEST_CASES: &[(&str, [&str; 4])] = &[ + ( + "system_u:object_r:bin_t:s0", + ["system_u", "object_r", "bin_t", "s0"], + ), + ( + "user_u:role_r:type_t:s0:c1,c2", + ["user_u", "role_r", "type_t", "s0:c1,c2"], + ), + ( + "user.dom:role-1:type_x:s0:c1.c2", + ["user.dom", "role-1", "type_x", "s0:c1.c2"], + ), + ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), + ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), + ]; + + for (case, attrs) in TEST_CASES { + let context = SecurityContext::parse(case).expect("Failed to parse security context"); + assert_eq!(context.get_user(), attrs[0]); + assert_eq!(context.get_role(), attrs[1]); + assert_eq!(context.get_type(), attrs[2]); + assert_eq!(context.get_level(), attrs[3]); + } + } + + #[test] + fn test_security_context_set() { + const DEFAULT_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; + const TEST_CASES: &[(&str, [&str; 4])] = &[ + ( + "system_u:object_r:bin_t:s0", + ["system_u", "object_r", "bin_t", "s0"], + ), + ( + "user_u:role_r:type_t:s0:c1,c2", + ["user_u", "role_r", "type_t", "s0:c1,c2"], + ), + ( + "user.dom:role-1:type_x:s0:c1.c2", + ["user.dom", "role-1", "type_x", "s0:c1.c2"], + ), + ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), + ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), + ]; + + for (result, attrs) in TEST_CASES { + let mut context = + SecurityContext::parse(DEFAULT_CONTEXT).expect("Failed to parse security context"); + assert!(context.set_user(attrs[0]).is_ok()); + assert!(context.set_role(attrs[1]).is_ok()); + assert!(context.set_type(attrs[2]).is_ok()); + assert!(context.set_level(attrs[3]).is_ok()); + println!("{:?}", context); + + assert_eq!(context.as_os_str(), *result); + } + } + + #[test] + fn test_get_set_security_context() { + const TEST_FILE: &str = "selinux_test"; + const TEST_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; + + let file_path = std::env::temp_dir().join(TEST_FILE); + + fs::remove_file(&file_path).ok(); + fs::write(&file_path, TEST_FILE).expect("Failed to write test file"); + + let set_context = SecurityContext::parse(TEST_CONTEXT).expect("Invalid security context"); + self::set_security_context(&file_path, &set_context) + .expect("Failed to set security context"); + println!("set context: {:#?}", set_context); + + let get_context = + self::get_security_context(&file_path).expect("Failed to get security context"); + println!("get context: {:#?}", get_context); + + assert_eq!(set_context, get_context); + fs::remove_file(&file_path).expect("Failed to remove test file"); + } } diff --git a/syscare-common/src/os/umask.rs b/syscare-common/src/os/umask.rs index ab624cf1..cb0d3ee6 100644 --- a/syscare-common/src/os/umask.rs +++ b/syscare-common/src/os/umask.rs @@ -12,45 +12,49 @@ * See the Mulan PSL v2 for more details. */ -use nix::sys::stat::{umask, Mode}; +use nix::{libc::mode_t, sys::stat}; -pub fn set_umask(mode: u32) -> u32 { - umask(Mode::from_bits_truncate(mode)).bits() +pub fn set_umask(mode: mode_t) -> mode_t { + stat::umask(stat::Mode::from_bits_truncate(mode)).bits() } -#[test] -fn test() { - use std::{fs, fs::File, os::unix::fs::PermissionsExt}; - - const FILE_PATH: &str = "/tmp/umask_test"; - const UMASK1: u32 = 0o077; // 10600 - const UMASK2: u32 = 0o022; // 10644 - - fs::remove_file(FILE_PATH).ok(); - - println!("Testing umask {:03o}...", UMASK1); - set_umask(UMASK1); - let file1 = File::create(FILE_PATH).expect("Failed to create file"); - let perm1 = file1 - .metadata() - .map(|s| s.permissions()) - .expect("Failed to read file permission"); - - println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); - - drop(file1); - fs::remove_file(FILE_PATH).ok(); - - println!("Testing umask {:03o}...", UMASK2); - set_umask(UMASK2); - let file2 = File::create(FILE_PATH).expect("Failed to create file"); - let perm2 = file2 - .metadata() - .map(|s| s.permissions()) - .expect("Failed to read file permission"); - - println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); - - drop(file2); - fs::remove_file(FILE_PATH).ok(); +#[cfg(test)] +mod test { + #[test] + fn test_set_umask() { + use std::{fs, fs::File, os::unix::fs::PermissionsExt}; + + let file_path = std::env::temp_dir().join("umask_test"); + const UMASK1: u32 = 0o077; // 10600 + const UMASK2: u32 = 0o022; // 10644 + + fs::remove_file(&file_path).ok(); + + println!("Testing umask {:03o}...", UMASK1); + super::set_umask(UMASK1); + let file1 = File::create(&file_path).expect("Failed to create file"); + let perm1 = file1 + .metadata() + .map(|s| s.permissions()) + .expect("Failed to read file permission"); + println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); + assert_eq!(perm1.mode() & 0o777, 0o600); + + drop(file1); + fs::remove_file(&file_path).expect("Failed to remove file"); + + println!("Testing umask {:03o}...", UMASK2); + super::set_umask(UMASK2); + let file2 = File::create(&file_path).expect("Failed to create file"); + let perm2 = file2 + .metadata() + .map(|s| s.permissions()) + .expect("Failed to read file permission"); + + println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); + assert_eq!(perm2.mode() & 0o777, 0o644); + + drop(file2); + fs::remove_file(&file_path).expect("Failed to remove file"); + } } diff --git a/syscare-common/src/os/user.rs b/syscare-common/src/os/user.rs index db2e10ec..1e79cdb8 100644 --- a/syscare-common/src/os/user.rs +++ b/syscare-common/src/os/user.rs @@ -12,80 +12,94 @@ * See the Mulan PSL v2 for more details. */ -use std::{ - ffi::{CString, OsStr}, - path::{Path, PathBuf}, -}; - -use lazy_static::lazy_static; -use nix::unistd::{getuid, Gid, Uid, User}; - -use crate::ffi::CStrExt; - -fn info() -> &'static User { - lazy_static! { - static ref USER_INFO: User = User::from_uid(getuid()) - .unwrap_or_default() - .unwrap_or(User { - name: String::from("root"), - passwd: CString::default(), - uid: Uid::from_raw(0), - gid: Gid::from_raw(0), - gecos: CString::default(), - dir: PathBuf::from("/root"), - shell: PathBuf::from("/bin/sh"), - }); - } - &USER_INFO +use std::{ffi::OsString, os::unix::ffi::OsStringExt, path::PathBuf}; + +use nix::unistd; + +#[inline(always)] +fn userinfo() -> unistd::User { + unistd::User::from_uid(unistd::getuid()) + .expect("Failed to get user infomation") + .expect("Invalid user id") } -pub fn name() -> &'static str { - self::info().name.as_str() +pub fn uid() -> u32 { + unistd::getuid().as_raw() } -pub fn passwd() -> &'static OsStr { - self::info().passwd.as_os_str() +pub fn gid() -> u32 { + unistd::getgid().as_raw() } -pub fn id() -> u32 { - self::info().uid.as_raw() +pub fn name() -> String { + self::userinfo().name } -pub fn gid() -> u32 { - self::info().gid.as_raw() +pub fn passwd() -> OsString { + OsString::from_vec(self::userinfo().passwd.into_bytes()) } -pub fn gecos() -> &'static OsStr { - self::info().gecos.as_os_str() +pub fn gecos() -> OsString { + OsString::from_vec(self::userinfo().gecos.into_bytes()) } -pub fn home() -> &'static Path { - self::info().dir.as_path() +pub fn home() -> PathBuf { + self::userinfo().dir } -pub fn shell() -> &'static Path { - self::info().shell.as_path() +pub fn shell() -> PathBuf { + self::userinfo().shell } -#[test] -fn test() { - println!("name: {}", self::name()); - assert!(!self::name().is_empty()); +#[cfg(test)] +mod test { + use super::*; - println!("passwd: {}", self::passwd().to_string_lossy()); - assert!(!self::passwd().is_empty()); + #[test] + fn test_uid() { + let uid = self::uid(); + println!("uid: {}", uid); + assert!(uid < u32::MAX); + } - println!("id: {}", self::id()); - assert!(id() < u32::MAX); + #[test] + fn test_gid() { + let gid = self::gid(); + println!("gid: {}", gid); + assert!(gid < u32::MAX); + } - println!("gid: {}", self::gid()); - assert!(gid() < u32::MAX); + #[test] + fn test_name() { + let name = self::name(); + println!("name: {}", name); + assert!(!name.is_empty()); + } + + #[test] + fn test_passwd() { + let passwd = self::passwd(); + println!("passwd: {}", passwd.to_string_lossy()); + assert!(!passwd.is_empty()); + } - println!("gecos: {}", self::gecos().to_string_lossy()); + #[test] + fn test_gecos() { + let gecos = self::gecos(); + println!("gecos: {}", gecos.to_string_lossy()); + } - println!("home: {}", self::home().display()); - assert!(self::home().exists()); + #[test] + fn test_home() { + let home = self::home(); + println!("home: {}", home.display()); + assert!(home.exists()); + } - println!("shell: {}", self::shell().display()); - assert!(self::home().exists()); + #[test] + fn test_shell() { + let shell = self::shell(); + println!("shell: {}", shell.display()); + assert!(shell.exists()); + } } diff --git a/syscare-common/src/process/args.rs b/syscare-common/src/process/args.rs deleted file mode 100644 index 7d7e0941..00000000 --- a/syscare-common/src/process/args.rs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::{OsStr, OsString}; - -#[derive(Clone, Default)] -pub struct CommandArgs { - args: Vec, -} - -impl CommandArgs { - pub fn new() -> Self { - Self { args: Vec::new() } - } - - pub fn arg(&mut self, arg: S) -> &mut Self - where - S: AsRef, - { - self.args.push(arg.as_ref().to_os_string()); - self - } - - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - for arg in args { - self.arg(arg); - } - self - } -} - -impl IntoIterator for CommandArgs { - type Item = OsString; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.args.into_iter() - } -} diff --git a/syscare-common/src/process/child.rs b/syscare-common/src/process/child.rs index 0e8cf546..5379ef20 100644 --- a/syscare-common/src/process/child.rs +++ b/syscare-common/src/process/child.rs @@ -13,58 +13,57 @@ */ use std::{ - ffi::OsString, - ops::Deref, - os::unix::process::ExitStatusExt, - process::{Child as StdChild, ExitStatus as StdExitStatus}, - thread::JoinHandle, + ffi::OsString, ops::Deref, os::unix::process::ExitStatusExt, process, thread::JoinHandle, }; use anyhow::{anyhow, ensure, Context, Result}; use log::trace; -use super::{Stdio, StdioLevel}; +use super::output; pub struct Child { - pub(super) id: u32, pub(super) name: String, - pub(super) stdio_level: StdioLevel, - pub(super) inner: StdChild, + pub(super) child: process::Child, + pub(super) log_level: output::LogLevel, } impl Child { - fn capture_stdio(&mut self) -> Result> { - Stdio::new( - self.name.clone(), - self.inner - .stdout - .take() - .context("Failed to capture stdout")?, - self.inner - .stderr - .take() - .context("Failed to capture stderr")?, - self.stdio_level, - ) - .capture() + fn redirect_outputs(&mut self) -> Result> { + let stdout = self + .child + .stdout + .take() + .context("Failed to capture stdout")?; + let stderr = self + .child + .stderr + .take() + .context("Failed to capture stderr")?; + let outputs = output::Outputs::new(stdout, stderr, self.log_level); + + std::thread::Builder::new() + .name(self.name.clone()) + .spawn(|| outputs.redirect()) + .with_context(|| format!("Failed to create thread {}", self.name)) } } impl Child { pub fn kill(&mut self) -> Result<()> { - self.inner + let id = self.child.id(); + self.child .kill() - .with_context(|| format!("Failed to kill process {} ({})", self.name, self.id)) + .with_context(|| format!("Failed to kill process {} ({})", self.name, id)) } pub fn wait(&mut self) -> Result { + let id = self.child.id(); let status = self - .inner + .child .wait() - .with_context(|| format!("Failed to wait process {} ({})", self.name, self.id))?; - + .with_context(|| format!("Failed to wait process {} ({})", self.name, id))?; let exit_status = ExitStatus { - id: self.id, + id, name: self.name.clone(), status, }; @@ -79,24 +78,25 @@ impl Child { } pub fn wait_with_output(&mut self) -> Result { - let stdio_thread = self.capture_stdio()?; + let thread = self.redirect_outputs()?; let status = self.wait()?; - let (stdout, stderr) = stdio_thread + let (stdout, stderr) = thread .join() .map_err(|_| anyhow!("Failed to join stdio thread"))?; - Ok(Output { + let output = Output { status, stdout, stderr, - }) + }; + Ok(output) } } pub struct ExitStatus { id: u32, name: String, - status: StdExitStatus, + status: process::ExitStatus, } impl ExitStatus { diff --git a/syscare-common/src/process/command.rs b/syscare-common/src/process/command.rs deleted file mode 100644 index 06d3568e..00000000 --- a/syscare-common/src/process/command.rs +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::OsStr, - path::Path, - process::{Command as StdCommand, Stdio}, -}; - -use anyhow::{Context, Result}; -use log::{trace, Level}; - -use super::{Child, ExitStatus, Output, StdioLevel}; - -pub struct Command { - inner: StdCommand, - stdio_level: StdioLevel, -} - -impl Command { - pub fn new>(program: S) -> Self { - Self { - inner: StdCommand::new(program), - stdio_level: StdioLevel::default(), - } - } - - pub fn arg>(&mut self, arg: S) -> &mut Self { - self.inner.arg(arg); - self - } - - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - for arg in args { - self.arg(arg.as_ref()); - } - self - } - - pub fn env(&mut self, key: K, val: V) -> &mut Self - where - K: AsRef, - V: AsRef, - { - self.inner.env(key, val); - self - } - - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - for (ref key, ref val) in vars { - self.env(key, val); - } - self - } - - pub fn env_clear(&mut self) -> &mut Self { - self.inner.env_clear(); - self - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Self { - self.inner.current_dir(dir); - self - } - - pub fn stdin>(&mut self, cfg: T) -> &mut Self { - self.inner.stdin(cfg); - self - } - - pub fn stdout>>(&mut self, level: T) -> &mut Self { - self.stdio_level.stdout = level.into(); - self - } - - pub fn stderr(&mut self, level: Level) -> &mut Self { - self.stdio_level.stderr = level.into(); - self - } - - pub fn spawn(&mut self) -> Result { - let name = Path::new(self.inner.get_program()) - .file_name() - .context("Failed to get process name")? - .to_string_lossy() - .to_string(); - - trace!("Executing {:?}", self.inner); - let child = self - .inner - .spawn() - .with_context(|| format!("Failed to start {}", name))?; - - Ok(Child { - id: child.id(), - name, - stdio_level: self.stdio_level, - inner: child, - }) - } - - pub fn run(&mut self) -> Result { - self.inner.stdout(Stdio::null()).stderr(Stdio::null()); - - self.spawn()?.wait() - } - - pub fn run_with_output(&mut self) -> Result { - self.inner.stdout(Stdio::piped()).stderr(Stdio::piped()); - - self.spawn()?.wait_with_output() - } -} diff --git a/syscare-common/src/process/envs.rs b/syscare-common/src/process/envs.rs deleted file mode 100644 index 038ad60e..00000000 --- a/syscare-common/src/process/envs.rs +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, -}; - -pub struct CommandEnvs { - envs: HashMap, -} - -impl CommandEnvs { - pub fn new() -> Self { - Self { - envs: HashMap::new(), - } - } - - pub fn env(&mut self, key: K, value: V) -> &mut Self - where - K: AsRef, - V: AsRef, - { - self.envs - .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); - self - } - - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - for (key, value) in vars { - self.env(key, value); - } - self - } -} - -impl Default for CommandEnvs { - fn default() -> Self { - Self::new() - } -} - -impl IntoIterator for CommandEnvs { - type Item = (OsString, OsString); - - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.envs.into_iter() - } -} diff --git a/syscare-common/src/process/mod.rs b/syscare-common/src/process/mod.rs index 894707cc..1fe472c9 100644 --- a/syscare-common/src/process/mod.rs +++ b/syscare-common/src/process/mod.rs @@ -12,151 +12,434 @@ * See the Mulan PSL v2 for more details. */ -mod args; +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, + path::Path, + process, +}; + +use anyhow::{Context, Result}; +use log::{trace, Level}; + mod child; -mod command; -mod envs; -mod stdio; +mod output; + +#[derive(Debug, Clone)] +pub struct CommandArgs(Vec); + +impl CommandArgs { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn arg(&mut self, arg: S) -> &mut Self + where + S: AsRef, + { + self.0.push(arg.as_ref().to_os_string()); + self + } + + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for arg in args { + self.arg(arg); + } + self + } +} -pub use args::*; -pub use child::*; -pub use command::*; -pub use envs::*; +impl Default for CommandArgs { + fn default() -> Self { + Self::new() + } +} -use stdio::{Stdio, StdioLevel}; +impl IntoIterator for CommandArgs { + type Item = OsString; -#[test] -fn test() { - use log::Level; - use std::fs::File; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[derive(Debug, Clone)] +pub struct CommandEnvs(HashMap); + +impl CommandEnvs { + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn env(&mut self, key: K, value: V) -> &mut Self + where + K: AsRef, + V: AsRef, + { + self.0 + .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); + self + } + + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (key, value) in vars { + self.env(key, value); + } + self + } +} - use crate::ffi::OsStrExt; +impl Default for CommandEnvs { + fn default() -> Self { + Self::new() + } +} + +impl IntoIterator for CommandEnvs { + type Item = (OsString, OsString); - println!("Testing Command::new()..."); - let mut echo_cmd = Command::new("echo"); - let mut env_cmd = Command::new("env"); - let mut pwd_cmd = Command::new("pwd"); - let mut grep_cmd = Command::new("grep"); - let mut ls_cmd = Command::new("ls"); + type IntoIter = std::collections::hash_map::IntoIter; - let mut test_cmd = Command::new("test"); - let mut cat_cmd = Command::new("cat"); - - let mut err_cmd = Command::new("/cmd/not/exist"); - - println!("Testing Command::arg()..."); - echo_cmd.arg("Test:"); - - test_cmd.arg("1"); - ls_cmd.arg("/file/not/exist"); - - println!("Testing Command::args()..."); - echo_cmd.args(["Hello", "World!"]); - - println!("Testing Command::env_clear()..."); - env_cmd.env_clear(); - - println!("Testing Command::env()..."); - env_cmd.env("test_key1", "test_val1"); - - println!("Testing Command::envs()..."); - env_cmd.envs([("test_key2", "test_val2"), ("test_key3", "test_val3")]); - - println!("Testing Command::current_dir()..."); - pwd_cmd.current_dir("/tmp"); - - println!("Testing Command::stdin()..."); - grep_cmd.stdin(File::open("/proc/self/maps").expect("Failed to open file")); - grep_cmd.arg("vdso"); - - println!("Testing Command::stdout()..."); - echo_cmd.stdout(Level::Info); - - println!("Testing Command::stderr()..."); - echo_cmd.stderr(Level::Info); - - println!("Testing Command::spawn()..."); - let mut echo_proc = echo_cmd.spawn().expect("Failed to spawn process"); - let mut env_proc = env_cmd.spawn().expect("Failed to spawn process"); - let mut pwd_proc = pwd_cmd.spawn().expect("Failed to spawn process"); - let mut grep_proc = grep_cmd.spawn().expect("Failed to spawn process"); - let mut ls_proc = ls_cmd.spawn().expect("Failed to spawn process"); - - let mut test_proc = test_cmd.spawn().expect("Failed to spawn process"); - let mut cat_proc = cat_cmd.spawn().expect("Failed to spawn process"); - - assert_eq!(err_cmd.spawn().is_err(), true); - - println!("Testing Child::kill()..."); - cat_proc.kill().expect("Failed to kill process"); - - println!("Testing Child::wait()..."); - let test_status = test_proc.wait().expect("Failed to wait process"); - let cat_status = cat_proc.wait().expect("Process should not be waited"); - - println!("Testing Child::wait_with_output()..."); - let echo_output = echo_proc - .wait_with_output() - .expect("Failed to wait process"); - let env_output = env_proc.wait_with_output().expect("Failed to wait process"); - let pwd_output = pwd_proc.wait_with_output().expect("Failed to wait process"); - let grep_output = grep_proc - .wait_with_output() - .expect("Failed to wait process"); - let ls_output = ls_proc.wait_with_output().expect("Failed to wait process"); - - println!("Testing ExitStatus::exit_code()..."); - assert_eq!(test_status.exit_code(), 0); - assert_eq!(cat_status.exit_code(), 137); - - println!("Testing ExitStatus::exit_ok()..."); - assert_eq!(test_status.exit_ok().is_ok(), true); - assert_eq!(cat_status.exit_ok().is_ok(), false); - - println!("Testing ExitStatus::success()..."); - assert_eq!(test_status.success(), true); - assert_eq!(cat_status.success(), false); - - println!("Testing Output::exit_code()..."); - assert_eq!(echo_output.exit_code(), 0); - assert_eq!(env_output.exit_code(), 0); - assert_eq!(pwd_output.exit_code(), 0); - assert_eq!(grep_output.exit_code(), 0); - assert_eq!(ls_output.exit_code(), 2); - - println!("Testing Output::exit_ok()..."); - assert_eq!(echo_output.exit_ok().is_ok(), true); - assert_eq!(env_output.exit_ok().is_ok(), true); - assert_eq!(pwd_output.exit_ok().is_ok(), true); - assert_eq!(grep_output.exit_ok().is_ok(), true); - assert_eq!(ls_output.exit_ok().is_ok(), false); - - println!("Testing Output::success()..."); - assert_eq!(echo_output.success(), true); - assert_eq!(env_output.success(), true); - assert_eq!(pwd_output.success(), true); - assert_eq!(grep_output.success(), true); - assert_eq!(ls_output.success(), false); - - println!("Testing Output::stdout..."); - assert_eq!(echo_output.stdout.is_empty(), false); - assert_eq!(env_output.stdout.is_empty(), false); - assert_eq!(pwd_output.stdout.is_empty(), false); - assert_eq!(grep_output.stdout.is_empty(), false); - assert_eq!(ls_output.stdout.is_empty(), true); - - assert_eq!(echo_output.stdout, "Test: Hello World!"); - assert_eq!( - env_output.stdout, - "test_key1=test_val1\ntest_key2=test_val2\ntest_key3=test_val3" - ); - assert_eq!(pwd_output.stdout, "/tmp"); - assert_eq!(grep_output.stdout.contains("vdso"), true); - - println!("Testing ProcessOutput::stderr..."); - assert_eq!(echo_output.stderr.is_empty(), true); - assert_eq!(env_output.stderr.is_empty(), true); - assert_eq!(pwd_output.stderr.is_empty(), true); - assert_eq!(grep_output.stderr.is_empty(), true); - assert_eq!(ls_output.stderr.is_empty(), false); + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +pub struct Command { + inner: process::Command, + log_level: output::LogLevel, +} + +impl Command { + pub fn new>(program: S) -> Self { + Self { + inner: process::Command::new(program), + log_level: output::LogLevel::default(), + } + } + + pub fn arg>(&mut self, arg: S) -> &mut Self { + self.inner.arg(arg); + self + } + + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for arg in args { + self.arg(arg.as_ref()); + } + self + } + + pub fn env(&mut self, key: K, val: V) -> &mut Self + where + K: AsRef, + V: AsRef, + { + self.inner.env(key, val); + self + } + + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (key, val) in vars { + self.env(key, val); + } + self + } + + pub fn env_clear(&mut self) -> &mut Self { + self.inner.env_clear(); + self + } + + pub fn current_dir>(&mut self, dir: P) -> &mut Self { + self.inner.current_dir(dir); + self + } + + pub fn stdin>(&mut self, cfg: T) -> &mut Self { + self.inner.stdin(cfg); + self + } + + pub fn stdout(&mut self, level: Level) -> &mut Self { + self.log_level.stdout = Some(level); + self + } + + pub fn stderr(&mut self, level: Level) -> &mut Self { + self.log_level.stderr = Some(level); + self + } + + pub fn pipe_output(&mut self) -> &mut Self { + self.inner + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()); + self + } + + pub fn ignore_output(&mut self) -> &mut Self { + self.inner + .stdout(process::Stdio::null()) + .stderr(process::Stdio::null()); + self + } + + pub fn spawn(&mut self) -> Result { + let name = Path::new(self.inner.get_program()) + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(); + + trace!("Executing {:?}", self.inner); + let child = self + .inner + .spawn() + .with_context(|| format!("Failed to start {}", name))?; + let log_level = self.log_level; + + Ok(child::Child { + name, + child, + log_level, + }) + } + + pub fn run(&mut self) -> Result { + self.ignore_output().spawn()?.wait() + } + + pub fn run_with_output(&mut self) -> Result { + self.pipe_output().spawn()?.wait_with_output() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use log::Level; + use std::ffi::OsStr; + use std::path::Path; + use std::time::Duration; + + #[test] + fn test_command_args_new() { + let args = CommandArgs::new(); + assert!(args.0.is_empty()); + } + + #[test] + fn test_command_args_default() { + let args = CommandArgs::default(); + assert!(args.0.is_empty()); + } + + #[test] + fn test_command_args_arg() { + let mut args = CommandArgs::new(); + args.arg("test"); + assert_eq!(args.0.len(), 1); + assert_eq!(args.0[0], OsStr::new("test")); + } + + #[test] + fn test_command_args_args() { + let mut args = CommandArgs::new(); + args.args(vec!["arg1", "arg2"]); + assert_eq!(args.0.len(), 2); + assert_eq!(args.0[0], OsStr::new("arg1")); + assert_eq!(args.0[1], OsStr::new("arg2")); + } + + #[test] + fn test_command_args_into_iter() { + let mut args = CommandArgs::new(); + args.args(vec!["a", "b", "c"]); + let mut iter = args.into_iter(); + assert_eq!(iter.next(), Some(OsString::from("a"))); + assert_eq!(iter.next(), Some(OsString::from("b"))); + assert_eq!(iter.next(), Some(OsString::from("c"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_command_envs_new() { + let envs = CommandEnvs::new(); + assert!(envs.0.is_empty()); + } + + #[test] + fn test_command_envs_default() { + let envs = CommandEnvs::default(); + assert!(envs.0.is_empty()); + } + + #[test] + fn test_command_envs_env() { + let mut envs = CommandEnvs::new(); + envs.env("KEY", "VALUE"); + assert_eq!(envs.0.len(), 1); + assert_eq!( + envs.0.get(&OsString::from("KEY")), + Some(&OsString::from("VALUE")) + ); + } + + #[test] + fn test_command_envs_envs() { + let mut envs = CommandEnvs::new(); + envs.envs([("K1", "V1"), ("K2", "V2")]); + assert_eq!(envs.0.len(), 2); + assert_eq!( + envs.0.get(&OsString::from("K1")), + Some(&OsString::from("V1")) + ); + assert_eq!( + envs.0.get(&OsString::from("K2")), + Some(&OsString::from("V2")) + ); + } + + #[test] + fn test_command_new() { + let cmd = Command::new("test"); + assert_eq!(cmd.inner.get_program(), OsStr::new("test")); + assert!(cmd.inner.get_args().next().is_none()); + } + + #[test] + fn test_command_arg() { + let mut cmd = Command::new("test"); + cmd.arg("arg1"); + let mut args = cmd.inner.get_args(); + assert_eq!(args.next(), Some(OsStr::new("arg1"))); + assert!(args.next().is_none()); + } + + #[test] + fn test_command_args() { + let mut cmd = Command::new("test"); + cmd.args(["a", "b"]); + let mut args = cmd.inner.get_args(); + assert_eq!(args.next(), Some(OsStr::new("a"))); + assert_eq!(args.next(), Some(OsStr::new("b"))); + assert!(args.next().is_none()); + } + + #[test] + fn test_command_env() { + let mut cmd = Command::new("test"); + cmd.env("K", "V"); + let envs = cmd.inner.get_envs().collect::>(); + assert_eq!(envs.get(&OsStr::new("K")), Some(&Some(OsStr::new("V")))); + } + + #[test] + fn test_command_envs() { + let mut cmd = Command::new("test"); + cmd.envs([("K1", "V1"), ("K2", "V2")]); + + let envs = cmd.inner.get_envs().collect::>(); + assert_eq!(envs.len(), 2); + assert_eq!(envs.get(&OsStr::new("K1")), Some(&Some(OsStr::new("V1")))); + assert_eq!(envs.get(&OsStr::new("K2")), Some(&Some(OsStr::new("V2")))); + } + + #[test] + fn test_command_env_clear() { + let mut cmd = Command::new("test"); + cmd.env("K", "V").env_clear(); + let envs = cmd.inner.get_envs().collect::>(); + assert!(envs.is_empty()); + } + + #[test] + fn test_command_current_dir() { + let mut cmd = Command::new("test"); + cmd.current_dir("/tmp"); + assert_eq!(cmd.inner.get_current_dir(), Some(Path::new("/tmp"))); + } + + #[test] + fn test_command_stdout_stderr() { + let mut cmd = Command::new("test"); + cmd.stdout(Level::Info).stderr(Level::Error); + assert_eq!(cmd.log_level.stdout, Some(Level::Info)); + assert_eq!(cmd.log_level.stderr, Some(Level::Error)); + } + + #[test] + fn test_command_pipe_output() { + let mut cmd = Command::new("test"); + + cmd.pipe_output(); + assert!(cmd.inner.spawn().unwrap().stdout.is_some()); + assert!(cmd.inner.spawn().unwrap().stderr.is_some()); + } + + #[test] + fn test_command_ignore_output() { + let mut cmd = Command::new("test"); + + cmd.pipe_output(); + assert!(cmd.inner.spawn().unwrap().stdout.is_some()); + assert!(cmd.inner.spawn().unwrap().stderr.is_some()); + } + + #[test] + fn test_command_spawn_kill() { + let mut cmd = Command::new("yes"); + cmd.ignore_output(); + + let mut child = cmd.spawn().unwrap(); + std::thread::sleep(Duration::from_millis(100)); + + assert!(child.kill().is_ok()); + } + + #[test] + fn test_command_spawn_wait() { + let mut cmd = Command::new("echo"); + cmd.arg("test").ignore_output(); + + let mut child = cmd.spawn().unwrap(); + let status = child.wait().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_command_run() { + let mut cmd = Command::new("true"); + let status = cmd.run().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_command_run_with_output() { + let mut cmd = Command::new("env"); + let output = cmd.run_with_output().unwrap(); + + assert!(output.status.success()); + assert!(!output.stdout.is_empty()); + } } diff --git a/syscare-common/src/process/output.rs b/syscare-common/src/process/output.rs new file mode 100644 index 00000000..5e97c7ed --- /dev/null +++ b/syscare-common/src/process/output.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + ffi::OsString, + io::Read, + os::unix::{ffi::OsStringExt, io::AsRawFd}, + process::{ChildStderr, ChildStdout}, +}; + +use log::{error, log, Level}; +use nix::poll::{poll, PollFd, PollFlags}; + +const STREAM_BUFFER_SIZE: usize = 4096; + +#[derive(Debug, Clone, Copy)] +pub struct LogLevel { + pub stdout: Option, + pub stderr: Option, +} + +impl Default for LogLevel { + fn default() -> Self { + Self { + stdout: None, + stderr: Some(Level::Error), + } + } +} + +struct Stream { + stream: R, + buffer: Vec, + offset: usize, + log_level: Option, + is_closed: bool, +} + +impl Stream { + fn new(stream: R, log_level: Option) -> Self { + Self { + stream, + buffer: Vec::with_capacity(STREAM_BUFFER_SIZE), + offset: 0, + log_level, + is_closed: false, + } + } + + fn read_buf(&mut self) -> std::io::Result { + if self.buffer.capacity().wrapping_sub(self.buffer.len()) < STREAM_BUFFER_SIZE { + self.buffer.reserve(STREAM_BUFFER_SIZE); + } + + let spare_cap = self.buffer.spare_capacity_mut(); + let spare_buf = unsafe { + std::slice::from_raw_parts_mut(spare_cap.as_mut_ptr() as *mut u8, spare_cap.len()) + }; + + let len = self.stream.read(spare_buf)?; + unsafe { + self.buffer.set_len(self.buffer.len() + len); + } + + Ok(len) + } + + fn print_logs(&mut self) { + if let Some(level) = self.log_level { + let start = self.offset; + if start >= self.buffer.len() { + return; + } + + let slice = if !self.is_closed { + let end = match self.buffer[start..].iter().rposition(|&b| b == b'\n') { + Some(pos) => start + pos, + None => return, + }; + self.offset = end + 1; // skip '\n' + &self.buffer[start..end] + } else { + self.offset = self.buffer.len(); + &self.buffer[start..] + }; + if slice.is_empty() { + return; + } + + let lines = slice.split(|&b| b == b'\n').map(String::from_utf8_lossy); + for line in lines { + log!(level, "{}", line); + } + } + } + + fn handle_revents(&mut self, revents: PollFlags) { + if revents.contains(PollFlags::POLLIN) { + match self.read_buf() { + Ok(0) => self.is_closed = true, // EOF + Ok(_) => {} + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {} + Err(e) => { + error!("Failed to read stream, {}", e); + self.is_closed = true; + } + } + } + if revents.contains(PollFlags::POLLHUP) { + self.is_closed = true; + } + + self.print_logs(); + } +} + +pub struct Outputs { + fds: [PollFd; 2], + stdout: Stream, + stderr: Stream, +} + +impl Outputs { + pub fn new(stdout: ChildStdout, stderr: ChildStderr, log_level: LogLevel) -> Self { + Self { + fds: [ + PollFd::new(stdout.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), + PollFd::new(stderr.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), + ], + stdout: Stream::new(stdout, log_level.stdout), + stderr: Stream::new(stderr, log_level.stderr), + } + } + + pub fn redirect(mut self) -> (OsString, OsString) { + const POLL_TIMEOUT: i32 = -1; + + loop { + match poll(&mut self.fds, POLL_TIMEOUT) { + Ok(events) => { + if events == 0 { + break; + } + for (i, fd) in self.fds.iter().enumerate() { + let revents = fd.revents().expect("Invalid poll event"); + match i { + 0 => self.stdout.handle_revents(revents), + 1 => self.stderr.handle_revents(revents), + _ => unreachable!("Invalid poll fd"), + }; + } + } + Err(e) => { + error!("Failed to poll events, {}", e); + break; + } + } + if self.stdout.is_closed && self.stderr.is_closed { + break; + } + } + + ( + OsString::from_vec(self.stdout.buffer), + OsString::from_vec(self.stderr.buffer), + ) + } +} diff --git a/syscare-common/src/process/stdio.rs b/syscare-common/src/process/stdio.rs deleted file mode 100644 index 9a93e563..00000000 --- a/syscare-common/src/process/stdio.rs +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - collections::HashMap, - ffi::OsString, - io::BufReader, - os::unix::{ - ffi::OsStringExt, - io::{AsRawFd, RawFd}, - }, - process::{ChildStderr, ChildStdout}, - thread::JoinHandle, -}; - -use anyhow::{Context, Result}; -use log::{error, log, Level}; - -use crate::io::{BufReadOsLines, OsLines, Select, SelectResult}; - -#[derive(Debug, Clone, Copy)] -pub struct StdioLevel { - pub(super) stdout: Option, - pub(super) stderr: Option, -} - -impl Default for StdioLevel { - fn default() -> Self { - Self { - stdout: None, - stderr: Some(Level::Error), - } - } -} - -pub enum StdioOutput { - Stdout(OsString), - Stderr(OsString), -} - -enum StdioLines { - Stdout(OsLines>), - Stderr(OsLines>), -} - -struct StdioReader { - select: Select, - stdio_map: HashMap, - line_buf: Vec, -} - -impl StdioReader { - fn new(stdout: ChildStdout, stderr: ChildStderr) -> Self { - let line_buf = Vec::new(); - let stdio_map = HashMap::from([ - ( - stdout.as_raw_fd(), - StdioLines::Stdout(BufReader::new(stdout).os_lines()), - ), - ( - stderr.as_raw_fd(), - StdioLines::Stderr(BufReader::new(stderr).os_lines()), - ), - ]); - let select = Select::new(stdio_map.keys().copied()); - - Self { - select, - stdio_map, - line_buf, - } - } -} - -impl Iterator for StdioReader { - type Item = StdioOutput; - - fn next(&mut self) -> Option { - match self.select.select().context("Failed to select stdio") { - Ok(result) => { - let stdio_map = &mut self.stdio_map; - let outputs = result.into_iter().filter_map(|income| match income { - SelectResult::Readable(fd) => { - stdio_map.get_mut(&fd).and_then(|stdio| match stdio { - StdioLines::Stdout(lines) => { - lines.next().and_then(Result::ok).map(StdioOutput::Stdout) - } - StdioLines::Stderr(lines) => { - lines.next().and_then(Result::ok).map(StdioOutput::Stderr) - } - }) - } - _ => None, - }); - self.line_buf.extend(outputs); - } - Err(e) => { - error!("{:?}", e); - } - }; - - self.line_buf.pop() - } -} - -pub struct Stdio { - name: String, - stdout: ChildStdout, - stderr: ChildStderr, - level: StdioLevel, -} - -impl Stdio { - pub fn new(name: String, stdout: ChildStdout, stderr: ChildStderr, level: StdioLevel) -> Self { - Self { - name, - stdout, - stderr, - level, - } - } - - pub fn capture(self) -> Result> { - let stdio_level = self.level; - let stdio_reader = StdioReader::new(self.stdout, self.stderr); - - let thread_name = self.name.as_str(); - let thread = std::thread::Builder::new() - .name(thread_name.to_string()) - .spawn(move || -> (OsString, OsString) { - let mut stdout_buf = Vec::new(); - let mut stderr_buf = Vec::new(); - - for output in stdio_reader { - match output { - StdioOutput::Stdout(str) => { - if let Some(level) = stdio_level.stdout { - log!(level, "{}", str.to_string_lossy()); - } - stdout_buf.extend(str.into_vec()); - stdout_buf.push(b'\n'); - } - StdioOutput::Stderr(str) => { - if let Some(level) = stdio_level.stderr { - log!(level, "{}", str.to_string_lossy()); - } - stderr_buf.extend(str.into_vec()); - stderr_buf.push(b'\n'); - } - } - } - if stdout_buf.ends_with(b"\n") { - stdout_buf.pop(); - } - if stderr_buf.ends_with(b"\n") { - stderr_buf.pop(); - } - - ( - OsString::from_vec(stdout_buf), - OsString::from_vec(stderr_buf), - ) - }) - .with_context(|| format!("Failed to create thread {}", thread_name))?; - - Ok(thread) - } -} diff --git a/syscare-common/src/util/digest.rs b/syscare-common/src/util/digest.rs index 086b636d..4ed7357b 100644 --- a/syscare-common/src/util/digest.rs +++ b/syscare-common/src/util/digest.rs @@ -14,23 +14,21 @@ use std::path::Path; -use sha2::Digest; -use sha2::Sha256; +use nix::errno::Errno; +use sha2::{Digest, Sha256}; use crate::fs; -pub fn bytes>(bytes: S) -> String { - let mut hasher = Sha256::new(); - hasher.update(bytes); - - format!("{:#x}", hasher.finalize()) +pub fn bytes>(bytes: T) -> String { + format!("{:#x}", Sha256::digest(bytes)) } -pub fn file>(file: P) -> std::io::Result { - let mut hasher = Sha256::new(); - hasher.update(fs::read(file)?); - - Ok(format!("{:#x}", hasher.finalize())) +pub fn file>(path: P) -> std::io::Result { + let file_path = path.as_ref(); + if !file_path.is_file() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + Ok(self::bytes(&*fs::mmap(file_path)?)) } pub fn file_list(file_list: I) -> std::io::Result @@ -39,9 +37,201 @@ where P: AsRef, { let mut hasher = Sha256::new(); + for file in file_list { - hasher.update(fs::read(file)?); + let file_path = file.as_ref(); + if file_path.is_file() { + hasher.update(&*fs::mmap(file)?); + } } Ok(format!("{:#x}", hasher.finalize())) } + +pub fn dir>(path: P) -> std::io::Result { + let dir_path = path.as_ref(); + if !dir_path.is_dir() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + let mut file_list = fs::list_files(path, fs::TraverseOptions { recursive: true })?; + file_list.sort_unstable(); + + self::file_list(file_list) +} + +pub fn path>(path: P) -> std::io::Result { + if path.as_ref().is_file() { + self::file(path) + } else { + self::dir(path) + } +} + +#[cfg(test)] +mod tests { + use std::{ + fs::File, + io::Write, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, + }; + + use super::*; + + fn unique_name(prefix: &str) -> std::io::Result { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(format!("{}_{}", prefix, timestamp.as_nanos())) + } + + fn create_temp_file(content: &[u8]) -> std::io::Result { + let temp_file = std::env::temp_dir().join(self::unique_name("digest_test")?); + + let mut file = File::create(&temp_file)?; + file.write_all(content)?; + + Ok(temp_file) + } + + fn create_temp_dir() -> std::io::Result { + let temp_dir = std::env::temp_dir(); + let test_dir = temp_dir.join(unique_name("test_dir")?); + + fs::create_dir(&test_dir)?; + File::create(test_dir.join("file1.txt"))?.write_all(b"file1")?; + File::create(test_dir.join("file2.bin"))?.write_all(b"file2")?; + fs::create_dir(test_dir.join("subdir"))?; + File::create(test_dir.join("subdir/file3.txt"))?.write_all(b"file3")?; + + Ok(test_dir) + } + + #[test] + fn test_bytes() -> std::io::Result<()> { + assert_eq!( + self::bytes(b"hello"), + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + Ok(()) + } + + #[test] + fn test_file() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let hash = self::file(&file_path)?; + + std::fs::remove_file(&file_path).ok(); + assert_eq!( + hash, + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + + Ok(()) + } + + #[test] + fn test_file_not_exists() -> std::io::Result<()> { + let result = self::file("/non_exist_file"); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_file_is_dir() -> std::io::Result<()> { + let result = self::file(std::env::temp_dir()); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_file_list() -> std::io::Result<()> { + let files = vec![ + self::create_temp_file(b"file1")?, + self::create_temp_file(b"file2")?, + self::create_temp_file(b"file3")?, + ]; + + let hash = self::file_list(&files)?; + for file in files { + std::fs::remove_file(file)?; + } + assert_eq!( + hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + + Ok(()) + } + + #[test] + fn test_dir() -> std::io::Result<()> { + let test_dir = self::create_temp_dir()?; + let hash = self::dir(&test_dir)?; + std::fs::remove_dir_all(test_dir)?; + + assert_eq!( + hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + Ok(()) + } + + #[test] + fn test_dir_not_exists() -> std::io::Result<()> { + let result = self::dir("/non_exist_file"); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_dir_is_file() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let result = self::dir(&file_path); + std::fs::remove_file(file_path)?; + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_path() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let dir_path = self::create_temp_dir()?; + + let file_hash = self::path(&file_path)?; + let dir_hash = self::path(&dir_path)?; + std::fs::remove_file(&file_path)?; + std::fs::remove_dir_all(&dir_path)?; + + assert_eq!( + file_hash, + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + assert_eq!( + dir_hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + Ok(()) + } +} -- Gitee From a3b5a45f8eb86f46972c482b3e3297bec06f8b00 Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 29 May 2025 18:13:14 +0800 Subject: [PATCH 6/9] syscared: adapt common crate change Signed-off-by: renoseven --- syscared/src/main.rs | 21 ++++++++------------- syscared/src/patch/driver/kpatch/sys.rs | 12 ++++++------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/syscared/src/main.rs b/syscared/src/main.rs index f965497e..bfff3d7d 100644 --- a/syscared/src/main.rs +++ b/syscared/src/main.rs @@ -59,10 +59,6 @@ const LOG_DIR_PERM: u32 = 0o700; const SOCKET_FILE_PERM: u32 = 0o660; const SOCKET_FILE_PERM_STRICT: u32 = 0o600; -const MAIN_THREAD_NAME: &str = "main"; -const UNNAMED_THREAD_NAME: &str = ""; -const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; - struct Daemon { args: Arguments, config: Config, @@ -74,15 +70,14 @@ impl Daemon { now: &mut DeferredNow, record: &Record, ) -> std::io::Result<()> { + const UNNAMED_THREAD_NAME: &str = ""; + const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; + thread_local! { - static THREAD_NAME: String = std::thread::current().name().and_then(|name| { - if name == MAIN_THREAD_NAME { - return os::process::name().to_str(); - } - Some(name) - }) - .unwrap_or(UNNAMED_THREAD_NAME) - .to_string(); + static THREAD_NAME: String = std::thread::current() + .name() + .unwrap_or(UNNAMED_THREAD_NAME) + .to_string(); } THREAD_NAME.with(|thread_name| { @@ -100,7 +95,7 @@ impl Daemon { fn new() -> Result { // Check root permission ensure!( - os::user::id() == 0, + os::user::uid() == 0, "This command has to be run with superuser privileges (under the root user on most systems)." ); diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs index fd5160cb..adabfb48 100644 --- a/syscared/src/patch/driver/kpatch/sys.rs +++ b/syscared/src/patch/driver/kpatch/sys.rs @@ -19,7 +19,7 @@ use log::debug; use nix::kmod; use syscare_abi::PatchStatus; -use syscare_common::{ffi::OsStrExt, fs, os}; +use syscare_common::{ffi::OsStrExt, fs, os::selinux}; use crate::patch::entity::KernelPatch; @@ -39,7 +39,7 @@ pub fn list_kernel_modules() -> Result> { pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; - if os::selinux::get_status()? != os::selinux::Status::Enforcing { + if selinux::get_status() != selinux::Status::Enforcing { return Ok(()); } @@ -47,10 +47,10 @@ pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { "Relabeling patch module '{}'...", patch.module_name.to_string_lossy() ); - let mut sec_context = os::selinux::get_security_context(&patch.patch_file)?; - if sec_context.kind != KPATCH_PATCH_SEC_TYPE { - sec_context.kind = OsString::from(KPATCH_PATCH_SEC_TYPE); - os::selinux::set_security_context(&patch.patch_file, sec_context)?; + let mut sec_context = selinux::get_security_context(&patch.patch_file)?; + if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { + sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; + selinux::set_security_context(&patch.patch_file, &sec_context)?; } Ok(()) -- Gitee From 84d926841bd6d49fed630242957255a822ae502a Mon Sep 17 00:00:00 2001 From: renoseven Date: Tue, 3 Jun 2025 15:55:03 +0800 Subject: [PATCH 7/9] syscared: rewrite patch parsing & management Signed-off-by: renoseven --- syscared/src/config.rs | 22 +- syscared/src/patch/driver/kpatch/mod.rs | 302 +++++++---------- syscared/src/patch/driver/kpatch/sys.rs | 119 +++---- syscared/src/patch/driver/kpatch/target.rs | 144 +++----- syscared/src/patch/driver/mod.rs | 84 +++-- syscared/src/patch/driver/upatch/entity.rs | 60 ---- syscared/src/patch/driver/upatch/mod.rs | 348 +++++++------------- syscared/src/patch/driver/upatch/monitor.rs | 30 +- syscared/src/patch/driver/upatch/sys.rs | 32 +- syscared/src/patch/driver/upatch/target.rs | 158 ++++----- syscared/src/patch/entity/kpatch.rs | 224 ++++++++++++- syscared/src/patch/entity/patch.rs | 5 +- syscared/src/patch/entity/symbol.rs | 54 --- syscared/src/patch/entity/upatch.rs | 181 +++++++++- syscared/src/patch/manager.rs | 169 +++++++--- syscared/src/patch/mod.rs | 1 - syscared/src/patch/resolver/kpatch.rs | 226 ------------- syscared/src/patch/resolver/mod.rs | 63 ---- syscared/src/patch/resolver/upatch.rs | 182 ---------- 19 files changed, 1053 insertions(+), 1351 deletions(-) delete mode 100644 syscared/src/patch/driver/upatch/entity.rs delete mode 100644 syscared/src/patch/entity/symbol.rs delete mode 100644 syscared/src/patch/resolver/kpatch.rs delete mode 100644 syscared/src/patch/resolver/mod.rs delete mode 100644 syscared/src/patch/resolver/upatch.rs diff --git a/syscared/src/config.rs b/syscared/src/config.rs index 71032731..5e0d7361 100644 --- a/syscared/src/config.rs +++ b/syscared/src/config.rs @@ -83,17 +83,27 @@ pub struct Config { impl Config { pub fn parse>(path: P) -> Result { let config_path = path.as_ref(); - let instance = serde_yaml::from_reader(fs::open_file(config_path)?) - .map_err(|_| anyhow!("Failed to parse config {}", config_path.display()))?; - - Ok(instance) + let config = serde_yaml::from_reader(fs::open_file(config_path)?).map_err(|e| { + anyhow!( + "Failed to parse config '{}', {}", + config_path.display(), + e.to_string().to_lowercase() + ) + })?; + + Ok(config) } pub fn write>(&self, path: P) -> Result<()> { let config_path = path.as_ref(); let config_file = fs::create_file(config_path)?; - serde_yaml::to_writer(config_file, self) - .map_err(|_| anyhow!("Failed to write config {}", config_path.display()))?; + serde_yaml::to_writer(config_file, self).map_err(|e| { + anyhow!( + "Failed to write config '{}', {}", + config_path.display(), + e.to_string().to_lowercase() + ) + })?; Ok(()) } diff --git a/syscared/src/patch/driver/kpatch/mod.rs b/syscared/src/patch/driver/kpatch/mod.rs index ba177ac5..8e396703 100644 --- a/syscared/src/patch/driver/kpatch/mod.rs +++ b/syscared/src/patch/driver/kpatch/mod.rs @@ -13,103 +13,58 @@ */ use std::{ - ffi::{OsStr, OsString}, + collections::{HashMap, HashSet}, + ffi::OsString, fmt::Write, iter::FromIterator, }; -use anyhow::{ensure, Result}; -use indexmap::{indexset, IndexMap, IndexSet}; -use log::{debug, info}; +use anyhow::{anyhow, ensure, Context, Result}; +use log::debug; use syscare_abi::PatchStatus; -use syscare_common::{concat_os, os, util::digest}; - -use crate::{ - config::KernelPatchConfig, - patch::entity::{KernelPatch, KernelPatchFunction}, +use syscare_common::{ + concat_os, + os::{self, kernel, selinux}, + util::digest, }; +use crate::{config::KernelPatchConfig, patch::entity::KernelPatch}; + mod sys; mod target; use target::PatchTarget; pub struct KernelPatchDriver { - target_map: IndexMap, - blocked_targets: IndexSet, + target_map: HashMap, // object name -> object + blocked_targets: HashSet, } impl KernelPatchDriver { pub fn new(config: &KernelPatchConfig) -> Result { Ok(Self { - target_map: IndexMap::new(), - blocked_targets: IndexSet::from_iter(config.blocked.iter().cloned()), + target_map: HashMap::new(), + blocked_targets: HashSet::from_iter(config.blocked.iter().cloned()), }) } } impl KernelPatchDriver { - fn group_patch_targets(patch: &KernelPatch) -> IndexSet<&OsStr> { - let mut patch_targets = IndexSet::new(); - - for function in &patch.functions { - patch_targets.insert(function.object.as_os_str()); - } - patch_targets - } - - pub fn group_patch_functions( - patch: &KernelPatch, - ) -> IndexMap<&OsStr, Vec<&KernelPatchFunction>> { - let mut patch_function_map: IndexMap<&OsStr, Vec<&KernelPatchFunction>> = IndexMap::new(); - - for function in &patch.functions { - patch_function_map - .entry(function.object.as_os_str()) - .or_default() - .push(function); - } - patch_function_map - } -} - -impl KernelPatchDriver { - fn add_patch_target(&mut self, patch: &KernelPatch) { - for target_name in Self::group_patch_targets(patch) { - if !self.target_map.contains_key(target_name) { - self.target_map.insert( - target_name.to_os_string(), - PatchTarget::new(target_name.to_os_string()), - ); - } + fn register_patch(&mut self, patch: &KernelPatch) { + for object_name in patch.functions.keys() { + self.target_map + .entry(object_name.clone()) + .or_insert_with(|| PatchTarget::new(object_name.clone())) + .add_patch(patch); } } - fn remove_patch_target(&mut self, patch: &KernelPatch) { - for target_name in Self::group_patch_targets(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - if !target.has_function() { - self.target_map.remove(target_name); - } - } - } - } - - fn add_patch_functions(&mut self, patch: &KernelPatch) { - for (target_name, functions) in Self::group_patch_functions(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - target.add_functions(patch.uuid, functions); - } - } - } - - fn remove_patch_functions(&mut self, patch: &KernelPatch) { - for (target_name, functions) in Self::group_patch_functions(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - target.remove_functions(&patch.uuid, functions); - } - } + fn unregister_patch(&mut self, patch: &KernelPatch) { + self.target_map.retain(|_, object| { + object.remove_patch(patch); + object.is_patched() + }); } } @@ -130,13 +85,14 @@ impl KernelPatchDriver { const KERNEL_NAME_PREFIX: &str = "kernel-"; let patch_target = patch.pkg_name.as_str(); + if !patch_target.starts_with(KERNEL_NAME_PREFIX) { + return Ok(()); + } + let current_kernel = concat_os!(KERNEL_NAME_PREFIX, os::kernel::version()); debug!("Patch target: '{}'", patch_target); debug!("Current kernel: '{}'", current_kernel.to_string_lossy()); - if !patch_target.starts_with(KERNEL_NAME_PREFIX) { - return Ok(()); - } ensure!( current_kernel == patch_target, "Kpatch: Patch is incompatible", @@ -147,156 +103,134 @@ impl KernelPatchDriver { fn check_dependency(patch: &KernelPatch) -> Result<()> { const VMLINUX_MODULE_NAME: &str = "vmlinux"; - let mut non_exist_kmod = IndexSet::new(); - - let kmod_list = sys::list_kernel_modules()?; - for kmod_name in Self::group_patch_targets(patch) { - if kmod_name == VMLINUX_MODULE_NAME { - continue; - } - if kmod_list.iter().any(|name| name == kmod_name) { - continue; - } - non_exist_kmod.insert(kmod_name); - } - - ensure!(non_exist_kmod.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Kpatch: Patch target does not exist")?; - for kmod_name in non_exist_kmod { - writeln!(&mut err_msg, "* Module '{}'", kmod_name.to_string_lossy())?; + let depend_modules = patch.functions.keys().cloned().collect::>(); + let inserted_modules = + kernel::list_modules().context("Kpatch: Failed to list kernel modules")?; + let needed_modules = depend_modules + .difference(&inserted_modules) + .filter(|&module_name| module_name != VMLINUX_MODULE_NAME) + .collect::>(); + + ensure!(needed_modules.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch target does not exist")?; + for name in needed_modules { + writeln!(msg, "* Module '{}'", name.to_string_lossy())?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_conflict_functions(&self, patch: &KernelPatch) -> Result<()> { - let mut conflict_patches = indexset! {}; - - let target_functions = Self::group_patch_functions(patch); - for (target_name, functions) in target_functions { - if let Some(target) = self.target_map.get(target_name) { - conflict_patches.extend( - target - .get_conflicts(functions) - .into_iter() - .map(|record| record.uuid), - ); - } - } - - ensure!(conflict_patches.is_empty(), { - let mut err_msg = String::new(); + pub fn check_conflicted_patches(&self, patch: &KernelPatch) -> Result<()> { + let conflicted: HashSet<_> = self + .target_map + .values() + .flat_map(|object| object.get_conflicted_patches(patch)) + .collect(); - writeln!(&mut err_msg, "Kpatch: Patch is conflicted with")?; - for uuid in conflict_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(conflicted.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch is conflicted with")?; + for uuid in conflicted { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_override_functions(&self, patch: &KernelPatch) -> Result<()> { - let mut override_patches = indexset! {}; - - let target_functions = Self::group_patch_functions(patch); - for (target_name, functions) in target_functions { - if let Some(target) = self.target_map.get(target_name) { - override_patches.extend( - target - .get_overrides(&patch.uuid, functions) - .into_iter() - .map(|record| record.uuid), - ); - } - } - - ensure!(override_patches.is_empty(), { - let mut err_msg = String::new(); + pub fn check_overridden_patches(&self, patch: &KernelPatch) -> Result<()> { + let overridden: HashSet<_> = self + .target_map + .values() + .flat_map(|object| object.get_overridden_patches(patch)) + .collect(); - writeln!(&mut err_msg, "Kpatch: Patch is overrided by")?; - for uuid in override_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(overridden.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch is overridden by")?; + for uuid in overridden { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } } impl KernelPatchDriver { - pub fn status(&self, patch: &KernelPatch) -> Result { - sys::read_patch_status(patch) - } - - pub fn check(&self, patch: &KernelPatch) -> Result<()> { + pub fn check_patch(&self, patch: &KernelPatch) -> Result<()> { Self::check_consistency(patch)?; Self::check_compatiblity(patch)?; Self::check_dependency(patch)?; - Ok(()) } - pub fn apply(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Applying patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); + pub fn get_patch_status(&self, patch: &KernelPatch) -> Result { + sys::get_patch_status(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to get patch status, {}", + e.to_string().to_lowercase() + ) + }) + } + pub fn load_patch(&mut self, patch: &KernelPatch) -> Result<()> { ensure!( !self.blocked_targets.contains(&patch.target_name), - "Patch target '{}' is blocked", + "Kpatch: Patch target '{}' is blocked", patch.target_name.to_string_lossy(), ); - sys::selinux_relable_patch(patch)?; - sys::apply_patch(patch)?; - self.add_patch_target(patch); - Ok(()) + if selinux::get_status() == selinux::Status::Enforcing { + kernel::relable_module_file(&patch.patch_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to relable patch file, {}", + e.to_string().to_lowercase() + ) + })?; + } + sys::load_patch(&patch.patch_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to load patch, {}", + e.to_string().to_lowercase() + ) + }) } - pub fn remove(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Removing patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::remove_patch(patch)?; - self.remove_patch_target(patch); - - Ok(()) + pub fn remove_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::remove_patch(&patch.module.name).map_err(|e| { + anyhow!( + "Kpatch: Failed to remove patch, {}", + e.to_string().to_lowercase() + ) + }) } - pub fn active(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Activating patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::active_patch(patch)?; - self.add_patch_functions(patch); + pub fn active_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::active_patch(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to active patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.register_patch(patch); Ok(()) } - pub fn deactive(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Deactivating patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::deactive_patch(patch)?; - self.remove_patch_functions(patch); + pub fn deactive_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::deactive_patch(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to deactive patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.unregister_patch(patch); Ok(()) } diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs index adabfb48..838fe3b8 100644 --- a/syscared/src/patch/driver/kpatch/sys.rs +++ b/syscared/src/patch/driver/kpatch/sys.rs @@ -12,106 +12,69 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::{CString, OsString}; +use std::{ffi::OsStr, fs, path::Path}; -use anyhow::{anyhow, bail, Context, Result}; use log::debug; -use nix::kmod; +use nix::errno::Errno; use syscare_abi::PatchStatus; -use syscare_common::{ffi::OsStrExt, fs, os::selinux}; +use syscare_common::os::kernel; -use crate::patch::entity::KernelPatch; +const KPATCH_STATUS_DISABLE: &str = "0"; +const KPATCH_STATUS_ENABLE: &str = "1"; -const SYS_MODULE_DIR: &str = "/sys/module"; -const KPATCH_STATUS_DISABLED: &str = "0"; -const KPATCH_STATUS_ENABLED: &str = "1"; - -pub fn list_kernel_modules() -> Result> { - let module_names = fs::list_dirs(SYS_MODULE_DIR, fs::TraverseOptions { recursive: false })? - .into_iter() - .filter_map(|dir| dir.file_name().map(|name| name.to_os_string())) - .collect(); - - Ok(module_names) -} - -pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { - const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; - - if selinux::get_status() != selinux::Status::Enforcing { - return Ok(()); - } +pub fn load_patch>(patch_file: P) -> std::io::Result<()> { + let patch_file = patch_file.as_ref(); debug!( - "Relabeling patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Inserting patch module '{}'...", + patch_file.display() ); - let mut sec_context = selinux::get_security_context(&patch.patch_file)?; - if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { - sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; - selinux::set_security_context(&patch.patch_file, &sec_context)?; - } - - Ok(()) + kernel::insert_module(patch_file) } -pub fn read_patch_status(patch: &KernelPatch) -> Result { - let sys_file = patch.sys_file.as_path(); - debug!("Reading {}", sys_file.display()); - - let status = match fs::read_to_string(sys_file) { - Ok(str) => match str.trim() { - KPATCH_STATUS_DISABLED => Ok(PatchStatus::Deactived), - KPATCH_STATUS_ENABLED => Ok(PatchStatus::Actived), - _ => bail!("Kpatch: Invalid patch status"), - }, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(PatchStatus::NotApplied), - Err(e) => Err(e), - } - .context("Kpatch: Failed to read patch status")?; +pub fn remove_patch>(module_name: S) -> std::io::Result<()> { + let module_name = module_name.as_ref(); - Ok(status) + debug!( + "Kpatch: Removing patch module '{}'...", + module_name.to_string_lossy() + ); + kernel::remove_module(module_name) } -fn write_patch_status(patch: &KernelPatch, value: &str) -> Result<()> { - let sys_file = patch.sys_file.as_path(); +pub fn active_patch>(status_file: P) -> std::io::Result<()> { + let status_file = status_file.as_ref(); - debug!("Writing '{}' to {}", value, sys_file.display()); - fs::write(sys_file, value).context("Kpatch: Failed to write patch status") -} - -pub fn apply_patch(patch: &KernelPatch) -> Result<()> { debug!( - "Inserting patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Writing '{}' to '{}'...", + stringify!(KPATCH_STATUS_ENABLE), + status_file.display() ); - let patch_module = fs::open_file(&patch.patch_file)?; - kmod::finit_module( - &patch_module, - CString::new("")?.as_c_str(), - kmod::ModuleInitFlags::empty(), - ) - .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) + fs::write(status_file, KPATCH_STATUS_ENABLE) } -pub fn remove_patch(patch: &KernelPatch) -> Result<()> { +pub fn deactive_patch>(status_file: P) -> std::io::Result<()> { + let status_file = status_file.as_ref(); + debug!( - "Removing patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Writing '{}' to '{}'...", + stringify!(KPATCH_STATUS_DISABLE), + status_file.display() ); - - kmod::delete_module( - patch.module_name.to_cstring()?.as_c_str(), - kmod::DeleteModuleFlags::O_NONBLOCK, - ) - .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) + fs::write(status_file, KPATCH_STATUS_DISABLE) } -pub fn active_patch(patch: &KernelPatch) -> Result<()> { - self::write_patch_status(patch, KPATCH_STATUS_ENABLED) -} +pub fn get_patch_status>(status_file: P) -> std::io::Result { + let status_file = status_file.as_ref(); + if !status_file.exists() { + return Ok(PatchStatus::NotApplied); + } -pub fn deactive_patch(patch: &KernelPatch) -> Result<()> { - self::write_patch_status(patch, KPATCH_STATUS_DISABLED) + debug!("Kpatch: Reading '{}'...", status_file.display()); + match fs::read_to_string(status_file)?.trim() { + KPATCH_STATUS_DISABLE => Ok(PatchStatus::Deactived), + KPATCH_STATUS_ENABLE => Ok(PatchStatus::Actived), + _ => Err(std::io::Error::from(Errno::EINVAL)), + } } diff --git a/syscared/src/patch/driver/kpatch/target.rs b/syscared/src/patch/driver/kpatch/target.rs index 49f1185e..3f4a4650 100644 --- a/syscared/src/patch/driver/kpatch/target.rs +++ b/syscared/src/patch/driver/kpatch/target.rs @@ -12,126 +12,88 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; +use std::{ + collections::{hash_map::Entry, HashMap}, + ffi::OsString, +}; -use indexmap::IndexMap; +use indexmap::IndexSet; use uuid::Uuid; -use crate::patch::entity::KernelPatchFunction; - -#[derive(Debug)] -pub struct PatchFunction { - pub uuid: Uuid, - pub name: OsString, - pub size: u64, -} - -impl PatchFunction { - fn new(uuid: Uuid, function: &KernelPatchFunction) -> Self { - Self { - uuid, - name: function.name.to_os_string(), - size: function.new_size, - } - } - - fn is_same_function(&self, uuid: &Uuid, function: &KernelPatchFunction) -> bool { - (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) - } -} +use crate::patch::entity::KernelPatch; #[derive(Debug)] pub struct PatchTarget { - name: OsString, - function_map: IndexMap>, // function addr -> function collision list + object_name: OsString, + collision_map: HashMap>, // function name -> patch collision list } impl PatchTarget { - pub fn new(name: OsString) -> Self { + pub fn new(object_name: OsString) -> Self { Self { - name, - function_map: IndexMap::new(), + object_name, + collision_map: HashMap::new(), } } } impl PatchTarget { - pub fn has_function(&self) -> bool { - self.function_map.is_empty() - } - - pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if self.name != function.object { - continue; + pub fn add_patch(&mut self, patch: &KernelPatch) { + if let Some(functions) = patch.functions.get(&self.object_name) { + for function in functions { + self.collision_map + .entry(function.name.clone()) + .or_default() + .insert(patch.uuid); } - self.function_map - .entry(function.name.clone()) - .or_default() - .push(PatchFunction::new(uuid, function)); } } - pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if self.name != function.object { - continue; - } - if let Some(collision_list) = self.function_map.get_mut(&function.name) { - if let Some(index) = collision_list - .iter() - .position(|patch_function| patch_function.is_same_function(uuid, function)) + pub fn remove_patch(&mut self, patch: &KernelPatch) { + if let Some(functions) = patch.functions.get(&self.object_name) { + for function in functions { + if let Entry::Occupied(mut entry) = self.collision_map.entry(function.name.clone()) { - collision_list.remove(index); - if collision_list.is_empty() { - self.function_map.remove(&function.name); + let patch_set = entry.get_mut(); + patch_set.shift_remove(&patch.uuid); + + if patch_set.is_empty() { + entry.remove(); } } } } } -} -impl PatchTarget { - pub fn get_conflicts<'a, I>( + pub fn is_patched(&self) -> bool { + !self.collision_map.is_empty() + } + + pub fn get_conflicted_patches<'a>( &'a self, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - if self.name != function.object { - return None; - } - self.function_map - .get(&function.name) - .and_then(|list| list.last()) - }) + patch: &'a KernelPatch, + ) -> impl Iterator + 'a { + let functions = patch.functions.get(&self.object_name).into_iter().flatten(); + functions + .filter_map(move |function| self.collision_map.get(&function.name)) + .flatten() + .copied() + .filter(move |&uuid| uuid != patch.uuid) } - pub fn get_overrides<'a, I>( + pub fn get_overridden_patches<'a>( &'a self, - uuid: &'a Uuid, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - if self.name != function.object { - return None; - } - self.function_map - .get(&function.name) - .and_then(|list| list.last()) - .filter(|patch_function| !patch_function.is_same_function(uuid, function)) - }) + patch: &'a KernelPatch, + ) -> impl Iterator + 'a { + let functions = patch.functions.get(&self.object_name).into_iter().flatten(); + functions + .filter_map(move |function| self.collision_map.get(&function.name)) + .flat_map(move |collision_list| { + collision_list + .iter() + .copied() + .skip_while(move |&uuid| uuid != patch.uuid) + .skip(1) + }) } } diff --git a/syscared/src/patch/driver/mod.rs b/syscared/src/patch/driver/mod.rs index 707bf2f9..3752ca85 100644 --- a/syscared/src/patch/driver/mod.rs +++ b/syscared/src/patch/driver/mod.rs @@ -14,7 +14,7 @@ use anyhow::{Context, Result}; -use log::info; +use log::{debug, info}; use syscare_abi::PatchStatus; mod kpatch; @@ -39,17 +39,17 @@ pub struct PatchDriver { } impl PatchDriver { - fn check_conflict_functions(&self, patch: &Patch) -> Result<()> { + fn check_conflicted_patches(&self, patch: &Patch) -> Result<()> { match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check_conflict_functions(kpatch), - Patch::UserPatch(upatch) => self.upatch.check_conflict_functions(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_conflicted_patches(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_conflicted_patches(upatch), } } - fn check_override_functions(&self, patch: &Patch) -> Result<()> { + fn check_overridden_patches(&self, patch: &Patch) -> Result<()> { match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check_override_functions(kpatch), - Patch::UserPatch(upatch) => self.upatch.check_override_functions(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_overridden_patches(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_overridden_patches(upatch), } } } @@ -70,54 +70,62 @@ impl PatchDriver { }) } - /// Fetch and return the patch status. - pub fn patch_status(&self, patch: &Patch) -> Result { - match patch { - Patch::KernelPatch(kpatch) => self.kpatch.status(kpatch), - Patch::UserPatch(upatch) => self.upatch.status(upatch), + /// Perform patch confliction check.
+ /// Used for patch check. + pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + if flag == PatchOpFlag::Force { + return Ok(()); } - .with_context(|| format!("Failed to get patch '{}' status", patch)) + self.check_conflicted_patches(patch) + .with_context(|| format!("Patch '{}' is conflicted", patch)) } /// Perform patch file intergrity & consistency check.
/// Should be used before patch application. pub fn check_patch(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Checking patch '{}'...", patch); + if flag == PatchOpFlag::Force { return Ok(()); } match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check(kpatch), - Patch::UserPatch(upatch) => self.upatch.check(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_patch(upatch), } .with_context(|| format!("Patch '{}' is not patchable", patch)) } - /// Perform patch confliction check.
- /// Used for patch check. - pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { - if flag == PatchOpFlag::Force { - return Ok(()); + /// Fetch and return the patch status. + pub fn get_patch_status(&self, patch: &Patch) -> Result { + debug!("Fetching patch '{}' status...", patch); + + match patch { + Patch::KernelPatch(kpatch) => self.kpatch.get_patch_status(kpatch), + Patch::UserPatch(upatch) => self.upatch.get_patch_status(upatch), } - self.check_conflict_functions(patch) - .with_context(|| format!("Patch '{}' is conflicted", patch)) + .with_context(|| format!("Failed to get patch '{}' status", patch)) } - /// Apply a patch.
+ /// Load a patch.
/// After this action, the patch status would be changed to 'DEACTIVED'. - pub fn apply_patch(&mut self, patch: &Patch) -> Result<()> { + pub fn load_patch(&mut self, patch: &Patch) -> Result<()> { + info!("Loading patch '{}'...", patch); + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.apply(kpatch), - Patch::UserPatch(upatch) => self.upatch.apply(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.load_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.load_patch(upatch), } - .with_context(|| format!("Failed to apply patch '{}'", patch)) + .with_context(|| format!("Failed to load patch '{}'", patch)) } /// Remove a patch.
/// After this action, the patch status would be changed to 'NOT-APPLIED'. pub fn remove_patch(&mut self, patch: &Patch) -> Result<()> { + info!("Removing patch '{}'...", patch); + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.remove(kpatch), - Patch::UserPatch(upatch) => self.upatch.remove(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.remove_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.remove_patch(upatch), } .with_context(|| format!("Failed to remove patch '{}'", patch)) } @@ -125,12 +133,15 @@ impl PatchDriver { /// Active a patch.
/// After this action, the patch status would be changed to 'ACTIVED'. pub fn active_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Activating patch '{}'...", patch); + if flag != PatchOpFlag::Force { - self.check_conflict_functions(patch)?; + self.check_conflicted_patches(patch)?; } + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.active(kpatch), - Patch::UserPatch(upatch) => self.upatch.active(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.active_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.active_patch(upatch), } .with_context(|| format!("Failed to active patch '{}'", patch)) } @@ -138,12 +149,15 @@ impl PatchDriver { /// Deactive a patch.
/// After this action, the patch status would be changed to 'DEACTIVED'. pub fn deactive_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Deactivating patch '{}'...", patch); + if flag != PatchOpFlag::Force { - self.check_override_functions(patch)?; + self.check_overridden_patches(patch)?; } + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.deactive(kpatch), - Patch::UserPatch(upatch) => self.upatch.deactive(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.deactive_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.deactive_patch(upatch), } .with_context(|| format!("Failed to deactive patch '{}'", patch)) } diff --git a/syscared/src/patch/driver/upatch/entity.rs b/syscared/src/patch/driver/upatch/entity.rs deleted file mode 100644 index 5d0a9c01..00000000 --- a/syscared/src/patch/driver/upatch/entity.rs +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::path::PathBuf; - -use indexmap::{indexset, IndexSet}; - -#[derive(Debug)] -pub struct PatchEntity { - pub patch_file: PathBuf, - process_list: IndexSet, -} - -impl PatchEntity { - pub fn new(patch_file: PathBuf) -> Self { - Self { - patch_file, - process_list: indexset! {}, - } - } -} - -impl PatchEntity { - pub fn add_process(&mut self, pid: i32) { - self.process_list.insert(pid); - } - - pub fn remove_process(&mut self, pid: i32) { - self.process_list.remove(&pid); - } - - pub fn clean_dead_process(&mut self, process_list: &IndexSet) { - self.process_list.retain(|pid| process_list.contains(pid)); - } - - pub fn need_actived(&self, process_list: &IndexSet) -> IndexSet { - process_list - .difference(&self.process_list) - .copied() - .collect() - } - - pub fn need_deactived(&self, process_list: &IndexSet) -> IndexSet { - process_list - .intersection(&self.process_list) - .copied() - .collect() - } -} diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs index c31ef78e..cc00f531 100644 --- a/syscared/src/patch/driver/upatch/mod.rs +++ b/syscared/src/patch/driver/upatch/mod.rs @@ -13,6 +13,7 @@ */ use std::{ + collections::{HashMap, HashSet}, ffi::OsStr, fmt::Write, iter::FromIterator, @@ -21,21 +22,16 @@ use std::{ sync::Arc, }; -use anyhow::{bail, ensure, Context, Result}; -use indexmap::{indexset, IndexMap, IndexSet}; -use log::{debug, info, warn}; +use anyhow::{bail, ensure, Result}; +use log::{debug, warn}; use parking_lot::RwLock; use uuid::Uuid; use syscare_abi::PatchStatus; use syscare_common::{fs, util::digest}; -use crate::{ - config::UserPatchConfig, - patch::{driver::upatch::entity::PatchEntity, entity::UserPatch}, -}; +use crate::{config::UserPatchConfig, patch::entity::UserPatch}; -mod entity; mod monitor; mod sys; mod target; @@ -44,19 +40,19 @@ use monitor::UserPatchMonitor; use target::PatchTarget; pub struct UserPatchDriver { - status_map: IndexMap, - target_map: Arc>>, - skipped_files: Arc>, + status_map: HashMap, + target_map: Arc>>, + skipped_files: Arc>, monitor: UserPatchMonitor, } impl UserPatchDriver { pub fn new(config: &UserPatchConfig) -> Result { - let target_map = Arc::new(RwLock::new(IndexMap::new())); - let skipped_files = Arc::new(IndexSet::from_iter(config.skipped.iter().cloned())); + let target_map = Arc::new(RwLock::new(HashMap::new())); + let skipped_files = Arc::new(HashSet::from_iter(config.skipped.iter().cloned())); Ok(Self { - status_map: IndexMap::new(), + status_map: HashMap::new(), target_map: target_map.clone(), skipped_files: skipped_files.clone(), monitor: UserPatchMonitor::new(move |target_elfs| { @@ -70,7 +66,7 @@ impl UserPatchDriver { impl UserPatchDriver { #[inline] - fn get_patch_status(&self, uuid: &Uuid) -> PatchStatus { + fn read_patch_status(&self, uuid: &Uuid) -> PatchStatus { self.status_map .get(uuid) .copied() @@ -78,7 +74,7 @@ impl UserPatchDriver { } #[inline] - fn set_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { + fn write_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { *self.status_map.entry(*uuid).or_default() = value; } @@ -87,28 +83,6 @@ impl UserPatchDriver { } } -impl UserPatchDriver { - fn add_patch_target(&mut self, patch: &UserPatch) { - let target_elf = patch.target_elf.as_path(); - let mut target_map = self.target_map.write(); - - if !target_map.contains_key(target_elf) { - target_map.insert(target_elf.to_path_buf(), PatchTarget::default()); - } - } - - fn remove_patch_target(&mut self, patch: &UserPatch) { - let target_elf = patch.target_elf.as_path(); - let mut target_map = self.target_map.write(); - - if let Some(target) = target_map.get_mut(target_elf) { - if !target.is_patched() { - target_map.remove(target_elf); - } - } - } -} - impl UserPatchDriver { fn check_consistency(patch: &UserPatch) -> Result<()> { let real_checksum = digest::file(&patch.patch_file)?; @@ -122,56 +96,39 @@ impl UserPatchDriver { Ok(()) } - fn check_compatiblity(_patch: &UserPatch) -> Result<()> { - Ok(()) - } - - pub fn check_conflict_functions(&self, patch: &UserPatch) -> Result<()> { - let conflict_patches = match self.target_map.read().get(&patch.target_elf) { - Some(target) => target - .get_conflicts(&patch.functions) - .into_iter() - .map(|record| record.uuid) - .collect(), - None => indexset! {}, + pub fn check_conflicted_patches(&self, patch: &UserPatch) -> Result<()> { + let conflicted = match self.target_map.read().get(&patch.target_elf) { + Some(target) => target.get_conflicted_patches(patch).collect(), + None => HashSet::new(), }; - ensure!(conflict_patches.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Upatch: Patch is conflicted with")?; - for uuid in conflict_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(conflicted.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Upatch: Patch is conflicted with")?; + for uuid in conflicted.into_iter() { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_override_functions(&self, patch: &UserPatch) -> Result<()> { - let override_patches = match self.target_map.read().get(&patch.target_elf) { - Some(target) => target - .get_overrides(&patch.uuid, &patch.functions) - .into_iter() - .map(|record| record.uuid) - .collect(), - None => indexset! {}, + pub fn check_overridden_patches(&self, patch: &UserPatch) -> Result<()> { + let overridden = match self.target_map.read().get(&patch.target_elf) { + Some(target) => target.get_overridden_patches(patch).collect(), + None => HashSet::new(), }; - ensure!(override_patches.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Upatch: Patch is overrided by")?; - for uuid in override_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(overridden.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Upatch: Patch is overridden by")?; + for uuid in overridden.into_iter() { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); - Ok(()) } } @@ -187,10 +144,10 @@ impl UserPatchDriver { } fn find_target_process>( - skipped_files: &IndexSet, + skipped_files: &HashSet, target_elf: P, - ) -> Result> { - let mut target_pids = IndexSet::new(); + ) -> Result> { + let mut target_pids = HashSet::new(); let target_path = target_elf.as_ref(); let target_inode = target_path.metadata()?.st_ino(); @@ -233,8 +190,8 @@ impl UserPatchDriver { } fn patch_new_process( - target_map: &RwLock>, - skipped_files: &IndexSet, + target_map: &RwLock>, + skipped_files: &HashSet, target_elf: &Path, ) { let process_list = match Self::find_target_process(skipped_files, target_elf) { @@ -242,33 +199,32 @@ impl UserPatchDriver { Err(_) => return, }; - let mut patch_target_map = target_map.write(); - let patch_target = match patch_target_map.get_mut(target_elf) { + let mut target_map = target_map.write(); + let patch_target = match target_map.get_mut(target_elf) { Some(target) => target, None => return, }; + patch_target.clean_dead_process(&process_list); - for (patch_uuid, patch_entity) in patch_target.all_patches() { - patch_entity.clean_dead_process(&process_list); + let all_patches = patch_target.all_patches().collect::>(); + let need_actived = patch_target.need_actived(&process_list); - // Active patch - let need_actived = patch_entity.need_actived(&process_list); + for (uuid, patch_file) in all_patches { if !need_actived.is_empty() { debug!( - "Activating patch '{}' ({}) for process {:?}", - patch_uuid, + "Upatch: Activating patch '{}' ({}) for process {:?}", + uuid, target_elf.display(), need_actived, ); } - - for pid in need_actived { - match sys::active_patch(patch_uuid, pid, target_elf, &patch_entity.patch_file) { - Ok(_) => patch_entity.add_process(pid), + for &pid in &need_actived { + match sys::active_patch(&uuid, pid, target_elf, &patch_file) { + Ok(_) => patch_target.add_process(pid), Err(e) => { warn!( "Upatch: Failed to active patch '{}' for process {}, {}", - patch_uuid, + uuid, pid, e.to_string().to_lowercase(), ); @@ -280,198 +236,140 @@ impl UserPatchDriver { } impl UserPatchDriver { - pub fn status(&self, patch: &UserPatch) -> Result { - Ok(self.get_patch_status(&patch.uuid)) - } - - pub fn check(&self, patch: &UserPatch) -> Result<()> { + pub fn check_patch(&self, patch: &UserPatch) -> Result<()> { Self::check_consistency(patch)?; - Self::check_compatiblity(patch)?; - Ok(()) } - pub fn apply(&mut self, patch: &UserPatch) -> Result<()> { - info!( - "Applying patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - - self.add_patch_target(patch); - self.set_patch_status(&patch.uuid, PatchStatus::Deactived); + pub fn get_patch_status(&self, patch: &UserPatch) -> Result { + Ok(self.read_patch_status(&patch.uuid)) + } + pub fn load_patch(&mut self, patch: &UserPatch) -> Result<()> { + self.write_patch_status(&patch.uuid, PatchStatus::Deactived); Ok(()) } - pub fn remove(&mut self, patch: &UserPatch) -> Result<()> { - info!( - "Removing patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - - self.remove_patch_target(patch); + pub fn remove_patch(&mut self, patch: &UserPatch) -> Result<()> { self.remove_patch_status(&patch.uuid); - Ok(()) } - pub fn active(&mut self, patch: &UserPatch) -> Result<()> { - let patch_uuid = &patch.uuid; - let patch_file = patch.patch_file.as_path(); - let patch_functions = patch.functions.as_slice(); - let target_elf = patch.target_elf.as_path(); - - let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; + pub fn active_patch(&mut self, patch: &UserPatch) -> Result<()> { + let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; let mut target_map = self.target_map.write(); - let patch_target = target_map - .get_mut(target_elf) - .context("Upatch: Cannot find patch target")?; - let mut patch_entity = match patch_target.get_patch(patch_uuid) { - Some(_) => bail!("Upatch: Patch is already exist"), - None => PatchEntity::new(patch_file.to_path_buf()), - }; + let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); + patch_target.clean_dead_process(&process_list); + + // If target is not patched before, start watching it + let start_watch = !patch_target.is_patched(); // Active patch - info!( - "Activating patch '{}' ({}) for {}", - patch_uuid, - patch_file.display(), - target_elf.display(), - ); + let need_actived = patch_target.need_actived(&process_list); + let mut results = Vec::new(); - for pid in patch_entity.need_actived(&process_list) { - let result = sys::active_patch(patch_uuid, pid, target_elf, patch_file); - if result.is_ok() { - patch_entity.add_process(pid); - } + for pid in need_actived { + let result = sys::active_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); results.push((pid, result)); } - // Check results, return error if all process fails + // Return error if all process fails if !results.is_empty() && results.iter().all(|(_, result)| result.is_err()) { - let mut err_msg = String::new(); - - writeln!(err_msg, "Upatch: Failed to active patch")?; + let mut msg = String::new(); + writeln!(msg, "Upatch: Failed to active patch")?; for (pid, result) in &results { if let Err(e) = result { - writeln!(err_msg, "* Process {}: {}", pid, e)?; + writeln!(msg, "* Process {}: {}", pid, e)?; } } - err_msg.pop(); - bail!(err_msg); + msg.pop(); + bail!(msg); } - // Print failure results - for (pid, result) in &results { - if let Err(e) = result { - warn!( - "Upatch: Failed to active patch '{}' for process {}, {}", - patch_uuid, - pid, - e.to_string().to_lowercase(), - ); + // Process results + for (pid, result) in results { + match result { + Ok(_) => patch_target.add_process(pid), + Err(e) => { + warn!( + "Upatch: Failed to active patch '{}' for process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), + ); + } } } - - // If target is no patched before, start watching it - let need_start_watch = !patch_target.is_patched(); - - // Apply patch to target - patch_target.add_patch(*patch_uuid, patch_entity); - patch_target.add_functions(*patch_uuid, patch_functions); + patch_target.add_patch(patch); // Drop the lock drop(target_map); - if need_start_watch { - self.monitor.watch_file(target_elf)?; + if start_watch { + self.monitor.watch_file(&patch.target_elf)?; } - self.set_patch_status(patch_uuid, PatchStatus::Actived); + self.write_patch_status(&patch.uuid, PatchStatus::Actived); Ok(()) } - pub fn deactive(&mut self, patch: &UserPatch) -> Result<()> { - let patch_uuid = &patch.uuid; - let patch_file = patch.patch_file.as_path(); - let patch_functions = patch.functions.as_slice(); - let target_elf = patch.target_elf.as_path(); - - let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; + pub fn deactive_patch(&mut self, patch: &UserPatch) -> Result<()> { + let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; let mut target_map = self.target_map.write(); - let patch_target = target_map - .get_mut(target_elf) - .context("Upatch: Cannot find patch target")?; - let patch_entity = patch_target - .get_patch(patch_uuid) - .context("Upatch: Cannot find patch entity")?; - - // Remove dead process - patch_entity.clean_dead_process(&process_list); + let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); + patch_target.clean_dead_process(&process_list); // Deactive patch - info!( - "Deactivating patch '{}' ({}) for {}", - patch_uuid, - patch_file.display(), - target_elf.display(), - ); - - let need_deactived = patch_entity.need_deactived(&process_list); + let need_deactive = patch_target.need_deactived(&process_list); let mut results = Vec::new(); - for pid in need_deactived { - let result = sys::deactive_patch(patch_uuid, pid, target_elf, patch_file); - if result.is_ok() { - patch_entity.remove_process(pid) - } + for pid in need_deactive { + let result = + sys::deactive_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); results.push((pid, result)); } - // Check results, return error if any process failes + // Return error if all process fails if !results.is_empty() && results.iter().any(|(_, result)| result.is_err()) { - let mut err_msg = String::new(); - - writeln!(err_msg, "Upatch: Failed to deactive patch")?; + let mut msg = String::new(); + writeln!(msg, "Upatch: Failed to deactive patch")?; for (pid, result) in &results { if let Err(e) = result { - writeln!(err_msg, "* Process {}: {}", pid, e)?; + writeln!(msg, "* Process {}: {}", pid, e)?; } } - err_msg.pop(); - bail!(err_msg); + msg.pop(); + bail!(msg); } - // Print failure results - for (pid, result) in &results { - if let Err(e) = result { - warn!( - "Upatch: Failed to deactive patch '{}' for process {}, {}", - patch_uuid, - pid, - e.to_string().to_lowercase(), - ); + // Process results + for (pid, result) in results { + match result { + Ok(_) => patch_target.remove_process(pid), + Err(e) => { + warn!( + "Upatch: Failed to deactive patch '{}' for process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), + ); + } } } + patch_target.remove_patch(patch); - // Remove patch functions from target - patch_target.remove_patch(patch_uuid); - patch_target.remove_functions(patch_uuid, patch_functions); - - // If target is no longer patched, stop watching it - let need_stop_watch = !patch_target.is_patched(); + // If target is no longer has patch, stop watching it + let stop_watch = !patch_target.is_patched(); drop(target_map); - if need_stop_watch { - self.monitor.ignore_file(target_elf)?; + if stop_watch { + self.monitor.ignore_file(&patch.target_elf)?; } - self.set_patch_status(patch_uuid, PatchStatus::Deactived); + self.write_patch_status(&patch.uuid, PatchStatus::Deactived); Ok(()) } } diff --git a/syscared/src/patch/driver/upatch/monitor.rs b/syscared/src/patch/driver/upatch/monitor.rs index c0c1b3cb..150d1a35 100644 --- a/syscared/src/patch/driver/upatch/monitor.rs +++ b/syscared/src/patch/driver/upatch/monitor.rs @@ -20,7 +20,7 @@ use std::{ time::Duration, }; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; use inotify::{Inotify, WatchDescriptor, WatchMask}; use log::info; @@ -75,13 +75,19 @@ impl UserPatchMonitor { Some(inotify) => { let wd = inotify .add_watch(watch_file, WatchMask::OPEN) - .with_context(|| format!("Failed to watch file {}", watch_file.display()))?; + .map_err(|e| { + anyhow!( + "Failed to watch file '{}', {}", + watch_file.display(), + e.to_string().to_lowercase() + ) + })?; self.watch_file_map .write() .insert(wd.clone(), watch_file.to_owned()); self.watch_wd_map.lock().insert(watch_file.to_owned(), wd); - info!("Start watching file {}", watch_file.display()); + info!("Start watching file '{}'", watch_file.display()); } None => bail!("Inotify does not exist"), } @@ -97,10 +103,14 @@ impl UserPatchMonitor { Some(inotify) => { self.watch_file_map.write().remove(&wd); - inotify.rm_watch(wd).with_context(|| { - format!("Failed to stop watch file {}", ignore_file.display()) + inotify.rm_watch(wd).map_err(|e| { + anyhow!( + "Failed to stop watch file '{}', {}", + ignore_file.display(), + e.to_string().to_lowercase() + ) })?; - info!("Stop watching file {}", ignore_file.display()); + info!("Stop watching file '{}'", ignore_file.display()); } None => bail!("Inotify does not exist"), } @@ -124,7 +134,13 @@ where thread::Builder::new() .name(MONITOR_THREAD_NAME.to_string()) .spawn(move || self.thread_main()) - .with_context(|| format!("Failed to create thread '{}'", MONITOR_THREAD_NAME)) + .map_err(|e| { + anyhow!( + "Failed to create thread '{}', {}", + MONITOR_THREAD_NAME, + e.to_string().to_lowercase() + ) + }) } #[inline] diff --git a/syscared/src/patch/driver/upatch/sys.rs b/syscared/src/patch/driver/upatch/sys.rs index ecc05224..85188401 100644 --- a/syscared/src/patch/driver/upatch/sys.rs +++ b/syscared/src/patch/driver/upatch/sys.rs @@ -1,14 +1,27 @@ use std::path::Path; use anyhow::{bail, Result}; -use log::Level; +use log::{debug, Level}; use uuid::Uuid; use syscare_common::process::Command; const UPATCH_MANAGE_BIN: &str = "upatch-manage"; -pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { +pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> +where + P: AsRef, + Q: AsRef, +{ + let target_elf = target_elf.as_ref(); + let patch_file = patch_file.as_ref(); + + debug!( + "Upatch: Patching '{}' to '{}' (pid: {})...", + patch_file.display(), + target_elf.display(), + pid, + ); let exit_code = Command::new(UPATCH_MANAGE_BIN) .arg("patch") .arg("--uuid") @@ -29,7 +42,20 @@ pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) } } -pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { +pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> +where + P: AsRef, + Q: AsRef, +{ + let target_elf = target_elf.as_ref(); + let patch_file = patch_file.as_ref(); + + debug!( + "Upatch: Unpatching '{}' from '{}' (pid: {})...", + patch_file.display(), + target_elf.display(), + pid, + ); let exit_code = Command::new(UPATCH_MANAGE_BIN) .arg("unpatch") .arg("--uuid") diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs index 26c3ed32..e0e9f882 100644 --- a/syscared/src/patch/driver/upatch/target.rs +++ b/syscared/src/patch/driver/upatch/target.rs @@ -12,125 +12,113 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + path::PathBuf, +}; -use indexmap::IndexMap; +use indexmap::IndexSet; use uuid::Uuid; -use crate::patch::entity::UserPatchFunction; - -use super::entity::PatchEntity; - -#[derive(Debug)] -pub struct PatchFunction { - pub uuid: Uuid, - pub name: OsString, - pub size: u64, -} - -impl PatchFunction { - pub fn new(uuid: Uuid, function: &UserPatchFunction) -> Self { - Self { - uuid, - name: function.name.to_os_string(), - size: function.new_size, - } - } - - pub fn is_same_function(&self, uuid: &Uuid, function: &UserPatchFunction) -> bool { - (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) - } -} +use crate::patch::entity::UserPatch; #[derive(Debug, Default)] pub struct PatchTarget { - patch_map: IndexMap, // patched file data - function_map: IndexMap>, // function addr -> function collision list + process_list: HashSet, + patch_map: HashMap, // uuid -> patch file + collision_map: HashMap>, // function old addr -> patch collision list } impl PatchTarget { - pub fn is_patched(&self) -> bool { - !self.patch_map.is_empty() + pub fn add_process(&mut self, pid: i32) { + self.process_list.insert(pid); } - pub fn add_patch(&mut self, uuid: Uuid, entity: PatchEntity) { - self.patch_map.insert(uuid, entity); + pub fn remove_process(&mut self, pid: i32) { + self.process_list.remove(&pid); } - pub fn remove_patch(&mut self, uuid: &Uuid) { - self.patch_map.remove(uuid); + pub fn clean_dead_process(&mut self, process_list: &HashSet) { + self.process_list.retain(|pid| process_list.contains(pid)); } - pub fn get_patch(&mut self, uuid: &Uuid) -> Option<&mut PatchEntity> { - self.patch_map.get_mut(uuid) + pub fn need_actived(&self, process_list: &HashSet) -> HashSet { + process_list + .difference(&self.process_list) + .copied() + .collect() } - pub fn all_patches(&mut self) -> impl IntoIterator { - self.patch_map.iter_mut() + pub fn need_deactived(&self, process_list: &HashSet) -> HashSet { + process_list + .intersection(&self.process_list) + .copied() + .collect() } } impl PatchTarget { - pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - self.function_map + pub fn add_patch(&mut self, patch: &UserPatch) { + for function in &patch.functions { + self.collision_map .entry(function.old_addr) .or_default() - .push(PatchFunction::new(uuid, function)); + .insert(patch.uuid); } + self.patch_map.insert(patch.uuid, patch.patch_file.clone()); } - pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if let Some(collision_list) = self.function_map.get_mut(&function.old_addr) { - if let Some(index) = collision_list - .iter() - .position(|patch_function| patch_function.is_same_function(uuid, function)) - { - collision_list.remove(index); - if collision_list.is_empty() { - self.function_map.remove(&function.old_addr); - } + pub fn remove_patch(&mut self, patch: &UserPatch) { + for function in &patch.functions { + if let Entry::Occupied(mut entry) = self.collision_map.entry(function.old_addr) { + let patch_set = entry.get_mut(); + patch_set.shift_remove(&patch.uuid); + + if patch_set.is_empty() { + entry.remove(); } } } + self.patch_map.remove(&patch.uuid); } -} -impl PatchTarget { - pub fn get_conflicts<'a, I>( + pub fn is_patched(&self) -> bool { + !self.collision_map.is_empty() + } + + pub fn all_patches(&self) -> impl Iterator + '_ { + self.patch_map + .iter() + .map(|(uuid, path)| (*uuid, path.to_path_buf())) + } + + pub fn get_conflicted_patches<'a>( &'a self, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - self.function_map - .get(&function.old_addr) - .and_then(|list| list.last()) - }) + patch: &'a UserPatch, + ) -> impl Iterator + 'a { + patch + .functions + .iter() + .filter_map(move |function| self.collision_map.get(&function.old_addr)) + .flatten() + .copied() + .filter(move |&uuid| uuid != patch.uuid) } - pub fn get_overrides<'a, I>( + pub fn get_overridden_patches<'a>( &'a self, - uuid: &'a Uuid, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - self.function_map - .get(&function.old_addr) - .and_then(|list| list.last()) - .filter(|patch_function| !patch_function.is_same_function(uuid, function)) - }) + patch: &'a UserPatch, + ) -> impl Iterator + 'a { + patch + .functions + .iter() + .filter_map(move |function| self.collision_map.get(&function.old_addr)) + .flat_map(move |collision_list| { + collision_list + .iter() + .copied() + .skip_while(move |&uuid| uuid != patch.uuid) + .skip(1) + }) } } diff --git a/syscared/src/patch/entity/kpatch.rs b/syscared/src/patch/entity/kpatch.rs index 4c0be9c8..09e759dd 100644 --- a/syscared/src/patch/entity/kpatch.rs +++ b/syscared/src/patch/entity/kpatch.rs @@ -12,11 +12,78 @@ * See the Mulan PSL v2 for more details. */ -use std::{ffi::OsString, path::PathBuf, sync::Arc}; +use std::{ + collections::HashMap, + ffi::{CStr, OsStr, OsString}, + path::{Path, PathBuf}, + sync::Arc, +}; -use syscare_abi::PatchInfo; +use anyhow::{anyhow, Context, Result}; +use object::{File, Object, ObjectSection}; use uuid::Uuid; +use syscare_abi::{PatchEntity, PatchInfo}; +use syscare_common::{ffi::CStrExt, fs, os::kernel}; + +mod ffi { + use std::os::raw::{c_char, c_long, c_ulong}; + + use object::{Pod, Relocation, SectionRelocationIterator}; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` + pub struct KpatchFunction { + pub new_addr: c_ulong, + pub new_size: c_ulong, + pub old_addr: c_ulong, + pub old_size: c_ulong, + pub sympos: u64, + pub name: *const c_char, + pub obj_name: *const c_char, + pub ref_name: *const c_char, + pub ref_offset: c_long, + } + + pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); + pub const KPATCH_FUNCTION_OFFSET: usize = 40; + pub const KPATCH_OBJECT_OFFSET: usize = 48; + + /* + * SAFETY: This struct is + * - #[repr(C)] + * - have no invalid byte values + * - have no padding + */ + unsafe impl Pod for KpatchFunction {} + + pub struct KpatchRelocation { + pub _addr: (u64, Relocation), + pub name: (u64, Relocation), + pub object: (u64, Relocation), + } + + pub struct KpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); + + impl Iterator for KpatchRelocationIterator<'_, '_> { + type Item = KpatchRelocation; + + fn next(&mut self) -> Option { + if let (Some(addr), Some(name), Some(object)) = + (self.0.next(), self.0.next(), self.0.next()) + { + return Some(KpatchRelocation { + _addr: addr, + name, + object, + }); + } + None + } + } +} + /// Kernel patch function definition #[derive(Clone)] pub struct KernelPatchFunction { @@ -63,10 +130,157 @@ pub struct KernelPatch { pub name: OsString, pub info: Arc, pub pkg_name: String, - pub module_name: OsString, pub target_name: OsString, - pub functions: Vec, pub patch_file: PathBuf, - pub sys_file: PathBuf, + pub status_file: PathBuf, + pub module: kernel::ModuleInfo, + pub functions: HashMap>, // object name -> function list pub checksum: String, } + +impl KernelPatch { + fn parse_functions(patch_file: &Path) -> Result>> { + const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; + const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; + + let mmap = fs::mmap(patch_file).map_err(|e| { + anyhow!( + "Failed to mmap '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + let file = File::parse(mmap.as_ref()).map_err(|e| { + anyhow!( + "Failed to parse '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + // Read sections + let function_section = file + .section_by_name(KPATCH_FUNCS_SECTION) + .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; + let string_section = file + .section_by_name(KPATCH_STRINGS_SECTION) + .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; + let function_data = function_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + KPATCH_FUNCS_SECTION, + e.to_string().to_lowercase() + ) + })?; + let string_data = string_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + KPATCH_STRINGS_SECTION, + e.to_string().to_lowercase() + ) + })?; + + // Resolve patch functions + let (slice, _) = object::slice_from_bytes::( + function_data, + function_data.len() / ffi::KPATCH_FUNCTION_SIZE, + ) + .map_err(|_| anyhow!("Invalid patch function layout"))?; + + let mut functions: Vec<_> = slice + .iter() + .map(|function| KernelPatchFunction { + name: OsString::new(), + object: OsString::new(), + old_addr: function.old_addr, + old_size: function.old_size, + new_addr: function.new_addr, + new_size: function.new_size, + }) + .collect(); + + // Relocate patch functions + for relocation in ffi::KpatchRelocationIterator(function_section.relocations()) { + let (name_offset, name_reloc) = relocation.name; + let (object_offset, obj_reloc) = relocation.object; + + // Relocate patch function name + let name_index = + (name_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; + let name_function = functions + .get_mut(name_index) + .with_context(|| format!("Invalid patch function index, index={}", name_index))?; + let name_addend = name_reloc.addend() as usize; + name_function.name = CStr::from_bytes_with_next_nul(&string_data[name_addend..]) + .map_err(|_| anyhow!("Invalid patch function name"))? + .to_os_string(); + + // Relocate patch function object + let object_index = + (object_offset as usize - ffi::KPATCH_OBJECT_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; + let object_function = functions + .get_mut(object_index) + .with_context(|| format!("Invalid patch object index, index={}", object_index))?; + let object_addend = obj_reloc.addend() as usize; + object_function.object = CStr::from_bytes_with_next_nul(&string_data[object_addend..]) + .map_err(|_| anyhow!("Invalid patch object name"))? + .to_os_string(); + } + + // group functions by it's object + let mut function_map: HashMap<_, Vec<_>> = HashMap::new(); + for function in functions { + function_map + .entry(function.object.clone()) + .or_default() + .push(function); + } + + Ok(function_map) + } + + pub fn parse( + name: S, + patch_info: Arc, + patch_entity: &PatchEntity, + patch_file: P, + ) -> Result + where + S: AsRef, + P: AsRef, + { + const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; + const KPATCH_STATUS_FILE_NAME: &str = "enabled"; + + let patch_file = patch_file.as_ref(); + let module = kernel::module_info(patch_file).map_err(|e| { + anyhow!( + "Failed to parse '{}' modinfo, {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + let patch = Self { + uuid: patch_entity.uuid, + name: name.as_ref().to_os_string(), + info: patch_info.clone(), + pkg_name: patch_info.target.full_name(), + target_name: patch_entity.patch_name.as_os_str().to_os_string(), + patch_file: patch_file.to_path_buf(), + status_file: PathBuf::from(KPATCH_SYS_DIR) + .join(&module.name) + .join(KPATCH_STATUS_FILE_NAME), + module, + functions: Self::parse_functions(patch_file)?, + checksum: patch_entity.checksum.clone(), + }; + Ok(patch) + } +} + +impl std::fmt::Display for KernelPatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name.to_string_lossy()) + } +} diff --git a/syscared/src/patch/entity/patch.rs b/syscared/src/patch/entity/patch.rs index 2ff398a6..09413899 100644 --- a/syscared/src/patch/entity/patch.rs +++ b/syscared/src/patch/entity/patch.rs @@ -78,6 +78,9 @@ impl std::cmp::Ord for Patch { impl std::fmt::Display for Patch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name().to_string_lossy()) + match self { + Patch::KernelPatch(patch) => write!(f, "{}", patch), + Patch::UserPatch(patch) => write!(f, "{}", patch), + } } } diff --git a/syscared/src/patch/entity/symbol.rs b/syscared/src/patch/entity/symbol.rs deleted file mode 100644 index c7845aa6..00000000 --- a/syscared/src/patch/entity/symbol.rs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::OsString; - -/// Patch function definiation -#[derive(Clone)] -pub struct PatchFunction { - pub name: OsString, - pub target: OsString, - pub old_addr: u64, - pub old_size: u64, - pub new_addr: u64, - pub new_size: u64, -} - -impl std::fmt::Debug for PatchFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PatchFunction") - .field("name", &self.name) - .field("target", &self.target) - .field("old_addr", &format!("0x{}", self.old_addr)) - .field("old_size", &format!("0x{}", self.old_size)) - .field("new_addr", &format!("0x{}", self.new_addr)) - .field("new_size", &format!("0x{}", self.new_size)) - .finish() - } -} - -impl std::fmt::Display for PatchFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "name: {}, target: {}, old_addr: 0x{:x}, old_size: 0x{:x}, new_addr: 0x{:x}, new_size: 0x{:x}", - self.name.to_string_lossy(), - self.target.to_string_lossy(), - self.old_addr, - self.old_size, - self.new_addr, - self.new_size, - ) - } -} diff --git a/syscared/src/patch/entity/upatch.rs b/syscared/src/patch/entity/upatch.rs index 0fbacb4a..aaaee1c7 100644 --- a/syscared/src/patch/entity/upatch.rs +++ b/syscared/src/patch/entity/upatch.rs @@ -12,11 +12,66 @@ * See the Mulan PSL v2 for more details. */ -use std::{ffi::OsString, path::PathBuf, sync::Arc}; +use std::{ + ffi::{CStr, OsStr, OsString}, + path::{Path, PathBuf}, + sync::Arc, +}; -use syscare_abi::PatchInfo; +use anyhow::{anyhow, Context, Result}; +use object::{File, Object, ObjectSection}; use uuid::Uuid; +use syscare_abi::{PatchEntity, PatchInfo}; +use syscare_common::{ffi::CStrExt, fs}; + +mod ffi { + use std::os::raw::{c_char, c_ulong}; + + use object::{Pod, Relocation, SectionRelocationIterator}; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` + pub struct UpatchFunction { + pub new_addr: c_ulong, + pub new_size: c_ulong, + pub old_addr: c_ulong, + pub old_size: c_ulong, + pub sympos: c_ulong, + pub name: *const c_char, + } + + /* + * SAFETY: This struct is + * - #[repr(C)] + * - have no invalid byte values + * - have no padding + */ + unsafe impl Pod for UpatchFunction {} + + pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); + pub const UPATCH_FUNCTION_OFFSET: usize = 40; + + pub struct UpatchRelocation { + pub _addr: (u64, Relocation), + pub name: (u64, Relocation), + } + + pub struct UpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); + + impl Iterator for UpatchRelocationIterator<'_, '_> { + type Item = UpatchRelocation; + + fn next(&mut self) -> Option { + if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { + return Some(UpatchRelocation { _addr: addr, name }); + } + None + } + } +} + /// User patch function definition #[derive(Clone)] pub struct UserPatchFunction { @@ -31,10 +86,10 @@ impl std::fmt::Debug for UserPatchFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UserPatchFunction") .field("name", &self.name) - .field("old_addr", &format!("0x{}", self.old_addr)) - .field("old_size", &format!("0x{}", self.old_size)) - .field("new_addr", &format!("0x{}", self.new_addr)) - .field("new_size", &format!("0x{}", self.new_size)) + .field("old_addr", &format!("0x{:x}", self.old_addr)) + .field("old_size", &format!("0x{:x}", self.old_size)) + .field("new_addr", &format!("0x{:x}", self.new_addr)) + .field("new_size", &format!("0x{:x}", self.new_size)) .finish() } } @@ -60,8 +115,118 @@ pub struct UserPatch { pub name: OsString, pub info: Arc, pub pkg_name: String, - pub functions: Vec, - pub patch_file: PathBuf, pub target_elf: PathBuf, + pub patch_file: PathBuf, + pub functions: Vec, pub checksum: String, } + +impl UserPatch { + fn parse_functions(patch_file: &Path) -> Result> { + const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; + const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; + + let mmap = fs::mmap(patch_file).map_err(|e| { + anyhow!( + "Failed to mmap '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + let file = File::parse(mmap.as_ref()).map_err(|e| { + anyhow!( + "Failed to parse '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + // Read sections + let function_section = file + .section_by_name(UPATCH_FUNCS_SECTION) + .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; + let string_section = file + .section_by_name(UPATCH_STRINGS_SECTION) + .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; + let function_data = function_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + UPATCH_FUNCS_SECTION, + e.to_string().to_lowercase() + ) + })?; + let string_data = string_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + UPATCH_STRINGS_SECTION, + e.to_string().to_lowercase() + ) + })?; + + // Resolve patch functions + let (slice, _) = object::slice_from_bytes::( + function_data, + function_data.len() / ffi::UPATCH_FUNCTION_SIZE, + ) + .map_err(|_| anyhow!("Invalid patch function layout"))?; + + let mut functions: Vec<_> = slice + .iter() + .map(|function| UserPatchFunction { + name: OsString::new(), + old_addr: function.old_addr, + old_size: function.old_size, + new_addr: function.new_addr, + new_size: function.new_size, + }) + .collect(); + + // Relocate patch functions + for relocation in ffi::UpatchRelocationIterator(function_section.relocations()) { + let (value, reloc) = relocation.name; + + let index = (value as usize - ffi::UPATCH_FUNCTION_OFFSET) / ffi::UPATCH_FUNCTION_SIZE; + let function = functions + .get_mut(index) + .with_context(|| format!("Invalid patch function index, index={}", index))?; + let addend = reloc.addend() as usize; + + function.name = CStr::from_bytes_with_next_nul(&string_data[addend..]) + .map_err(|_| anyhow!("Invalid patch function name"))? + .to_os_string(); + } + + Ok(functions) + } + + pub fn parse( + name: S, + patch_info: Arc, + patch_entity: &PatchEntity, + patch_file: P, + ) -> Result + where + S: AsRef, + P: AsRef, + { + let patch_file = patch_file.as_ref(); + + let patch = Self { + uuid: patch_entity.uuid, + name: name.as_ref().to_os_string(), + info: patch_info.clone(), + pkg_name: patch_info.target.full_name(), + target_elf: patch_entity.patch_target.clone(), + patch_file: patch_file.to_path_buf(), + functions: Self::parse_functions(patch_file)?, + checksum: patch_entity.checksum.clone(), + }; + Ok(patch) + } +} + +impl std::fmt::Display for UserPatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name.to_string_lossy()) + } +} diff --git a/syscared/src/patch/manager.rs b/syscared/src/patch/manager.rs index 4bf65ac9..72484300 100644 --- a/syscared/src/patch/manager.rs +++ b/syscared/src/patch/manager.rs @@ -24,10 +24,16 @@ use lazy_static::lazy_static; use log::{debug, error, info, trace, warn}; use uuid::Uuid; -use syscare_abi::PatchStatus; +use syscare_abi::{PatchEntity, PatchInfo, PatchStatus, PatchType, PATCH_INFO_MAGIC}; use syscare_common::{concat_os, ffi::OsStrExt, fs, util::serde}; -use crate::{config::PatchConfig, patch::resolver::PatchResolver}; +use crate::{ + config::PatchConfig, + patch::{ + entity::{KernelPatch, UserPatch}, + PATCH_INFO_FILE_NAME, + }, +}; use super::{ driver::{PatchDriver, PatchOpFlag}, @@ -40,7 +46,7 @@ type TransitionAction = &'static (dyn Fn(&mut PatchManager, &Patch, PatchOpFlag) -> Result<()> + Sync); const PATCH_CHECK: TransitionAction = &PatchManager::driver_check_patch; -const PATCH_APPLY: TransitionAction = &PatchManager::driver_apply_patch; +const PATCH_LOAD: TransitionAction = &PatchManager::driver_load_patch; const PATCH_REMOVE: TransitionAction = &PatchManager::driver_remove_patch; const PATCH_ACTIVE: TransitionAction = &PatchManager::driver_active_patch; const PATCH_DEACTIVE: TransitionAction = &PatchManager::driver_deactive_patch; @@ -49,9 +55,9 @@ const PATCH_DECLINE: TransitionAction = &PatchManager::driver_decline_patch; lazy_static! { static ref STATUS_TRANSITION_MAP: IndexMap> = indexmap! { - (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_APPLY], - (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE], - (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE, PATCH_ACCEPT], + (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_LOAD], + (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE], + (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE, PATCH_ACCEPT], (PatchStatus::Deactived, PatchStatus::NotApplied) => vec![PATCH_REMOVE], (PatchStatus::Deactived, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_ACTIVE], (PatchStatus::Deactived, PatchStatus::Accepted) => vec![PATCH_ACTIVE, PATCH_ACCEPT], @@ -133,7 +139,7 @@ impl PatchManager { .unwrap_or_default(); if status == PatchStatus::Unknown { - status = self.driver_patch_status(patch, PatchOpFlag::Normal)?; + status = self.driver_get_patch_status(patch)?; self.set_patch_status(patch, status)?; } @@ -141,6 +147,7 @@ impl PatchManager { } pub fn check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Check patch '{}'", patch); self.driver.check_patch(patch, flag)?; self.driver.check_confliction(patch, flag)?; @@ -245,20 +252,20 @@ impl PatchManager { debug!("Reading patch status..."); let status_map: IndexMap = - serde::deserialize(&self.patch_status_file).context("Failed to read patch status")?; + serde::deserialize(&self.patch_status_file) + .context("Failed to read patch status file")?; for (uuid, status) in &status_map { debug!("Patch '{}' status: {}", uuid, status); } - - let restore_list = status_map - .into_iter() - .filter(|(_, status)| !accepted_only || (*status == PatchStatus::Accepted)); - for (uuid, status) in restore_list { + for (uuid, status) in status_map { + if accepted_only && (status != PatchStatus::Accepted) { + continue; + } match self.find_patch_by_uuid(&uuid) { Ok(patch) => { - debug!("Restore patch '{}' status to '{}'", patch, status); + info!("Restore patch '{}' status to '{}'", patch, status); if let Err(e) = self.do_status_transition(&patch, status, PatchOpFlag::Force) { - error!("{}", e); + error!("{:?}", e); } } Err(e) => { @@ -277,7 +284,7 @@ impl PatchManager { let status_keys = self.status_map.keys().copied().collect::>(); for patch_uuid in status_keys { if !self.patch_map.contains_key(&patch_uuid) { - trace!("Patch '{}' was removed, remove its status", patch_uuid); + trace!("Patch '{}' was removed", patch_uuid); self.status_map.remove(&patch_uuid); } } @@ -329,19 +336,110 @@ impl PatchManager { } impl PatchManager { + fn parse_user_patch( + root_dir: &Path, + patch_info: Arc, + patch_entity: &PatchEntity, + ) -> Result { + let patch_name = concat_os!( + patch_info.target.short_name(), + "/", + patch_info.name(), + "/", + patch_entity.patch_target.file_name().unwrap_or_default() + ); + let patch_file = root_dir.join(&patch_entity.patch_name); + + let patch = UserPatch::parse(&patch_name, patch_info, patch_entity, patch_file) + .with_context(|| { + format!( + "Failed to parse patch '{}' ({})", + patch_entity.uuid, + patch_name.to_string_lossy(), + ) + })?; + + debug!("Found patch '{}' ({})", patch.uuid, patch); + Ok(patch) + } + + fn parse_kernel_patch( + root_dir: &Path, + patch_info: Arc, + patch_entity: &PatchEntity, + ) -> Result { + const KPATCH_EXTENSION: &str = "ko"; + + let patch_name = concat_os!( + patch_info.target.short_name(), + "/", + patch_info.name(), + "/", + &patch_entity.patch_target, + ); + let mut patch_file = root_dir.join(&patch_entity.patch_name); + patch_file.set_extension(KPATCH_EXTENSION); + + let patch = KernelPatch::parse(&patch_name, patch_info, patch_entity, patch_file) + .with_context(|| { + format!( + "Failed to parse patch '{}' ({})", + patch_entity.uuid, + patch_name.to_string_lossy(), + ) + })?; + + debug!("Found patch '{}' ({})", patch.uuid, patch); + Ok(patch) + } + + fn parse_patches(root_dir: &Path) -> Result> { + let root_name = root_dir.file_name().expect("Invalid patch root directory"); + let patch_metadata = root_dir.join(PATCH_INFO_FILE_NAME); + let patch_info = Arc::new( + serde::deserialize_with_magic::(patch_metadata, PATCH_INFO_MAGIC) + .with_context(|| { + format!( + "Failed to parse patch '{}' metadata", + root_name.to_string_lossy(), + ) + })?, + ); + + patch_info + .entities + .iter() + .map(|patch_entity| { + let patch_info = patch_info.clone(); + match patch_info.kind { + PatchType::UserPatch => Ok(Patch::UserPatch(Self::parse_user_patch( + root_dir, + patch_info, + patch_entity, + )?)), + PatchType::KernelPatch => Ok(Patch::KernelPatch(Self::parse_kernel_patch( + root_dir, + patch_info, + patch_entity, + )?)), + } + }) + .collect::>>() + } + fn scan_patches>(directory: P) -> Result>> { const TRAVERSE_OPTION: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; let mut patch_map = IndexMap::new(); - info!("Scanning patches from {}...", directory.as_ref().display()); - for patch_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { - let resolve_result = PatchResolver::resolve_patch(&patch_dir) - .with_context(|| format!("Failed to resolve patch from {}", patch_dir.display())); - match resolve_result { + info!( + "Scanning patches from '{}'...", + directory.as_ref().display() + ); + for root_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { + match Self::parse_patches(&root_dir) { Ok(patches) => { for patch in patches { - debug!("Detected patch '{}'", patch); patch_map.insert(*patch.uuid(), Arc::new(patch)); } } @@ -399,15 +497,12 @@ impl PatchManager { bail!("Cannot set patch '{}' status to '{}'", patch, value); } - let (index, _) = self.status_map.insert_full(*patch.uuid(), value); - if let Some(last_index) = self - .status_map - .last() - .and_then(|(key, _)| self.status_map.get_index_of(key)) - { - if index != last_index { - self.status_map.move_index(index, last_index); - } + let uuid = *patch.uuid(); + let (curr_index, _) = self.status_map.insert_full(uuid, value); + + let last_index = self.status_map.len().saturating_sub(1); + if curr_index != last_index { + self.status_map.move_index(curr_index, last_index); } Ok(()) @@ -415,16 +510,16 @@ impl PatchManager { } impl PatchManager { - fn driver_patch_status(&self, patch: &Patch, _flag: PatchOpFlag) -> Result { - self.driver.patch_status(patch) - } - fn driver_check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { self.driver.check_patch(patch, flag) } - fn driver_apply_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { - self.driver.apply_patch(patch)?; + fn driver_get_patch_status(&self, patch: &Patch) -> Result { + self.driver.get_patch_status(patch) + } + + fn driver_load_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { + self.driver.load_patch(patch)?; self.set_patch_status(patch, PatchStatus::Deactived) } diff --git a/syscared/src/patch/mod.rs b/syscared/src/patch/mod.rs index 26a97da6..edd247ab 100644 --- a/syscared/src/patch/mod.rs +++ b/syscared/src/patch/mod.rs @@ -16,7 +16,6 @@ pub mod driver; pub mod entity; pub mod manager; pub mod monitor; -pub mod resolver; pub mod transaction; const PATCH_INFO_FILE_NAME: &str = "patch_info"; diff --git a/syscared/src/patch/resolver/kpatch.rs b/syscared/src/patch/resolver/kpatch.rs deleted file mode 100644 index f8885ff1..00000000 --- a/syscared/src/patch/resolver/kpatch.rs +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::{CStr, OsString}, - path::{Path, PathBuf}, - sync::Arc, -}; - -use anyhow::{anyhow, Context, Result}; -use object::{NativeFile, Object, ObjectSection}; - -use syscare_abi::{PatchEntity, PatchInfo}; -use syscare_common::{ - concat_os, - ffi::{CStrExt, OsStrExt}, - fs, -}; - -use super::PatchResolverImpl; -use crate::patch::entity::{KernelPatch, KernelPatchFunction, Patch}; - -const KPATCH_SUFFIX: &str = ".ko"; -const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; -const KPATCH_SYS_FILE_NAME: &str = "enabled"; - -mod ffi { - use std::os::raw::{c_char, c_long, c_ulong}; - - use object::{ - read::elf::{ElfSectionRelocationIterator, FileHeader}, - Pod, Relocation, - }; - - #[repr(C)] - #[derive(Debug, Clone, Copy)] - /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` - pub struct KpatchFunction { - pub new_addr: c_ulong, - pub new_size: c_ulong, - pub old_addr: c_ulong, - pub old_size: c_ulong, - pub sympos: u64, - pub name: *const c_char, - pub obj_name: *const c_char, - pub ref_name: *const c_char, - pub ref_offset: c_long, - } - - pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); - pub const KPATCH_FUNCTION_OFFSET: usize = 40; - pub const KPATCH_OBJECT_OFFSET: usize = 48; - - /* - * SAFETY: This struct is - * - #[repr(C)] - * - have no invalid byte values - * - have no padding - */ - unsafe impl Pod for KpatchFunction {} - - pub struct KpatchRelocation { - pub _addr: (u64, Relocation), - pub name: (u64, Relocation), - pub object: (u64, Relocation), - } - - pub struct KpatchRelocationIterator<'data, 'file, Elf: FileHeader>( - ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, - ); - - impl<'data, 'file, Elf: FileHeader> KpatchRelocationIterator<'data, 'file, Elf> { - pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { - Self(relocations) - } - } - - impl Iterator for KpatchRelocationIterator<'_, '_, Elf> { - type Item = KpatchRelocation; - - fn next(&mut self) -> Option { - if let (Some(addr), Some(name), Some(object)) = - (self.0.next(), self.0.next(), self.0.next()) - { - return Some(KpatchRelocation { - _addr: addr, - name, - object, - }); - } - None - } - } -} - -const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; -const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; - -pub struct KpatchResolverImpl; - -impl KpatchResolverImpl { - #[inline] - fn resolve_patch_file(patch: &mut KernelPatch) -> Result<()> { - let patch_file = fs::mmap(&patch.patch_file) - .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; - let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; - - // Read sections - let function_section = patch_elf - .section_by_name(KPATCH_FUNCS_SECTION) - .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; - let string_section = patch_elf - .section_by_name(KPATCH_STRINGS_SECTION) - .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; - let function_data = function_section - .data() - .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; - let string_data = string_section - .data() - .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; - - // Resolve patch functions - let patch_functions = &mut patch.functions; - let kpatch_function_slice = object::slice_from_bytes::( - function_data, - function_data.len() / ffi::KPATCH_FUNCTION_SIZE, - ) - .map(|(f, _)| f) - .map_err(|_| anyhow!("Invalid data format")) - .context("Failed to resolve patch functions")?; - - for function in kpatch_function_slice { - patch_functions.push(KernelPatchFunction { - name: OsString::new(), - object: OsString::new(), - old_addr: function.old_addr, - old_size: function.old_size, - new_addr: function.new_addr, - new_size: function.new_size, - }); - } - - // Relocate patch functions - for relocation in ffi::KpatchRelocationIterator::new(function_section.relocations()) { - let (name_reloc_offset, name_reloc) = relocation.name; - let (object_reloc_offset, obj_reloc) = relocation.object; - - // Relocate patch function name - let name_index = (name_reloc_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) - / ffi::KPATCH_FUNCTION_SIZE; - let name_function = patch_functions - .get_mut(name_index) - .context("Failed to find patch function")?; - let name_offset = name_reloc.addend() as usize; - let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) - .context("Failed to parse patch object name")? - .to_os_string(); - - name_function.name = name_string; - - // Relocate patch function object - let object_index = (object_reloc_offset as usize - ffi::KPATCH_OBJECT_OFFSET) - / ffi::KPATCH_FUNCTION_SIZE; - let object_function = patch_functions - .get_mut(object_index) - .context("Failed to find patch function")?; - let object_offset = obj_reloc.addend() as usize; - let object_string = CStr::from_bytes_with_next_nul(&string_data[object_offset..]) - .context("Failed to parse patch function name")? - .to_os_string(); - - object_function.object = object_string; - } - - Ok(()) - } -} - -impl PatchResolverImpl for KpatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result { - let target_name = patch_entity.patch_name.as_os_str().to_os_string(); - let module_name = patch_entity.patch_name.replace(['-', '.'], "_"); - let patch_file = patch_root.join(concat_os!(&patch_entity.patch_name, KPATCH_SUFFIX)); - let sys_file = PathBuf::from(KPATCH_SYS_DIR) - .join(&module_name) - .join(KPATCH_SYS_FILE_NAME); - - let mut patch = KernelPatch { - uuid: patch_entity.uuid, - name: concat_os!( - patch_info.target.short_name(), - "/", - patch_info.name(), - "/", - &patch_entity.patch_target - ), - info: patch_info.clone(), - pkg_name: patch_info.target.full_name(), - target_name, - module_name, - patch_file, - sys_file, - functions: Vec::new(), - checksum: patch_entity.checksum.clone(), - }; - Self::resolve_patch_file(&mut patch).context("Failed to resolve patch")?; - - Ok(Patch::KernelPatch(patch)) - } -} diff --git a/syscared/src/patch/resolver/mod.rs b/syscared/src/patch/resolver/mod.rs deleted file mode 100644 index 7c4a504d..00000000 --- a/syscared/src/patch/resolver/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{path::Path, sync::Arc}; - -use super::{entity::Patch, PATCH_INFO_FILE_NAME}; - -use anyhow::{Context, Result}; -use syscare_abi::{PatchEntity, PatchInfo, PatchType, PATCH_INFO_MAGIC}; -use syscare_common::util::serde; - -mod kpatch; -mod upatch; - -use kpatch::KpatchResolverImpl; -use upatch::UpatchResolverImpl; - -pub trait PatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result; -} - -pub struct PatchResolver; - -impl PatchResolver { - pub fn resolve_patch>(directory: P) -> Result> { - let patch_root = directory.as_ref(); - let patch_info = Arc::new( - serde::deserialize_with_magic::( - patch_root.join(PATCH_INFO_FILE_NAME), - PATCH_INFO_MAGIC, - ) - .context("Failed to resolve patch metadata")?, - ); - let resolver: &dyn PatchResolverImpl = match patch_info.kind { - PatchType::UserPatch => &UpatchResolverImpl, - PatchType::KernelPatch => &KpatchResolverImpl, - }; - - let mut patch_list = Vec::with_capacity(patch_info.entities.len()); - for patch_entity in &patch_info.entities { - let patch = resolver.resolve_patch(patch_root, patch_info.clone(), patch_entity)?; - patch_list.push(patch); - } - - Ok(patch_list) - } -} diff --git a/syscared/src/patch/resolver/upatch.rs b/syscared/src/patch/resolver/upatch.rs deleted file mode 100644 index a5e4ad0c..00000000 --- a/syscared/src/patch/resolver/upatch.rs +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::{CStr, OsString}, - path::Path, - sync::Arc, -}; - -use anyhow::{anyhow, Context, Result}; -use object::{NativeFile, Object, ObjectSection}; - -use syscare_abi::{PatchEntity, PatchInfo}; -use syscare_common::{concat_os, ffi::CStrExt, fs}; - -use super::PatchResolverImpl; -use crate::patch::entity::{Patch, UserPatch, UserPatchFunction}; - -mod ffi { - use std::os::raw::{c_char, c_ulong}; - - use object::{ - read::elf::{ElfSectionRelocationIterator, FileHeader}, - Pod, Relocation, - }; - - #[repr(C)] - #[derive(Debug, Clone, Copy)] - /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` - pub struct UpatchFunction { - pub new_addr: c_ulong, - pub new_size: c_ulong, - pub old_addr: c_ulong, - pub old_size: c_ulong, - pub sympos: c_ulong, - pub name: *const c_char, - } - - /* - * SAFETY: This struct is - * - #[repr(C)] - * - have no invalid byte values - * - have no padding - */ - unsafe impl Pod for UpatchFunction {} - - pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); - pub const UPATCH_FUNCTION_OFFSET: usize = 40; - - pub struct UpatchRelocation { - pub _addr: (u64, Relocation), - pub name: (u64, Relocation), - } - - pub struct UpatchRelocationIterator<'data, 'file, Elf: FileHeader>( - ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, - ); - - impl<'data, 'file, Elf: FileHeader> UpatchRelocationIterator<'data, 'file, Elf> { - pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { - Self(relocations) - } - } - - impl Iterator for UpatchRelocationIterator<'_, '_, Elf> { - type Item = UpatchRelocation; - - fn next(&mut self) -> Option { - if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { - return Some(UpatchRelocation { _addr: addr, name }); - } - None - } - } -} - -const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; -const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; - -pub struct UpatchResolverImpl; - -impl UpatchResolverImpl { - #[inline] - fn resolve_patch_elf(patch: &mut UserPatch) -> Result<()> { - let patch_file = fs::mmap(&patch.patch_file) - .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; - let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; - - // Read sections - let function_section = patch_elf - .section_by_name(UPATCH_FUNCS_SECTION) - .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; - let string_section = patch_elf - .section_by_name(UPATCH_STRINGS_SECTION) - .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; - let function_data = function_section - .data() - .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; - let string_data = string_section - .data() - .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; - - // Resolve patch functions - let patch_functions = &mut patch.functions; - let upatch_function_slice = object::slice_from_bytes::( - function_data, - function_data.len() / ffi::UPATCH_FUNCTION_SIZE, - ) - .map(|(f, _)| f) - .map_err(|_| anyhow!("Invalid data format")) - .context("Failed to resolve patch functions")?; - - for function in upatch_function_slice { - patch_functions.push(UserPatchFunction { - name: OsString::new(), - old_addr: function.old_addr, - old_size: function.old_size, - new_addr: function.new_addr, - new_size: function.new_size, - }); - } - - // Relocate patch functions - for relocation in ffi::UpatchRelocationIterator::new(function_section.relocations()) { - let (name_reloc_offset, name_reloc) = relocation.name; - - let name_index = (name_reloc_offset as usize - ffi::UPATCH_FUNCTION_OFFSET) - / ffi::UPATCH_FUNCTION_SIZE; - let name_function = patch_functions - .get_mut(name_index) - .context("Failed to find patch function")?; - let name_offset = name_reloc.addend() as usize; - let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) - .context("Failed to parse patch function name")? - .to_os_string(); - - name_function.name = name_string; - } - - Ok(()) - } -} - -impl PatchResolverImpl for UpatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result { - let mut patch = UserPatch { - uuid: patch_entity.uuid, - name: concat_os!( - patch_info.target.short_name(), - "/", - patch_info.name(), - "/", - fs::file_name(&patch_entity.patch_target) - ), - info: patch_info.clone(), - pkg_name: patch_info.target.full_name(), - patch_file: patch_root.join(&patch_entity.patch_name), - target_elf: patch_entity.patch_target.clone(), - functions: Vec::new(), - checksum: patch_entity.checksum.clone(), - }; - Self::resolve_patch_elf(&mut patch).context("Failed to resolve patch")?; - - Ok(Patch::UserPatch(patch)) - } -} -- Gitee From d0717c9e2b36107812447364e69ba8b9743615b4 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 28 May 2025 18:29:34 +0800 Subject: [PATCH 8/9] project: update Cargo.lock Signed-off-by: renoseven --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b4798dec..38324d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,10 +1146,11 @@ name = "syscare-common" version = "1.2.2" dependencies = [ "anyhow", - "lazy_static", "log", "memmap2", "nix", + "num_cpus", + "object", "regex", "serde", "serde_cbor", -- Gitee From 26c5b86719766316cefecd5987afc19d9d3ba261 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 11 Jun 2025 13:31:46 +0800 Subject: [PATCH 9/9] project: update CMakeLists.txt Signed-off-by: renoseven --- CMakeLists.txt | 80 ++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95a917f1..843abc67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,29 +37,29 @@ if(GIT_FOUND) ERROR_QUIET WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) - set(PROJECT_BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") + set(BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") else() - set(PROJECT_BUILD_VERSION "${BUILD_VERSION}") + set(BUILD_VERSION "${BUILD_VERSION}") endif() # Build configurations if(ENABLE_ASAN) - set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-asan") - list(APPEND PROJECT_C_BUILD_FLAGS -fsanitize=address -fno-omit-frame-pointer) - list(APPEND PROJECT_C_LIBRARIES asan) + set(BUILD_VERSION "${BUILD_VERSION}-asan") + list(APPEND BUILD_FLAGS_C -fsanitize=address -fno-omit-frame-pointer) + list(APPEND LINK_LIBRARIES_C asan) endif() if(ENABLE_GCOV) - set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-gcov") - list(APPEND PROJECT_C_BUILD_FLAGS -ftest-coverage -fprofile-arcs) - list(APPEND PROJECT_RUST_FLAGS -C instrument-coverage) - list(APPEND PROJECT_C_LIBRARIES gcov) + set(BUILD_VERSION "${BUILD_VERSION}-gcov") + list(APPEND BUILD_FLAGS_C -ftest-coverage -fprofile-arcs) + list(APPEND BUILD_FLAGS_RUST -C instrument-coverage) + list(APPEND LINK_LIBRARIES_C gcov) endif() # Build flags -list(APPEND PROJECT_C_BUILD_FLAGS - -std=gnu99 -Wall -O2 -Werror -Wextra - -DBUILD_VERSION="${PROJECT_BUILD_VERSION}" -D_FORTIFY_SOURCE=2 +list(APPEND BUILD_FLAGS_C + -std=gnu99 -O2 -Wall -Wextra -Werror + -DBUILD_VERSION="${BUILD_VERSION}" -D_FORTIFY_SOURCE=2 -Wtrampolines -Wformat=2 -Wstrict-prototypes -Wdate-time -Wstack-usage=8192 -Wfloat-equal -Wswitch-default -Wshadow -Wconversion -Wcast-qual -Wunused -Wundef @@ -70,16 +70,16 @@ list(APPEND PROJECT_C_BUILD_FLAGS # The -Werror=cast-align compiler flag causes issues on riscv64 GCC, # while the same operations do not error on aarch64. This appears to be -# a compiler-specific problem. Temporarily disable this option as a +# a compiler-specific problem. Temporarily disable this option as a # workaround since applying fixes would require intrusive code changes # across multiple files. if(NOT ARCH STREQUAL "riscv64") - list(APPEND PROJECT_C_BUILD_FLAGS + list(APPEND BUILD_FLAGS_C -Wcast-align ) endif() -list(APPEND PROJECT_RUST_FLAGS +list(APPEND BUILD_FLAGS_RUST --cfg unsound_local_offset -D warnings -C link-arg=-s @@ -91,7 +91,7 @@ list(APPEND PROJECT_RUST_FLAGS ) # Link flags -list(APPEND PROJECT_C_LINK_FLAGS +list(APPEND LINK_FLAGS_C -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -rdynamic @@ -100,9 +100,9 @@ list(APPEND PROJECT_C_LINK_FLAGS ) if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - list(APPEND PROJECT_C_BUILD_FLAGS -g) + list(APPEND BUILD_FLAGS_C -g) elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - list(APPEND PROJECT_C_LINK_FLAGS -s) + list(APPEND LINK_FLAGS_C -s) endif() # Install directories @@ -120,44 +120,42 @@ message("███████║ ██║ ███████║╚█ message("╚══════╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝") message("---------------------------------------------------------") message("-- Build type: ${CMAKE_BUILD_TYPE}") -message("-- Build version: ${PROJECT_BUILD_VERSION}") -message("-- Rust flags: ${PROJECT_RUST_FLAGS}") -message("-- Build flags: ${PROJECT_C_BUILD_FLAGS}") -message("-- Link flags: ${PROJECT_C_LINK_FLAGS}") -message("-- Link libraries: ${PROJECT_C_LIBRARIES}") +message("-- Build version: ${BUILD_VERSION}") +message("-- Rust flags: ${BUILD_FLAGS_RUST}") +message("-- Build flags: ${BUILD_FLAGS_C}") +message("-- Link flags: ${LINK_FLAGS_C}") +message("-- Link libraries: ${LINK_LIBRARIES_C}") message("-- Binary directory: ${SYSCARE_BINARY_DIR}") message("-- Libexec directory: ${SYSCARE_LIBEXEC_DIR}") message("-- Service directory: ${SYSCARE_SERVICE_DIR}") message("---------------------------------------------------------") # Apply all flags -add_compile_options(${PROJECT_C_BUILD_FLAGS}) -add_link_options(${PROJECT_C_LINK_FLAGS}) -link_libraries(${PROJECT_C_LIBRARIES}) +add_compile_options(${BUILD_FLAGS_C}) +add_link_options(${LINK_FLAGS_C}) +link_libraries(${LINK_LIBRARIES_C}) # Build rust executables -set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust") +set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/target") set(RUST_OUTPUT_DIR "${RUST_TARGET_DIR}/release") -foreach(FLAG IN LISTS PROJECT_RUST_FLAGS) - set(RUST_FLAGS "${RUST_FLAGS} ${FLAG}") +foreach(CURR_FLAG IN LISTS BUILD_FLAGS_RUST) + set(RUST_FLAGS "${RUST_FLAGS} ${CURR_FLAG}") endforeach() -add_custom_target(rust-build ALL - COMMENT "Building rust executables..." - COMMAND ${CMAKE_COMMAND} -E env - "BUILD_VERSION=${PROJECT_BUILD_VERSION}" - "RUSTFLAGS=${RUST_FLAGS}" - cargo build --release --target-dir "${RUST_TARGET_DIR}" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +add_custom_target(syscare ALL + COMMAND cargo build --release --target-dir ${RUST_TARGET_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) -# Generate upatch helpers -add_custom_target(generate-upatch-helpers ALL - COMMENT "Generating upatch helpers..." +set_target_properties(syscare PROPERTIES + ENVIRONMENT "RUSTFLAGS=${RUST_FLAGS};BUILD_VERSION=${BUILD_VERSION}" + ADDITIONAL_CLEAN_FILES "${RUST_TARGET_DIR}" +) + +add_custom_target(upatch-helpers ALL COMMAND ln -sf upatch-helper upatch-cc COMMAND ln -sf upatch-helper upatch-c++ - DEPENDS - rust-build + DEPENDS syscare WORKING_DIRECTORY ${RUST_OUTPUT_DIR} ) -- Gitee