因为客户的需求,我们需要在标准linux上支持c,d盘的基本功能,这样用户在安装程序时,需要可以将程序安装在D盘,这样从某种意义上来说达到了用户和系统的应用程序隔离。本文提供一种思路,用于设计C,D盘的设计,基于此思路的衍生,可以完全实施C,D盘的基本功能
为了将补丁更突出出来,这里第一时间将补丁贴出,如下:
From 75d9bc4ee5c2b6b2b0a7efa7beed7c2b8cfe51e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B3=B0?= <tangfeng@kylinos.cn> Date: Wed, 14 Aug 2024 12:17:16 +0800 Subject: [PATCH] support appfs for C/D disk export USE_APPFS marco to open this feature --- src/archives.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++- src/remove.c | 59 +++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/src/archives.c b/src/archives.c index fe4d8a9..bca81b5 100644 --- a/src/archives.c +++ b/src/archives.c @@ -387,6 +387,65 @@ does_replace(struct pkginfo *new_pkg, struct pkgbin *new_pkgbin, return false; } +static void copy_file(const char *src, const char *dest) { + struct stat stat_buf; + int sfd, dfd; + char *buf; + int buf_read = 0; + int buf_write = 0; + struct timespec times[2]; + + if (!strcmp(src, dest)) + return; + + sfd = open(src, O_RDONLY); + dfd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, stat_buf.st_mode); + if (sfd < 0 || dfd < 0) { + return ; + } + + stat(src, &stat_buf); + + buf = (char *) malloc(4096); + while ((buf_read = read(sfd, buf, 4096))) { + buf_write = write(dfd, buf, buf_read); + if(buf_write != buf_read){ + } + } + free(buf); + + if (buf_read < 0) { + printf("buf_read le 0\n"); + } + + fchmod(dfd, stat_buf.st_mode); + if(fchown(dfd, stat_buf.st_uid, stat_buf.st_gid)){ + printf("change owner failed\n"); + } + + times[0] = stat_buf.st_atim; + times[1] = stat_buf.st_mtim; + futimens(dfd, times); + + close(sfd); + close(dfd); +} + +#define USE_APPFS +static bool get_useappfs(void) +{ + static bool use_appfs = false; + +#ifdef USE_APPFS + const char *env = getenv("USE_APPFS"); + if(env) + use_appfs = true; + else + use_appfs = false; +#endif + return use_appfs; +} + static void tarobject_extract(struct tarcontext *tc, struct tar_entry *te, const char *path, struct file_stat *st, @@ -401,6 +460,9 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, char fnamenewbuf[256]; char *newhash; int rc; + char appfs[256]; + + snprintf(appfs, 256, "%s%s", appfs_prefix, path); switch (te->type) { case TAR_FILETYPE_FILE: @@ -450,10 +512,18 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, pop_cleanup(ehflag_normaltidy); /* fd = open(path) */ if (close(fd)) ohshite(_("error closing/writing '%.255s'"), te->name); + + /* copy file to appfs */ + if(get_useappfs()) + copy_file(path, appfs); break; case TAR_FILETYPE_FIFO: if (mkfifo(path, 0)) ohshite(_("error creating pipe '%.255s'"), te->name); + + /* mkfifo at appfs */ + if(get_useappfs()) + mkfifo(appfs, 0); debug(dbg_eachfiledetail, "tarobject fifo"); break; case TAR_FILETYPE_CHARDEV: @@ -477,6 +547,13 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, varbuf_end_str(&hardlinkfn); if (link(hardlinkfn.buf, path)) ohshite(_("error creating hard link '%.255s'"), te->name); + + /* hardlink at appfs */ + if(get_useappfs()){ + if(link(hardlinkfn.buf, appfs)) + printf("make hardlink on %s failed\n", appfs); + } + namenode->newhash = linknode->newhash; debug(dbg_eachfiledetail, "tarobject hardlink hash=%s", namenode->newhash); break; @@ -484,12 +561,32 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, /* We've already checked for an existing directory. */ if (symlink(te->linkname, path)) ohshite(_("error creating symbolic link '%.255s'"), te->name); + + /* symlink at appfs */ + if(get_useappfs()) { + /* this symlink maybe fail */ + if (symlink(te->linkname, appfs)){ + unlink(appfs); + if(symlink(te->linkname, appfs)){ + printf("make symlink on %s failed\n", appfs); + } + } + } + debug(dbg_eachfiledetail, "tarobject symlink creating"); break; case TAR_FILETYPE_DIR: /* We've already checked for an existing directory. */ if (mkdir(path, 0)) ohshite(_("error creating directory '%.255s'"), te->name); + + /* create directory on appfs */ + if(get_useappfs()){ + struct stat stab; + if (!(stat(appfs, &stab) == 0 && S_ISDIR(stab.st_mode))) + mkdir(appfs, 0); + } + debug(dbg_eachfiledetail, "tarobject directory creating"); break; default: @@ -808,6 +905,14 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) "installing another version"), ti->name); debug(dbg_eachfiledetail,"tarobject nonexistent"); } else { + /* rename file on appfs */ + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + rename(appfsnew, appfs); + } debug(dbg_eachfiledetail,"tarobject restored tmp to main"); statr= lstat(fnamevb.buf,&stab); if (statr) @@ -838,6 +943,14 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) if (!stat(fnamevb.buf,&stabtmp) && S_ISDIR(stabtmp.st_mode)) { debug(dbg_eachfiledetail, "tarobject directory exists"); existingdir = true; + /* mkdir on appfs */ + if(get_useappfs()){ + char appfs[256]; + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if (!(!stat(appfs,&stabtmp) && S_ISDIR(stabtmp.st_mode))) { + mkdir(appfs, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + } + } } break; case TAR_FILETYPE_FILE: @@ -1058,6 +1171,17 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) if (nifd->namenode->flags & FNNF_NEW_CONFF) { debug(dbg_conffdetail,"tarobject conffile extracted"); nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS; + + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if(S_ISDIR(stab.st_mode) || S_ISREG(stab.st_mode)){ + rename(appfsnew, appfs); + } + } + return 0; } @@ -1120,7 +1244,16 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) } else { if (rename(fnamenewvb.buf, fnamevb.buf)) ohshite(_("unable to install new version of '%.255s'"), ti->name); - + /* rename on appfs */ + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if(access(appfs, F_OK)){ + rename(appfsnew, appfs); + } + } /* * CLEANUP: Now the new file is in the destination file, and the * old file is in .dpkg-tmp to be cleaned up later. We now need @@ -1306,6 +1439,14 @@ tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg) ohshite(_("unable to install new version of '%.255s'"), cfile->namenode->name); + /* rename on appfs */ + if(get_useappfs()) { + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + rename(appfsnew, appfs); + } if(kysec_whlist_exectl_multi_add_for_dpkg == NULL) { if(kysec_whlist_exectl_add_for_dpkg != NULL) @@ -1750,6 +1891,18 @@ archivefiles(const char *const *argv) ohshit(_("archive '%s' is not a regular file"), argp[i]); } + /* check appfs directory exist */ + if(get_useappfs()){ + if(access(appfs_prefix, F_OK)){ + struct stat st; + stat(appfs_prefix, &st); + if(!S_ISDIR(st.st_mode)){ + mkdir(appfs_prefix, 0); + chmod(appfs_prefix, S_IRWXU|S_IRWXG|S_IRWXO); + } + } + } + currenttime = time(NULL); /* Initialize fname variables contents. */ diff --git a/src/remove.c b/src/remove.c index b8727b6..d24f21b 100644 --- a/src/remove.c +++ b/src/remove.c @@ -551,6 +551,21 @@ removal_bulk_file_is_shared(struct pkginfo *pkg, struct fsys_namenode *namenode) return shared; } +#define USE_APPFS +static bool get_useappfs(void) +{ + static bool use_appfs = false; + +#ifdef USE_APPFS + const char *env = getenv("USE_APPFS"); + if(env) + use_appfs = true; + else + use_appfs = false; +#endif + return use_appfs; +} + static void removal_bulk_remove_files(struct pkginfo *pkg) { @@ -560,6 +575,7 @@ removal_bulk_remove_files(struct pkginfo *pkg) static struct varbuf fnvb; struct varbuf_state fnvb_state; struct stat stab; + char appfs[256]; pkg_set_status(pkg, PKG_STAT_HALFINSTALLED); modstatdb_note(pkg); @@ -584,6 +600,10 @@ removal_bulk_remove_files(struct pkginfo *pkg) is_dir = stat(fnvb.buf, &stab) == 0 && S_ISDIR(stab.st_mode); + if(get_useappfs()){ + snprintf(appfs, 256, "%s%s", appfs_prefix, fnvb.buf); + } + /* A pkgset can share files between its instances that we * don't want to remove, we just want to forget them. This * applies to shared conffiles too. */ @@ -593,6 +613,11 @@ removal_bulk_remove_files(struct pkginfo *pkg) /* Non-shared conffiles are kept. */ if (namenode->flags & FNNF_OLD_CONFF) { push_leftover(&leftover, namenode); + + /* unlink non-shared conffiles file */ + if(get_useappfs()) + secure_unlink(appfs); + continue; } @@ -638,6 +663,19 @@ removal_bulk_remove_files(struct pkginfo *pkg) varbuf_end_str(&fnvb); debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf); + + /* remove appfs dir */ + if(get_useappfs()){ + if (stat(appfs, &stab) == 0 && S_ISDIR(stab.st_mode)){ + /* ignore /$appfs/. directory */ + char prefix[256]; + snprintf(prefix, 256, "%s%s", appfs_prefix, "/."); + if (strcmp(appfs, prefix) == 0) { + continue; + } + rmdir(appfs); + } + } if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue; if (errno == ENOTEMPTY || errno == EEXIST) { debug(dbg_eachfiledetail, @@ -657,6 +695,12 @@ removal_bulk_remove_files(struct pkginfo *pkg) debug(dbg_eachfiledetail, "removal_bulk unlinking '%s'", fnvb.buf); if (secure_unlink(fnvb.buf)) ohshite(_("unable to securely remove '%.250s'"), fnvb.buf); + + /* delete appfs file */ + if(get_useappfs()){ + if((access(appfs, F_OK)==0) || (lstat(appfs, &stab) == 0 && S_ISLNK(stab.st_mode))) + secure_unlink(appfs); + } } write_filelist_except(pkg, &pkg->installed, leftover, 0); maintscript_installed(pkg, POSTRMFILE, "post-removal", "remove", NULL); @@ -681,6 +725,7 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { struct fsys_namenode *namenode; static struct varbuf fnvb; struct stat stab; + char appfs[256]; /* We may have modified this previously. */ ensure_packagefiles_available(pkg); @@ -710,6 +755,10 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { varbuf_add_str(&fnvb, usenode->name); varbuf_end_str(&fnvb); + if(get_useappfs()){ + snprintf(appfs, 256, "%s%s", appfs_prefix, fnvb.buf); + } + if (!stat(fnvb.buf,&stab) && S_ISDIR(stab.st_mode)) { debug(dbg_eachfiledetail, "removal_bulk is a directory"); /* Only delete a directory or a link to one if we're the only @@ -733,6 +782,16 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { trig_path_activate(usenode, pkg); debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf); + /* remove appfs file */ + if(get_useappfs()){ + if (!stat(appfs, &stab) && S_ISDIR(stab.st_mode)) { + rmdir(appfs); + } + if (lstat(appfs, &stab) == 0 && S_ISLNK(stab.st_mode)) { + unlink(appfs); + } + } + if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue; if (errno == ENOTEMPTY || errno == EEXIST) { warning(_("while removing %.250s, directory '%.250s' not empty so not removed"), -- 2.25.1
我们可以知道,linux默认情况下将so和elf程序放在/usr/bin和/usr/lib下,与系统核心库不做区分。这样用户在安装deb包的时候,也会默认在/usr/bin和/usr/lib中安装,这就导致了所有的应用软件包都安装在系统核心目录下。
同样的,我们知道,windows默认将应用程序和dll安装的c盘,但是应用程序可以选择默认安装环境,这里可以手动指定为D盘。其差别如下:
这里需要明确的是windows核心的系统库,例如vs c++等相关库,只能安装在C盘。
根据此现状,Linux系统需要提供一个功能,让其应用程序可以安装在D盘等类似的盘符
我们知道麒麟系统的包生态基于deb,虽然现在存在开明包格式,但是为了不改变太多,默认deb包格式的方式短期内不能抛弃。
而deb包默认将其安装的根目录,设置其preinstall,postinstall,preremove,postremove都需要在根目录运行。
并且,dpkg管理的包列表默认存放在/var/lib/dpkg/的list中,而这里的list记录了实际的文件内容,举例如下:
root@kylin:~# cat /var/lib/dpkg/info/libhw265dec.list /. /etc /etc/dbus-1 /etc/dbus-1/system.d /etc/dbus-1/system.d/com.huawei.dassistant.conf /usr /usr/bin /usr/bin/DAssistantd /usr/bin/qs-smc-module-installer.sh /usr/include /usr/include/hwd_api.h /usr/lib /usr/lib/libhw265dec.so /usr/lib/pkgconfig /usr/lib/pkgconfig/hw265dec.pc /usr/lib/systemd /usr/lib/systemd/system /usr/lib/systemd/system/qs-smc-module-installer.service /usr/share /usr/share/doc /usr/share/doc/libhw265dec /usr/share/doc/libhw265dec/changelog.gz
所以,如果我们一味的将deb包安装在其他目录,则问题太多,太开放,解决问题不可实现。
根据上述2.1 和 2.2 的现状,我们可以知道,我们长期依赖的ubuntu 系列的生态,导致我们没有办法轻易的将系统划分为单独的C盘和D盘。
为了解决这个问题,我们可以两个思路
麒麟系统集成了ubuntu和debian系列的应用生态,所以deb包格式能够直接安装,而正是因为如此,deb包在设计的时候没有考虑C,D盘的划分。所以我们没办法直接划分C和D盘。
所以我们需要抛弃这块的生态,重新定义包的安装方式。例如开明包格式,如下:
https://gitlab2.kylin.com/lixinyue/kaiming-design-docs/-/tree/master/%E5%BC%80%E6%98%8E%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C
其每个应用程序都需要遵循开明包格式发布,而开明包自带沙箱隔离机制,故实现C/D盘将十分简单,当前成果状态如下:
但此文档主要目的不是讨论此方案
为了兼容ubuntu/debian系列的安装方式,又体现C/D盘的基本功能,我们必须两方都兼顾,所以我们实施的方案如下:
这样,程序被默认安装在根,并且原封不动安装在appfs中,此时应用程序的安装可以在appfs中体现,而根的文件,我们可以通过dm-snapshot实现在大更新的时候进行merge操作。从而完成系统的整体更新,关于dm-snapshot的操作,可参考其他文章。
对于ubuntu/debian系统的软件包,均是通过apt来安装的deb包,而实际解包安装的动作是dpkg实现。所以我们需要基于dpkg来进行修改。
针对src/archives.c
文件,我们需要将在deb包的tar包解压过程中,执行tarobject_extract
时,对于普通文件进行copy,目录进程创建,对于fifo/link/symlink进行重建,如下
针对src/remove.c文件,我们需要将在deb包卸载删除过程中,执行removal_bulk时,对普通文件和目录进行删除,对fifo/link/symlink进行unlink,如下
默认情况下,我们不启用此功能,如果想要启用,则可以导出环境变量即可,如下:
export USE_APPFS=1 对于代码,实现如下: #define USE_APPFS static bool get_useappfs(void) { static bool use_appfs = false; #ifdef USE_APPFS const char *env = getenv("USE_APPFS"); if(env) use_appfs = true; else use_appfs = false; #endif return use_appfs; }
这里我们假设系统支持了此功能,那么如果我们想要启动appfs的基本功能,如下:
export USE_APPFS=1
然后,我们正常通过apt/dpkg 安装应用,安装之前,我们确认没有appfs,如下:
root@kylin:~# file /appfs /appfs: cannot open `/appfs' (No such file or directory)
然后我们正常安装程序,这里以tree为例
root@kylin:~# apt install tree 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 下列软件包是自动安装的并且现在不需要了: libutempter0 python3-click python3-colorama python3-itsdangerous python3-jinja2 python3-markupsafe 使用'apt autoremove'来卸载它(它们)。 下列【新】软件包将被安装: tree 升级了 0 个软件包,新安装了 1 个软件包,要卸载 0 个软件包,有 14 个软件包未被升级。 需要下载 44.5 kB 的归档。 解压缩后会消耗 111 kB 的额外空间。 获取:1 http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-rk3588/universe arm64 tree arm64 1.8.0-1 [44.5 kB] 已下载 44.5 kB,耗时 0秒 (262 kB/s) 正在选中未选择的软件包 tree。 (正在读取数据库 ... 系统当前共安装有 210448 个文件和目录。) 准备解压 .../tree_1.8.0-1_arm64.deb ... 正在解压 tree (1.8.0-1) ... 正在设置 tree (1.8.0-1) ... 正在处理用于 man-db (2.9.1-1kylin0k1.0) 的触发器 ... 正在处理用于 kysec-utils (3.3.6.1-0k8.18) 的触发器 ...
此时我们可以发现,tree在appfs中存在,如下
root@kylin:~# find /appfs/ /appfs/ /appfs/usr /appfs/usr/bin /appfs/usr/bin/tree /appfs/usr/share /appfs/usr/share/doc /appfs/usr/share/doc/tree /appfs/usr/share/doc/tree/README.gz /appfs/usr/share/doc/tree/changelog.Debian.gz /appfs/usr/share/doc/tree/copyright /appfs/usr/share/doc/tree/TODO /appfs/usr/share/man /appfs/usr/share/man/man1 /appfs/usr/share/man/man1/tree.1.gz
如果我们想要使用appfs中的tree,我们可以直接运行,如果程序带so,则我们修改ldconfig即可,如下
LD_LIBRARY_PATH=/appfs/pathtolibrary/ exe
此时如果我们卸载tree,如下:
root@kylin:~# dpkg -P tree (正在读取数据库 ... 系统当前共安装有 210455 个文件和目录。) 正在卸载 tree (1.8.0-1) ... 正在处理用于 man-db (2.9.1-1kylin0k1.0) 的触发器 ... 正在处理用于 kysec-utils (3.3.6.1-0k8.18) 的触发器 ...
此时我们可以看到appfs的文件均被删除,如下
root@kylin:~# find /appfs/ /appfs/ /appfs/usr /appfs/usr/bin /appfs/usr/share /appfs/usr/share/doc /appfs/usr/share/man /appfs/usr/share/man/man1
这里遗留了几个文件夹没有删除,因为这几个文件夹不是tree包带来的,所以没有删除是正常的
windows在安装包后卸载,D盘也是遗留空文件夹。这不是异常。