根据之前的讨论,我们CD盘的短期目标是基于overlayfs来实现系统数据和应用数据的隔离(形式上)。长期目标是将应用数据和系统数据完全隔离开来。本文主要讨论peony上显示应用盘的基本改造流程
根据上图我们可以知道,对于overlayfs,区分lower和upper两层。我们可以将lower设置为sysroot,将upper设置为appfs。这样,我们发行操作系统的时候,默认情况下lower是发行的原始操作系统,而upper是除了原始发行的操作系统之外的所有改动。
我们将发行操作系统之外的所有改动都认为是应用的改动,所以直接认为upper就是应用盘。
对于peony,我们需要修改sidebar上的显示,显示系统盘和应用盘。效果如下:
这里我们文件系统盘是默认的overlay,如下:
kylinoverlay 19G 8.3G 9.5G 47% /
这里应用盘是appfs目录,如下:
/dev/mmcblk0p5 19G 8.3G 9.5G 47% /appfs
这里系统盘是sysroot目录,如下:
root@kylin:~# ls /sysroot/ bin boot data dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var work
对于代码,主要如下:
From 79f4bfabfde8d8d2deaaf2908933c1be36a1d198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B3=B0?= <tangfeng@kylinos.cn> Date: Wed, 15 Jan 2025 17:36:13 +0800 Subject: [PATCH] changelog: 3.14.4.5-0k3.0tablet18rk3.egf1.3 --- .../model/side-bar-file-system-item.cpp | 22 ++++++++++++++----- translations/libpeony-qt/libpeony-qt_zh_CN.ts | 4 ++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/libpeony-qt/model/side-bar-file-system-item.cpp b/libpeony-qt/model/side-bar-file-system-item.cpp index 6268ddfb6..0b6664966 100644 --- a/libpeony-qt/model/side-bar-file-system-item.cpp +++ b/libpeony-qt/model/side-bar-file-system-item.cpp @@ -169,6 +169,11 @@ void SideBarFileSystemItem::initDirInfo(const QString &uri) m_displayName = tr("Data"); m_iconName = "drive-harddisk"; } + + if (uri == "file:///appfs") { + m_displayName = tr("Appfs"); + m_iconName = "drive-harddisk"; + } } void SideBarFileSystemItem::initComputerInfo() @@ -259,6 +264,7 @@ void SideBarFileSystemItem::initVolumeInfo(const Experimental_Peony::Volume &vol }else{ m_uri = "file://" + m_uri; } + /* 文件系统项特殊处理 */ if("file:///" == m_uri){ m_unmountable = m_mountable = m_ejectable = m_stopable = false; @@ -270,11 +276,11 @@ void SideBarFileSystemItem::initVolumeInfo(const Experimental_Peony::Volume &vol m_mounted = true; m_displayName = QObject::tr("Data"); m_iconName = "drive-harddisk"; - } - - /* 隐藏指定的挂载点 */ - if(m_device == getDeviceMount("/media/root-rw") || m_device == getDeviceMount("/media/root-ro")){ - m_hidden = true; + }else if("file:///appfs" == m_uri){ + m_unmountable = m_mountable = m_ejectable = m_stopable = false; + m_mounted = true; + m_displayName = QObject::tr("Appfs"); + m_iconName = "drive-harddisk"; } // kydrive特殊处理 @@ -612,6 +618,12 @@ void SideBarFileSystemItem::findChildren() m_model->endInsertRows(); } + if (FileUtils::isFileDirectory("file:///appfs")) { + m_model->beginInsertRows(this->firstColumnIndex(), m_children->count(), m_children->count()); + SideBarFileSystemItem* item = new SideBarFileSystemItem("file:///appfs", nullptr, this, m_model); + m_children->append(item); + m_model->endInsertRows(); + } }else{ //对挂载点进行已存在文件的枚举操作 QString enumdir = m_uri; diff --git a/translations/libpeony-qt/libpeony-qt_zh_CN.ts b/translations/libpeony-qt/libpeony-qt_zh_CN.ts index d8de5ad92..68bdf8f4b 100644 --- a/translations/libpeony-qt/libpeony-qt_zh_CN.ts +++ b/translations/libpeony-qt/libpeony-qt_zh_CN.ts @@ -3792,6 +3792,10 @@ Do you want to delete the link file?</source> <source>Data</source> <translation>数据盘</translation> </message> + <message> + <source>Appfs</source> + <translation>应用盘</translation> + </message> </context> <context> <name>Peony::SideBarMenu</name> -- 2.25.1
这里完成了应用盘的新增工作,识别/appfs的目录,将其作为单独的硬盘显示在peony的侧边栏
diff --git a/libpeony-qt/model/side-bar-file-system-item.cpp b/libpeony-qt/model/side-bar-file-system-item.cpp index 0b6664966..f38d678e7 100644 --- a/libpeony-qt/model/side-bar-file-system-item.cpp +++ b/libpeony-qt/model/side-bar-file-system-item.cpp @@ -174,6 +174,11 @@ void SideBarFileSystemItem::initDirInfo(const QString &uri) m_displayName = tr("Appfs"); m_iconName = "drive-harddisk"; } + + if (uri == "file:///sysroot") { + m_displayName = tr("Sysroot"); + m_iconName = "drive-harddisk"; + } } void SideBarFileSystemItem::initComputerInfo() @@ -281,6 +286,11 @@ void SideBarFileSystemItem::initVolumeInfo(const Experimental_Peony::Volume &vol m_mounted = true; m_displayName = QObject::tr("Appfs"); m_iconName = "drive-harddisk"; + }else if("file:///sysroot" == m_uri){ + m_unmountable = m_mountable = m_ejectable = m_stopable = false; + m_mounted = true; + m_displayName = QObject::tr("Sysroot"); + m_iconName = "drive-harddisk"; } // kydrive特殊处理 @@ -618,6 +628,13 @@ void SideBarFileSystemItem::findChildren() m_model->endInsertRows(); } + if (FileUtils::isFileDirectory("file:///sysroot")) { + m_model->beginInsertRows(this->firstColumnIndex(), m_children->count(), m_children->count()); + SideBarFileSystemItem* item = new SideBarFileSystemItem("file:///sysroot", nullptr, this, m_model); + m_children->append(item); + m_model->endInsertRows(); + } + if (FileUtils::isFileDirectory("file:///appfs")) { m_model->beginInsertRows(this->firstColumnIndex(), m_children->count(), m_children->count()); SideBarFileSystemItem* item = new SideBarFileSystemItem("file:///appfs", nullptr, this, m_model); diff --git a/translations/libpeony-qt/libpeony-qt_zh_CN.ts b/translations/libpeony-qt/libpeony-qt_zh_CN.ts index 68bdf8f4b..3c0814fc8 100644 --- a/translations/libpeony-qt/libpeony-qt_zh_CN.ts +++ b/translations/libpeony-qt/libpeony-qt_zh_CN.ts @@ -3792,6 +3792,10 @@ Do you want to delete the link file?</source> <source>Data</source> <translation>数据盘</translation> </message> + <message> + <source>Sysroot</source> + <translation>系统盘</translation> + </message> <message> <source>Appfs</source> <translation>应用盘</translation>
这里新增了系统盘的显示,指向了sysroot目录。
通过上述操作,我们可以在文件管理器中正常的导航到/appfs和/sysroot两个目录。这两个目录实际上就是overlay的lower和upper。至此,在当前阶段,系统的文件存放在sysroot,而应用的文件存放在appfs。我们完成了应用程序和操作系统的分离。
我们在看内核代码的时候,有一个关于C语言的一个小技巧,个人觉得可以介绍分享一下,做个记录。从而方便大家看代码的时候心里直接有个答案,无需脑子里面再转个弯。
我们知道内核头文件会定义结构体,在定义结构体的时候,默认会把重要的结构体的第一项作为子结构体。如下示例:
struct __drm_planes_state { struct drm_plane *ptr; struct drm_plane_state *state, *old_state, *new_state; };
这里我们有个C语言的知识点如下:
struct drm_plane *ptr 的地址是struct __drm_planes_state的地址
也就是说,如果我们一直在操作ptr指针,其实我可以随时和任意的操作struct __drm_planes_state指针,伪代码如下:
struct __drm_planes_state* stat = (struct __drm_planes_state*) &ptr; if(stat->state){ ...... }
这里必须要清楚的是,ptr指针一定要是__drm_planes_state的成员,不能是从其他地方构造和赋值的指针值地址,错误的例子如下:
struct drm_plane *p1 = ptr; stat = (struct __drm_planes_state*) &p1;
这里p1我们不能直接去做取&运算,因为它本身不是__drm_planes_state的成员, 它只是普普通通的一个栈区地址。
#include <stdio.h> #include <stdlib.h> struct lower { int a; }; struct upper { struct lower *k; int b; }; int main() { struct upper *u = malloc(sizeof(struct upper)); struct lower *l = u->k; u->b = 2; struct upper* t1 = (struct upper*)&(u->k); struct upper* t2 = (struct upper*)&l; printf("t1=%p t2=%p t1->b=%d \n", t1, t2, t1->b); }
这里我们构造了一个upper的结构体,设置成员b的值为2,然后我们提取了t1和t2,并打印了地址,得到输出如下:
# gcc test.c -o test && ./test t1=0x558beacb70 t2=0x7ffd087628 t1->b=2
发现没,t1大概在堆区地址范围上,t2在栈区范围上,我们通过t1能够直接找到b,其值为2。
这里我们知道了一个内核通用技巧,我们可以在内核代码中经常看到直接强制类型转换就拿到了父的结构体的指针,然后直接操作代码。非常方便大家理解内核的逻辑。
以后大家看内核代码的时候,这类操作就不需要停顿下来脑子去转弯了。
如果经常看代码,我们可以发现内核充斥着大量的container_of函数,这个函数的意思是:
输入一个成员变量实体,一个父结构体类型,一个结构体成员变量声明,输出结构体父指针
为什么会有这样的函数呢,我们可以根据上文我们可以很容易提出疑问
如果我想找到父结构体指针,那么我的成员必定是第一个成员,那多不方便啊。如果这个成员不是第一个,那有啥好办法能够找到父结构体指针呢? 关于此,我们有两个知识点需要准备:
那基于此,很容易得出这么一个想法
我知道结构体的成员地址,然后推算我之前有多少的偏移,直接拿自己的指针减去这个偏移不就是父指针的地址了么 所以,我们开始解析container_of的宏定义,如下:
#define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ ((type *)(__mptr - offsetof(type, member))); })
我们还需要看offsetof的定义,根据内核头文件,我们可以查到:
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
这里有一个技巧,那就是
运用0地址做强制类型转换,然后去取类型的成员,这样就能拿到成员在结构体的偏移量,然后将其强制类型转换成size_t类型用作指针的减法运算
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) #endif #define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ ((type *)(__mptr - offsetof(type, member))); }) struct lower { int a; }; struct upper { int c,d,e,f; struct lower *k; int b; }; int main() { struct upper *u = malloc(sizeof(struct upper)); // struct upper* t = ({ void *__mptr = (void *)(&u->k); ((struct upper *)(__mptr - ((size_t)&((struct upper *)0)->k))); }); struct upper* t = container_of(&u->k, struct upper, k); printf("t=%p up=%p k=%p\n", t, u, &u->k); }
这里我们传入u->k的地址,upper的结构体定义,k成员,我们就能拿到upper的指针t
此时我们运行如下:
# gcc test.c -o test && ./test t=0x559829eb70 up=0x559829eb70 k=0x559829eb80
可以发现t,up的地址完全相等,k刚刚差一个offset地址偏移,也就是4*4=16,就是int c,d,e,f;的占用空间
这里我们知道了内核非常普遍的container_of的实现,它能够直接获取成员的父结构体指针
至此,我们知道了内核操作结构体的小技巧,通过这个技巧,我们可以轻松的找到结构体指针以及成员。这里作为内核的入门知识,对了解内核的人而言至关重要。
对于安卓系统,开机起来之后会全局发送一个BOOT_COMPLETED广播。应用程序通过这个广播就知道系统正常启动完成了。通常情况下,是安卓启动完全完成之后,ams会发送一个FINISH_BOOTING_MSG消息后,开启了30s的timeout检查是否为boot-completed。这里30s是开机动画,如果开机动画正常退出或者超过30s退出,则认为boot-completed。但是,在linux中,我们的系统好像没有利用起来这个机制。
本文基于systemd的默认机制,说明一下linux如何实现boot-completed信号。
对于系统而言,应该主动发送boot-completed信号,让其他程序能够获取当前系统的启动状态,如何此信号接收到,则代表系统已经准备完成,ui类型的应用可以运行,可以检测系统是否破损,可以判断系统是否异常等等。
对于安卓,我们可以通过如下获取信号
getprop sys.boot_completed
对于systemd,默认提供了类似此机制的信号,如下:
systemctl is-system-running --wait
此方法会一直阻塞,知道systemd认为系统启动完成。引入此功能的patch如下:
https://github.com/systemd/systemd/pull/9796/commits/02d9350cda6b330669607ae88c74ac7256212741
我们可以知道,此命令一直等待的是StartupFinished的dbus信号。
当systemd的一系列jobs都正常处理完成,则发送此信号,我们可以通过list-jobs来查看jobs运行状态,如下:
systemctl list-jobs
针对上面提到的,我们可以知道,我们直接监听dbus的StartupFinished信号,那么应用程序就能接收到systemd发送的boot_completed,所以我们借助yocto的工具源码dbus-wait如下:
/* * Copyright (C) 2008 Intel Corporation. * * Author: Ross Burton <ross@linux.intel.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <dbus/dbus.h> static char *interface, *member, *path; static void alarm_handler (int signum) { fprintf(stderr, "Timeout waiting for %s.%s\n", interface, member); exit (EXIT_SUCCESS); } static DBusHandlerResult filter (DBusConnection *conn, DBusMessage *message, void *user_data) { /* If the interface matches */ if (strcmp (interface, dbus_message_get_interface (message)) == 0) /* And the member matches */ if (strcmp (member, dbus_message_get_member (message)) == 0) /* And the path is NULL or matches */ if (path == NULL || strcmp (path, dbus_message_get_path (message)) == 0) /* Then exit */ exit (EXIT_SUCCESS); return DBUS_HANDLER_RESULT_HANDLED; } int main (int argc, char **argv) { DBusError error = DBUS_ERROR_INIT; DBusConnection *conn; char *match = NULL; if (argc < 3 || argc > 4) { fprintf (stderr, "$ dbus-wait <INTERFACE> <MEMBER> [PATH]\n"); return EXIT_FAILURE; } signal (SIGALRM, alarm_handler); alarm (60); /* TODO: allow system or session */ conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error); if (!conn) { fprintf (stderr, "Cannot get system bus: %s\n", error.message); dbus_error_free (&error); return EXIT_FAILURE; } switch (argc) { case 3: interface = argv[1]; member = argv[2]; path = NULL; asprintf (&match, "type='signal',interface='%s',member='%s'", interface, member); break; case 4: interface = argv[1]; member = argv[2]; path = argv[3]; asprintf (&match, "type='signal',interface='%s',member='%s',path='%s'", interface, member, path); break; } dbus_bus_add_match (conn, match, &error); free (match); if (dbus_error_is_set (&error)) { fprintf (stderr, "Cannot add match: %s\n", error.message); dbus_error_free (&error); return EXIT_FAILURE; } dbus_connection_add_filter (conn, filter, NULL, NULL); while (dbus_connection_read_write_dispatch (conn, -1)) ; dbus_connection_unref (conn); return EXIT_SUCCESS; }
编译如下:
gcc dbus-wait.c $(pkg-config --libs --cflags dbus-1)-o dbus-wait
至此,我们可以直接监听此dbus信号如下:
dbus-wait org.freedesktop.systemd1.Manager StartupFinished
对于通常的boot_completed信号,它只能判断systemd的service是否启动完成。我们知道,对于一个桌面系统而言,systemd服务并不代表系统真实的启动完成,例如ukui-session带起来的一系列应用程序,如果peony未启动,systemd并不能察觉,StartupFinished信号的发送和peony等应用程序的是否正常运行没有产生关联。所以我们需要产生关联。
至此,我们需要在ukui-session中,一边接受此信号,一边等待系统核心应用完成。然后,构造一个完整的boot_completed信号,提供给用户,至此我们在linux中,也具备了boot_completed信号。
我们开发工作中,如果是面向嵌入式的场景,很多客户其实倾向于使用交叉编译工具链,我们作为操作系统可以提供与系统本身gcc版本一致的交叉编译工具,这样构建的时候就不用担心libc/binutils等版本不匹配导致的符号问题从而构建的二进制无法运行的问题,关于交叉编译工具链的文章和工具获取,可以参考交叉编译环境搭建,本文主要谈一谈关于qt工程中,如果直接使用qmake来进行交叉编译过程中,我们需要修改qt.conf和qmake.conf的一些细节性问题
我们可以通过qt官方的wiki找到支持qt的交叉编译的办法,根据此办法,我相信大家能够很轻易的支持到qt环境的交叉编译,关于qt环境的交叉编译的细节就不再赘述,有兴趣可以仔细看看文档,文章链接如下:
https://wiki.qt.io/Cross-Compile_Qt_6_for_Raspberry_Pi
如果wiki.qt.io
无法正常访问,那么github上关于qt的cross compiling的文章也是一样的意思,如下
https://github.com/UvinduW/Cross-Compiling-Qt-for-Raspberry-Pi-4
还有一篇文章可以辅助了解
https://www.stefanocottafavi.com/crosscompile-qt-for-rpi/
根据上面的信息,我简单描述如下:
根据上面的简述,可以知道,我们在讨论qt的交叉编译的时候,往往是通过构建了qt的源码库。从而生成了对应的qmake和相关配置,我们直接使用qmake和相关配置即可完成交叉构建。这也是qt官方推荐的做法。
但是,本文需要的是直接基于sysroot中已安装的qt版本,来手动配置qmake和qtconf来实现交叉编译,那么这可以省略编译qt源码安插–sysroot这一个步骤。与上面官方的方法不同的点我列表如下:
qt官方:需要构建qt源码工程
本文讨论的:无需构建qt源码工程,程序链接sysroot下的qt库文件
我们知道如果不想通过重新构建qt源码来实现交叉编译的话,我们就需要在host上强行指定好sysroot,头文件位置,库文件位置,数据文件位置等。这需要对qmake和qt.conf有详细的了解。这也是本文讨论的重点。主要步骤如下:
我们可以通过linaro官网上下载工具链,也可以使用我们提供的工具链,交叉编译环境搭建有提到工具链地址是
gcc-kylin-9.3.0-2024.10-x86_64_aarch64-linux-gnu.tar.gz 文件获取位置(110服务器):/home/yangquan/develop/嵌入式相关/01_嵌入式定制部门/嵌入式版本项目&产品版本问题解决集/KylinSdk
我们需要一个编译的sysroot环境,这个环境可以在已有的机器环境上打包其rootfs,也可以在麒麟平台上根据系统iso的filesystem.squashfs来进行解压获取,里面是一个标准的操作系统环境,我们还需要在这个环境中根据deb包来下载安装必要的头文件和动态库
qt默认提供了基于aarch64的交叉编译配置,地址如下:
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-aarch64-gnu-g++/qmake.conf
我们可以发现其会设置相应的环境变量,例如
QMAKE_CC = aarch64-linux-gnu-gcc QMAKE_CXX = aarch64-linux-gnu-g++ QMAKE_LINK = aarch64-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ # modifications to linux.conf QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-linux-gnu-objcopy QMAKE_NM = aarch64-linux-gnu-nm -P QMAKE_STRIP = aarch64-linux-gnu-strip
可以发现,这里指向的是交叉编译工具链中的编译工具
我们配置了qmake默认使用交叉编译工具之后,还需要配置qt.conf,如下:
[Paths] Prefix=/usr ArchData=lib/aarch64-linux-gnu/qt5 Binaries=lib/qt5/bin Data=share/qt5 TargetSpec=linux-aarch64-gnu-g++ HostSpec=linux-aarch64-gnu-g++ Documentation=share/qt5/doc Examples=lib/aarch64-linux-gnu/qt5/examples Headers=include/aarch64-linux-gnu/qt5 HostBinaries=lib/qt5/bin HostData=lib/x86_64-linux-gnu/qt5 HostLibraries=lib/x86_64-linux-gnu Imports=lib/aarch64-linux-gnu/qt5/imports Libraries=lib/aarch64-linux-gnu LibraryExecutables=lib/aarch64-linux-gnu/qt5/libexec Plugins=lib/aarch64-linux-gnu/qt5/plugins Qml2Imports=lib/aarch64-linux-gnu/qt5/qml Settings=/etc/xdg Translations=share/qt5/translations Sysroot=/root/tf/kylin_test_cross/sysroot SysrootifyPrefix=true
例如sysroot位置,header文件查找位置,libraries库查找位置。
有了这个qt.conf,我们的一些环境变量会发生改变,例如,如果没有qt.conf,我们query的信息如下:
# qmake -query QT_SYSROOT: QT_INSTALL_PREFIX:/usr QT_INSTALL_ARCHDATA:/usr/lib/x86_64-linux-gnu/qt5 QT_INSTALL_DATA:/usr/share/qt5 QT_INSTALL_DOCS:/usr/share/qt5/doc QT_INSTALL_HEADERS:/usr/include/x86_64-linux-gnu/qt5 QT_INSTALL_LIBS:/usr/lib/x86_64-linux-gnu QT_INSTALL_LIBEXECS:/usr/lib/x86_64-linux-gnu/qt5/libexec QT_INSTALL_BINS:/usr/lib/qt5/bin QT_INSTALL_TESTS:/usr/tests QT_INSTALL_PLUGINS:/usr/lib/x86_64-linux-gnu/qt5/plugins QT_INSTALL_IMPORTS:/usr/lib/x86_64-linux-gnu/qt5/imports QT_INSTALL_QML:/usr/lib/x86_64-linux-gnu/qt5/qml QT_INSTALL_TRANSLATIONS:/usr/share/qt5/translations QT_INSTALL_CONFIGURATION:/etc/xdg QT_INSTALL_EXAMPLES:/usr/lib/x86_64-linux-gnu/qt5/examples QT_INSTALL_DEMOS:/usr/lib/x86_64-linux-gnu/qt5/examples QT_HOST_PREFIX:/usr QT_HOST_DATA:/usr/lib/x86_64-linux-gnu/qt5 QT_HOST_BINS:/usr/lib/qt5/bin QT_HOST_LIBS:/usr/lib/x86_64-linux-gnu QMAKE_SPEC:linux-g++ QMAKE_XSPEC:linux-g++ QMAKE_VERSION:3.1 QT_VERSION:5.12.8
可以发现其默认指向了本地的库和头文件,并未设置sysroot地址,如果我们指定qt.conf,则如下
# qmake -query -qtconf ./qt.conf QT_SYSROOT:/root/tf/kylin_test_cross/sysroot/ QT_SYSROOT:/root/tf/kylin_test_cross/sysroot QT_INSTALL_PREFIX:/root/tf/kylin_test_cross/sysroot/usr QT_INSTALL_PREFIX/raw:/usr QT_INSTALL_ARCHDATA:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5 QT_INSTALL_ARCHDATA/raw:/usr/lib/aarch64-linux-gnu/qt5 QT_INSTALL_DATA:/root/tf/kylin_test_cross/sysroot/usr/share/qt5 QT_INSTALL_DATA/raw:/usr/share/qt5 QT_INSTALL_DOCS:/root/tf/kylin_test_cross/sysroot/usr/share/qt5/doc QT_INSTALL_DOCS/raw:/usr/share/qt5/doc QT_INSTALL_HEADERS:/root/tf/kylin_test_cross/sysroot/usr/include/aarch64-linux-gnu/qt5 QT_INSTALL_HEADERS/raw:/usr/include/aarch64-linux-gnu/qt5 QT_INSTALL_LIBS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu QT_INSTALL_LIBS/raw:/usr/lib/aarch64-linux-gnu QT_INSTALL_LIBEXECS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/libexec QT_INSTALL_LIBEXECS/raw:/usr/lib/aarch64-linux-gnu/qt5/libexec QT_INSTALL_BINS:/root/tf/kylin_test_cross/sysroot/usr/lib/qt5/bin QT_INSTALL_BINS/raw:/usr/lib/qt5/bin QT_INSTALL_TESTS:/root/tf/kylin_test_cross/sysroot/usr/tests QT_INSTALL_TESTS/raw:/usr/tests QT_INSTALL_PLUGINS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/plugins QT_INSTALL_PLUGINS/raw:/usr/lib/aarch64-linux-gnu/qt5/plugins QT_INSTALL_IMPORTS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/imports QT_INSTALL_IMPORTS/raw:/usr/lib/aarch64-linux-gnu/qt5/imports QT_INSTALL_QML:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/qml QT_INSTALL_QML/raw:/usr/lib/aarch64-linux-gnu/qt5/qml QT_INSTALL_TRANSLATIONS:/root/tf/kylin_test_cross/sysroot/usr/share/qt5/translations QT_INSTALL_TRANSLATIONS/raw:/usr/share/qt5/translations QT_INSTALL_CONFIGURATION:/root/tf/kylin_test_cross/sysroot/etc/xdg QT_INSTALL_CONFIGURATION/raw:/etc/xdg QT_INSTALL_EXAMPLES:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_EXAMPLES/raw:/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_DEMOS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_DEMOS/raw:/usr/lib/aarch64-linux-gnu/qt5/examples QT_HOST_PREFIX:/usr QT_HOST_DATA:/usr/lib/x86_64-linux-gnu/qt5 QT_HOST_BINS:/usr/lib/qt5/bin QT_HOST_LIBS:/usr/lib/x86_64-linux-gnu QMAKE_SPEC:linux-aarch64-gnu-g++ QMAKE_XSPEC:linux-aarch64-gnu-g++ QMAKE_VERSION:3.1 QT_VERSION:5.12.8
可以发现,我们headers和libs等等都从正确的路径寻找。
至此,我们发现,整个qt.conf和qmake.conf已经正确配置,我们可以良好的进行编译构建。
如果对于一个qt仓库,我们以qt的example为例,/usr/lib/x86_64-linux-gnu/qt5/examples/gui/analogclock
。我们可以如下:
cd /usr/lib/x86_64-linux-gnu/qt5/examples/gui/analogclock
我们需要根据qmake生成Makefile,如下
/usr/lib/qt5/bin/qmake -qtconf ./qt.conf
然后直接make,如下:
make
此时,我们会生成如下文件
# file analogclock analogclock: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=5f848734c8f59a6958bf829be3a72dd4d6b0a124, for GNU/Linux 3.7.0, not stripped
此时,文件生成完成,可以发现是aarch64的qt程序,我们将其发送到实际机器上即可正常运行。
因为客户的需求,我们需要在标准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盘也是遗留空文件夹。这不是异常。