├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── container ├── build-control-panel.sh ├── build-turnip-driver.sh ├── control-panel │ ├── .gitignore │ ├── CMakeLists.txt │ ├── config.h │ ├── listeners │ │ ├── ReadEventListener.cpp │ │ └── ReadEventListener.h │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── utils │ │ ├── Utils.cpp │ │ ├── Utils.h │ │ ├── XmlHelper.cpp │ │ └── XmlHelper.h │ └── widgets │ │ ├── HomeWidget.cpp │ │ ├── HomeWidget.h │ │ ├── HomeWidget.ui │ │ ├── LabwcWidget.cpp │ │ ├── LabwcWidget.h │ │ ├── LabwcWidget.ui │ │ ├── MiscWidget.cpp │ │ ├── MiscWidget.h │ │ ├── MiscWidget.ui │ │ ├── NetworkWidget.cpp │ │ ├── NetworkWidget.h │ │ ├── NetworkWidget.ui │ │ ├── ScreenWidget.cpp │ │ ├── ScreenWidget.h │ │ └── ScreenWidget.ui ├── menu.xml └── wra │ ├── cancel_trigger.sh │ ├── display.sh │ ├── init.sh │ ├── labwc.pixman.sh │ ├── screen_kbd.sh │ └── start_systemd.sh ├── docs ├── README.md └── build.md ├── scripts ├── _base_env.sh ├── avbtool.py ├── build_all.sh ├── build_container_files.sh ├── build_system_files.sh ├── configure_rootfs.sh ├── copy_built_files.sh ├── extract_gsi.sh ├── mke2fs.conf ├── mkuserimg_mke2fs.py ├── packaging_rootfs.sh ├── packaging_system.sh ├── prepare_build_env.sh └── setup_system_structure.sh └── system ├── bin ├── init.sh └── trigger.sh ├── build-tools.sh ├── ext └── qualcomm_cnss.sh ├── skip_mount.cfg └── src ├── .gitignore ├── CMakeLists.txt ├── drm-test ├── drm-api.c ├── drm-api.h ├── drm-test.cpp └── libdrm │ ├── drm.h │ ├── drm_mode.h │ ├── xf86drm.c │ ├── xf86drm.h │ ├── xf86drmMode.c │ └── xf86drmMode.h ├── init └── init.c ├── uevent ├── init │ ├── devices.cpp │ ├── devices.h │ ├── firmware_handler.cpp │ ├── firmware_handler.h │ ├── modalias_handler.cpp │ ├── modalias_handler.h │ ├── uevent.h │ ├── uevent_handler.h │ ├── uevent_listener.cpp │ ├── uevent_listener.h │ ├── ueventd.cpp │ ├── ueventd.h │ └── ueventd_parser.h ├── libcutils │ ├── uevent.cpp │ └── uevent.h ├── libmodprobe │ ├── libmodprobe.cpp │ ├── libmodprobe_ext.cpp │ └── modprobe.h ├── main.cpp └── main.h ├── utils ├── DeviceMapper.cpp ├── DeviceMapper.h ├── logger.h ├── utils.cpp └── utils.h └── wra-utils └── main.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [pull_request, push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: checkout repository 9 | uses: actions/checkout@v4 10 | 11 | - name: install debootstrap 12 | run: | 13 | git clone --depth=1 https://salsa.debian.org/installer-team/debootstrap/ 14 | sudo chmod 0755 debootstrap/debootstrap 15 | sudo ln -s $(realpath debootstrap/debootstrap) -t /usr/bin 16 | sudo ln -s $(realpath debootstrap) -t /usr/share/ 17 | - name: install qemu-user-static 18 | run: sudo apt install qemu-user-static 19 | 20 | - name: download base gsi.img 21 | run: | 22 | mkdir -p build 23 | wget https://github.com/TrebleDroid/treble_experimentations/releases/download/ci-20240226/system-td-arm64-ab-vanilla.img.xz 24 | xz -d system-td-arm64-ab-vanilla.img.xz 25 | mv system-td-arm64-ab-vanilla.img build/gsi.img 26 | 27 | - name: setup permissions 28 | run: sudo bash -c "chmod -R 777 ." 29 | 30 | - name: exec scripts/build_all.sh 31 | run: sudo bash -c "cd scripts && ./build_all.sh" 32 | 33 | - name: upload build artifacts 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: Artifacts 37 | path: build/system.img 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .history 4 | .idea -------------------------------------------------------------------------------- /container/build-control-panel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | apt-get install -y qt6-base-dev cmake 6 | 7 | cd /build || exit 1 8 | mkdir -p contorl-panel 9 | cd contorl-panel || exit 1 10 | cmake /container/control-panel -DCMAKE_BUILD_TYPE=Release 11 | cmake --build . -j -------------------------------------------------------------------------------- /container/build-turnip-driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | source() { 6 | command -v git &> /dev/null || apt-get install git ca-certificates -y --no-install-suggests --no-install-recommends 7 | [ ! -d "mesa" ] && git clone https://gitlab.freedesktop.org/zsnow/mesa --depth=1 8 | echo "" 9 | } 10 | 11 | build() { 12 | cd mesa || exit 1 13 | git pull 14 | mkdir -p build 15 | cd build || exit 1 16 | command -v meson &> /dev/null || apt-get install meson ninja-build -y 17 | apt-get -y install python3-mako python3-yaml zlib1g-dev libexpat1-dev libdrm-dev bison flex cmake wayland-protocols \ 18 | libwayland-dev xorg-dev libxml2-dev libxcb-dri2-0-dev libxcb-dri3-dev libxcb-glx0-dev libxcb-present-dev libxcb-randr0-dev \ 19 | libxcb-shm0-dev libxcb-sync-dev libxcb-xfixes0-dev libxshmfence-dev libxcb1-dev libx11-xcb-dev libxcb-keysyms1-dev 20 | meson setup '-Dfreedreno-kmds=['\''msm'\'','\''kgsl'\'']' '-Dgallium-drivers=[]' '-Dvulkan-drivers=['\''freedreno'\'']' 21 | ninja 22 | } 23 | 24 | install() { 25 | ninja install 26 | strip --strip-unneeded /usr/local/lib/aarch64-linux-gnu/libvulkan_freedreno.so 27 | } 28 | 29 | case "$1" in 30 | "source") 31 | source 32 | ;; 33 | "build") 34 | build 35 | ;; 36 | "install") 37 | install 38 | ;; 39 | *) 40 | source 41 | build 42 | install 43 | ;; 44 | esac 45 | -------------------------------------------------------------------------------- /container/control-panel/.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-* 2 | build 3 | CMakeLists.txt.user 4 | .idea -------------------------------------------------------------------------------- /container/control-panel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(control-panel VERSION 0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_AUTOUIC ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | set(CMAKE_CXX_STANDARD 20) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | find_package(Qt6 REQUIRED COMPONENTS Widgets Xml) 13 | 14 | file(GLOB WIDGETS widgets/*) 15 | 16 | set(PROJECT_SOURCES 17 | main.cpp 18 | mainwindow.cpp 19 | mainwindow.h 20 | mainwindow.ui 21 | ${WIDGETS} 22 | utils/Utils.cpp 23 | utils/Utils.h 24 | utils/XmlHelper.cpp 25 | utils/XmlHelper.h 26 | config.h 27 | listeners/ReadEventListener.cpp 28 | listeners/ReadEventListener.h 29 | ) 30 | 31 | qt_add_executable(control-panel 32 | MANUAL_FINALIZATION 33 | ${PROJECT_SOURCES} 34 | ) 35 | 36 | target_link_libraries(control-panel PRIVATE Qt6::Widgets Qt6::Xml) 37 | 38 | target_include_directories(control-panel PRIVATE . utils) 39 | 40 | include(GNUInstallDirs) 41 | install(TARGETS control-panel 42 | BUNDLE DESTINATION . 43 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 44 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 45 | ) 46 | 47 | qt_finalize_executable(control-panel) 48 | -------------------------------------------------------------------------------- /container/control-panel/config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-9-14. 3 | // 4 | 5 | #ifndef CONTROL_PANEL_CONFIG_H 6 | #define CONTROL_PANEL_CONFIG_H 7 | 8 | static constexpr char QSETTINGS_ORGANIZATION[] = "wra"; 9 | static constexpr char QSETTINGS_APPLICATION[] = "control-panel"; 10 | 11 | #endif //CONTROL_PANEL_CONFIG_H 12 | -------------------------------------------------------------------------------- /container/control-panel/listeners/ReadEventListener.cpp: -------------------------------------------------------------------------------- 1 | #include "ReadEventListener.h" 2 | 3 | ReadEventListener::ReadEventListener(std::function trigger) : trigger(std::move(trigger)) { 4 | std::thread([this]() { 5 | loop(); 6 | }).detach(); 7 | } 8 | 9 | ReadEventListener::~ReadEventListener() { 10 | running = false; 11 | } 12 | 13 | void ReadEventListener::tri() { 14 | auto now = std::chrono::steady_clock::now(); 15 | if ((now - lastTri) > std::chrono::seconds(1)) { 16 | lastTri = now; 17 | trigger(); 18 | } 19 | } 20 | 21 | void ReadEventListener::loop() { 22 | while (running) { 23 | char buf[1024]; 24 | ssize_t r = ::read(fd, buf, sizeof(buf)); 25 | if (r == -1) { 26 | std::cerr << "ReadEventListener:Waiting!\n"; 27 | perror("ReadEventListener"); 28 | std::this_thread::sleep_for(std::chrono::seconds(1)); 29 | continue; 30 | } 31 | tri(); 32 | } 33 | } 34 | 35 | void ReadEventListener::setPath(const std::string &path) { 36 | ::close(fd); 37 | fd = ::open(path.c_str(), O_RDONLY); 38 | } 39 | -------------------------------------------------------------------------------- /container/control-panel/listeners/ReadEventListener.h: -------------------------------------------------------------------------------- 1 | #ifndef READEVENTLISTENER_H 2 | #define READEVENTLISTENER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class ReadEventListener { 11 | public: 12 | typedef std::function Trigger; 13 | private: 14 | Trigger trigger; 15 | int fd = -1; 16 | bool running = true; 17 | std::chrono::steady_clock::time_point lastTri; 18 | 19 | public: 20 | explicit ReadEventListener(Trigger trigger); 21 | 22 | ~ReadEventListener(); 23 | 24 | void setPath(const std::string &path); 25 | 26 | private: 27 | void tri(); 28 | 29 | void loop(); 30 | }; 31 | 32 | 33 | #endif // READEVENTLISTENER_H 34 | -------------------------------------------------------------------------------- /container/control-panel/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | // set up env var for start from lxqt-sudo 6 | for (int i = 0; i < argc; ++i) { 7 | QString arg(argv[i]); 8 | if (arg.contains("=")) { 9 | putenv(argv[i]); 10 | } 11 | } 12 | 13 | QApplication a(argc, argv); 14 | MainWindow w; 15 | w.show(); 16 | return QApplication::exec(); 17 | } 18 | -------------------------------------------------------------------------------- /container/control-panel/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | #include "./ui_mainwindow.h" 6 | #include 7 | #include 8 | 9 | MainWindow::MainWindow(QWidget *parent) 10 | : QMainWindow(parent), ui(new Ui::MainWindow) { 11 | ui->setupUi(this); 12 | connect(ui->list, &QListWidget::currentRowChanged, ui->stackedWidget, &QStackedWidget::setCurrentIndex); 13 | connect(this, &MainWindow::runOnUiThread, this, &MainWindow::doRunOnUiThread); 14 | instance = this; 15 | if (geteuid() == 0) { 16 | setWindowTitle(windowTitle() + " [root]"); 17 | } 18 | } 19 | 20 | MainWindow::~MainWindow() { 21 | delete ui; 22 | } 23 | 24 | UI_THREAD 25 | void MainWindow::doRunOnUiThread(const std::function &function) { 26 | function(); 27 | } 28 | 29 | 30 | UI_THREAD 31 | int MainWindow::addStatusStr(const QString &string) { 32 | for (int i = 0;; ++i) { 33 | QLabel *res = statusLabels.value(i, nullptr); 34 | if (res == nullptr) { 35 | res = new QLabel(">" + string); 36 | fprintf(stderr, "StatusBar: add %d %s\n", i, string.toUtf8().data()); 37 | ui->statusbar->addWidget(res); 38 | statusLabels[i] = res; 39 | return i; 40 | } 41 | } 42 | } 43 | 44 | UI_THREAD 45 | void MainWindow::replaceStatusStr(int index, const QString &string) { 46 | QLabel *res = statusLabels.value(index, nullptr); 47 | if (res == nullptr) 48 | assert(0); 49 | fprintf(stderr, "StatusBar: replace %d %s\n", index, string.toUtf8().data()); 50 | res->setText(string); 51 | } 52 | 53 | ANY_THREAD 54 | void MainWindow::replaceStatusStrA(int index, const QString &string) { 55 | runOnUiThread([this, index, string] { 56 | replaceStatusStr(index, string); 57 | }); 58 | } 59 | 60 | UI_THREAD 61 | void MainWindow::removeStatusStr(int index, const QString &finishStr, int timeout) { 62 | QLabel *res = statusLabels.value(index, nullptr); 63 | if (res == nullptr) 64 | assert(0); 65 | res->setText(">" + finishStr); 66 | fprintf(stderr, "StatusBar: remove %d %s\n", index, finishStr.toUtf8().data()); 67 | std::thread([this, res, index, timeout] { 68 | std::this_thread::sleep_for(std::chrono::seconds(timeout)); 69 | runOnUiThread([this, res, index] { 70 | ui->statusbar->removeWidget(res); 71 | delete res; 72 | statusLabels.remove(index); 73 | }); 74 | }).detach(); 75 | } 76 | 77 | ANY_THREAD 78 | int MainWindow::addStatusStrA(const QString &string) { 79 | QSemaphore semaphore; 80 | int *r = new int; 81 | runOnUiThread([this, string, &semaphore, r] { 82 | *r = addStatusStr(string); 83 | semaphore.release(); 84 | }); 85 | semaphore.acquire(); 86 | int rr = *r; 87 | delete r; 88 | return rr; 89 | } 90 | 91 | ANY_THREAD 92 | void MainWindow::removeStatusStrA(int index, const QString &finishStr, int timeout) { 93 | runOnUiThread([this, index, finishStr, timeout] { 94 | removeStatusStr(index, finishStr, timeout); 95 | }); 96 | } 97 | 98 | MainWindow *MainWindow::getInstance() { 99 | return instance; 100 | } 101 | 102 | void MainWindow::runOnUiThreadBlocked(const std::function &function) { 103 | std::counting_semaphore<1> semaphore{0}; 104 | runOnUiThread([&] { 105 | function(); 106 | semaphore.release(); 107 | }); 108 | semaphore.acquire(); 109 | } 110 | -------------------------------------------------------------------------------- /container/control-panel/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | QT_BEGIN_NAMESPACE 9 | namespace Ui { 10 | class MainWindow; 11 | } 12 | QT_END_NAMESPACE 13 | 14 | class MainWindow : public QMainWindow { 15 | Q_OBJECT 16 | 17 | #define ANY_THREAD 18 | #define UI_THREAD 19 | 20 | public: 21 | MainWindow(QWidget *parent = nullptr); 22 | 23 | ~MainWindow(); 24 | 25 | void runOnUiThreadBlocked(const std::function &function); 26 | 27 | signals: 28 | 29 | void runOnUiThread(const std::function &function); 30 | 31 | private: 32 | Ui::MainWindow *ui; 33 | QMap statusLabels; 34 | 35 | static void doRunOnUiThread(const std::function &function); 36 | 37 | public: 38 | void replaceStatusStr(int index, const QString &string); 39 | 40 | int addStatusStr(const QString &string); 41 | 42 | int addStatusStrA(const QString &string); 43 | 44 | void removeStatusStrA(int index, const QString &finishStr, int timeout = 2); 45 | 46 | void replaceStatusStrA(int index, const QString &string); 47 | 48 | void removeStatusStr(int index, const QString &qString, int timeout = 2); 49 | 50 | static MainWindow *getInstance(); 51 | }; 52 | 53 | static MainWindow *instance; 54 | 55 | #endif // MAINWINDOW_H 56 | -------------------------------------------------------------------------------- /container/control-panel/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 640 10 | 480 11 | 12 | 13 | 14 | Wra Control Panel 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 0 24 | 25 | 26 | 27 | QAbstractScrollArea::AdjustToContents 28 | 29 | 30 | QListView::ListMode 31 | 32 | 33 | -1 34 | 35 | 36 | 37 | Home 38 | 39 | 40 | 41 | 12 42 | 43 | 44 | 45 | 46 | 47 | Labwc 48 | 49 | 50 | 51 | 12 52 | 53 | 54 | 55 | 56 | 57 | Network 58 | 59 | 60 | 61 | 12 62 | 63 | 64 | 65 | 66 | 67 | Screen 68 | 69 | 70 | 71 | 12 72 | 73 | 74 | 75 | 76 | 77 | Misc 78 | 79 | 80 | 81 | 12 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | HomeWidget 103 | QWidget 104 |
widgets/HomeWidget.h
105 | 1 106 |
107 | 108 | NetworkWidget 109 | QWidget 110 |
widgets/NetworkWidget.h
111 | 1 112 |
113 | 114 | LabwcWidget 115 | QWidget 116 |
widgets/LabwcWidget.h
117 | 1 118 |
119 | 120 | ScreenWidget 121 | QWidget 122 |
widgets/ScreenWidget.h
123 | 1 124 |
125 | 126 | MiscWidget 127 | QWidget 128 |
widgets/MiscWidget.h
129 | 1 130 |
131 |
132 | 133 | 134 |
135 | -------------------------------------------------------------------------------- /container/control-panel/utils/Utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-8-22. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "Utils.h" 9 | #include 10 | 11 | QString Utils::cmdToString(const QString &program, const QStringList &arguments) { 12 | QString cmd = program; 13 | for (const auto &item: arguments) { 14 | cmd += " " + item; 15 | } 16 | return cmd; 17 | } 18 | 19 | int Utils::exec(const QString &program, const QStringList &arguments, QString &result) { 20 | QProcess p; 21 | std::cerr << "Utils::exec " << cmdToString(program, arguments).toStdString() << "\n"; 22 | p.start(program, arguments); 23 | p.waitForStarted(); 24 | p.waitForFinished(); 25 | result = p.readAllStandardOutput(); 26 | result += p.readAllStandardError(); 27 | std::cerr << "Utils::exec " << program.toStdString() << ":\n" << result.toStdString(); 28 | std::cerr << "Utils::exec\n"; 29 | return p.exitCode(); 30 | } 31 | 32 | void Utils::unableToExecMsgBox(const QString &program, const QStringList &arguments, const QString &result) { 33 | QMessageBox messageBox{MainWindow::getInstance()}; 34 | messageBox.setText("Unable to exec [" + cmdToString(program, arguments) + "]\n\n" + result); 35 | messageBox.exec(); 36 | } 37 | 38 | bool Utils::unableToExecContinueMsgBox(const QString &program, const QStringList &arguments, const QString &result) { 39 | QMessageBox messageBox{MainWindow::getInstance()}; 40 | messageBox.setText("Unable to exec [" + cmdToString(program, arguments) + "]\n\n" + result + "\nContinue?"); 41 | messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); 42 | int r = messageBox.exec(); 43 | return r == QMessageBox::Yes; 44 | } 45 | 46 | bool Utils::checkRoot() { 47 | if (geteuid() == 0) 48 | return true; 49 | QMessageBox messageBox{MainWindow::getInstance()}; 50 | messageBox.setText("Must be root"); 51 | messageBox.exec(); 52 | return false; 53 | } 54 | 55 | void Utils::execInTerminal(const QString &cmd) { 56 | QProcess::execute("/bin/qterminal", {"-e", "/bin/bash", "-c", cmd}); 57 | } -------------------------------------------------------------------------------- /container/control-panel/utils/Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-8-22. 3 | // 4 | 5 | #ifndef CONTROL_PANEL_UTILS_H 6 | #define CONTROL_PANEL_UTILS_H 7 | 8 | 9 | class Utils { 10 | public: 11 | static int exec(const QString &program, const QStringList &arguments, QString &result); 12 | 13 | static void unableToExecMsgBox(const QString &program, const QStringList &arguments, const QString &result); 14 | 15 | static QString cmdToString(const QString &program, const QStringList &arguments); 16 | 17 | static bool unableToExecContinueMsgBox(const QString &program, const QStringList &arguments, const QString &result); 18 | 19 | static bool checkRoot(); 20 | 21 | static void execInTerminal(const QString &cmd); 22 | }; 23 | 24 | 25 | #endif //CONTROL_PANEL_UTILS_H 26 | -------------------------------------------------------------------------------- /container/control-panel/utils/XmlHelper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-8-22. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "XmlHelper.h" 9 | 10 | bool XmlHelper::openFile(const QString &filePath) { 11 | QFile file(filePath); 12 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 13 | std::cerr << "Failed to open file:" << filePath.toStdString(); 14 | return false; 15 | } 16 | if (!doc.setContent(&file)) { 17 | std::cerr << "Failed to parse XML content."; 18 | file.close(); 19 | return false; 20 | } 21 | file.close(); 22 | return true; 23 | } 24 | 25 | bool XmlHelper::writeToFile(const QString &filePath) { 26 | QFile outFile(filePath); 27 | if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 28 | std::cerr << "Failed to open file for writing:" << filePath.toStdString(); 29 | return false; 30 | } 31 | QTextStream stream(&outFile); 32 | stream << doc.toString(); 33 | outFile.close(); 34 | return true; 35 | } 36 | 37 | void XmlHelper::replaceElement(QDomElement &element, const QString &newValue) { 38 | QDomText matrixText = doc.createTextNode(newValue); 39 | element.removeChild(element.firstChild()); 40 | element.appendChild(matrixText); 41 | } 42 | 43 | QDomElement XmlHelper::getOrCreateElement(QDomElement &parent, const QString &elementName) { 44 | QDomElement element = parent.firstChildElement(elementName); 45 | if (element.isNull()) { 46 | element = doc.createElement(elementName); 47 | parent.appendChild(element); 48 | } 49 | return element; 50 | } 51 | 52 | QDomElement XmlHelper::getRoot() { 53 | return doc.documentElement(); 54 | } -------------------------------------------------------------------------------- /container/control-panel/utils/XmlHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-8-22. 3 | // 4 | 5 | #ifndef CONTROL_PANEL_XMLHELPER_H 6 | #define CONTROL_PANEL_XMLHELPER_H 7 | 8 | #include 9 | 10 | class XmlHelper { 11 | QDomDocument doc; 12 | public: 13 | bool openFile(const QString &filePath); 14 | 15 | bool writeToFile(const QString &filePath); 16 | 17 | void replaceElement(QDomElement &element, const QString &newValue); 18 | 19 | QDomElement getOrCreateElement(QDomElement &parent, const QString &elementName); 20 | 21 | QDomElement getRoot(); 22 | }; 23 | 24 | 25 | #endif //CONTROL_PANEL_XMLHELPER_H 26 | -------------------------------------------------------------------------------- /container/control-panel/widgets/HomeWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "HomeWidget.h" 3 | #include "ui_HomeWidget.h" 4 | #include 5 | #include 6 | #include 7 | 8 | HomeWidget::HomeWidget(QWidget *parent) 9 | : QWidget(parent), ui(new Ui::HomeWidget) { 10 | ui->setupUi(this); 11 | connect(ui->userButton, &QPushButton::clicked, this, &HomeWidget::onClickStartRootMode); 12 | uint uid = geteuid(); 13 | struct passwd *pw = getpwuid(uid); 14 | ui->userLabel->setText("Current Effective User " + QString(pw->pw_name)); 15 | if (uid == 0) 16 | ui->userButton->setEnabled(false); 17 | } 18 | 19 | HomeWidget::~HomeWidget() { 20 | delete ui; 21 | } 22 | 23 | void HomeWidget::onClickStartRootMode() { 24 | QStringList args; 25 | args.append(QApplication::applicationFilePath()); 26 | args.append(QProcessEnvironment::systemEnvironment().toStringList()); 27 | QProcess::startDetached("/bin/lxqt-sudo", args); 28 | } -------------------------------------------------------------------------------- /container/control-panel/widgets/HomeWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef HOMEWIDGET_H 2 | #define HOMEWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class HomeWidget; 8 | } 9 | 10 | class HomeWidget : public QWidget { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit HomeWidget(QWidget *parent = nullptr); 15 | 16 | ~HomeWidget(); 17 | 18 | private: 19 | Ui::HomeWidget *ui; 20 | 21 | static void onClickStartRootMode(); 22 | }; 23 | 24 | #endif // HOMEWIDGET_H 25 | -------------------------------------------------------------------------------- /container/control-panel/widgets/HomeWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | HomeWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | https://github.com/snowNF/wra 21 | 22 | 23 | 24 | 25 | 26 | 27 | Current user: 28 | 29 | 30 | 31 | 32 | 33 | 34 | Launch in root 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /container/control-panel/widgets/LabwcWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "LabwcWidget.h" 4 | #include "ui_LabwcWidget.h" 5 | #include "mainwindow.h" 6 | #include 7 | 8 | static struct { 9 | QString name; 10 | QString value; 11 | } Values[] = {{"90 deg cw", "0 -1 1 1 0 0"}, 12 | {"180 deg cw", "-1 0 1 0 -1 1"}, 13 | {"270 deg cw", "0 1 0 -1 0 1"}, 14 | {"reflect along y axis", "-1 0 1 1 0 0"}}; 15 | 16 | static QString getCfgPath() { 17 | QString path = QDir::homePath(); 18 | path += "/.config/labwc/rc.xml"; 19 | return QDir(path).canonicalPath(); 20 | } 21 | 22 | LabwcWidget::LabwcWidget(QWidget *parent) 23 | : QWidget(parent), ui(new Ui::LabwcWidget) { 24 | ui->setupUi(this); 25 | connect(ui->saveButton, &QPushButton::clicked, this, &LabwcWidget::onClickSave); 26 | for (const auto &item: Values) { 27 | ui->cbComboBox->addItem(item.name); 28 | } 29 | ui->saveLabel->setText(getCfgPath()); 30 | } 31 | 32 | LabwcWidget::~LabwcWidget() { 33 | delete ui; 34 | } 35 | 36 | void LabwcWidget::onClickSave() { 37 | QString value = Values[ui->cbComboBox->currentIndex()].value; 38 | QString path = getCfgPath(); 39 | int i = MainWindow::getInstance()->addStatusStrA("Writing " + value + " to " + path); 40 | XmlHelper xml; 41 | xml.openFile(path); 42 | auto root = xml.getRoot(); 43 | auto libinput = xml.getOrCreateElement(root, "libinput"); 44 | auto device = xml.getOrCreateElement(libinput, "device"); 45 | auto cm = xml.getOrCreateElement(device, "calibrationMatrix"); 46 | xml.replaceElement(cm, value); 47 | xml.writeToFile(path); 48 | MainWindow::getInstance()->removeStatusStrA(i, "Finished"); 49 | } -------------------------------------------------------------------------------- /container/control-panel/widgets/LabwcWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef LABWCWIDGET_H 2 | #define LABWCWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class LabwcWidget; 8 | } 9 | 10 | class LabwcWidget : public QWidget { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit LabwcWidget(QWidget *parent = nullptr); 15 | 16 | ~LabwcWidget(); 17 | 18 | private: 19 | Ui::LabwcWidget *ui; 20 | 21 | void onClickSave(); 22 | }; 23 | 24 | #endif // LABWCWIDGET_H 25 | -------------------------------------------------------------------------------- /container/control-panel/widgets/LabwcWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LabwcWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | <html><head/><body><p>On some devices (especially tablets), the touch coordinates are incorrect and a rotation needs to be performed.</p></body></html> 21 | 22 | 23 | TouchCalibrationMatrix 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Save 34 | 35 | 36 | 37 | 38 | 39 | 40 | Save to ~/.config/labwc/rc.xml 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /container/control-panel/widgets/MiscWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "MiscWidget.h" 2 | #include "ui_MiscWidget.h" 3 | #include "mainwindow.h" 4 | #include 5 | #include 6 | 7 | MiscWidget::MiscWidget(QWidget *parent) 8 | : QWidget(parent), ui(new Ui::MiscWidget) { 9 | ui->setupUi(this); 10 | connect(ui->systemdButton, &QPushButton::clicked, this, &MiscWidget::onClickStartSystemd); 11 | } 12 | 13 | MiscWidget::~MiscWidget() { 14 | delete ui; 15 | } 16 | 17 | void MiscWidget::onClickStartSystemd() { 18 | if (!Utils::checkRoot()) 19 | return; 20 | int i = MainWindow::getInstance()->addStatusStrA("Writing"); 21 | QString out; 22 | QString prog = "busybox"; 23 | QStringList args = {"touch", "/wra/.systemd"}; 24 | if (Utils::exec(prog, args, out) != 0) { 25 | Utils::unableToExecMsgBox(prog, args, out); 26 | } 27 | MainWindow::getInstance()->removeStatusStrA(i, "Finished"); 28 | } 29 | -------------------------------------------------------------------------------- /container/control-panel/widgets/MiscWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef MISCWIDGET_H 2 | #define MISCWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MiscWidget; 8 | } 9 | 10 | class MiscWidget : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MiscWidget(QWidget *parent = nullptr); 16 | ~MiscWidget(); 17 | 18 | private: 19 | Ui::MiscWidget *ui; 20 | 21 | static void onClickStartSystemd(); 22 | }; 23 | 24 | #endif // MISCWIDGET_H 25 | -------------------------------------------------------------------------------- /container/control-panel/widgets/MiscWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MiscWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Start systemd 21 | 22 | 23 | 24 | 25 | 26 | 27 | Execute 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /container/control-panel/widgets/NetworkWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkWidget.h" 2 | #include "ui_NetworkWidget.h" 3 | #include "Utils.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | NetworkWidget::NetworkWidget(QWidget *parent) 10 | : QWidget(parent), ui(new Ui::NetworkWidget) { 11 | ui->setupUi(this); 12 | connect(ui->socketButton, &QPushButton::clicked, this, &NetworkWidget::onCLickFixSocketPermission); 13 | } 14 | 15 | NetworkWidget::~NetworkWidget() { 16 | delete ui; 17 | } 18 | 19 | static bool msg(const QString &prog, const QStringList &arg, const QString &result) { 20 | bool ret; 21 | MainWindow::getInstance()->runOnUiThreadBlocked([&] { 22 | ret = Utils::unableToExecContinueMsgBox(prog, arg, result); 23 | }); 24 | return ret; 25 | } 26 | 27 | static bool groupAdd(const QString &id, const QString &name) { 28 | QString result; 29 | QString prog = "/usr/sbin/groupadd"; 30 | QStringList arg = {"-g", id, name}; 31 | int r = Utils::exec(prog, arg, result); 32 | if (r != 0 && r != 9) { 33 | return msg(prog, arg, result); 34 | } 35 | return true; 36 | } 37 | 38 | static bool userMod(const QString &groupName, const QString &userName) { 39 | QString result; 40 | QString prog = "/usr/sbin/usermod"; 41 | QStringList arg = {"-a", "-G", groupName, userName}; 42 | int r = Utils::exec(prog, arg, result); 43 | if (r != 0 && r != 9) { 44 | return msg(prog, arg, result); 45 | } 46 | return true; 47 | } 48 | 49 | static bool readAllUsers(QStringList &users) { 50 | QString path = "/etc/passwd"; 51 | QFile passwd(path); 52 | if (!passwd.open(QIODevice::ReadOnly | QIODevice::Text)) { 53 | std::cerr << "Failed to open file:" << path.toStdString(); 54 | return false; 55 | } 56 | QString content = passwd.readAll(); 57 | QStringList lines = content.split("\n", Qt::SkipEmptyParts); 58 | for (const auto &line: lines) { 59 | QStringList parts = line.split(':'); 60 | if (!parts.empty()) { 61 | users.append(parts.at(0)); 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | void NetworkWidget::onCLickFixSocketPermission() { 68 | if (!Utils::checkRoot()) 69 | return; 70 | int i = MainWindow::getInstance()->addStatusStrA("Performing"); 71 | std::thread([i] { 72 | //https://android.googlesource.com/platform/system/core/+/master/libcutils/include/private/android_filesystem_config.h 73 | struct { 74 | QString id; 75 | QString name; 76 | } cmd[] = { 77 | {"3001", "aid_net_bt_admin"}, 78 | {"3002", "aid_net_bt"}, 79 | {"3003", "aid_inet"}, 80 | {"3004", "aid_net_raw"}, 81 | {"3005", "aid_net_admin"}, 82 | {"3006", "aid_net_bw_stats"}, 83 | {"3007", "aid_net_bw_acct"}, 84 | {"3009", "aid_readproc"}, 85 | {"3010", "aid_wakelock"}, 86 | {"3011", "aid_uhid"}, 87 | {"3012", "aid_readtracefs"}, 88 | {"3013", "aid_virtualmachine"} 89 | }; 90 | QStringList users; 91 | for (const auto &item: cmd) { 92 | if (!groupAdd(item.id, item.name)) 93 | goto end; 94 | } 95 | readAllUsers(users); 96 | for (const auto &user: users) { 97 | for (const auto &group: cmd) { 98 | if (!userMod(group.name, user)) 99 | goto end; 100 | } 101 | } 102 | end: 103 | MainWindow::getInstance()->runOnUiThread([i] { 104 | MainWindow::getInstance()->removeStatusStrA(i, "Finished"); 105 | }); 106 | }).detach(); 107 | } -------------------------------------------------------------------------------- /container/control-panel/widgets/NetworkWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKWIDGET_H 2 | #define NETWORKWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class NetworkWidget; 8 | } 9 | 10 | class NetworkWidget : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit NetworkWidget(QWidget *parent = nullptr); 16 | ~NetworkWidget(); 17 | 18 | private: 19 | Ui::NetworkWidget *ui; 20 | 21 | static void onCLickFixSocketPermission(); 22 | }; 23 | 24 | #endif // NETWORKWIDGET_H 25 | -------------------------------------------------------------------------------- /container/control-panel/widgets/NetworkWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | NetworkWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Form 21 | 22 | 23 | 24 | 25 | 26 | <html><head/><body><p>Due to ANDROID PARANOID NETWORK, we need to add 3000 series group IDs (such as AID_INET, AID_NET_RAW) for all users to allow processes to create sockets</p></body></html> 27 | 28 | 29 | Fix socket permissions 30 | 31 | 32 | 33 | 34 | 35 | 36 | Execute 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /container/control-panel/widgets/ScreenWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ScreenWidget.h" 5 | #include "ui_ScreenWidget.h" 6 | #include "mainwindow.h" 7 | #include "config.h" 8 | #include 9 | 10 | void ScreenWidget::initEventPath() { 11 | QDir dir("/dev/input/"); 12 | 13 | if (!dir.exists()) { 14 | std::cerr << "Directory does not exist:" << dir.path().toStdString(); 15 | } 16 | 17 | QDirIterator it(dir.path(), QDir::System | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); 18 | 19 | while (it.hasNext()) { 20 | QString filePath = it.next(); 21 | eventPath.append(filePath); 22 | } 23 | } 24 | 25 | ScreenWidget::ScreenWidget(QWidget *parent) 26 | : QWidget(parent), ui(new Ui::ScreenWidget) { 27 | ui->setupUi(this); 28 | connect(ui->kbdButton, &QPushButton::clicked, this, &ScreenWidget::onClickKbd); 29 | connect(ui->wlopmComboBox, &QComboBox::currentIndexChanged, this, &ScreenWidget::onEventPathSelected); 30 | 31 | QString none = "None"; 32 | 33 | eventPath.append(none); 34 | initEventPath(); 35 | 36 | QString event; 37 | { 38 | QSettings settings{QSETTINGS_ORGANIZATION, QSETTINGS_APPLICATION}; 39 | event = settings.value(EVENT_TRIGGER_CFG, none).toString(); 40 | } 41 | 42 | listener = ReadEventListener{trigger}; 43 | listener.setPath(event.toStdString()); 44 | 45 | for (int i = 0; i < eventPath.size(); ++i) { 46 | auto &item = eventPath[i]; 47 | ui->wlopmComboBox->addItem(item); 48 | if (item == event) { 49 | ui->wlopmComboBox->setCurrentIndex(i); 50 | } 51 | } 52 | } 53 | 54 | void ScreenWidget::onEventPathSelected(int index) { 55 | QSettings settings{QSETTINGS_ORGANIZATION, QSETTINGS_APPLICATION}; 56 | QString path = eventPath.at(index); 57 | settings.setValue(EVENT_TRIGGER_CFG, path); 58 | 59 | if (index == 0) 60 | return; 61 | listener.setPath(path.toStdString()); 62 | } 63 | 64 | ScreenWidget::~ScreenWidget() { 65 | delete ui; 66 | } 67 | 68 | void ScreenWidget::updateKbdText() { 69 | ui->kbdButton->setText(kbdPid == 0 ? "Execute" : "Kill"); 70 | } 71 | 72 | void ScreenWidget::onClickKbd() { 73 | int i = MainWindow::getInstance()->addStatusStrA("Performing"); 74 | if (kbdPid == 0) { 75 | std::cerr << "Executing wvkbd-mobintl\n"; 76 | QProcess::startDetached("/usr/bin/wvkbd-mobintl", {}, QString(), &kbdPid); 77 | updateKbdText(); 78 | } else { 79 | std::cerr << "Killing wvkbd-mobintl pid: " << kbdPid << "\n"; 80 | QProcess::startDetached("/usr/bin/kill", {QString::number(kbdPid)}); 81 | QProcess::startDetached("busybox", {"killall", "wvkbd-mobintl"}); 82 | kbdPid = 0; 83 | updateKbdText(); 84 | } 85 | MainWindow::getInstance()->removeStatusStrA(i, "Finished"); 86 | } -------------------------------------------------------------------------------- /container/control-panel/widgets/ScreenWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREENWIDGET_H 2 | #define SCREENWIDGET_H 3 | 4 | #include 5 | #include 6 | #include "listeners/ReadEventListener.h" 7 | 8 | namespace Ui { 9 | class ScreenWidget; 10 | } 11 | 12 | class ScreenWidget : public QWidget { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit ScreenWidget(QWidget *parent = nullptr); 17 | 18 | ~ScreenWidget(); 19 | 20 | private: 21 | static constexpr char EVENT_TRIGGER_CFG[] = "event-trigger"; 22 | Ui::ScreenWidget *ui; 23 | qint64 kbdPid = 0; 24 | QStringList eventPath; 25 | 26 | ReadEventListener::Trigger trigger = []() { 27 | QProcess::startDetached("wlopm", {"--toggle", "*"}); 28 | }; 29 | 30 | ReadEventListener listener{trigger}; 31 | 32 | void onClickKbd(); 33 | 34 | void updateKbdText(); 35 | 36 | void initEventPath(); 37 | 38 | void onEventPathSelected(int index); 39 | }; 40 | 41 | #endif // SCREENWIDGET_H 42 | -------------------------------------------------------------------------------- /container/control-panel/widgets/ScreenWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScreenWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Start wvkbd-mobintl 21 | 22 | 23 | 24 | 25 | 26 | 27 | Execute 28 | 29 | 30 | 31 | 32 | 33 | 34 | wlopm toggled when event 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /container/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /container/wra/cancel_trigger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PID=$(cat /wra/.trigger) 4 | 5 | export XDG_RUNTIME_DIR=/tmp 6 | 7 | while true; do 8 | yad --title="Panic Trigger" --text=" Panic Trigger is running in pid $PID. \n Press OK to stop it. " --button=OK 9 | 10 | if [ $? -eq 0 ]; then 11 | 12 | rm /wra/.trigger 13 | touch /wra/.trigger 14 | 15 | exit 0 16 | fi 17 | 18 | # wait for wayland compositor started 19 | sleep 1 20 | done 21 | -------------------------------------------------------------------------------- /container/wra/display.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SEATD_VTBOUND=0 4 | /sbin/seatd& 5 | /usr/lib/systemd/systemd-udevd & 6 | UDEVD_PID=$! 7 | 8 | SEATD_SOCK="/run/seatd.sock" 9 | while [ ! -e "$SEATD_SOCK" ]; do 10 | echo "wait $SEATD_SOCK ..." 11 | sleep 1 12 | done 13 | 14 | chmod 777 "$SEATD_SOCK" 15 | 16 | sleep 2 17 | 18 | for device in /dev/input/event*; do 19 | SYSTEMD_IGNORE_CHROOT=1 udevadm trigger "$device" 20 | chmod 777 "$device" 21 | done 22 | 23 | kill $UDEVD_PID 24 | echo kill systemd-udevd pid $UDEVD_PID 25 | 26 | libinput list-devices | wc -l 27 | echo "libinput list-devices" return $? 28 | 29 | bash /wra/labwc.pixman.sh -------------------------------------------------------------------------------- /container/wra/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running $0" 4 | 5 | bash /wra/display.sh & 6 | bash /wra/cancel_trigger.sh & 7 | 8 | export SYSTEMD_IGNORE_CHROOT=1 9 | 10 | SYSTEMD_FILE=/wra/.systemd 11 | while true; do 12 | if [ -e "$SYSTEMD_FILE" ]; then 13 | echo "Init: $SYSTEMD_FILE exists, prepare to run systemd" 14 | break 15 | fi 16 | sleep 5 17 | done 18 | 19 | rm $SYSTEMD_FILE 20 | 21 | echo '# stub for immediately telling the kernel that userspace firmware loading 22 | # failed; necessary to avoid long timeouts with CONFIG_FW_LOADER_USER_HELPER=y 23 | # diabled by wra, do NOT edit 24 | #SUBSYSTEM=="firmware", ACTION=="add", ATTR{loading}="-1"' >/usr/lib/udev/rules.d/50-firmware.rules 25 | 26 | echo "Exec /sbin/init" 27 | exec /sbin/init --log-level=debug --log-target=kmsg --default-standard-output=kmsg --default-standard-error=kmsg 28 | -------------------------------------------------------------------------------- /container/wra/labwc.pixman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cpu_render_info(){ 4 | while true; do 5 | XDG_RUNTIME_DIR=/tmp yad --title="CPU Rendering Notice" --text=" You are now in CPU rendering. \n Perfrormance may not good. " --button=OK 6 | 7 | if [ $? -eq 0 ]; then 8 | break 9 | fi 10 | 11 | # wait for wayland compositor started 12 | sleep 1 13 | done 14 | } 15 | 16 | # Loop to avoid labwc exit 17 | # TODO: add refresh inputs support? 18 | # TODO: add zink and its fallback support 19 | while true; do 20 | cpu_render_info& 21 | 22 | chmod -R 777 /dev/dri/ 23 | chmod -R 777 /dev/input/ 24 | /bin/busybox su -l wra -c "XDG_RUNTIME_DIR=/tmp WLR_RENDERER=pixman labwc" 25 | LABWC_RET=$? 26 | echo labwc pixman exited with $LABWC_RET 27 | if [ $LABWC_RET -ne 0 ]; then 28 | break 29 | fi 30 | 31 | chmod -R 777 /dev/dri/ 32 | chmod -R 777 /dev/input/ 33 | /bin/busybox su -l wra -c "XDG_RUNTIME_DIR=/tmp VK_ICD_FILENAMES=/usr/local/share/vulkan/icd.d/freedreno_icd.aarch64.json WLR_RENDERER=vulkan TU_FORCE_KGSL_DRM=1 labwc" 34 | 35 | echo labwc vulkan exited with $? 36 | done -------------------------------------------------------------------------------- /container/wra/screen_kbd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export XDG_RUNTIME_DIR=/tmp 4 | 5 | /bin/wvkbd-mobintl -------------------------------------------------------------------------------- /container/wra/start_systemd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SYSTEMD_FILE=/wra/.systemd 4 | 5 | echo touch $SYSTEMD_FILE 6 | 7 | sudo touch $SYSTEMD_FILE 8 | 9 | echo return $? 10 | 11 | sudo dmesg 12 | 13 | sleep 100 -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Wayland Replace Android 2 | 3 | ## 1. Project Description 4 | 5 | ### 1.1 What Is This? 6 | 7 | WRA is a very experimental little project to make bootable GNU/Linux GSIs 8 | for Android devices (mainly Qualcomm). 9 | 10 | The goal is to enable GNU/Linux booting and basic functionality on these devices 11 | without the need to recompile the Android kernel. 12 | 13 | ### 1.2 Why? 14 | 15 | Simply for fun and experimentation 16 | 17 | ## 2. Build 18 | 19 | Refer to the [build.md](docs/build.md) file for detailed build instructions. 20 | 21 | You can also get testing builds from [Github Actions](https://github.com/SnowNF/wra/actions) 22 | 23 | ## 3. Run 24 | 25 | > Do not use it on a device with important data; use at your own risk. 26 | 27 | After the build is complete, just copy `build/system.img` to your device 28 | and then use [DSU-Sideloader](https://github.com/VegaBobo/DSU-Sideloader) to flash 29 | and run it 30 | -------------------------------------------------------------------------------- /docs/build.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | To build android-tools, we need `libgtest-dev` `cmake` etc 4 | 5 | To create an debian rootfs, we need `debootstrap` 6 | 7 | To run aarch64 programs in rootfs, we need `qemu-user-static` 8 | 9 | ```shell 10 | sudo apt install libgtest-dev debootstrap cmake qemu-user-static 11 | ``` 12 | 13 | # Preparaton 14 | 15 | `gsi.img` in `build` directory, in order to extract `system/bin/linker64` and `system/lib64` 16 | 17 | # Build 18 | ```shell 19 | sudo ./scripts/build_all.sh 20 | ``` 21 | 22 | If you want build step by step, you can refer to `scripts/build_all.sh` -------------------------------------------------------------------------------- /scripts/_base_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROJECT_ROOT=./../ 4 | PROJECT_ROOT=$(realpath "$PROJECT_ROOT") 5 | # shellcheck disable=SC2034 6 | BUILD_ROOT=$PROJECT_ROOT/build 7 | # shellcheck disable=SC2034 8 | SCRIPTS_ROOT=$PROJECT_ROOT/scripts 9 | 10 | # set -x 11 | set -e 12 | 13 | _notice_() { 14 | echo "########################" 15 | echo "$@" 16 | echo "########################" 17 | } -------------------------------------------------------------------------------- /scripts/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | set -e 6 | set -x 7 | 8 | sudo ./prepare_build_env.sh 9 | 10 | sudo ./build_container_files.sh 11 | 12 | sudo ./build_system_files.sh 13 | 14 | sudo ./copy_built_files.sh 15 | 16 | sudo ./configure_rootfs.sh 17 | 18 | sudo ./packaging_rootfs.sh 19 | 20 | ./setup_system_structure.sh 21 | 22 | sudo ./extract_gsi.sh 23 | 24 | ./packaging_system.sh -------------------------------------------------------------------------------- /scripts/build_container_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p $BUILD_ROOT 8 | 9 | cd $BUILD_ROOT 10 | 11 | do_main() { 12 | mkdir -p rootfs-build/build 13 | _notice_ "Installing sudo in rootfs" 14 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "apt-get update" 15 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "command -v sudo || apt-get install sudo -y" 16 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "cd /build&&bash -e ../container/build-turnip-driver.sh" 17 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "cd /build&&bash -e ../container/build-control-panel.sh" 18 | } 19 | 20 | mkdir -p rootfs-build/container 21 | mount --bind $PROJECT_ROOT/container rootfs-build/container 22 | mount --bind /proc rootfs-build/proc 23 | mount --bind /dev rootfs-build/dev/ 24 | mount -t devpts devpts rootfs-build/dev/pts 25 | _notice_ "Building container files" 26 | do_main || true 27 | umount rootfs-build/container 28 | umount rootfs-build/proc 29 | umount rootfs-build/dev/pts 30 | umount rootfs-build/dev/ -------------------------------------------------------------------------------- /scripts/build_system_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p "$BUILD_ROOT" 8 | 9 | cd "$BUILD_ROOT" || exit 1 10 | 11 | main() { 12 | mkdir -p rootfs-build/build 13 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "apt-get update" 14 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "mkdir -p /build/system && cd /build/system && cmake /ext/system/src -DCMAKE_BUILD_TYPE=Release" 15 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "cd /build/system && cmake --build . -j" 16 | chroot rootfs-build /bin/su -l root -c /bin/bash -c "/ext/system/build-tools.sh" 17 | } 18 | 19 | mkdir -p rootfs-build/ext/system 20 | mount --bind /proc rootfs-build/proc 21 | mount --bind "$PROJECT_ROOT"/system rootfs-build/ext/system 22 | main || true 23 | umount rootfs-build/ext/system 24 | umount rootfs-build/proc -------------------------------------------------------------------------------- /scripts/configure_rootfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p $BUILD_ROOT 8 | 9 | cd $BUILD_ROOT 10 | 11 | main() { 12 | set -x 13 | chroot rootfs /bin/su -l root -c /bin/bash -c "chmod -R 777 /tmp" 14 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get update" 15 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y sudo" 16 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y bash-completion" 17 | chroot rootfs /bin/su -l root -c /bin/bash -c "useradd wra -m" | true 18 | # chroot rootfs /bin/su -l root -c /bin/bash -c "echo wra:wra | chpasswd" 19 | chroot rootfs /bin/su -l root -c /bin/bash -c "chsh -s /bin/bash wra && echo 'wra ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/wra" 20 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y libqt6widgets6 libqt6xml6" # control-panel 21 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y qterminal" 22 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y labwc pcmanfm-qt dbus-x11 --no-install-suggests --no-install-recommends" 23 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y yad --no-install-suggests --no-install-recommends" 24 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y cmst" 25 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y wvkbd" 26 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y seatd libinput-tools" 27 | chroot rootfs /bin/su -l root -c /bin/bash -c "apt-get install -y busybox-static" 28 | chroot rootfs /bin/su -l wra -c /bin/bash -c "mkdir -p /home/wra/.config/labwc" 29 | chroot rootfs /bin/su -l wra -c /bin/bash -c "cp -f /usr/share/doc/labwc/rc.xml /home/wra/.config/labwc/" 30 | cp -f $PROJECT_ROOT/container/menu.xml rootfs/home/wra/.config/labwc/ 31 | chroot rootfs /bin/su -l root -c /bin/bash -c "chmod -R 777 /home/wra/.config/labwc" 32 | 33 | rm -r rootfs/wra | true 34 | cp -r $PROJECT_ROOT/container/wra rootfs/ 35 | chmod -R 777 $PROJECT_ROOT/container/wra rootfs/wra 36 | chroot rootfs /bin/su -l root -c /bin/bash -c "usermod -a -G cdrom,floppy,sudo,audio,dip,video,plugdev,users,render,netdev,bluetooth,input wra" 37 | set +x 38 | } 39 | 40 | mount --bind /proc rootfs/proc 41 | mount --bind /dev rootfs/dev # for apt-get update 42 | _notice_ "Configuring rootfs" 43 | main || true 44 | _notice_ "Finished" 45 | umount rootfs/dev 46 | umount rootfs/proc 47 | -------------------------------------------------------------------------------- /scripts/copy_built_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p $BUILD_ROOT 8 | 9 | cd $BUILD_ROOT || exit 1 10 | 11 | _notice_ "Copying control-panel" 12 | mkdir -p rootfs/opt/wra 13 | cp -f rootfs-build/build/contorl-panel/control-panel rootfs/opt/wra 14 | 15 | _notice_ "Copying turnip driver" 16 | mkdir -p rootfs/usr/local/lib/aarch64-linux-gnu/ 17 | cp -f rootfs-build/usr/local/lib/aarch64-linux-gnu/libvulkan_freedreno.so \ 18 | rootfs/usr/local/lib/aarch64-linux-gnu/libvulkan_freedreno.so 19 | 20 | _notice_ "Copying turnip icd" 21 | mkdir -p rootfs/usr/local/share/vulkan/icd.d/ 22 | cp rootfs-build/usr/local/share/vulkan/icd.d/freedreno_icd.aarch64.json \ 23 | rootfs/usr/local/share/vulkan/icd.d/freedreno_icd.aarch64.json 24 | 25 | _notice_ "Copying system binaries" 26 | mkdir -p system/system/bin 27 | cp -f rootfs-build/build/system/drm-test system/system/bin 28 | cp -f rootfs-build/build/system/init system/system/bin 29 | cp -f rootfs-build/build/system/ueventd system/system/bin 30 | cp -f rootfs-build/build/system/wra-utils system/system/bin 31 | cp -f rootfs-build/build/f2fs-tools/f2fs-tools-master/mkfs/mkfs.f2fs system/system/bin 32 | cp -f rootfs-build/usr/bin/busybox system/system/bin 33 | 34 | chmod -R 777 system/ 35 | -------------------------------------------------------------------------------- /scripts/extract_gsi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | cd $BUILD_ROOT 8 | 9 | if [ ! -f "gsi.img" ]; then 10 | echo "Missing $(pwd)/gsi.img" 11 | exit 1 12 | fi 13 | 14 | do_copy() { 15 | rm -rf system/system/lib64 16 | cp -rf gsi/system/lib64 system/system/ 17 | cp -f gsi/system/bin/bootstrap/linker64 system/system/bin/ 18 | } 19 | 20 | mkdir -p gsi 21 | _notice_ "Mounting gsi.img" 22 | mount --read-only ./gsi.img ./gsi 23 | do_copy || true 24 | _notice_ "Umounting gsi.img" 25 | umount ./gsi 26 | -------------------------------------------------------------------------------- /scripts/mke2fs.conf: -------------------------------------------------------------------------------- 1 | [defaults] 2 | base_features = sparse_super,large_file,filetype,dir_index,ext_attr 3 | default_mntopts = acl,user_xattr 4 | enable_periodic_fsck = 0 5 | blocksize = 4096 6 | inode_size = 256 7 | inode_ratio = 16384 8 | reserved_ratio = 1.0 9 | [fs_types] 10 | ext3 = { 11 | features = has_journal 12 | } 13 | ext4 = { 14 | features = has_journal,extent,huge_file,dir_nlink,extra_isize,uninit_bg 15 | inode_size = 256 16 | } 17 | ext4dev = { 18 | features = has_journal,extent,huge_file,flex_bg,inline_data,64bit,dir_nlink,extra_isize 19 | inode_size = 256 20 | options = test_fs=1 21 | } 22 | small = { 23 | blocksize = 1024 24 | inode_size = 128 25 | inode_ratio = 4096 26 | } 27 | floppy = { 28 | blocksize = 1024 29 | inode_size = 128 30 | inode_ratio = 8192 31 | } 32 | big = { 33 | inode_ratio = 32768 34 | } 35 | huge = { 36 | inode_ratio = 65536 37 | } 38 | news = { 39 | inode_ratio = 4096 40 | } 41 | largefile = { 42 | inode_ratio = 1048576 43 | blocksize = -1 44 | } 45 | largefile4 = { 46 | inode_ratio = 4194304 47 | blocksize = -1 48 | } 49 | hurd = { 50 | blocksize = 4096 51 | inode_size = 128 52 | } 53 | -------------------------------------------------------------------------------- /scripts/mkuserimg_mke2fs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (C) 2018 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | import argparse 17 | import logging 18 | import os 19 | import pkgutil 20 | import subprocess 21 | import sys 22 | import tempfile 23 | def RunCommand(cmd, env): 24 | """Runs the given command. 25 | Args: 26 | cmd: the command represented as a list of strings. 27 | env: a dictionary of additional environment variables. 28 | Returns: 29 | A tuple of the output and the exit code. 30 | """ 31 | env_copy = os.environ.copy() 32 | env_copy.update(env) 33 | cmd[0] = FindProgram(cmd[0]) 34 | logging.info("Env: %s", env) 35 | logging.info("Running: " + " ".join(cmd)) 36 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 37 | env=env_copy, text=True) 38 | output, _ = p.communicate() 39 | return output, p.returncode 40 | def FindProgram(prog_name): 41 | """Finds the path to prog_name. 42 | Args: 43 | prog_name: the program name to find. 44 | Returns: 45 | path to the progName if found. The program is searched in the same directory 46 | where this script is located at. If not found, progName is returned. 47 | """ 48 | exec_dir = os.path.dirname(os.path.realpath(sys.argv[0])) 49 | prog_path = os.path.join(exec_dir, prog_name) 50 | if os.path.exists(prog_path): 51 | return prog_path 52 | else: 53 | return prog_name 54 | def ParseArguments(argv): 55 | """Parses the input arguments to the program.""" 56 | parser = argparse.ArgumentParser( 57 | description=__doc__, 58 | formatter_class=argparse.RawDescriptionHelpFormatter) 59 | parser.add_argument("src_dir", help="The source directory for user image.") 60 | parser.add_argument("output_file", help="The path of the output image file.") 61 | parser.add_argument("ext_variant", choices=["ext2", "ext4"], 62 | help="Variant of the extended filesystem.") 63 | parser.add_argument("mount_point", help="The mount point for user image.") 64 | parser.add_argument("fs_size", help="Size of the file system.") 65 | parser.add_argument("file_contexts", nargs='?', 66 | help="The selinux file context.") 67 | parser.add_argument("--android_sparse", "-s", action="store_true", 68 | help="Outputs an android sparse image (mke2fs).") 69 | parser.add_argument("--journal_size", "-j", 70 | help="Journal size (mke2fs).") 71 | parser.add_argument("--timestamp", "-T", 72 | help="Fake timetamp for the output image.") 73 | parser.add_argument("--fs_config", "-C", 74 | help="Path to the fs config file (e2fsdroid).") 75 | parser.add_argument("--product_out", "-D", 76 | help="Path to the directory with device specific fs" 77 | " config files (e2fsdroid).") 78 | parser.add_argument("--block_list_file", "-B", 79 | help="Path to the block list file (e2fsdroid).") 80 | parser.add_argument("--base_alloc_file_in", "-d", 81 | help="Path to the input base fs file (e2fsdroid).") 82 | parser.add_argument("--base_alloc_file_out", "-A", 83 | help="Path to the output base fs file (e2fsdroid).") 84 | parser.add_argument("--label", "-L", 85 | help="The mount point (mke2fs).") 86 | parser.add_argument("--inodes", "-i", 87 | help="The extfs inodes count (mke2fs).") 88 | parser.add_argument("--inode_size", "-I", 89 | help="The extfs inode size (mke2fs).") 90 | parser.add_argument("--reserved_percent", "-M", 91 | help="The reserved blocks percentage (mke2fs).") 92 | parser.add_argument("--flash_erase_block_size", "-e", 93 | help="The flash erase block size (mke2fs).") 94 | parser.add_argument("--flash_logical_block_size", "-o", 95 | help="The flash logical block size (mke2fs).") 96 | parser.add_argument("--mke2fs_uuid", "-U", 97 | help="The mke2fs uuid (mke2fs) .") 98 | parser.add_argument("--mke2fs_hash_seed", "-S", 99 | help="The mke2fs hash seed (mke2fs).") 100 | parser.add_argument("--share_dup_blocks", "-c", action="store_true", 101 | help="ext4 share dup blocks (e2fsdroid).") 102 | args, remainder = parser.parse_known_args(argv) 103 | # The current argparse doesn't handle intermixed arguments well. Checks 104 | # manually whether the file_contexts exists as the last argument. 105 | # TODO(xunchang) use parse_intermixed_args() when we switch to python 3.7. 106 | if len(remainder) == 1 and remainder[0] == argv[-1]: 107 | args.file_contexts = remainder[0] 108 | elif remainder: 109 | parser.print_usage() 110 | sys.exit(1) 111 | return args 112 | def ConstructE2fsCommands(args): 113 | """Builds the mke2fs & e2fsdroid command based on the input arguments. 114 | Args: 115 | args: The result of ArgumentParser after parsing the command line arguments. 116 | Returns: 117 | A tuple of two lists that serve as the command for mke2fs and e2fsdroid. 118 | """ 119 | BLOCKSIZE = 4096 120 | e2fsdroid_opts = [] 121 | mke2fs_extended_opts = [] 122 | mke2fs_opts = [] 123 | if args.android_sparse: 124 | mke2fs_extended_opts.append("android_sparse") 125 | else: 126 | e2fsdroid_opts.append("-e") 127 | if args.timestamp: 128 | e2fsdroid_opts += ["-T", args.timestamp] 129 | if args.fs_config: 130 | e2fsdroid_opts += ["-C", args.fs_config] 131 | if args.product_out: 132 | e2fsdroid_opts += ["-p", args.product_out] 133 | if args.block_list_file: 134 | e2fsdroid_opts += ["-B", args.block_list_file] 135 | if args.base_alloc_file_in: 136 | e2fsdroid_opts += ["-d", args.base_alloc_file_in] 137 | if args.base_alloc_file_out: 138 | e2fsdroid_opts += ["-D", args.base_alloc_file_out] 139 | if args.share_dup_blocks: 140 | e2fsdroid_opts.append("-s") 141 | if args.file_contexts: 142 | e2fsdroid_opts += ["-S", args.file_contexts] 143 | if args.flash_erase_block_size: 144 | mke2fs_extended_opts.append("stripe_width={}".format( 145 | int(args.flash_erase_block_size) // BLOCKSIZE)) 146 | if args.flash_logical_block_size: 147 | # stride should be the max of 8kb and the logical block size 148 | stride = max(int(args.flash_logical_block_size), 8192) 149 | mke2fs_extended_opts.append("stride={}".format(stride // BLOCKSIZE)) 150 | if args.mke2fs_hash_seed: 151 | mke2fs_extended_opts.append("hash_seed=" + args.mke2fs_hash_seed) 152 | if args.journal_size: 153 | if args.journal_size == "0": 154 | mke2fs_opts += ["-O", "^has_journal"] 155 | else: 156 | mke2fs_opts += ["-J", "size=" + args.journal_size] 157 | if args.label: 158 | mke2fs_opts += ["-L", args.label] 159 | if args.inodes: 160 | mke2fs_opts += ["-N", args.inodes] 161 | if args.inode_size: 162 | mke2fs_opts += ["-I", args.inode_size] 163 | if args.mount_point: 164 | mke2fs_opts += ["-M", args.mount_point] 165 | if args.reserved_percent: 166 | mke2fs_opts += ["-m", args.reserved_percent] 167 | if args.mke2fs_uuid: 168 | mke2fs_opts += ["-U", args.mke2fs_uuid] 169 | if mke2fs_extended_opts: 170 | mke2fs_opts += ["-E", ','.join(mke2fs_extended_opts)] 171 | # Round down the filesystem length to be a multiple of the block size 172 | blocks = int(args.fs_size) // BLOCKSIZE 173 | mke2fs_cmd = (["mke2fs.android"] + mke2fs_opts + 174 | ["-t", args.ext_variant, "-b", str(BLOCKSIZE), args.output_file, 175 | str(blocks)]) 176 | e2fsdroid_cmd = (["e2fsdroid"] + e2fsdroid_opts + 177 | ["-f", args.src_dir, "-a", args.mount_point, 178 | args.output_file]) 179 | return mke2fs_cmd, e2fsdroid_cmd 180 | def main(argv): 181 | logging_format = '%(asctime)s %(filename)s %(levelname)s: %(message)s' 182 | logging.basicConfig(level=logging.INFO, format=logging_format, 183 | datefmt='%H:%M:%S') 184 | args = ParseArguments(argv) 185 | if not os.path.isdir(args.src_dir): 186 | logging.error("Can not find directory %s", args.src_dir) 187 | sys.exit(2) 188 | if not args.mount_point: 189 | logging.error("Mount point is required") 190 | sys.exit(2) 191 | if args.mount_point[0] != '/': 192 | args.mount_point = '/' + args.mount_point 193 | if not args.fs_size: 194 | logging.error("Size of the filesystem is required") 195 | sys.exit(2) 196 | mke2fs_cmd, e2fsdroid_cmd = ConstructE2fsCommands(args) 197 | # truncate output file since mke2fs will keep verity section in existing file 198 | with open(args.output_file, 'w') as output: 199 | output.truncate() 200 | # run mke2fs 201 | with tempfile.NamedTemporaryFile() as conf_file: 202 | conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf') 203 | conf_file.write(conf_data) 204 | conf_file.flush() 205 | mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name} 206 | if args.timestamp: 207 | mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 208 | output, ret = RunCommand(mke2fs_cmd, mke2fs_env) 209 | print(output) 210 | if ret != 0: 211 | logging.error("Failed to run mke2fs: " + output) 212 | sys.exit(4) 213 | # run e2fsdroid 214 | e2fsdroid_env = {} 215 | if args.timestamp: 216 | e2fsdroid_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 217 | output, ret = RunCommand(e2fsdroid_cmd, e2fsdroid_env) 218 | # The build script is parsing the raw output of e2fsdroid; keep the pattern 219 | # unchanged for now. 220 | print(output) 221 | if ret != 0: 222 | logging.error("Failed to run e2fsdroid_cmd: " + output) 223 | os.remove(args.output_file) 224 | sys.exit(4) 225 | if __name__ == '__main__': 226 | main(sys.argv[1:]) 227 | -------------------------------------------------------------------------------- /scripts/packaging_rootfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | cd $BUILD_ROOT 8 | 9 | echo "Clean Rootfs..." 10 | rm -r rootfs/var/cache/apt/ | true 11 | rm -r rootfs/var/log/ | true 12 | rm -r rootfs/var/lib/apt/lists/ | true 13 | 14 | echo "Compressing..." 15 | tar -cf - rootfs | pigz -p "$(nproc)" > system/system/rootfs.tgz 16 | chmod -R 777 system/ 17 | -------------------------------------------------------------------------------- /scripts/packaging_system.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | export PATH=$SCRIPTS_ROOT:$BUILD_ROOT/android-tools/build/vendor:$PATH 8 | 9 | # rm -r $BUILD_ROOT/system/system/bin/ 10 | 11 | mkdir -p $BUILD_ROOT/system/system/bin/ 12 | mkdir -p $BUILD_ROOT/system/system/ext/ 13 | 14 | cp -r $PROJECT_ROOT/system/bin/* $BUILD_ROOT/system/system/bin/ 15 | cp -r $PROJECT_ROOT/system/ext/* $BUILD_ROOT/system/system/ext/ 16 | 17 | cd $BUILD_ROOT 18 | 19 | arr=($(du -sb ./system)) 20 | size=${arr[0]} 21 | echo "Total size in bytes: $size" 22 | 23 | extra=$((100 * 1024 * 1024)) 24 | 25 | size=$((size + extra)) 26 | 27 | mkuserimg_mke2fs.py ./system/ system.img ext4 / $size --label / 28 | 29 | avbtool.py add_hashtree_footer --image ./system.img --do_not_generate_fec --partition_name system 30 | 31 | avbtool.py verify_image --image ./system.img 32 | 33 | _notice_ "File: $(realpath ./system.img)" -------------------------------------------------------------------------------- /scripts/prepare_build_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p $BUILD_ROOT 8 | 9 | cd "$BUILD_ROOT" || exit 1 10 | 11 | if [ ! -d "rootfs-build" ]; then 12 | _notice_ "Bootstraping Debian sid...in "$(pwd)"/rootfs-build for build" 13 | debootstrap --arch=arm64 sid rootfs-build 14 | else 15 | echo $(pwd)"/rootfs-build already exists,skiping" 16 | fi 17 | 18 | if [ ! -d "rootfs" ]; then 19 | _notice_ "Copy Debian sid...to "$(pwd)"/rootfs for run" 20 | cp -r rootfs-build rootfs 21 | else 22 | echo $(pwd)"/rootfs already exists,skiping" 23 | fi 24 | 25 | _notice_ "Building andoroid-tools" 26 | 27 | if [ ! -f "android-tools-35.0.1.tar.xz" ]; then 28 | _notice_ "Downloading android-tools source" 29 | wget https://github.com/nmeum/android-tools/releases/download/35.0.1/android-tools-35.0.1.tar.xz 30 | fi 31 | 32 | if [ ! -d "android-tools" ]; then 33 | _notice_ "Decompressing android-tools source" 34 | tar xf android-tools-35.0.1.tar.xz 35 | mv android-tools-35.0.1 android-tools 36 | fi 37 | 38 | _notice_ "Building android-tools" 39 | mkdir -p android-tools/build 40 | cd android-tools/build || exit 1 41 | apt install golang-go liblz4-dev libusb-1.0-0-dev libprotobuf-dev libunwind-dev protobuf-compiler libgtest-dev 42 | cmake .. 43 | cmake --build . -j 44 | -------------------------------------------------------------------------------- /scripts/setup_system_structure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | source _base_env.sh 6 | 7 | mkdir -p $BUILD_ROOT 8 | 9 | cd "$BUILD_ROOT" || exit 1 10 | 11 | _notice_ "Creating directories" 12 | set -x 13 | 14 | mkdir -p system/acct 15 | mkdir -p system/apex 16 | mkdir -p system/bootstrap-apex 17 | mkdir -p system/bt_firmware 18 | mkdir -p system/cache 19 | mkdir -p system/config 20 | mkdir -p system/data 21 | mkdir -p system/data_mirror 22 | mkdir -p system/debug_ramdisk 23 | mkdir -p system/dev 24 | mkdir -p system/efs 25 | mkdir -p system/firmware/radio 26 | mkdir -p system/linkerconfig 27 | mkdir -p system/metadata 28 | mkdir -p system/mnt 29 | mkdir -p system/odm 30 | mkdir -p system/odm_dlkm 31 | mkdir -p system/oem 32 | mkdir -p system/persist 33 | mkdir -p system/postinstall 34 | mkdir -p system/proc 35 | mkdir -p system/sec_storage 36 | mkdir -p system/second_stage_resources 37 | mkdir -p system/sys 38 | mkdir -p system/system/bin 39 | mkdir -p system/system/ext 40 | mkdir -p system/system/lib64 41 | mkdir -p system/system/product 42 | mkdir -p system/system/system_ext/etc/init/config 43 | mkdir -p system/system_dlkm 44 | mkdir -p system/vendor 45 | mkdir -p system/vendor_dlkm 46 | 47 | set +x 48 | 49 | _ln_() { 50 | local target="$1" 51 | local link_name="$2" 52 | 53 | if [ ! -e "$link_name" ]; then 54 | ln -sf "$target" "$link_name" 55 | echo "Created symlink: $link_name -> $target" 56 | else 57 | echo "Skipped: $link_name already exists" 58 | fi 59 | } 60 | 61 | _notice_ "Creating symlinks" 62 | cd system || exit 1 63 | _ln_ /sys/kernel/debug d 64 | 65 | cd odm || exit 1 66 | _ln_ /vendor/odm/app app 67 | _ln_ /vendor/odm/bin bin 68 | _ln_ /vendor/odm/etc etc 69 | _ln_ /vendor/odm/firmware firmware 70 | _ln_ /vendor/odm/framework framework 71 | _ln_ /vendor/odm/lib lib 72 | _ln_ /vendor/odm/lib64 lib64 73 | _ln_ /vendor/odm/overlay overlay 74 | _ln_ /vendor/odm/priv-app priv-app 75 | _ln_ /vendor/odm/usr usr 76 | cd .. 77 | 78 | cd odm_dlkm || exit 1 79 | _ln_ /odm/odm_dlkm/etc etc 80 | cd .. 81 | 82 | cd vendor_dlkm || exit 1 83 | _ln_ /vendor/vendor_dlkm/etc etc 84 | cd .. 85 | 86 | _ln_ /system/bin bin 87 | _ln_ /system/etc etc 88 | _ln_ /system/bin/init init 89 | _ln_ /system/product product 90 | _ln_ /system/system_ext system_ext 91 | 92 | _notice_ "Copying config" 93 | set -x 94 | cp -f $PROJECT_ROOT/system/skip_mount.cfg system/system_ext/etc/init/config 95 | -------------------------------------------------------------------------------- /system/bin/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/busybox sh 2 | 3 | wra_exec() { 4 | echo "INIT: $*" 5 | $* 2>&1 6 | local return_code=$? 7 | echo "INIT: $* finished with return code: $return_code" 8 | return $return_code 9 | } 10 | 11 | busybox_exec() { 12 | local cmd="/bin/busybox $@" 13 | wra_exec "$cmd" 14 | } 15 | 16 | capture_stderr() { 17 | output=$( $* 2>&1 ) 18 | echo "$output" 19 | } 20 | 21 | echo "INIT: $0" 22 | 23 | wra_exec /bin/wra-utils GetDmDevicePathByName userdata_gsi 24 | 25 | sleep 1 26 | 27 | echo "INIT: echo 8 4 1 7 > /proc/sys/kernel/printk" 28 | echo "8 4 1 7" > /proc/sys/kernel/printk 29 | 30 | # 31 | # Create /dev/ Nodes 32 | # 33 | 34 | wra_exec /bin/ueventd --no-loop 35 | 36 | wra_exec /bin/ueventd --no-cold-boot& 37 | 38 | wra_exec /bin/drm-test 39 | 40 | # 41 | # Prepare /data Mount Point 42 | # 43 | 44 | wra_exec /bin/wra-utils GetDmDevicePathByName userdata_gsi 45 | 46 | /bin/wra-utils FormatPartitionIfWiped "/dev/block/$(capture_stderr /bin/wra-utils GetDmDevicePathByName userdata_gsi s)" 47 | 48 | busybox_exec mount "/dev/block/$(capture_stderr /bin/wra-utils GetDmDevicePathByName userdata_gsi s)" /data 49 | 50 | wra_exec cat /data/testFile 51 | 52 | echo "test" >> /data/testFile 53 | 54 | wra_exec cat /data/testFile 55 | 56 | # 57 | # Mount necessary Points 58 | # 59 | 60 | busybox_exec mkdir -p /dev/shm 61 | busybox_exec mount -t tmpfs tmpfs /dev/shm 62 | 63 | busybox_exec mkdir -p /mnt/vendor/persist 64 | 65 | # TODO DO NOT HARD CODE 66 | attempt=0 67 | while [ $attempt -le 3 ]; do 68 | echo "attempt mount $attempt times" 69 | wra_exec /bin/busybox mount /dev/block/by-name/persist /mnt/vendor/persist -o ro 70 | wra_exec /bin/busybox mount /dev/block/by-name/modem_a /vendor/firmware_mnt -o ro 71 | if [ $? -eq 0 ]; then 72 | break 73 | fi 74 | attempt=$((attempt + 1)) 75 | sleep 1 76 | done 77 | 78 | # 79 | # Decompress Rootfs 80 | # 81 | 82 | if [ ! -d "/data/root" ]; then 83 | echo "/data/root does not exist, decompressing..." 84 | busybox_exec tar xvfz /system/rootfs.tgz -C /data 85 | busybox_exec mv /data/rootfs /data/root 86 | busybox_exec sync 87 | else 88 | echo "/data/root exists" 89 | busybox_exec ls /data/root 90 | busybox_exec ls /data/root/usr 91 | busybox_exec ls /data/root/wra 92 | busybox_exec ls /data/root/run 93 | fi 94 | 95 | busybox_exec sh /system/ext/qualcomm_cnss.sh stage1 96 | 97 | wra_exec /bin/wra-utils LoadListedModules --base-path /odm/lib/modules --base-path /vendor/lib/modules 98 | 99 | busybox_exec sh /system/ext/qualcomm_cnss.sh stage2 100 | 101 | busybox_exec free -h 102 | 103 | busybox_exec sh /system/bin/trigger.sh& 104 | 105 | # 106 | # Prepare to Exec Init 107 | # 108 | 109 | busybox_exec env 110 | 111 | echo "Propare to exec init" 112 | 113 | # Prepare the /dev directory 114 | [ ! -h /dev/fd ] && busybox_exec ln -s /proc/self/fd /dev/fd 115 | [ ! -h /dev/stdin ] && busybox_exec ln -s /proc/self/fd/0 /dev/stdin 116 | [ ! -h /dev/stdout ] && busybox_exec ln -s /proc/self/fd/1 /dev/stdout 117 | [ ! -h /dev/stderr ] && busybox_exec ln -s /proc/self/fd/2 /dev/stderr 118 | 119 | unset SHLVL 120 | unset FIRST_STAGE_STARTED_AT 121 | unset MOUNT_SYSTEM_OK 122 | unset INIT_AVB_VERSION 123 | unset LD_PRELOAD 124 | 125 | # IMPORTANT 126 | busybox_exec mount -o bind -o rshared /data/root /data/root 127 | 128 | busybox_exec umount /tmp 129 | busybox_exec mount -t tmpfs tmpfs /tmp 130 | busybox_exec chmod -R 777 /tmp 131 | 132 | busybox_exec mount -o rbind /dev /data/root/dev 133 | busybox_exec mount -o rbind /proc /data/root/proc 134 | busybox_exec mount -o rbind /sys /data/root/sys 135 | busybox_exec mount -o rbind /tmp /data/root/tmp 136 | 137 | export PATH=/sbin:/usr/sbin:/bin:/usr/bin 138 | 139 | echo "exec /bin/busybox chroot /data/root /bin/bash /wra/init.sh" 140 | 141 | # shellcheck disable=SC2093 142 | exec /bin/busybox chroot /data/root /bin/bash /wra/init.sh 143 | 144 | busybox_exec reboot -------------------------------------------------------------------------------- /system/bin/trigger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/busybox sh 2 | echo "Panic Trigger: pid $$" 3 | 4 | PID_FILE="/data/root/wra/.trigger" 5 | 6 | echo "Panic Trigger: writing pid $$ to $PID_FILE" 7 | echo $$ > $PID_FILE 8 | 9 | chmod 666 $PID_FILE 10 | 11 | echo "Panic Trigger: test 60s" 12 | 13 | MAX_WAIT=60 14 | CURRENT_WAIT=0 15 | 16 | while [ $CURRENT_WAIT -lt $MAX_WAIT ]; do 17 | if [ -e "$PID_FILE" ]; then 18 | if [ ! -s "$PID_FILE" ]; then 19 | echo "Panic Trigger: $PID_FILE exists && is empty..." 20 | exit 0 21 | fi 22 | fi 23 | sleep 1 24 | CURRENT_WAIT=$((CURRENT_WAIT + 1)) 25 | done 26 | 27 | echo "Panic Trigger: sync" 28 | /bin/busybox sync 29 | echo "Panic Trigger: umount" 30 | /bin/busybox umount -l -f /data/ 31 | echo "Panic Trigger: echo 1 > /proc/sys/kernel/sysrq" 32 | echo 1 > /proc/sys/kernel/sysrq 33 | echo "Panic Trigger: echo c > /proc/sysrq-trigger" 34 | echo c > /proc/sysrq-trigger 35 | -------------------------------------------------------------------------------- /system/build-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | apt-get install -y busybox-static 6 | 7 | command -v wget &>/dev/null || apt-get install -y wget 8 | 9 | apt-get install -y libblkid-dev libselinux1-dev pkg-config uuid-dev automake libtool 10 | 11 | cd /build || exit 1 12 | mkdir -p f2fs-tools 13 | cd f2fs-tools || exit 1 14 | if [ ! -f "f2fs-tools-master.zip" ]; then 15 | wget https://salsa.debian.org/debian/f2fs-tools/-/archive/master/f2fs-tools-master.zip 16 | fi 17 | 18 | rm -rf f2fs-tools-master 19 | 20 | busybox unzip f2fs-tools-master.zip 21 | 22 | cd f2fs-tools-master 23 | ./autogen.sh 24 | 25 | echo "mkfs_f2fs_LDFLAGS = -all-static" >>mkfs/Makefile.am 26 | echo "fsck_f2fs_LDFLAGS = -all-static" >>fsck/Makefile.am 27 | 28 | LDFLAGS=--static ./configure --without-selinux 29 | 30 | make -j 31 | -------------------------------------------------------------------------------- /system/ext/qualcomm_cnss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/busybox sh 2 | 3 | wra_exec() { 4 | echo "QCNSS: $*" 5 | $* 2>&1 6 | local return_code=$? 7 | echo "QCNSS: $* finished with return code: $return_code" 8 | return $return_code 9 | } 10 | 11 | unset LD_PRELOAD 12 | 13 | VENDOR_DIR=/data/vendor/wifi/ 14 | 15 | stage2(){ 16 | if [ ! -d "$VENDOR_DIR" ]; then 17 | echo "$VENDOR_DIR does not exist, creating..." 18 | mkdir -p $VENDOR_DIR 19 | mkdir -p "$VENDOR_DIR/sockets" 20 | wra_exec /bin/busybox dd if=/dev/zero of=/data/vendor/wifi/iotap_ps.bin bs=120 count=1 21 | wra_exec /system/bin/linker64 --list /vendor/bin/cnss-daemon 22 | else 23 | echo "$VENDOR_DIR exists" 24 | fi 25 | 26 | # wra_exec /system/bin/wlan_control ON 27 | # wra_exec /system/bin/wlan_control OFF 28 | # wra_exec /system/bin/wlan_control ON 29 | wra_exec /vendor/bin/cnss-daemon -n -dddd& 30 | } 31 | 32 | # form /vendor/etc/init/hw/init.target.rc 33 | stage1(){ 34 | echo "QCNSS: echo 8 4 1 7 > /proc/sys/kernel/printk" 35 | echo "8 4 1 7" > /proc/sys/kernel/printk 36 | 37 | wra_exec /vendor/bin/qrtr-lookup 38 | wra_exec /vendor/bin/qrtr-ns -f& 39 | # wra_exec /bin/busybox chroot /data/root /usr/bin/qrtr-ns -f& 40 | 41 | echo "QCNSS: echo 1 > /dev/ipa" 42 | 43 | wra_exec /vendor/bin/qrtr-lookup 44 | 45 | # echo 1 > /proc/sys/kernel/firmware_config/force_sysfs_fallback 46 | echo 1 > /dev/ipa 47 | echo 1 > /sys/kernel/boot_adsp/boot 48 | # echo 1 > /sys/kernel/boot_cdsp/boot 49 | 50 | sleep 5 51 | 52 | # Enable WLAN cold boot calibration 53 | # Enable WLAN SSR recovery 54 | # echo "QCNSS: echo 1 > /sys/kernel/cnss/recovery" 55 | # echo 1 > /sys/kernel/cnss/recovery 56 | 57 | # wra_exec /vendor/bin/ipacm 58 | wra_exec /vendor/bin/qrtr-lookup 59 | 60 | # echo 1 > /sys/kernel/boot_cdsp/boot 61 | # echo 1 > /sys/devices/virtual/npu/msm_npu/boot 62 | # echo 1 > /sys/devices/virtual/cvp/cvp/boot 63 | 64 | stage2 65 | echo "sleep 10" 66 | sleep 10 67 | echo "QCNSS: echo 1 > /sys/kernel/cnss/fs_ready" 68 | echo 1 > /sys/kernel/cnss/fs_ready 69 | wra_exec /vendor/bin/qrtr-lookup 70 | echo "sleep 10?" 71 | } 72 | 73 | case "$1" in 74 | stage1) 75 | # stage2& 76 | # sleep 1 77 | stage1 78 | ;; 79 | stage2) 80 | # wra_exec /system/bin/wlan_control ON 81 | # wra_exec /system/bin/wlan_control OFF 82 | # wra_exec /system/bin/wlan_control ON 83 | ;; 84 | *) 85 | echo "Usage: $0 {stage1|stage2}" 86 | exit 1 87 | ;; 88 | esac -------------------------------------------------------------------------------- /system/skip_mount.cfg: -------------------------------------------------------------------------------- 1 | # Skip "system" mountpoints. 2 | /oem 3 | /product 4 | /system_ext 5 | # Skip sub-mountpoints of system mountpoints. 6 | /oem/* 7 | /product/* 8 | /system_ext/* 9 | /system/* 10 | -------------------------------------------------------------------------------- /system/src/.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-* -------------------------------------------------------------------------------- /system/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(wra) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | link_libraries("-static") 7 | 8 | add_executable(ueventd 9 | uevent/main.h 10 | uevent/main.cpp 11 | 12 | uevent/init/uevent.h 13 | uevent/init/uevent_handler.h 14 | uevent/init/uevent_listener.cpp 15 | uevent/init/uevent_listener.h 16 | uevent/init/ueventd.cpp 17 | uevent/init/ueventd.h 18 | 19 | uevent/init/devices.h 20 | uevent/init/devices.cpp 21 | uevent/init/modalias_handler.h 22 | uevent/init/modalias_handler.cpp 23 | uevent/init/firmware_handler.h 24 | uevent/init/firmware_handler.cpp 25 | uevent/init/ueventd_parser.h 26 | 27 | uevent/libcutils/uevent.h 28 | uevent/libcutils/uevent.cpp 29 | uevent/libmodprobe/libmodprobe.cpp 30 | uevent/libmodprobe/libmodprobe_ext.cpp 31 | uevent/libmodprobe/modprobe.h 32 | 33 | utils/logger.h 34 | utils/utils.cpp 35 | utils/utils.h 36 | utils/DeviceMapper.cpp 37 | utils/DeviceMapper.h 38 | ) 39 | 40 | target_include_directories(ueventd PRIVATE 41 | utils 42 | uevent 43 | ) 44 | 45 | add_executable(init 46 | init/init.c 47 | ) 48 | 49 | add_executable(wra-utils 50 | wra-utils/main.cpp 51 | 52 | utils/logger.h 53 | utils/DeviceMapper.cpp 54 | utils/DeviceMapper.h 55 | utils/utils.cpp 56 | utils/utils.h 57 | 58 | uevent/libmodprobe/libmodprobe.cpp 59 | uevent/libmodprobe/libmodprobe_ext.cpp 60 | uevent/libmodprobe/modprobe.h 61 | ) 62 | 63 | target_include_directories(wra-utils PRIVATE 64 | utils 65 | uevent 66 | ) 67 | 68 | add_executable(drm-test 69 | drm-test/drm-test.cpp 70 | drm-test/drm-api.c 71 | drm-test/drm-api.h 72 | 73 | drm-test/libdrm/xf86drm.c 74 | drm-test/libdrm/xf86drmMode.c 75 | ) 76 | 77 | target_include_directories(drm-test PRIVATE 78 | drm-test/libdrm/) -------------------------------------------------------------------------------- /system/src/drm-test/drm-api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "drm-api.h" 3 | #include 4 | -------------------------------------------------------------------------------- /system/src/drm-test/drm-api.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-7-22. 3 | // 4 | 5 | #ifndef WRA_DRM_API_H 6 | #define WRA_DRM_API_H 7 | 8 | #if defined(__cplusplus) 9 | extern "C" { 10 | #endif 11 | 12 | #include 13 | #include "libdrm/drm.h" 14 | #include "libdrm/drm_mode.h" 15 | #include "libdrm/xf86drm.h" 16 | #include "libdrm/xf86drmMode.h" 17 | 18 | 19 | #define drm_public __attribute__((visibility("default"))) 20 | 21 | 22 | #if defined(__cplusplus) 23 | } 24 | #endif 25 | 26 | #endif //WRA_DRM_API_H 27 | -------------------------------------------------------------------------------- /system/src/drm-test/libdrm/xf86drmMode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * \file xf86drmMode.h 3 | * Header for DRM modesetting interface. 4 | * 5 | * \author Jakob Bornecrantz 6 | * 7 | * \par Acknowledgements: 8 | * Feb 2007, Dave Airlie 9 | */ 10 | 11 | /* 12 | * Copyright (c) 2007-2008 Tungsten Graphics, Inc., Cedar Park, Texas. 13 | * Copyright (c) 2007-2008 Dave Airlie 14 | * Copyright (c) 2007-2008 Jakob Bornecrantz 15 | * 16 | * Permission is hereby granted, free of charge, to any person obtaining a 17 | * copy of this software and associated documentation files (the "Software"), 18 | * to deal in the Software without restriction, including without limitation 19 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 20 | * and/or sell copies of the Software, and to permit persons to whom the 21 | * Software is furnished to do so, subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be included in 24 | * all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 32 | * IN THE SOFTWARE. 33 | * 34 | */ 35 | 36 | #ifndef _XF86DRMMODE_H_ 37 | #define _XF86DRMMODE_H_ 38 | 39 | #if defined(__cplusplus) 40 | extern "C" { 41 | #endif 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | /* 50 | * This is the interface for modesetting for drm. 51 | * 52 | * It aims to provide a randr1.2 compatible interface for modesettings in the 53 | * kernel, the interface is also meant to be used by libraries like EGL. 54 | * 55 | * More information can be found in randrproto.txt which can be found here: 56 | * http://gitweb.freedesktop.org/?p=xorg/proto/randrproto.git 57 | * 58 | * There are some major differences to be noted. Unlike the randr1.2 proto you 59 | * need to create the memory object of the framebuffer yourself with the ttm 60 | * buffer object interface. This object needs to be pinned. 61 | */ 62 | 63 | /* 64 | * Feature defines 65 | * 66 | * Just because these are defined doesn't mean that the kernel 67 | * can do that feature, its just for new code vs old libdrm. 68 | */ 69 | #define DRM_MODE_FEATURE_KMS 1 70 | #define DRM_MODE_FEATURE_DIRTYFB 1 71 | 72 | 73 | typedef struct _drmModeRes { 74 | 75 | int count_fbs; 76 | uint32_t *fbs; 77 | 78 | int count_crtcs; 79 | uint32_t *crtcs; 80 | 81 | int count_connectors; 82 | uint32_t *connectors; 83 | 84 | int count_encoders; 85 | uint32_t *encoders; 86 | 87 | uint32_t min_width, max_width; 88 | uint32_t min_height, max_height; 89 | } drmModeRes, *drmModeResPtr; 90 | 91 | typedef struct _drmModeModeInfo { 92 | uint32_t clock; 93 | uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; 94 | uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; 95 | 96 | uint32_t vrefresh; 97 | 98 | uint32_t flags; 99 | uint32_t type; 100 | char name[DRM_DISPLAY_MODE_LEN]; 101 | } drmModeModeInfo, *drmModeModeInfoPtr; 102 | 103 | typedef struct _drmModeFB { 104 | uint32_t fb_id; 105 | uint32_t width, height; 106 | uint32_t pitch; 107 | uint32_t bpp; 108 | uint32_t depth; 109 | /* driver specific handle */ 110 | uint32_t handle; 111 | } drmModeFB, *drmModeFBPtr; 112 | 113 | typedef struct _drmModeFB2 { 114 | uint32_t fb_id; 115 | uint32_t width, height; 116 | uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ 117 | uint64_t modifier; /* applies to all buffers */ 118 | uint32_t flags; 119 | 120 | /* per-plane GEM handle; may be duplicate entries for multiple planes */ 121 | uint32_t handles[4]; 122 | uint32_t pitches[4]; /* bytes */ 123 | uint32_t offsets[4]; /* bytes */ 124 | } drmModeFB2, *drmModeFB2Ptr; 125 | 126 | typedef struct drm_clip_rect drmModeClip, *drmModeClipPtr; 127 | 128 | typedef struct _drmModePropertyBlob { 129 | uint32_t id; 130 | uint32_t length; 131 | void *data; 132 | } drmModePropertyBlobRes, *drmModePropertyBlobPtr; 133 | 134 | typedef struct _drmModeProperty { 135 | uint32_t prop_id; 136 | uint32_t flags; 137 | char name[DRM_PROP_NAME_LEN]; 138 | int count_values; 139 | uint64_t *values; /* store the blob lengths */ 140 | int count_enums; 141 | struct drm_mode_property_enum *enums; 142 | int count_blobs; 143 | uint32_t *blob_ids; /* store the blob IDs */ 144 | } drmModePropertyRes, *drmModePropertyPtr; 145 | 146 | static inline uint32_t drmModeGetPropertyType(const drmModePropertyRes *prop) 147 | { 148 | return prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE); 149 | } 150 | 151 | static inline int drm_property_type_is(const drmModePropertyPtr property, 152 | uint32_t type) 153 | { 154 | return drmModeGetPropertyType(property) == type; 155 | } 156 | 157 | typedef struct _drmModeCrtc { 158 | uint32_t crtc_id; 159 | uint32_t buffer_id; /**< FB id to connect to 0 = disconnect */ 160 | 161 | uint32_t x, y; /**< Position on the framebuffer */ 162 | uint32_t width, height; 163 | int mode_valid; 164 | drmModeModeInfo mode; 165 | 166 | int gamma_size; /**< Number of gamma stops */ 167 | 168 | } drmModeCrtc, *drmModeCrtcPtr; 169 | 170 | typedef struct _drmModeEncoder { 171 | uint32_t encoder_id; 172 | uint32_t encoder_type; 173 | uint32_t crtc_id; 174 | uint32_t possible_crtcs; 175 | uint32_t possible_clones; 176 | } drmModeEncoder, *drmModeEncoderPtr; 177 | 178 | /** 179 | * Describes the connector status. 180 | * 181 | * DRM_MODE_CONNECTED means that the connector has a sink plugged in. 182 | * DRM_MODE_DISCONNECTED means the contrary. DRM_MODE_UNKNOWNCONNECTION is used 183 | * when it could be either. 184 | * 185 | * User-space should first try to enable DRM_MODE_CONNECTED connectors and 186 | * ignore other connectors. If there are no DRM_MODE_CONNECTED connectors, 187 | * user-space should then try to probe and enable DRM_MODE_UNKNOWNCONNECTION 188 | * connectors. 189 | */ 190 | typedef enum { 191 | DRM_MODE_CONNECTED = 1, 192 | DRM_MODE_DISCONNECTED = 2, 193 | DRM_MODE_UNKNOWNCONNECTION = 3 194 | } drmModeConnection; 195 | 196 | typedef enum { 197 | DRM_MODE_SUBPIXEL_UNKNOWN = 1, 198 | DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2, 199 | DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3, 200 | DRM_MODE_SUBPIXEL_VERTICAL_RGB = 4, 201 | DRM_MODE_SUBPIXEL_VERTICAL_BGR = 5, 202 | DRM_MODE_SUBPIXEL_NONE = 6 203 | } drmModeSubPixel; 204 | 205 | typedef struct _drmModeConnector { 206 | uint32_t connector_id; 207 | uint32_t encoder_id; /**< Encoder currently connected to */ 208 | uint32_t connector_type; 209 | uint32_t connector_type_id; 210 | drmModeConnection connection; 211 | uint32_t mmWidth, mmHeight; /**< HxW in millimeters */ 212 | drmModeSubPixel subpixel; 213 | 214 | int count_modes; 215 | drmModeModeInfoPtr modes; 216 | 217 | int count_props; 218 | uint32_t *props; /**< List of property ids */ 219 | uint64_t *prop_values; /**< List of property values */ 220 | 221 | int count_encoders; 222 | uint32_t *encoders; /**< List of encoder ids */ 223 | } drmModeConnector, *drmModeConnectorPtr; 224 | 225 | #define DRM_PLANE_TYPE_OVERLAY 0 226 | #define DRM_PLANE_TYPE_PRIMARY 1 227 | #define DRM_PLANE_TYPE_CURSOR 2 228 | 229 | typedef struct _drmModeObjectProperties { 230 | uint32_t count_props; 231 | uint32_t *props; 232 | uint64_t *prop_values; 233 | } drmModeObjectProperties, *drmModeObjectPropertiesPtr; 234 | 235 | typedef struct _drmModeFormatModifierIterator { 236 | uint32_t fmt_idx, mod_idx; 237 | uint32_t fmt; 238 | uint64_t mod; 239 | } drmModeFormatModifierIterator; 240 | 241 | typedef struct _drmModePlane { 242 | uint32_t count_formats; 243 | uint32_t *formats; 244 | uint32_t plane_id; 245 | 246 | uint32_t crtc_id; 247 | uint32_t fb_id; 248 | 249 | uint32_t crtc_x, crtc_y; 250 | uint32_t x, y; 251 | 252 | uint32_t possible_crtcs; 253 | uint32_t gamma_size; 254 | } drmModePlane, *drmModePlanePtr; 255 | 256 | typedef struct _drmModePlaneRes { 257 | uint32_t count_planes; 258 | uint32_t *planes; 259 | } drmModePlaneRes, *drmModePlaneResPtr; 260 | 261 | extern void drmModeFreeModeInfo( drmModeModeInfoPtr ptr ); 262 | extern void drmModeFreeResources( drmModeResPtr ptr ); 263 | extern void drmModeFreeFB( drmModeFBPtr ptr ); 264 | extern void drmModeFreeFB2( drmModeFB2Ptr ptr ); 265 | extern void drmModeFreeCrtc( drmModeCrtcPtr ptr ); 266 | extern void drmModeFreeConnector( drmModeConnectorPtr ptr ); 267 | extern void drmModeFreeEncoder( drmModeEncoderPtr ptr ); 268 | extern void drmModeFreePlane( drmModePlanePtr ptr ); 269 | extern void drmModeFreePlaneResources(drmModePlaneResPtr ptr); 270 | 271 | /** 272 | * Check whether the DRM node supports Kernel Mode-Setting. 273 | * 274 | * Returns 1 if suitable for KMS, 0 otherwise. 275 | */ 276 | extern int drmIsKMS(int fd); 277 | 278 | /** 279 | * Retrieves all of the resources associated with a card. 280 | */ 281 | extern drmModeResPtr drmModeGetResources(int fd); 282 | 283 | /* 284 | * FrameBuffer manipulation. 285 | */ 286 | 287 | /** 288 | * Retrieve information about framebuffer bufferId 289 | */ 290 | extern drmModeFBPtr drmModeGetFB(int fd, uint32_t bufferId); 291 | extern drmModeFB2Ptr drmModeGetFB2(int fd, uint32_t bufferId); 292 | 293 | /** 294 | * Creates a new framebuffer with an buffer object as its scanout buffer. 295 | */ 296 | extern int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, 297 | uint8_t bpp, uint32_t pitch, uint32_t bo_handle, 298 | uint32_t *buf_id); 299 | /* ...with a specific pixel format */ 300 | extern int drmModeAddFB2(int fd, uint32_t width, uint32_t height, 301 | uint32_t pixel_format, const uint32_t bo_handles[4], 302 | const uint32_t pitches[4], const uint32_t offsets[4], 303 | uint32_t *buf_id, uint32_t flags); 304 | 305 | /* ...with format modifiers */ 306 | int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height, 307 | uint32_t pixel_format, const uint32_t bo_handles[4], 308 | const uint32_t pitches[4], const uint32_t offsets[4], 309 | const uint64_t modifier[4], uint32_t *buf_id, 310 | uint32_t flags); 311 | 312 | /** 313 | * Destroies the given framebuffer. 314 | */ 315 | extern int drmModeRmFB(int fd, uint32_t bufferId); 316 | 317 | /** 318 | * Close a framebuffer. 319 | * 320 | * Same as drmModeRmFB(), except it doesn't implicitly disable planes and CRTCs. 321 | */ 322 | extern int drmModeCloseFB(int fd, uint32_t buffer_id); 323 | 324 | /** 325 | * Mark a region of a framebuffer as dirty. 326 | */ 327 | extern int drmModeDirtyFB(int fd, uint32_t bufferId, 328 | drmModeClipPtr clips, uint32_t num_clips); 329 | 330 | 331 | /* 332 | * Crtc functions 333 | */ 334 | 335 | /** 336 | * Retrieve information about the ctrt crtcId 337 | */ 338 | extern drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId); 339 | 340 | /** 341 | * Set the mode on a crtc crtcId with the given mode modeId. 342 | */ 343 | int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, 344 | uint32_t x, uint32_t y, uint32_t *connectors, int count, 345 | drmModeModeInfoPtr mode); 346 | 347 | /* 348 | * Cursor functions 349 | */ 350 | 351 | /** 352 | * Set the cursor on crtc 353 | */ 354 | int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height); 355 | 356 | int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y); 357 | /** 358 | * Move the cursor on crtc 359 | */ 360 | int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y); 361 | 362 | /** 363 | * Encoder functions 364 | */ 365 | drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id); 366 | 367 | /* 368 | * Connector manipulation 369 | */ 370 | 371 | /** 372 | * Retrieve all information about the connector connectorId. This will do a 373 | * forced probe on the connector to retrieve remote information such as EDIDs 374 | * from the display device. 375 | */ 376 | extern drmModeConnectorPtr drmModeGetConnector(int fd, 377 | uint32_t connectorId); 378 | 379 | /** 380 | * Retrieve current information, i.e the currently active mode and encoder, 381 | * about the connector connectorId. This will not do any probing on the 382 | * connector or remote device, and only reports what is currently known. 383 | * For the complete set of modes and encoders associated with the connector 384 | * use drmModeGetConnector() which will do a probe to determine any display 385 | * link changes first. 386 | */ 387 | extern drmModeConnectorPtr drmModeGetConnectorCurrent(int fd, 388 | uint32_t connector_id); 389 | 390 | /** 391 | * Get a bitmask of CRTCs a connector is compatible with. 392 | * 393 | * The bits reference CRTC indices. If the n-th CRTC is compatible with the 394 | * connector, the n-th bit will be set. The indices are taken from the array 395 | * returned by drmModeGetResources(). The indices are different from the object 396 | * IDs. 397 | * 398 | * Zero is returned on error. 399 | */ 400 | extern uint32_t drmModeConnectorGetPossibleCrtcs(int fd, 401 | const drmModeConnector *connector); 402 | 403 | /** 404 | * Attaches the given mode to an connector. 405 | */ 406 | extern int drmModeAttachMode(int fd, uint32_t connectorId, drmModeModeInfoPtr mode_info); 407 | 408 | /** 409 | * Detaches a mode from the connector 410 | * must be unused, by the given mode. 411 | */ 412 | extern int drmModeDetachMode(int fd, uint32_t connectorId, drmModeModeInfoPtr mode_info); 413 | 414 | extern drmModePropertyPtr drmModeGetProperty(int fd, uint32_t propertyId); 415 | extern void drmModeFreeProperty(drmModePropertyPtr ptr); 416 | 417 | extern drmModePropertyBlobPtr drmModeGetPropertyBlob(int fd, uint32_t blob_id); 418 | extern bool drmModeFormatModifierBlobIterNext(const drmModePropertyBlobRes *blob, 419 | drmModeFormatModifierIterator *iter); 420 | extern void drmModeFreePropertyBlob(drmModePropertyBlobPtr ptr); 421 | extern int drmModeConnectorSetProperty(int fd, uint32_t connector_id, uint32_t property_id, 422 | uint64_t value); 423 | extern int drmCheckModesettingSupported(const char *busid); 424 | 425 | extern int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size, 426 | const uint16_t *red, const uint16_t *green, const uint16_t *blue); 427 | extern int drmModeCrtcGetGamma(int fd, uint32_t crtc_id, uint32_t size, 428 | uint16_t *red, uint16_t *green, uint16_t *blue); 429 | extern int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, 430 | uint32_t flags, void *user_data); 431 | extern int drmModePageFlipTarget(int fd, uint32_t crtc_id, uint32_t fb_id, 432 | uint32_t flags, void *user_data, 433 | uint32_t target_vblank); 434 | 435 | extern drmModePlaneResPtr drmModeGetPlaneResources(int fd); 436 | extern drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id); 437 | extern int drmModeSetPlane(int fd, uint32_t plane_id, uint32_t crtc_id, 438 | uint32_t fb_id, uint32_t flags, 439 | int32_t crtc_x, int32_t crtc_y, 440 | uint32_t crtc_w, uint32_t crtc_h, 441 | uint32_t src_x, uint32_t src_y, 442 | uint32_t src_w, uint32_t src_h); 443 | 444 | extern drmModeObjectPropertiesPtr drmModeObjectGetProperties(int fd, 445 | uint32_t object_id, 446 | uint32_t object_type); 447 | extern void drmModeFreeObjectProperties(drmModeObjectPropertiesPtr ptr); 448 | extern int drmModeObjectSetProperty(int fd, uint32_t object_id, 449 | uint32_t object_type, uint32_t property_id, 450 | uint64_t value); 451 | 452 | 453 | typedef struct _drmModeAtomicReq drmModeAtomicReq, *drmModeAtomicReqPtr; 454 | 455 | extern drmModeAtomicReqPtr drmModeAtomicAlloc(void); 456 | extern drmModeAtomicReqPtr drmModeAtomicDuplicate(const drmModeAtomicReqPtr req); 457 | extern int drmModeAtomicMerge(drmModeAtomicReqPtr base, 458 | const drmModeAtomicReqPtr augment); 459 | extern void drmModeAtomicFree(drmModeAtomicReqPtr req); 460 | extern int drmModeAtomicGetCursor(const drmModeAtomicReqPtr req); 461 | extern void drmModeAtomicSetCursor(drmModeAtomicReqPtr req, int cursor); 462 | extern int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, 463 | uint32_t object_id, 464 | uint32_t property_id, 465 | uint64_t value); 466 | extern int drmModeAtomicCommit(int fd, 467 | const drmModeAtomicReqPtr req, 468 | uint32_t flags, 469 | void *user_data); 470 | 471 | extern int drmModeCreatePropertyBlob(int fd, const void *data, size_t size, 472 | uint32_t *id); 473 | extern int drmModeDestroyPropertyBlob(int fd, uint32_t id); 474 | 475 | /* 476 | * DRM mode lease APIs. These create and manage new drm_masters with 477 | * access to a subset of the available DRM resources 478 | */ 479 | 480 | extern int drmModeCreateLease(int fd, const uint32_t *objects, int num_objects, int flags, uint32_t *lessee_id); 481 | 482 | typedef struct drmModeLesseeList { 483 | uint32_t count; 484 | uint32_t lessees[]; 485 | } drmModeLesseeListRes, *drmModeLesseeListPtr; 486 | 487 | extern drmModeLesseeListPtr drmModeListLessees(int fd); 488 | 489 | typedef struct drmModeObjectList { 490 | uint32_t count; 491 | uint32_t objects[]; 492 | } drmModeObjectListRes, *drmModeObjectListPtr; 493 | 494 | extern drmModeObjectListPtr drmModeGetLease(int fd); 495 | 496 | extern int drmModeRevokeLease(int fd, uint32_t lessee_id); 497 | 498 | /** 499 | * Get a string describing a connector type. 500 | * 501 | * NULL is returned if the connector type is unsupported. Callers should handle 502 | * this gracefully, e.g. by falling back to "Unknown" or printing the raw value. 503 | */ 504 | extern const char * 505 | drmModeGetConnectorTypeName(uint32_t connector_type); 506 | 507 | /** 508 | * Create a dumb buffer. 509 | * 510 | * Given a width, height and bits-per-pixel, the kernel will return a buffer 511 | * handle, pitch and size. The flags must be zero. 512 | * 513 | * Returns 0 on success, negative errno on error. 514 | */ 515 | extern int 516 | drmModeCreateDumbBuffer(int fd, uint32_t width, uint32_t height, uint32_t bpp, 517 | uint32_t flags, uint32_t *handle, uint32_t *pitch, 518 | uint64_t *size); 519 | 520 | /** 521 | * Destroy a dumb buffer. 522 | * 523 | * Returns 0 on success, negative errno on error. 524 | */ 525 | extern int 526 | drmModeDestroyDumbBuffer(int fd, uint32_t handle); 527 | 528 | /** 529 | * Prepare a dumb buffer for mapping. 530 | * 531 | * The kernel returns an offset which can be used as an argument to mmap(2) on 532 | * the DRM FD. 533 | * 534 | * Returns 0 on success, negative errno on error. 535 | */ 536 | extern int 537 | drmModeMapDumbBuffer(int fd, uint32_t handle, uint64_t *offset); 538 | 539 | #if defined(__cplusplus) 540 | } 541 | #endif 542 | 543 | #endif 544 | -------------------------------------------------------------------------------- /system/src/init/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void set_stdio_to_dev_null() { 8 | int fd = open("/dev/null", O_RDWR); 9 | if (fd == -1) { 10 | return; 11 | } 12 | dup2(fd, STDIN_FILENO); 13 | dup2(fd, STDOUT_FILENO); 14 | dup2(fd, STDERR_FILENO); 15 | if (fd > STDERR_FILENO) 16 | close(fd); 17 | } 18 | 19 | int init_kmsg() { 20 | int fd = 0; 21 | char *msg; 22 | if (access("/dev/kmsg", W_OK) == 0) { 23 | fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); 24 | msg = "creating from /dev/kmsg"; 25 | } else { 26 | mknod("/kmsg", S_IFCHR | 0666, MKDEV(1, 11)); 27 | fd = open("/kmsg", O_WRONLY | O_CLOEXEC); 28 | unlink("/kmsg"); 29 | msg = "creating from /kmsg"; 30 | } 31 | dup2(fd, STDIN_FILENO); 32 | dup2(fd, STDOUT_FILENO); 33 | dup2(fd, STDERR_FILENO); 34 | puts(msg); 35 | return fd; 36 | } 37 | 38 | int main() { 39 | set_stdio_to_dev_null(); 40 | int fd = init_kmsg(); 41 | printf("kmsg fd %d\n", fd); 42 | char *argv[] = {"/bin/busybox", "sh", "/bin/init.sh", NULL}; 43 | printf("execvp /bin/busybox sh /bin/init.sh\n"); 44 | return execvp(argv[0], argv); 45 | } -------------------------------------------------------------------------------- /system/src/uevent/init/devices.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "devices.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | using namespace std::chrono_literals; 34 | 35 | 36 | namespace android::init { 37 | 38 | /* Given a path that may start with a PCI device, populate the supplied buffer 39 | * with the PCI domain/bus number and the peripheral ID and return 0. 40 | * If it doesn't start with a PCI device, or there is some error, return -1 */ 41 | static bool FindPciDevicePrefix(const std::string &path, std::string *result) { 42 | result->clear(); 43 | 44 | if (!wra::StartsWith(path, "/devices/pci")) return false; 45 | 46 | /* Beginning of the prefix is the initial "pci" after "/devices/" */ 47 | std::string::size_type start = 9; 48 | 49 | /* End of the prefix is two path '/' later, capturing the domain/bus number 50 | * and the peripheral ID. Example: pci0000:00/0000:00:1f.2 */ 51 | auto end = path.find('/', start); 52 | if (end == std::string::npos) return false; 53 | 54 | end = path.find('/', end + 1); 55 | if (end == std::string::npos) return false; 56 | 57 | auto length = end - start; 58 | if (length <= 4) { 59 | // The minimum string that will get to this check is 'pci/', which is malformed, 60 | // so return false 61 | return false; 62 | } 63 | 64 | *result = path.substr(start, length); 65 | return true; 66 | } 67 | 68 | /* Given a path that may start with a virtual block device, populate 69 | * the supplied buffer with the virtual block device ID and return 0. 70 | * If it doesn't start with a virtual block device, or there is some 71 | * error, return -1 */ 72 | static bool FindVbdDevicePrefix(const std::string &path, std::string *result) { 73 | result->clear(); 74 | 75 | if (!wra::StartsWith(path, "/devices/vbd-")) return false; 76 | 77 | /* Beginning of the prefix is the initial "vbd-" after "/devices/" */ 78 | std::string::size_type start = 13; 79 | 80 | /* End of the prefix is one path '/' later, capturing the 81 | virtual block device ID. Example: 768 */ 82 | auto end = path.find('/', start); 83 | if (end == std::string::npos) return false; 84 | 85 | auto length = end - start; 86 | if (length == 0) return false; 87 | 88 | *result = path.substr(start, length); 89 | return true; 90 | } 91 | 92 | // Given a path that may start with a virtual dm block device, populate 93 | // the supplied buffer with the dm module's instantiated name. 94 | // If it doesn't start with a virtual block device, or there is some 95 | // error, return false. 96 | static bool FindDmDevice(const Uevent &uevent, std::string *name, std::string *uuid) { 97 | if (!wra::StartsWith(uevent.path, "/devices/virtual/block/dm-")) return false; 98 | if (uevent.action == "remove") return false; // Avoid error spam from ioctl 99 | 100 | dev_t dev = makedev(uevent.major, uevent.minor); 101 | 102 | auto &dm = DeviceMapper::Instance(); 103 | return dm.GetDeviceNameAndUuid(dev, name, uuid); 104 | } 105 | 106 | std::string DeviceHandler::GetPartitionNameForDevice(const std::string &query_device) { 107 | return {}; 108 | } 109 | 110 | // Given a path that may start with a platform device, find the parent platform device by finding a 111 | // parent directory with a 'subsystem' symlink that points to the platform bus. 112 | // If it doesn't start with a platform device, return false 113 | bool DeviceHandler::FindPlatformDevice(std::string path, std::string *platform_device_path) const { 114 | platform_device_path->clear(); 115 | 116 | // Uevents don't contain the mount point, so we need to add it here. 117 | path.insert(0, sysfs_mount_point_); 118 | 119 | std::string directory = wra::Dirname(path); 120 | 121 | while (directory != "/" && directory != ".") { 122 | std::string subsystem_link_path; 123 | if (wra::Realpath(directory + "/subsystem", &subsystem_link_path) && 124 | (subsystem_link_path == sysfs_mount_point_ + "/bus/platform" || 125 | subsystem_link_path == sysfs_mount_point_ + "/bus/amba")) { 126 | // We need to remove the mount point that we added above before returning. 127 | directory.erase(0, sysfs_mount_point_.size()); 128 | *platform_device_path = directory; 129 | return true; 130 | } 131 | 132 | auto last_slash = path.rfind('/'); 133 | if (last_slash == std::string::npos) return false; 134 | 135 | path.erase(last_slash); 136 | directory = wra::Dirname(path); 137 | } 138 | 139 | return false; 140 | } 141 | 142 | void DeviceHandler::FixupSysPermissions(const std::string &upath, 143 | const std::string &subsystem) const { 144 | // upaths omit the "/sys" that paths in this list 145 | // contain, so we prepend it... 146 | std::string path = "/sys" + upath; 147 | if (uEvent_Config.debug) 148 | wra_debug("FixupSysPermission %s", path.c_str()); 149 | } 150 | 151 | std::tuple DeviceHandler::GetDevicePermissions( 152 | const std::string &path, const std::vector &links) const { 153 | /* Default if nothing found. */ 154 | return {0666, 0, 0}; 155 | } 156 | 157 | void DeviceHandler::MakeDevice(const std::string &path, bool block, int major, int minor, 158 | const std::vector &links) const { 159 | auto [mode, uid, gid] = GetDevicePermissions(path, links); 160 | mode |= (block ? S_IFBLK : S_IFCHR); 161 | 162 | gid_t new_group = -1; 163 | 164 | dev_t dev = makedev(major, minor); 165 | /* Temporarily change egid to avoid race condition setting the gid of the 166 | * device node. Unforunately changing the euid would prevent creation of 167 | * some device nodes, so the uid has to be set with chown() and is still 168 | * racy. Fixing the gid race at least fixed the issue with system_server 169 | * opening dynamic input devices under the AID_INPUT gid. */ 170 | if (setegid(gid)) { 171 | std::stringstream ss; 172 | ss << "setegid(" << gid << ") for " << path << " device failed"; 173 | wra_error("%s", ss.str().c_str()); 174 | goto out; 175 | } 176 | /* If the node already exists update its SELinux label and the file mode to handle cases when 177 | * it was created with the wrong context and file mode during coldboot procedure. */ 178 | if (mknod(path.c_str(), mode, dev) != 0) { 179 | if (errno != EEXIST || uEvent_Config.debug) 180 | wra_error("mknod failed for %s: %s", path.c_str(), ::strerror(errno)); 181 | } else 182 | wra_info("added device %s", path.c_str()); 183 | 184 | out: 185 | if (chown(path.c_str(), uid, new_group) < 0) { 186 | std::stringstream ss; 187 | ss << "Cannot chown " << path << " " << uid << " " << new_group; 188 | wra_error("%s", ss.str().c_str()); 189 | } 190 | } 191 | 192 | // replaces any unacceptable characters with '_', the 193 | // length of the resulting string is equal to the input string 194 | void SanitizePartitionName(std::string *string) { 195 | const char *accept = 196 | "abcdefghijklmnopqrstuvwxyz" 197 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 198 | "0123456789" 199 | "_-."; 200 | 201 | if (!string) return; 202 | 203 | std::string::size_type pos = 0; 204 | while ((pos = string->find_first_not_of(accept, pos)) != std::string::npos) { 205 | (*string)[pos] = '_'; 206 | } 207 | } 208 | 209 | std::vector DeviceHandler::GetBlockDeviceSymlinks(const Uevent &uevent) const { 210 | std::string device; 211 | std::string type; 212 | std::string partition; 213 | std::string uuid; 214 | 215 | if (FindPlatformDevice(uevent.path, &device)) { 216 | // Skip /devices/platform or /devices/ if present 217 | static constexpr std::string_view devices_platform_prefix = "/devices/platform/"; 218 | static constexpr std::string_view devices_prefix = "/devices/"; 219 | 220 | if (wra::StartsWith(device, devices_platform_prefix)) { 221 | device = device.substr(devices_platform_prefix.length()); 222 | } else if (wra::StartsWith(device, devices_prefix)) { 223 | device = device.substr(devices_prefix.length()); 224 | } 225 | 226 | type = "platform"; 227 | } else if (FindPciDevicePrefix(uevent.path, &device)) { 228 | type = "pci"; 229 | } else if (FindVbdDevicePrefix(uevent.path, &device)) { 230 | type = "vbd"; 231 | } else if (FindDmDevice(uevent, &partition, &uuid)) { 232 | std::vector symlinks = {"/dev/block/mapper/" + partition}; 233 | if (!uuid.empty()) { 234 | symlinks.emplace_back("/dev/block/mapper/by-uuid/" + uuid); 235 | } 236 | return symlinks; 237 | } else { 238 | return {}; 239 | } 240 | 241 | std::vector links; 242 | 243 | if (uEvent_Config.debug) 244 | wra_debug("found new device [type %s] [device %s] [partition name %s] [path %s] [device name %s]", 245 | type.c_str(), device.c_str(), uevent.partition_name.c_str(), uevent.path.c_str(), 246 | uevent.device_name.c_str()); 247 | 248 | auto link_path = "/dev/block/" + type + "/" + device; 249 | 250 | if (!uevent.partition_name.empty()) { 251 | std::string partition_name_sanitized(uevent.partition_name); 252 | SanitizePartitionName(&partition_name_sanitized); 253 | if (partition_name_sanitized != uevent.partition_name) { 254 | std::stringstream ss; 255 | ss << "Linking partition '" << uevent.partition_name << "' as '" 256 | << partition_name_sanitized << "'"; 257 | wra_debug("%s", ss.str().c_str()); 258 | } 259 | links.emplace_back(link_path + "/by-name/" + partition_name_sanitized); 260 | // Adds symlink: /dev/block/by-name/. 261 | links.emplace_back("/dev/block/by-name/" + partition_name_sanitized); 262 | } 263 | 264 | std::string model; 265 | if (wra::ReadFileToString("/sys/class/block/" + uevent.device_name + "/queue/zoned", &model) && 266 | !wra::StartsWith(model, "none")) { 267 | links.emplace_back("/dev/block/by-name/zoned_device"); 268 | links.emplace_back("/dev/sys/block/by-name/zoned_device"); 269 | } 270 | 271 | auto last_slash = uevent.path.rfind('/'); 272 | links.emplace_back(link_path + "/" + uevent.path.substr(last_slash + 1)); 273 | 274 | return links; 275 | } 276 | 277 | static void RemoveDeviceMapperLinks(const std::string &devpath) { 278 | std::vector dirs = { 279 | "/dev/block/mapper", 280 | "/dev/block/mapper/by-uuid", 281 | }; 282 | for (const auto &dir: dirs) { 283 | if (access(dir.c_str(), F_OK) != 0) continue; 284 | DIR *dh = ::opendir(dir.c_str()); 285 | if (!dh) { 286 | std::stringstream ss; 287 | ss << "Failed to open directory " << dir; 288 | wra_error("%s", ss.str().c_str()); 289 | continue; 290 | } 291 | 292 | struct dirent *dp; 293 | std::string link_path; 294 | while ((dp = readdir(dh)) != nullptr) { 295 | if (dp->d_type != DT_LNK) continue; 296 | 297 | auto path = dir + "/" + dp->d_name; 298 | if (wra::Readlink(path, &link_path) && link_path == devpath) { 299 | unlink(path.c_str()); 300 | } 301 | } 302 | ::closedir(dh); 303 | } 304 | } 305 | 306 | void DeviceHandler::HandleDevice(const std::string &action, const std::string &devpath, bool block, 307 | int major, int minor, const std::vector &links) const { 308 | if (action == "add") 309 | MakeDevice(devpath, block, major, minor, links); 310 | 311 | 312 | // Handle device-mapper nodes. 313 | // On kernels <= 5.10, the "add" event is fired on DM_DEV_CREATE, but does not contain name 314 | // information until DM_TABLE_LOAD - thus, we wait for a "change" event. 315 | // On kernels >= 5.15, the "add" event is fired on DM_TABLE_LOAD, followed by a "change" 316 | // event. 317 | if (action == "add" || (action == "change" && wra::StartsWith(devpath, "/dev/block/dm-"))) { 318 | for (const auto &link: links) { 319 | std::string target; 320 | if (wra::StartsWith(link, "/dev/block/")) { 321 | target = devpath; 322 | } else if (wra::StartsWith(link, "/dev/sys/block/")) { 323 | target = "/sys/class/block/" + wra::Basename(devpath); 324 | } else { 325 | std::stringstream ss; 326 | ss << "Unrecognized link type: " << link; 327 | wra_error("%s", ss.str().c_str()); 328 | continue; 329 | } 330 | 331 | if (!wra::mkdir_recursive(wra::Dirname(link), 0755)) { 332 | std::stringstream ss; 333 | ss << "Failed to create directory " << wra::Dirname(link); 334 | wra_error("%s", ss.str().c_str()); 335 | } 336 | 337 | if (symlink(target.c_str(), link.c_str())) { 338 | if (errno != EEXIST) { 339 | std::stringstream ss; 340 | ss << "Failed to symlink " << devpath << " to " << link; 341 | wra_error("%s", ss.str().c_str()); 342 | } else if (std::string link_path; 343 | wra::Readlink(link, &link_path) && link_path != devpath) { 344 | std::stringstream ss; 345 | ss << "Failed to symlink " << devpath << " to " << link 346 | << ", which already links to: " << link_path; 347 | wra_error("%s", ss.str().c_str()); 348 | } 349 | } else 350 | wra_info("link device %s -> %s", target.c_str(), link.c_str()); 351 | } 352 | } 353 | 354 | if (action == "remove") { 355 | if (wra::StartsWith(devpath, "/dev/block/dm-")) { 356 | RemoveDeviceMapperLinks(devpath); 357 | } 358 | for (const auto &link: links) { 359 | std::string link_path; 360 | if (wra::Readlink(link, &link_path) && link_path == devpath) { 361 | unlink(link.c_str()); 362 | } 363 | } 364 | unlink(devpath.c_str()); 365 | } 366 | } 367 | 368 | void DeviceHandler::HandleAshmemUevent(const Uevent &uevent) { 369 | if (uevent.device_name == "ashmem") { 370 | static const std::string boot_id_path = "/proc/sys/kernel/random/boot_id"; 371 | std::string boot_id; 372 | if (!wra::ReadFileToString(boot_id_path, &boot_id)) { 373 | wra_error("Cannot duplicate ashmem device node. Failed to read %s", boot_id_path.c_str()); 374 | return; 375 | } 376 | boot_id = wra::Trim(boot_id); 377 | 378 | Uevent dup_ashmem_uevent = uevent; 379 | dup_ashmem_uevent.device_name += boot_id; 380 | dup_ashmem_uevent.path += boot_id; 381 | HandleUevent(dup_ashmem_uevent); 382 | } 383 | } 384 | 385 | void DeviceHandler::HandleUevent(const Uevent &uevent) { 386 | if (uevent.action == "add" || uevent.action == "change" || 387 | uevent.action == "bind" || uevent.action == "online") { 388 | FixupSysPermissions(uevent.path, uevent.subsystem); 389 | } 390 | 391 | // if it's not a /dev device, nothing to do 392 | if (uevent.major < 0 || uevent.minor < 0) return; 393 | 394 | std::string devpath; 395 | std::vector links; 396 | bool block = false; 397 | 398 | if (uevent.subsystem == "block") { 399 | block = true; 400 | devpath = "/dev/block/" + wra::Basename(uevent.path); 401 | 402 | if (wra::StartsWith(uevent.path, "/devices")) { 403 | links = GetBlockDeviceSymlinks(uevent); 404 | } 405 | } else if (const auto subsystem = 406 | std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem); 407 | subsystem != subsystems_.cend()) { 408 | devpath = subsystem->ParseDevPath(uevent); 409 | } else if (uevent.subsystem == "usb") { 410 | if (!uevent.device_name.empty()) { 411 | devpath = "/dev/" + uevent.device_name; 412 | } else { 413 | // This imitates the file system that would be created 414 | // if we were using devfs instead. 415 | // Minors are broken up into groups of 128, starting at "001" 416 | int bus_id = uevent.minor / 128 + 1; 417 | int device_id = uevent.minor % 128 + 1; 418 | size_t len = snprintf(nullptr, 0, "/dev/bus/usb/%03d/%03d", bus_id, device_id) + 1; 419 | char *str = (char *) alloca(len); 420 | snprintf(str, len, "/dev/bus/usb/%03d/%03d", bus_id, device_id); 421 | devpath = std::string(str); 422 | } 423 | } else if (wra::StartsWith(uevent.subsystem, "usb")) { 424 | // ignore other USB events 425 | return; 426 | } else if (uevent.subsystem == "misc" && wra::StartsWith(uevent.device_name, "dm-user/")) { 427 | devpath = "/dev/dm-user/" + uevent.device_name.substr(8); 428 | } else if (uevent.subsystem == "misc" && uevent.device_name == "vfio/vfio") { 429 | devpath = "/dev/" + uevent.device_name; 430 | } else { 431 | devpath = "/dev/" + wra::Basename(uevent.path); 432 | } 433 | 434 | wra::mkdir_recursive(wra::Dirname(devpath), 0755); 435 | 436 | HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links); 437 | 438 | // Duplicate /dev/ashmem device and name it /dev/ashmem. 439 | HandleAshmemUevent(uevent); 440 | } 441 | 442 | DeviceHandler::DeviceHandler(std::vector subsystems) 443 | : subsystems_(std::move(subsystems)) {} 444 | 445 | DeviceHandler::DeviceHandler() 446 | : DeviceHandler( 447 | std::vector{}) {} 448 | 449 | } 450 | 451 | -------------------------------------------------------------------------------- /system/src/uevent/init/devices.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _INIT_DEVICES_H 18 | #define _INIT_DEVICES_H 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "uevent.h" 29 | #include "uevent_handler.h" 30 | #include "main.h" 31 | #include "utils.h" 32 | 33 | 34 | namespace android::init { 35 | class Subsystem { 36 | public: 37 | enum DevnameSource { 38 | DEVNAME_UEVENT_DEVNAME, // uevent_devname 39 | DEVNAME_UEVENT_DEVPATH, // uevent_devpath 40 | DEVNAME_SYS_NAME, // sys_name 41 | }; 42 | 43 | Subsystem() = default; 44 | 45 | explicit Subsystem(std::string name) : name_(std::move(name)) {} 46 | 47 | Subsystem(std::string name, DevnameSource source, std::string dir_name) 48 | : name_(std::move(name)), devname_source_(source), dir_name_(std::move(dir_name)) {} 49 | 50 | // Returns the full path for a uevent of a device that is a member of this subsystem, 51 | // according to the rules parsed from ueventd.rc 52 | [[nodiscard]] std::string ParseDevPath(const Uevent &uevent) const { 53 | std::string devname; 54 | if (devname_source_ == DEVNAME_UEVENT_DEVNAME) { 55 | devname = uevent.device_name; 56 | } else if (devname_source_ == DEVNAME_UEVENT_DEVPATH) { 57 | devname = wra::Basename(uevent.path); 58 | } else if (devname_source_ == DEVNAME_SYS_NAME) { 59 | if (wra::ReadFileToString("/sys/" + uevent.path + "/name", &devname)) { 60 | devname.pop_back(); // Remove terminating newline 61 | } else { 62 | devname = uevent.device_name; 63 | } 64 | } 65 | return dir_name_ + "/" + devname; 66 | } 67 | 68 | bool operator==(const std::string &string_name) const { return name_ == string_name; } 69 | 70 | private: 71 | std::string name_; 72 | DevnameSource devname_source_ = DEVNAME_UEVENT_DEVNAME; 73 | std::string dir_name_ = "/dev"; 74 | }; 75 | 76 | class DeviceHandler : public UeventHandler { 77 | public: 78 | DeviceHandler(); 79 | 80 | explicit DeviceHandler(std::vector subsystems); 81 | 82 | ~DeviceHandler() override = default; 83 | 84 | void HandleUevent(const Uevent &uevent) override; 85 | 86 | // `androidboot.partition_map` allows associating a partition name for a raw block device 87 | // through a comma separated and semicolon deliminated list. For example, 88 | // `androidboot.partition_map=vdb,metadata;vdc,userdata` maps `vdb` to `metadata` and `vdc` to 89 | // `userdata`. 90 | static std::string GetPartitionNameForDevice(const std::string &device); 91 | 92 | private: 93 | 94 | bool FindPlatformDevice(std::string path, std::string *platform_device_path) const; 95 | 96 | [[nodiscard]] std::tuple GetDevicePermissions( 97 | const std::string &path, const std::vector &links) const; 98 | 99 | void MakeDevice(const std::string &path, bool block, int major, int minor, 100 | const std::vector &links) const; 101 | 102 | [[nodiscard]] std::vector GetBlockDeviceSymlinks(const Uevent &uevent) const; 103 | 104 | void HandleDevice(const std::string &action, const std::string &devpath, bool block, int major, 105 | int minor, const std::vector &links) const; 106 | 107 | void FixupSysPermissions(const std::string &upath, const std::string &subsystem) const; 108 | 109 | void HandleAshmemUevent(const Uevent &uevent); 110 | 111 | std::vector subsystems_; 112 | std::set boot_devices_ = {""}; 113 | std::string sysfs_mount_point_ = "/sys"; 114 | }; 115 | 116 | // Exposed for testing 117 | void SanitizePartitionName(std::string *string); 118 | 119 | } // namespace android::init 120 | 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /system/src/uevent/init/firmware_handler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "firmware_handler.h" 18 | #include "logger.h" 19 | #include "utils.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | namespace android::init { 32 | static void LoadFirmware(const std::string &firmware, const std::string &root, int fw_fd, 33 | size_t fw_size, int loading_fd, int data_fd) { 34 | // Start transfer. 35 | wra::WriteFully(loading_fd, "1", 1); 36 | 37 | // Copy the firmware. 38 | ssize_t rc = sendfile(data_fd, fw_fd, nullptr, fw_size); 39 | if (rc == -1) 40 | wra_error("firmware: sendfile failed { '%s', '%s' } %s fw_fd %d fw_size %zu loading_fd %d data_fd %d", 41 | root.c_str(), firmware.c_str(), strerror(errno), fw_fd, fw_size, loading_fd, data_fd); 42 | 43 | // Tell the firmware whether to abort or commit. 44 | const char *response = (rc != -1) ? "0" : "-1"; 45 | wra::WriteFully(loading_fd, response, strlen(response)); 46 | } 47 | 48 | FirmwareHandler::FirmwareHandler(std::vector firmware_directories) 49 | : firmware_directories_(std::move(firmware_directories)) {} 50 | 51 | std::string FirmwareHandler::GetFirmwarePath(const Uevent &uevent) { 52 | wra_info("firmware: loading { '%s', '%s' }", uevent.firmware.c_str(), uevent.path.c_str()); 53 | return uevent.firmware; 54 | } 55 | 56 | void FirmwareHandler::ProcessFirmwareEvent(const std::string &path, 57 | const std::string &firmware) const { 58 | std::string root = "/sys" + path; 59 | std::string loading = root + "/loading"; 60 | std::string data = root + "/data"; 61 | 62 | int loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC)); 63 | if (loading_fd == -1) { 64 | wra_error("couldn't open firmware loading fd for %s", firmware.c_str()); 65 | return; 66 | } 67 | 68 | int data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC)); 69 | if (data_fd == -1) { 70 | std::stringstream ss; 71 | ss << "couldn't open firmware data fd for " << firmware; 72 | wra_error("%s", ss.str().c_str()); 73 | close(loading_fd); 74 | return; 75 | } 76 | 77 | std::vector attempted_messages; 78 | auto TryLoadFirmware = [&](const std::string &firmware_directory) { 79 | std::string file = firmware_directory + firmware; 80 | int fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC)); 81 | if (fw_fd == -1) { 82 | attempted_messages.emplace_back("firmware: attempted " + file + 83 | ", open failed: " + strerror(errno)); 84 | return false; 85 | } 86 | struct stat sb{}; 87 | if (fstat(fw_fd, &sb) == -1) { 88 | attempted_messages.emplace_back("firmware: attempted " + file + 89 | ", fstat failed: " + strerror(errno)); 90 | close(fw_fd); 91 | return false; 92 | } 93 | wra_info("found %s for %s", file.c_str(), path.c_str()); 94 | LoadFirmware(firmware, root, fw_fd, sb.st_size, loading_fd, data_fd); 95 | wra_info("load %s successfully", file.c_str()); 96 | close(fw_fd); 97 | return true; 98 | }; 99 | 100 | int count = 0; 101 | 102 | while (true) { 103 | attempted_messages.clear(); 104 | if (ForEachFirmwareDirectory(TryLoadFirmware)) { 105 | close(loading_fd); 106 | close(data_fd); 107 | return; 108 | } 109 | count++; 110 | if (count > 3) 111 | break; 112 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 113 | } 114 | 115 | wra_error("firmware: could not find firmware for %s (%d times)", firmware.c_str(), count); 116 | for (const auto &message: attempted_messages) { 117 | wra_error("%s", message.c_str()); 118 | } 119 | 120 | // Write "-1" as our response to the kernel's firmware request, since we have nothing for it. 121 | write(loading_fd, "-1", 2); 122 | close(loading_fd); 123 | close(data_fd); 124 | } 125 | 126 | bool FirmwareHandler::ForEachFirmwareDirectory( 127 | const std::function &handler) const { 128 | for (const auto &firmware_directory: firmware_directories_) { 129 | if (std::invoke(handler, firmware_directory)) { 130 | return true; 131 | } 132 | } 133 | return false; 134 | } 135 | 136 | void FirmwareHandler::HandleUevent(const Uevent &uevent) { 137 | if (uevent.subsystem != "firmware" || uevent.action != "add") return; 138 | auto firmware = GetFirmwarePath(uevent); 139 | ProcessFirmwareEvent(uevent.path, firmware); 140 | } 141 | } -------------------------------------------------------------------------------- /system/src/uevent/init/firmware_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "uevent.h" 27 | #include "uevent_handler.h" 28 | 29 | 30 | namespace android::init { 31 | class FirmwareHandler : public UeventHandler { 32 | public: 33 | explicit FirmwareHandler(std::vector firmware_directories); 34 | 35 | ~FirmwareHandler() override = default; 36 | 37 | void HandleUevent(const Uevent &uevent) override; 38 | 39 | private: 40 | static std::string GetFirmwarePath(const Uevent &uevent); 41 | 42 | void ProcessFirmwareEvent(const std::string &path, const std::string &firmware) const; 43 | 44 | bool ForEachFirmwareDirectory(const std::function &handler) const; 45 | 46 | std::vector firmware_directories_; 47 | }; 48 | 49 | } // namespace android::init 50 | 51 | -------------------------------------------------------------------------------- /system/src/uevent/init/modalias_handler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "modalias_handler.h" 18 | #include "main.h" 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | 25 | namespace android::init { 26 | 27 | ModaliasHandler::ModaliasHandler(const std::vector &base_paths) 28 | : modprobe_(base_paths) {} 29 | 30 | void ModaliasHandler::HandleUevent(const Uevent &uevent) { 31 | if (uevent.modalias.empty()) return; 32 | if (uEvent_Config.debug) 33 | wra_debug("ModaliasHandler: do load for modalias %s", uevent.modalias.c_str()); 34 | modprobe_.LoadWithAliases(uevent.modalias, true); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /system/src/uevent/init/modalias_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "uevent.h" 25 | #include "uevent_handler.h" 26 | 27 | 28 | namespace android::init { 29 | 30 | class ModaliasHandler : public UeventHandler { 31 | public: 32 | explicit ModaliasHandler(const std::vector &); 33 | 34 | ~ModaliasHandler() override = default; 35 | 36 | void HandleUevent(const Uevent &uevent) override; 37 | 38 | private: 39 | Modprobe modprobe_; 40 | }; 41 | 42 | } // namespace android::init 43 | 44 | -------------------------------------------------------------------------------- /system/src/uevent/init/uevent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef INIT_UEVENT_H 18 | #define INIT_UEVENT_H 19 | 20 | #include 21 | 22 | namespace android::init { 23 | struct Uevent { 24 | std::string action; 25 | std::string path; 26 | std::string subsystem; 27 | std::string firmware; 28 | std::string partition_name; 29 | std::string device_name; 30 | std::string modalias; 31 | int partition_num; 32 | int major; 33 | int minor; 34 | }; 35 | 36 | } // namespace android::init 37 | 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /system/src/uevent/init/uevent_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "uevent.h" 20 | 21 | namespace android::init { 22 | 23 | class UeventHandler { 24 | public: 25 | virtual ~UeventHandler() = default; 26 | 27 | virtual void HandleUevent(const Uevent &uevent) = 0; 28 | }; 29 | 30 | } // namespace android::init 31 | 32 | -------------------------------------------------------------------------------- /system/src/uevent/init/uevent_listener.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "uevent_listener.h" 18 | #include "utils.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | 31 | namespace android::init { 32 | static void ParseEvent(const char *msg, Uevent *uevent) { 33 | uevent->partition_num = -1; 34 | uevent->major = -1; 35 | uevent->minor = -1; 36 | uevent->action.clear(); 37 | uevent->path.clear(); 38 | uevent->subsystem.clear(); 39 | uevent->firmware.clear(); 40 | uevent->partition_name.clear(); 41 | uevent->device_name.clear(); 42 | uevent->modalias.clear(); 43 | // currently ignoring SEQNUM 44 | while (*msg) { 45 | if (!strncmp(msg, "ACTION=", 7)) { 46 | msg += 7; 47 | uevent->action = msg; 48 | } else if (!strncmp(msg, "DEVPATH=", 8)) { 49 | msg += 8; 50 | uevent->path = msg; 51 | } else if (!strncmp(msg, "SUBSYSTEM=", 10)) { 52 | msg += 10; 53 | uevent->subsystem = msg; 54 | } else if (!strncmp(msg, "FIRMWARE=", 9)) { 55 | msg += 9; 56 | uevent->firmware = msg; 57 | } else if (!strncmp(msg, "MAJOR=", 6)) { 58 | msg += 6; 59 | uevent->major = atoi(msg); 60 | } else if (!strncmp(msg, "MINOR=", 6)) { 61 | msg += 6; 62 | uevent->minor = atoi(msg); 63 | } else if (!strncmp(msg, "PARTN=", 6)) { 64 | msg += 6; 65 | uevent->partition_num = atoi(msg); 66 | } else if (!strncmp(msg, "PARTNAME=", 9)) { 67 | msg += 9; 68 | uevent->partition_name = msg; 69 | } else if (!strncmp(msg, "DEVNAME=", 8)) { 70 | msg += 8; 71 | uevent->device_name = msg; 72 | } else if (!strncmp(msg, "MODALIAS=", 9)) { 73 | msg += 9; 74 | uevent->modalias = msg; 75 | } 76 | 77 | // advance to after the next \0 78 | while (*msg++); 79 | } 80 | 81 | if (uEvent_Config.debug) { 82 | std::stringstream ss; 83 | ss << "event { '" << uevent->action << "', '" << uevent->path << "', '" 84 | << uevent->subsystem << "', '" << uevent->firmware << "', " << uevent->major 85 | << ", " << uevent->minor << " }"; 86 | wra_debug("%s", ss.str().c_str()); 87 | } 88 | } 89 | 90 | UeventListener::UeventListener(size_t uevent_socket_rcvbuf_size) { 91 | device_fd = uevent_open_socket((int) uevent_socket_rcvbuf_size, true); 92 | if (device_fd == -1) { 93 | wra_fatal("Could not open uevent socket"); 94 | } 95 | 96 | fcntl(device_fd, F_SETFL, O_NONBLOCK); 97 | } 98 | 99 | ReadUeventResult UeventListener::ReadUevent(Uevent *uevent) const { 100 | char msg[UEVENT_MSG_LEN + 2]; 101 | int n = (int) uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN); 102 | if (n <= 0) { 103 | if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EIO) { 104 | wra_error("Error reading from Uevent Fd %s", strerror(errno)); 105 | } 106 | return ReadUeventResult::kFailed; 107 | } 108 | if (n >= UEVENT_MSG_LEN) { 109 | wra_error("Uevent overflowed buffer, discarding"); 110 | return ReadUeventResult::kInvalid; 111 | } 112 | 113 | msg[n] = '\0'; 114 | msg[n + 1] = '\0'; 115 | 116 | ParseEvent(msg, uevent); 117 | 118 | return ReadUeventResult::kSuccess; 119 | } 120 | 121 | // RegenerateUevents*() walks parts of the /sys tree and pokes the uevent files to cause the kernel 122 | // to regenerate device add uevents that have already happened. This is particularly useful when 123 | // starting ueventd, to regenerate all of the uevents that it had previously missed. 124 | // 125 | // We drain any pending events from the netlink socket every time we poke another uevent file to 126 | // make sure we don't overrun the socket's buffer. 127 | // 128 | ListenerAction UeventListener::RegenerateUeventsForDir(DIR *d, const ListenerCallback &callback) const { 129 | int dfd = dirfd(d); 130 | 131 | if (uEvent_Config.debug) { 132 | std::string path = "/proc/self/fd/" + std::to_string(dfd); 133 | std::string realPath; 134 | wra::Readlink(path, &realPath); 135 | wra_debug("Test %s", realPath.c_str()); 136 | } 137 | 138 | int fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC); 139 | 140 | if (fd >= 0) { 141 | write(fd, "add\n", 4); 142 | close(fd); 143 | 144 | Uevent uevent; 145 | ReadUeventResult result; 146 | while ((result = ReadUevent(&uevent)) != ReadUeventResult::kFailed) { 147 | // Skip processing the uevent if it is invalid. 148 | if (result == ReadUeventResult::kInvalid) continue; 149 | if (callback(uevent) == ListenerAction::kStop) return ListenerAction::kStop; 150 | } 151 | } else if (uEvent_Config.debug) { 152 | perror("openat"); 153 | } 154 | 155 | dirent *de; 156 | while ((de = readdir(d)) != nullptr) { 157 | if (de->d_type != DT_DIR || de->d_name[0] == '.') continue; 158 | 159 | fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 160 | if (fd < 0) continue; 161 | 162 | DIR *d2 = fdopendir(fd); 163 | if (d2 == nullptr) { 164 | close(fd); 165 | closedir(d2); 166 | } else { 167 | ListenerAction action = RegenerateUeventsForDir(d2, callback); 168 | closedir(d2); 169 | if (action == ListenerAction::kStop) { 170 | return ListenerAction::kStop; 171 | } 172 | } 173 | } 174 | 175 | // default is always to continue looking for uevents 176 | return ListenerAction::kContinue; 177 | } 178 | 179 | ListenerAction UeventListener::RegenerateUeventsForPath(const std::string &path, 180 | const ListenerCallback &callback) const { 181 | DIR *dir = opendir(path.c_str()); 182 | if (!dir) return ListenerAction::kContinue; 183 | 184 | auto r = RegenerateUeventsForDir(dir, callback); 185 | closedir(dir); 186 | return r; 187 | } 188 | 189 | static const char *kRegenerationPaths[] = {"/sys/devices"}; 190 | 191 | void UeventListener::RegenerateUevents(const ListenerCallback &callback) const { 192 | for (const auto path: kRegenerationPaths) { 193 | if (RegenerateUeventsForPath(path, callback) == ListenerAction::kStop) return; 194 | } 195 | } 196 | 197 | void UeventListener::Poll(const ListenerCallback &callback, 198 | const std::optional relative_timeout) const { 199 | using namespace std::chrono; 200 | 201 | pollfd ufd = { 202 | .fd = device_fd, 203 | .events = POLLIN, 204 | }; 205 | 206 | auto start_time = steady_clock::now(); 207 | 208 | while (true) { 209 | ufd.revents = 0; 210 | 211 | int timeout_ms = -1; 212 | if (relative_timeout) { 213 | auto now = steady_clock::now(); 214 | auto time_elapsed = duration_cast(now - start_time); 215 | if (time_elapsed > *relative_timeout) return; 216 | 217 | auto remaining_timeout = *relative_timeout - time_elapsed; 218 | timeout_ms = (int) remaining_timeout.count(); 219 | } 220 | 221 | int nr = poll(&ufd, 1, timeout_ms); 222 | if (nr == 0) return; 223 | if (nr < 0) { 224 | wra_error("poll() of uevent socket failed, continuing"); 225 | continue; 226 | } 227 | if (ufd.revents & POLLIN) { 228 | // We're non-blocking, so if we receive a poll event keep processing until 229 | // we have exhausted all uevent messages. 230 | Uevent uevent; 231 | ReadUeventResult result; 232 | while ((result = ReadUevent(&uevent)) != ReadUeventResult::kFailed) { 233 | // Skip processing the uevent if it is invalid. 234 | if (result == ReadUeventResult::kInvalid) continue; 235 | if (callback(uevent) == ListenerAction::kStop) return; 236 | } 237 | } 238 | } 239 | } 240 | 241 | UeventListener::~UeventListener() { 242 | close(device_fd); 243 | } 244 | } // namespace android::init 245 | 246 | -------------------------------------------------------------------------------- /system/src/uevent/init/uevent_listener.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _INIT_UEVENT_LISTENER_H 18 | #define _INIT_UEVENT_LISTENER_H 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "uevent.h" 27 | 28 | #define UEVENT_MSG_LEN 8192 29 | 30 | 31 | namespace android::init { 32 | 33 | enum class ListenerAction { 34 | kStop = 0, // Stop regenerating uevents as we've handled the one(s) we're interested in. 35 | kContinue, // Continue regenerating uevents as we haven't seen the one(s) we're interested in. 36 | }; 37 | 38 | enum class ReadUeventResult { 39 | kSuccess = 0, // Uevent was successfully read. 40 | kFailed, // Uevent reading has failed. 41 | kInvalid, // An Invalid Uevent was read (like say, the msg received is >= UEVENT_MSG_LEN). 42 | }; 43 | 44 | using ListenerCallback = std::function; 45 | 46 | class UeventListener { 47 | public: 48 | explicit UeventListener(size_t uevent_socket_rcvbuf_size); 49 | 50 | ~UeventListener(); 51 | 52 | void RegenerateUevents(const ListenerCallback &callback) const; 53 | 54 | [[nodiscard]] ListenerAction RegenerateUeventsForPath(const std::string &path, 55 | const ListenerCallback &callback) const; 56 | 57 | void Poll(const ListenerCallback &callback, 58 | std::optional relative_timeout = {}) const; 59 | 60 | private: 61 | ReadUeventResult ReadUevent(Uevent *uevent) const; 62 | 63 | ListenerAction RegenerateUeventsForDir(DIR *d, const ListenerCallback &callback) const; 64 | 65 | int device_fd; 66 | }; 67 | 68 | } // namespace android::init 69 | 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /system/src/uevent/init/ueventd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "ueventd.h" 18 | #include "logger.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include "devices.h" 38 | #include "firmware_handler.h" 39 | #include "modalias_handler.h" 40 | #include "uevent_handler.h" 41 | #include "uevent_listener.h" 42 | #include "ueventd_parser.h" 43 | 44 | // At a high level, ueventd listens for uevent messages generated by the kernel through a netlink 45 | // socket. When ueventd receives such a message it handles it by taking appropriate actions, 46 | // which can typically be creating a device node in /dev, setting file permissions, setting selinux 47 | // labels, etc. 48 | // Ueventd also handles loading of firmware that the kernel requests, and creates symlinks for block 49 | // and character devices. 50 | 51 | // When ueventd starts, it regenerates uevents for all currently registered devices by traversing 52 | // /sys and writing 'add' to each 'uevent' file that it finds. This causes the kernel to generate 53 | // and resend uevent messages for all of the currently registered devices. This is done, because 54 | // ueventd would not have been running when these devices were registered and therefore was unable 55 | // to receive their uevent messages and handle them appropriately. This process is known as 56 | // 'cold boot'. 57 | 58 | // 'init' currently waits synchronously on the cold boot process of ueventd before it continues 59 | // its boot process. For this reason, cold boot should be as quick as possible. One way to achieve 60 | // a speed up here is to parallelize the handling of ueventd messages, which consume the bulk of the 61 | // time during cold boot. 62 | 63 | // Handling of uevent messages has two unique properties: 64 | // 1) It can be done in isolation; it doesn't need to read or write any status once it is started. 65 | // 2) It uses setegid() and setfscreatecon() so either care (aka locking) must be taken to ensure 66 | // that no file system operations are done while the uevent process has an abnormal egid or 67 | // fscreatecon or this handling must happen in a separate process. 68 | // Given the above two properties, it is best to fork() subprocesses to handle the uevents. This 69 | // reduces the overhead and complexity that would be required in a solution with threads and locks. 70 | // In testing, a racy multithreaded solution has the same performance as the fork() solution, so 71 | // there is no reason to deal with the complexity of the former. 72 | 73 | // One other important caveat during the boot process is the handling of SELinux restorecon. 74 | // Since many devices have child devices, calling selinux_android_restorecon() recursively for each 75 | // device when its uevent is handled, results in multiple restorecon operations being done on a 76 | // given file. It is more efficient to simply do restorecon recursively on /sys during cold boot, 77 | // than to do restorecon on each device as its uevent is handled. This only applies to cold boot; 78 | // once that has completed, restorecon is done for each device as its uevent is handled. 79 | 80 | // With all of the above considered, the cold boot process has the below steps: 81 | // 1) ueventd regenerates uevents by doing the /sys traversal and listens to the netlink socket for 82 | // the generated uevents. It writes these uevents into a queue represented by a vector. 83 | // 84 | // 2) ueventd forks 'n' separate uevent handler subprocesses and has each of them to handle the 85 | // uevents in the queue based on a starting offset (their process number) and a stride (the total 86 | // number of processes). Note that no IPC happens at this point and only const functions from 87 | // DeviceHandler should be called from this context. 88 | // 89 | // 3) In parallel to the subprocesses handling the uevents, the main thread of ueventd calls 90 | // selinux_android_restorecon() recursively on /sys/class, /sys/block, and /sys/devices. 91 | // 92 | // 4) Once the restorecon operation finishes, the main thread calls waitpid() to wait for all 93 | // subprocess handlers to complete and exit. Once this happens, it marks coldboot as having 94 | // completed. 95 | // 96 | // At this point, ueventd is single threaded, poll()'s and then handles any future uevents. 97 | 98 | // Lastly, it should be noted that uevents that occur during the coldboot process are handled 99 | // without issue after the coldboot process completes. This is because the uevent listener is 100 | // paused while the uevent handler and restorecon actions take place. Once coldboot completes, 101 | // the uevent listener resumes in polling mode and will handle the uevents that occurred during 102 | // coldboot. 103 | 104 | 105 | namespace android::init { 106 | 107 | class ColdBoot { 108 | public: 109 | ColdBoot(UeventListener &uevent_listener, std::vector> &uevent_handlers) 110 | : uevent_listener_(uevent_listener), 111 | uevent_handlers_(uevent_handlers), 112 | num_handler_subprocesses_(std::thread::hardware_concurrency() ?: 4) {} 113 | 114 | void Run(); 115 | 116 | private: 117 | void UeventHandlerMain(unsigned int process_num, unsigned int total_processes); 118 | 119 | void RegenerateUevents(); 120 | 121 | void ForkSubProcesses(); 122 | 123 | void WaitForSubProcesses(); 124 | 125 | UeventListener &uevent_listener_; 126 | std::vector> &uevent_handlers_; 127 | 128 | unsigned int num_handler_subprocesses_; 129 | 130 | std::vector uevent_queue_; 131 | 132 | std::set subprocess_pids_; 133 | }; 134 | 135 | void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) { 136 | for (unsigned int i = process_num; i < uevent_queue_.size(); i += total_processes) { 137 | auto &uevent = uevent_queue_[i]; 138 | 139 | for (auto &uevent_handler: uevent_handlers_) { 140 | uevent_handler->HandleUevent(uevent); 141 | } 142 | } 143 | } 144 | 145 | void ColdBoot::RegenerateUevents() { 146 | uevent_listener_.RegenerateUevents([this](const Uevent &uevent) { 147 | uevent_queue_.emplace_back(uevent); 148 | return ListenerAction::kContinue; 149 | }); 150 | } 151 | 152 | void ColdBoot::ForkSubProcesses() { 153 | for (unsigned int i = 0; i < num_handler_subprocesses_; ++i) { 154 | auto pid = fork(); 155 | if (pid < 0) { 156 | wra_error("fork() failed!"); 157 | } 158 | 159 | if (pid == 0) { 160 | UeventHandlerMain(i, num_handler_subprocesses_); 161 | _exit(EXIT_SUCCESS); 162 | } 163 | 164 | subprocess_pids_.emplace(pid); 165 | } 166 | } 167 | 168 | void ColdBoot::WaitForSubProcesses() { 169 | // Treat subprocesses that crash or get stuck the same as if ueventd itself has crashed or gets 170 | // stuck. 171 | // 172 | // When a subprocess crashes, we fatally abort from ueventd. init will restart ueventd when 173 | // init reaps it, and the cold boot process will start again. If this continues to fail, then 174 | // since ueventd is marked as a critical service, init will reboot to bootloader. 175 | // 176 | // When a subprocess gets stuck, keep ueventd spinning waiting for it. init has a timeout for 177 | // cold boot and will reboot to the bootloader if ueventd does not complete in time. 178 | while (!subprocess_pids_.empty()) { 179 | int status; 180 | pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, 0)); 181 | if (pid == -1) { 182 | wra_error("waitpid() failed"); 183 | continue; 184 | } 185 | 186 | auto it = std::find(subprocess_pids_.begin(), subprocess_pids_.end(), pid); 187 | if (it == subprocess_pids_.end()) continue; 188 | 189 | if (WIFEXITED(status)) { 190 | if (WEXITSTATUS(status) == EXIT_SUCCESS) { 191 | subprocess_pids_.erase(it); 192 | } else { 193 | wra_error("subprocess exited with status %d", WEXITSTATUS(status)); 194 | } 195 | } else if (WIFSIGNALED(status)) { 196 | wra_error("subprocess killed by signal %d", WTERMSIG(status)); 197 | } 198 | } 199 | } 200 | 201 | void ColdBoot::Run() { 202 | auto start = std::chrono::steady_clock::now(); 203 | 204 | RegenerateUevents(); 205 | 206 | ForkSubProcesses(); 207 | 208 | WaitForSubProcesses(); 209 | 210 | auto end = std::chrono::steady_clock::now(); 211 | 212 | auto duration = std::chrono::duration_cast(end - start); 213 | 214 | wra_info("Coldboot took %f seconds", duration.count() / 1000.0f); 215 | } 216 | 217 | int ueventd_main(int argc, char **argv) { 218 | umask(000); 219 | 220 | wra_info("ueventd started!"); 221 | 222 | std::vector> uevent_handlers; 223 | 224 | 225 | UeventdConfiguration ueventd_configuration; 226 | 227 | uevent_handlers.emplace_back(std::make_unique( 228 | std::move(ueventd_configuration.subsystems))); 229 | uevent_handlers.emplace_back(std::make_unique( 230 | std::move(ueventd_configuration.firmware_directories))); 231 | uevent_handlers.emplace_back(std::make_unique( 232 | std::move(ueventd_configuration.module_base_paths))); 233 | 234 | UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); 235 | 236 | if (uEvent_Config.cold_boot) { 237 | ColdBoot cold_boot(uevent_listener, uevent_handlers); 238 | cold_boot.Run(); 239 | } 240 | 241 | // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now. 242 | signal(SIGCHLD, SIG_IGN); 243 | // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN 244 | // for SIGCHLD above. 245 | while (waitpid(-1, nullptr, WNOHANG) > 0) { 246 | } 247 | 248 | std::optional relative_timeout; 249 | if (!uEvent_Config.loop) 250 | relative_timeout = std::chrono::milliseconds(500); 251 | // Restore prio before main loop 252 | setpriority(PRIO_PROCESS, 0, 0); 253 | uevent_listener.Poll([&uevent_handlers](const Uevent &uevent) { 254 | for (auto &uevent_handler: uevent_handlers) { 255 | uevent_handler->HandleUevent(uevent); 256 | } 257 | return ListenerAction::kContinue; 258 | }, relative_timeout); 259 | 260 | return 0; 261 | } 262 | } 263 | 264 | -------------------------------------------------------------------------------- /system/src/uevent/init/ueventd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _INIT_UEVENTD_H_ 18 | #define _INIT_UEVENTD_H_ 19 | 20 | namespace android { 21 | namespace init { 22 | 23 | int ueventd_main(int argc, char** argv); 24 | 25 | } // namespace init 26 | } // namespace android 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /system/src/uevent/init/ueventd_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include "devices.h" 23 | #include "firmware_handler.h" 24 | 25 | 26 | namespace android::init { 27 | struct UeventdConfiguration { 28 | std::vector subsystems = { 29 | Subsystem("graphics", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/graphics"), 30 | Subsystem("drm", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/dri"), 31 | Subsystem("input", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/input"), 32 | Subsystem("sound", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/snd"), 33 | Subsystem("dma_heap", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/dma_heap"), 34 | Subsystem("vfio", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/vfio"), 35 | }; 36 | std::vector firmware_directories = {"/etc/firmware/", 37 | "/odm/firmware/", 38 | "/vendor/firmware/", 39 | "/firmware/image/", 40 | "/vendor/firmware_mnt/image/"}; 41 | size_t uevent_socket_rcvbuf_size = 16 * 1024 * 1024; //16MB 42 | std::vector module_base_paths = {"/odm/lib/modules", "/vendor/lib/modules"}; 43 | }; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /system/src/uevent/libcutils/uevent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include "uevent.h" 27 | #include 28 | 29 | extern "C" { 30 | 31 | /** 32 | * Like recv(), but checks that messages actually originate from the kernel. 33 | */ 34 | ssize_t uevent_kernel_multicast_recv(int socket, void *buffer, size_t length) { 35 | uid_t uid = -1; 36 | return uevent_kernel_multicast_uid_recv(socket, buffer, length, &uid); 37 | } 38 | 39 | /** 40 | * Like the above, but passes a uid_t in by pointer. In the event that this 41 | * fails due to a bad uid check, the uid_t will be set to the uid of the 42 | * socket's peer. 43 | * 44 | * If this method rejects a netlink message from outside the kernel, it 45 | * returns -1, sets errno to EIO, and sets "user" to the UID associated with the 46 | * message. If the peer UID cannot be determined, "user" is set to -1." 47 | */ 48 | ssize_t uevent_kernel_multicast_uid_recv(int socket, void *buffer, size_t length, uid_t *uid) { 49 | return uevent_kernel_recv(socket, buffer, length, true, uid); 50 | } 51 | 52 | ssize_t uevent_kernel_recv(int socket, void *buffer, size_t length, bool require_group, uid_t *uid) { 53 | struct iovec iov = {buffer, length}; 54 | struct sockaddr_nl addr{}; 55 | char control[CMSG_SPACE(sizeof(struct ucred))]; 56 | struct msghdr hdr = { 57 | &addr, sizeof(addr), &iov, 1, control, sizeof(control), 0, 58 | }; 59 | struct ucred *cred; 60 | 61 | *uid = -1; 62 | ssize_t n = TEMP_FAILURE_RETRY(recvmsg(socket, &hdr, 0)); 63 | if (n <= 0) { 64 | return n; 65 | } 66 | 67 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr); 68 | if (cmsg == nullptr || cmsg->cmsg_type != SCM_CREDENTIALS) { 69 | /* ignoring netlink message with no sender credentials */ 70 | goto out; 71 | } 72 | 73 | cred = (struct ucred *) CMSG_DATA(cmsg); 74 | *uid = cred->uid; 75 | 76 | if (addr.nl_pid != 0) { 77 | /* ignore non-kernel */ 78 | goto out; 79 | } 80 | if (require_group && addr.nl_groups == 0) { 81 | /* ignore unicast messages when requested */ 82 | goto out; 83 | } 84 | 85 | return n; 86 | 87 | out: 88 | /* clear residual potentially malicious data */ 89 | bzero(buffer, length); 90 | errno = EIO; 91 | return -1; 92 | } 93 | 94 | int uevent_open_socket(int buf_sz, bool passcred) { 95 | struct sockaddr_nl addr{}; 96 | int on = passcred; 97 | int buf_sz_readback = 0; 98 | socklen_t optlen = sizeof(buf_sz_readback); 99 | int s; 100 | 101 | memset(&addr, 0, sizeof(addr)); 102 | addr.nl_family = AF_NETLINK; 103 | addr.nl_pid = 0; 104 | addr.nl_groups = 0xffffffff; 105 | 106 | s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT); 107 | if (s < 0) return -1; 108 | 109 | if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)) < 0 || 110 | getsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz_readback, &optlen) < 0) { 111 | close(s); 112 | return -1; 113 | } 114 | /* Only if SO_RCVBUF was not effective, try SO_RCVBUFFORCE. Generally, we 115 | * want to avoid SO_RCVBUFFORCE, because it generates SELinux denials in 116 | * case we don't have CAP_NET_ADMIN. This is the case, for example, for 117 | * healthd. */ 118 | if (buf_sz_readback < 2 * buf_sz) { 119 | if (setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz)) < 0) { 120 | wra_warn("Failed to set receive buffer size to %d (now %d)", buf_sz, buf_sz_readback); 121 | } 122 | } 123 | 124 | setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); 125 | 126 | if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 127 | close(s); 128 | return -1; 129 | } 130 | 131 | return s; 132 | } 133 | 134 | } // extern "C" 135 | -------------------------------------------------------------------------------- /system/src/uevent/libcutils/uevent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __CUTILS_UEVENT_H 18 | #define __CUTILS_UEVENT_H 19 | 20 | 21 | #include 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | int uevent_open_socket(int buf_sz, bool passcred); 28 | ssize_t uevent_kernel_multicast_recv(int socket, void *buffer, size_t length); 29 | ssize_t uevent_kernel_multicast_uid_recv(int socket, void *buffer, size_t length, uid_t *uid); 30 | ssize_t uevent_kernel_recv(int socket, void *buffer, size_t length, bool require_group, uid_t *uid); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | 36 | #endif /* __CUTILS_UEVENT_H */ 37 | -------------------------------------------------------------------------------- /system/src/uevent/libmodprobe/libmodprobe_ext.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include "utils.h" 27 | 28 | std::string Modprobe::GetKernelCmdline() { 29 | std::string cmdline; 30 | if (!wra::ReadFileToString("/proc/cmdline", &cmdline)) { 31 | return ""; 32 | } 33 | return cmdline; 34 | } 35 | 36 | bool Modprobe::Insmod(const std::string &path_name, const std::string ¶meters) { 37 | int fd(TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); 38 | if (fd == -1) { 39 | wra_error("Could not open module '%s'", path_name.c_str()); 40 | return false; 41 | } 42 | 43 | auto canonical_name = MakeCanonical(path_name); 44 | std::string options; 45 | auto options_iter = module_options_.find(canonical_name); 46 | if (options_iter != module_options_.end()) { 47 | options = options_iter->second; 48 | } 49 | if (!parameters.empty()) { 50 | options = options + " " + parameters; 51 | } 52 | 53 | wra_info("Loading module %s with args '%s'", path_name.c_str(), options.c_str()); 54 | long ret = syscall(__NR_finit_module, fd, options.c_str(), 0); 55 | if (ret != 0) { 56 | if (errno == EEXIST) { 57 | // Module already loaded 58 | std::lock_guard guard(module_loaded_lock_); 59 | module_loaded_paths_.emplace(path_name); 60 | module_loaded_.emplace(canonical_name); 61 | close(fd); 62 | wra_info("Kernel module %s already loaded", path_name.c_str()); 63 | return true; 64 | } 65 | wra_error("Failed to insmod '%s' with args '%s'", path_name.c_str(), options.c_str()); 66 | close(fd); 67 | return false; 68 | } 69 | 70 | wra_info("Loading kernel module %s successfully", path_name.c_str()); 71 | std::lock_guard guard(module_loaded_lock_); 72 | module_loaded_paths_.emplace(path_name); 73 | module_loaded_.emplace(canonical_name); 74 | module_count_++; 75 | close(fd); 76 | return true; 77 | } 78 | 79 | bool Modprobe::Rmmod(const std::string &module_name) { 80 | auto canonical_name = MakeCanonical(module_name); 81 | long ret = syscall(__NR_delete_module, canonical_name.c_str(), O_NONBLOCK); 82 | if (ret != 0) { 83 | wra_error("Failed to remove module '%s'", module_name.c_str()); 84 | return false; 85 | } 86 | std::lock_guard guard(module_loaded_lock_); 87 | module_loaded_.erase(canonical_name); 88 | return true; 89 | } 90 | 91 | bool Modprobe::ModuleExists(const std::string &module_name) { 92 | struct stat fileStat{}; 93 | if (blocklist_enabled && module_blocklist_.count(module_name)) { 94 | wra_info("module %s is blocklisted", module_name.c_str()); 95 | return false; 96 | } 97 | auto deps = GetDependencies(module_name); 98 | if (deps.empty()) { 99 | // missing deps can happen in the case of an alias 100 | return false; 101 | } 102 | if (stat(deps.front().c_str(), &fileStat)) { 103 | wra_info("module %s can't be loaded; can't access %s", module_name.c_str(), deps.front().c_str()); 104 | return false; 105 | } 106 | if (!S_ISREG(fileStat.st_mode)) { 107 | wra_info("module %s is not a regular file", module_name.c_str()); 108 | return false; 109 | } 110 | return true; 111 | } 112 | -------------------------------------------------------------------------------- /system/src/uevent/libmodprobe/modprobe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | class Modprobe { 29 | public: 30 | explicit Modprobe(const std::vector &, bool use_blocklist = true); 31 | 32 | bool LoadModulesParallel(int num_threads); 33 | 34 | bool LoadListedModules(bool strict = true); 35 | 36 | bool LoadWithAliases(const std::string &module_name, bool strict, 37 | const std::string ¶meters = ""); 38 | 39 | bool Remove(const std::string &module_name); 40 | 41 | std::vector ListModules(const std::string &pattern); 42 | 43 | bool GetAllDependencies(const std::string &module, std::vector *pre_dependencies, 44 | std::vector *dependencies, 45 | std::vector *post_dependencies); 46 | 47 | void ResetModuleCount() { module_count_ = 0; } 48 | 49 | int GetModuleCount() const { return module_count_; } 50 | 51 | bool IsBlocklisted(const std::string &module_name); 52 | 53 | private: 54 | static std::string MakeCanonical(const std::string &module_path); 55 | 56 | bool InsmodWithDeps(const std::string &module_name, const std::string ¶meters); 57 | 58 | bool Insmod(const std::string &path_name, const std::string ¶meters); 59 | 60 | bool Rmmod(const std::string &module_name); 61 | 62 | std::vector GetDependencies(const std::string &module); 63 | 64 | bool ModuleExists(const std::string &module_name); 65 | 66 | void AddOption(const std::string &module_name, const std::string &option_name, 67 | const std::string &value); 68 | 69 | static std::string GetKernelCmdline(); 70 | 71 | bool ParseDepCallback(const std::string &base_path, const std::vector &args); 72 | 73 | bool ParseAliasCallback(const std::vector &args); 74 | 75 | bool ParseSoftdepCallback(const std::vector &args); 76 | 77 | bool ParseLoadCallback(const std::vector &args); 78 | 79 | bool ParseOptionsCallback(const std::vector &args); 80 | 81 | bool ParseBlocklistCallback(const std::vector &args); 82 | 83 | void ParseKernelCmdlineOptions(); 84 | 85 | static void ParseCfg(const std::string &cfg, const std::function &)>& f); 86 | 87 | std::vector> module_aliases_; 88 | std::unordered_map> module_deps_; 89 | std::vector> module_pre_softdep_; 90 | std::vector> module_post_softdep_; 91 | std::vector module_load_; 92 | std::unordered_map module_options_; 93 | std::set module_blocklist_; 94 | std::mutex module_loaded_lock_; 95 | std::unordered_set module_loaded_; 96 | std::unordered_set module_loaded_paths_; 97 | int module_count_ = 0; 98 | bool blocklist_enabled = false; 99 | }; 100 | -------------------------------------------------------------------------------- /system/src/uevent/main.cpp: -------------------------------------------------------------------------------- 1 | #include "init/ueventd.h" 2 | #include "logger.h" 3 | #include "main.h" 4 | #include 5 | 6 | UEvent_Config uEvent_Config{}; 7 | 8 | int main(int argc, char **argv) { 9 | for (int i = 0; i < argc; ++i) { 10 | std::string arg = std::string(argv[i]); 11 | wra_info("Find para %s", argv[i]); 12 | if (arg == "-h" || arg == "--help") { 13 | char info[] = "Options:\n" 14 | " --debug\n" 15 | " --help -h\n" 16 | " --no-cold-boot\n" 17 | " --no-loop\n"; 18 | wra_info("%s", info); 19 | return 0; 20 | } 21 | if (arg == "--debug") 22 | uEvent_Config.debug = true; 23 | if (arg == "--no-cold-boot") 24 | uEvent_Config.cold_boot = false; 25 | if (arg == "--no-loop") 26 | uEvent_Config.loop = false; 27 | } 28 | android::init::ueventd_main(argc, argv); 29 | return 0; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /system/src/uevent/main.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-7-16. 3 | // 4 | 5 | #ifndef WRA_MAIN_H 6 | #define WRA_MAIN_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | struct UEvent_Config { 13 | bool debug = false; 14 | bool cold_boot = true; 15 | bool loop = true; 16 | }; 17 | 18 | extern UEvent_Config uEvent_Config; 19 | 20 | #endif //WRA_MAIN_H 21 | -------------------------------------------------------------------------------- /system/src/utils/DeviceMapper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "DeviceMapper.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | DeviceMapper::DeviceMapper() { 25 | fd = open("/dev/device-mapper", O_RDWR | O_CLOEXEC); 26 | if (fd < 0) 27 | wra_error("Failed to open device-mapper"); 28 | } 29 | 30 | void DeviceMapper::InitIo(struct dm_ioctl *io, const std::string &name) { 31 | assert(io != nullptr); 32 | memset(io, 0, sizeof(*io)); 33 | 34 | io->version[0] = DM_VERSION0; 35 | io->version[1] = DM_VERSION1; 36 | io->version[2] = DM_VERSION2; 37 | io->data_size = sizeof(*io); 38 | io->data_start = 0; 39 | if (!name.empty()) { 40 | snprintf(io->name, sizeof(io->name), "%s", name.c_str()); 41 | } 42 | } 43 | 44 | bool DeviceMapper::GetDmDevicePathByName(const std::string &name, std::string *path) const { 45 | struct dm_ioctl io{}; 46 | InitIo(&io, name); 47 | if (ioctl(fd, DM_DEV_STATUS, &io) < 0) { 48 | wra_error("DM_DEV_STATUS failed for %s", name.c_str()); 49 | return false; 50 | } 51 | 52 | uint32_t dev_num = minor(io.dev); 53 | *path = "dm-" + std::to_string(dev_num); 54 | // *path = "/dev/block/dm-" + std::to_string(dev_num); 55 | return true; 56 | } 57 | 58 | bool DeviceMapper::GetDeviceNameAndUuid(dev_t dev, std::string *name, std::string *uuid) const { 59 | struct dm_ioctl io{}; 60 | InitIo(&io, {}); 61 | io.dev = dev; 62 | 63 | if (ioctl(fd, DM_DEV_STATUS, &io) < 0) { 64 | wra_error("Failed to find device dev: %d:%d", major(dev), minor(dev)); 65 | return false; 66 | } 67 | 68 | if (name) { 69 | *name = io.name; 70 | } 71 | if (uuid) { 72 | *uuid = io.uuid; 73 | } 74 | return true; 75 | } 76 | 77 | DeviceMapper &DeviceMapper::Instance() { 78 | static DeviceMapper instance{}; 79 | return instance; 80 | } 81 | 82 | static bool only_one_char(const uint8_t *buf, size_t len, uint8_t c) { 83 | for (int i = 0; i < len; i++) { 84 | if (buf[i] != c) { 85 | return false; 86 | } 87 | } 88 | return true; 89 | } 90 | 91 | bool DeviceMapper::partition_wiped(const char *source) { 92 | uint8_t buf[4096]; 93 | int fd; 94 | 95 | if ((fd = open(source, O_RDONLY)) < 0) { 96 | wra_error("Unable to open %s", source); 97 | return false; 98 | } 99 | 100 | size_t ret = read(fd, buf, sizeof(buf)); 101 | close(fd); 102 | 103 | if (ret != sizeof(buf)) { 104 | return false; 105 | } 106 | 107 | /* Check for all zeros */ 108 | if (only_one_char(buf, sizeof(buf), 0)) { 109 | return true; 110 | } 111 | 112 | /* Check for all ones */ 113 | if (only_one_char(buf, sizeof(buf), 0xff)) { 114 | return true; 115 | } 116 | 117 | return false; 118 | } -------------------------------------------------------------------------------- /system/src/utils/DeviceMapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WRA_DEVICEMAPPER_H 18 | #define WRA_DEVICEMAPPER_H 19 | 20 | #include 21 | #include "../utils/logger.h" 22 | #include 23 | #include 24 | 25 | 26 | // The minimum expected device mapper major.minor version 27 | #define DM_VERSION0 (4) 28 | #define DM_VERSION1 (0) 29 | #define DM_VERSION2 (0) 30 | 31 | class DeviceMapper { 32 | 33 | int fd = -1; 34 | public: 35 | DeviceMapper(); 36 | 37 | static DeviceMapper &Instance(); 38 | 39 | static void InitIo(struct dm_ioctl *io, const std::string &name); 40 | 41 | bool GetDmDevicePathByName(const std::string &name, std::string *path) const; 42 | 43 | bool GetDeviceNameAndUuid(dev_t dev, std::string *name, std::string *uuid) const; 44 | 45 | static bool partition_wiped(const char *source); 46 | }; 47 | 48 | #endif //WRA_DEVICEMAPPER_H 49 | -------------------------------------------------------------------------------- /system/src/utils/logger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by SnowNF on 24-7-12. 3 | // 4 | 5 | #ifndef WRA_LOGGER_H 6 | #define WRA_LOGGER_H 7 | 8 | #include 9 | #include 10 | 11 | typedef enum { 12 | WRA_P_ERROR = 0, 13 | WRA_P_WARNING, 14 | WRA_P_INFO, 15 | WRA_P_DEBUG, 16 | WRA_P_FATAL, 17 | } WRA_LOG_LEVEL; 18 | 19 | #define wra_print(level, msg, ...) do{ \ 20 | static const char *level_strings[] = { \ 21 | /* the order is important */ \ 22 | "error", \ 23 | "warning", \ 24 | "info", \ 25 | "debug", \ 26 | "fatal" \ 27 | }; \ 28 | \ 29 | static const char *levels[5] = {"0;31", "0;33", "0;32", "0;36", "0;33"}; \ 30 | fprintf(stderr,"\033[%sm%s : " msg"\033[0m\n", levels[level], level_strings[level], ## __VA_ARGS__); \ 31 | }while(0) 32 | 33 | #define wra_debug(format, ...) wra_print(WRA_P_DEBUG, format, ## __VA_ARGS__) 34 | #define wra_info(format, ...) wra_print(WRA_P_INFO, format, ## __VA_ARGS__) 35 | #define wra_warn(format, ...) wra_print(WRA_P_WARNING, format, ## __VA_ARGS__) 36 | #define wra_error(format, ...) wra_print(WRA_P_ERROR, format, ## __VA_ARGS__) 37 | #define wra_fatal(format, ...) do{wra_print(WRA_P_FATAL, format, ## __VA_ARGS__); assert(false);}while(0) 38 | 39 | #endif //WRA_LOGGER_H 40 | -------------------------------------------------------------------------------- /system/src/utils/utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-7-13. 3 | // 4 | 5 | #include "utils.h" 6 | #include "../utils/logger.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace wra { 15 | int exec(char **argv) { 16 | pid_t pid = fork(); 17 | if (pid < 0) { 18 | wra_error("Fork failed"); 19 | return -1; 20 | } else if (pid == 0) { 21 | // Child process 22 | if (execvp(argv[0], argv) < 0) { 23 | wra_error("Exec failed"); 24 | return -1; 25 | } 26 | } else { 27 | // Parent process 28 | int status; 29 | if (waitpid(pid, &status, 0) < 0) { 30 | wra_error("Waitpid failed"); 31 | return -1; 32 | } 33 | 34 | // Return the exit status of the child process 35 | if (WIFEXITED(status)) { 36 | return WEXITSTATUS(status); 37 | } else { 38 | wra_error("WIFEXITED() failed"); 39 | return -1; 40 | } 41 | } 42 | 43 | wra_error("Should never reach here"); 44 | return 0; 45 | } 46 | 47 | std::string Basename(std::string_view path) { 48 | size_t pos = path.find_last_of('/'); 49 | if (pos == std::string_view::npos) { 50 | return std::string(path); 51 | } 52 | return std::string(path.substr(pos + 1)); 53 | } 54 | 55 | bool WriteFully(int fd, const void *data, size_t byte_count) { 56 | const auto *p = reinterpret_cast(data); 57 | size_t remaining = byte_count; 58 | while (remaining > 0) { 59 | ssize_t n = TEMP_FAILURE_RETRY(::write(fd, p, remaining)); 60 | if (n == -1) return false; 61 | p += n; 62 | remaining -= n; 63 | } 64 | return true; 65 | } 66 | 67 | 68 | bool StartsWith(std::string_view s, std::string_view prefix) { 69 | return s.substr(0, prefix.size()) == prefix; 70 | } 71 | 72 | bool StartsWith(std::string_view s, char prefix) { 73 | return !s.empty() && s.front() == prefix; 74 | } 75 | 76 | bool EndsWith(std::string_view s, char prefix) { 77 | return s.ends_with(prefix); 78 | } 79 | 80 | bool EndsWith(std::string_view s, std::string_view prefix) { 81 | return s.ends_with(prefix); 82 | } 83 | 84 | std::string Dirname(std::string_view path) { 85 | if (path.empty()) { 86 | return "."; 87 | } 88 | 89 | size_t pos = path.rfind('/'); 90 | if (pos == std::string_view::npos) { 91 | return "."; 92 | } 93 | if (pos == 0) { 94 | return "/"; 95 | } 96 | 97 | return std::string(path.substr(0, pos)); 98 | } 99 | 100 | bool Realpath(const std::string &in, std::string *out) { 101 | char *r = realpath(in.c_str(), nullptr); 102 | if (r != nullptr) { 103 | *out = std::string(r); // 将解析后的路径存储到 out 中 104 | free(r); 105 | return true; 106 | } else { 107 | return false; 108 | } 109 | } 110 | 111 | bool ReadFileToString(const std::string &path, std::string *content, bool follow_symlinks) { 112 | if (content == nullptr) { 113 | return false; 114 | } 115 | 116 | std::filesystem::path file_path(path); 117 | 118 | if (!follow_symlinks && std::filesystem::is_symlink(file_path)) { 119 | return false; 120 | } 121 | return ReadFileToString(path, content); 122 | } 123 | 124 | bool ReadFileToString(const std::string &path, std::string *out) { 125 | if (out == nullptr) { 126 | return false; 127 | } 128 | 129 | std::ifstream file(path); 130 | if (!file.is_open()) { 131 | return false; 132 | } 133 | 134 | std::stringstream buffer; 135 | buffer << file.rdbuf(); 136 | *out = buffer.str(); 137 | 138 | return true; 139 | } 140 | 141 | bool Readlink(const std::string &in, std::string *out) { 142 | // Most Linux file systems (ext2 and ext4, say) limit symbolic links to 143 | // 4095 bytes. Since we'll copy out into the string anyway, it doesn't 144 | // waste memory to just start there. We add 1 so that we can recognize 145 | // whether it actually fit (rather than being truncated to 4095). 146 | std::vector buf(4095 + 1); 147 | while (true) { 148 | ssize_t size = readlink(in.c_str(), &buf[0], buf.size()); 149 | // Unrecoverable error? 150 | if (size == -1) return false; 151 | // It fit! (If size == buf.size(), it may have been truncated.) 152 | if (static_cast(size) < buf.size()) { 153 | out->assign(&buf[0], size); 154 | return true; 155 | } 156 | // Double our buffer and try again. 157 | buf.resize(buf.size() * 2); 158 | } 159 | } 160 | 161 | bool mkdir_recursive(const std::string &path, mode_t mode) { 162 | std::string::size_type slash = 0; 163 | while ((slash = path.find('/', slash + 1)) != std::string::npos) { 164 | auto directory = path.substr(0, slash); 165 | struct stat info{}; 166 | if (stat(directory.c_str(), &info) != 0) { 167 | auto ret = make_dir(directory, mode); 168 | if (!ret && errno != EEXIST) return false; 169 | } 170 | } 171 | auto ret = make_dir(path, mode); 172 | if (!ret && errno != EEXIST) return false; 173 | return true; 174 | } 175 | 176 | bool make_dir(const std::string &path, mode_t mode) { 177 | int rc = mkdir(path.c_str(), mode); 178 | return rc == 0; 179 | } 180 | 181 | std::string Trim(const std::string &in) { 182 | size_t start = 0; 183 | size_t end = in.length(); 184 | while (start < end && std::isspace(in[start])) { 185 | ++start; 186 | } 187 | while (end > start && std::isspace(in[end - 1])) { 188 | --end; 189 | } 190 | return in.substr(start, end - start); 191 | }; 192 | 193 | std::vector Split(const std::string &basicString, const char *delimiter) { 194 | std::vector result; 195 | size_t start = 0; 196 | size_t end = 0; 197 | 198 | while ((end = basicString.find(delimiter, start)) != std::string::npos) { 199 | result.push_back(basicString.substr(start, end - start)); 200 | start = end + std::strlen(delimiter); 201 | } 202 | result.push_back(basicString.substr(start)); 203 | 204 | return result; 205 | } 206 | 207 | std::string Join(const std::set &set, const char *delimiter) { 208 | std::string result; 209 | for (auto it = set.begin(); it != set.end(); ++it) { 210 | if (it != set.begin()) { 211 | result += delimiter; 212 | } 213 | result += *it; 214 | } 215 | return result; 216 | } 217 | } -------------------------------------------------------------------------------- /system/src/utils/utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by snownf on 24-7-13. 3 | // 4 | 5 | #ifndef WRA_UTILS_H 6 | #define WRA_UTILS_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace wra { 18 | int exec(char **argv); 19 | 20 | std::string Basename(std::string_view path); 21 | 22 | bool WriteFully(int fd, const void *data, size_t byte_count); 23 | 24 | bool StartsWith(std::string_view s, std::string_view prefix); 25 | 26 | bool StartsWith(std::string_view s, char prefix); 27 | 28 | bool EndsWith(std::string_view s, char prefix); 29 | 30 | bool EndsWith(std::string_view s, std::string_view prefix); 31 | 32 | std::string Dirname(std::string_view path); 33 | 34 | bool Realpath(const std::string &in, std::string *out); 35 | 36 | bool Readlink(const std::string &in, std::string *out); 37 | 38 | bool ReadFileToString(const std::string &path, std::string *content, bool follow_symlinks); 39 | 40 | bool ReadFileToString(const std::string &path, std::string *out); 41 | 42 | bool make_dir(const std::string &path, mode_t mode); 43 | 44 | bool mkdir_recursive(const std::string &path, mode_t mode); 45 | 46 | std::string Trim(const std::string &in); 47 | 48 | std::vector Split(const std::string &basicString, const char *delimiter); 49 | 50 | std::string Join(const std::set &set, const char *delimiter); 51 | } 52 | 53 | #endif //WRA_UTILS_H 54 | -------------------------------------------------------------------------------- /system/src/wra-utils/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "DeviceMapper.h" 3 | #include "utils.h" 4 | #include "main.h" 5 | #include 6 | 7 | UEvent_Config uEvent_Config{}; 8 | 9 | int main(int argc, char **argv) { 10 | if (argc < 2) { 11 | wra_error("argc < 2"); 12 | return -1; 13 | } 14 | 15 | auto cmd = std::string(argv[1]); 16 | 17 | if (cmd == "GetDmDevicePathByName") { 18 | auto dm = DeviceMapper::Instance(); 19 | std::string path; 20 | dm.GetDmDevicePathByName(argv[2], &path); 21 | if (argc > 3) 22 | printf("%s", path.c_str()); 23 | else 24 | wra_info("DevicePath: %s for Device %s", path.c_str(), argv[1]); 25 | return 0; 26 | } 27 | 28 | if (cmd == "FormatPartitionIfWiped") { 29 | if (DeviceMapper::partition_wiped(argv[2])) { 30 | wra_info("Partition %s is wiped", argv[2]); 31 | char f2fsPath[] = "/system/bin/mkfs.f2fs"; 32 | char *f2fs[] = {f2fsPath, argv[2], nullptr}; 33 | wra_info("exec %s %s", f2fsPath, argv[2]); 34 | wra::exec(f2fs); 35 | } else { 36 | wra_info("Partition %s is not wiped", argv[2]); 37 | } 38 | return 0; 39 | } 40 | if (cmd == "LoadListedModules") { 41 | std::vector basePaths; 42 | bool useBlocklist = true; 43 | bool strictMode = false; 44 | for (int i = 2; i < argc; ++i) { 45 | std::string arg = argv[i]; 46 | if (arg == "--help" || arg == "-h") { 47 | wra_info("%s %s [opt]", argv[0], argv[1]); 48 | wra_info("Available opts are:"); 49 | wra_info(" --base-path : base path for modules"); 50 | wra_info(" --no-use-blocklist : do not use modules.blocklist in base path"); 51 | wra_info(" --strict-mode : quit when one failed"); 52 | wra_info(" --debug : enable debug output"); 53 | return 0; 54 | } 55 | if (arg == "--base-path") { 56 | basePaths.emplace_back(argv[i + 1]); 57 | i++; 58 | continue; 59 | } 60 | if (arg == "--no-use-blocklist") { 61 | useBlocklist = false; 62 | continue; 63 | } 64 | if (arg == "--strict-mode") { 65 | strictMode = true; 66 | continue; 67 | } 68 | if (arg == "--debug") { 69 | uEvent_Config.debug = true; 70 | continue; 71 | } 72 | } 73 | Modprobe modprobe(basePaths, useBlocklist); 74 | modprobe.LoadListedModules(strictMode); 75 | return 0; 76 | } 77 | wra_error("Unknown cmd %s", cmd.c_str()); 78 | wra_error("Available cmds are:"); 79 | wra_error(" GetDmDevicePathByName"); 80 | wra_error(" FormatPartitionIfWiped"); 81 | wra_error(" LoadListedModules"); 82 | return 1; 83 | } --------------------------------------------------------------------------------