├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── build ├── build-all.sh ├── build-amd64.sh ├── build-arm64.sh ├── build-armhf.sh ├── build-armv7.sh ├── build-loong64.sh ├── build-ppc64le.sh ├── build-riscv64.sh ├── build-s390x.sh ├── build-x86.sh ├── build.sh └── setup.sh ├── gen-config └── src ├── archive.c ├── checkdep.c ├── config.c ├── dockerhub.c ├── exec.c ├── hook.c ├── include ├── rurima.h └── version.h ├── info.c ├── lxcmirror.c ├── main.c ├── shared.c ├── signal.c └── subcommand.c /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # clang-format configuration file. Intended for clang-format >= 11. 4 | # 5 | # For more information, see: 6 | # 7 | # Documentation/process/clang-format.rst 8 | # https://clang.llvm.org/docs/ClangFormat.html 9 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 10 | # 11 | ############## 12 | # This file is from kernel.org, it is only used to format the code. 13 | # It's not part of my project. 14 | # The original file can be found at: 15 | # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/.clang-format 16 | --- 17 | AccessModifierOffset: -4 18 | AlignAfterOpenBracket: Align 19 | AlignConsecutiveAssignments: false 20 | AlignConsecutiveDeclarations: false 21 | AlignEscapedNewlines: Left 22 | AlignOperands: true 23 | AlignTrailingComments: false 24 | AllowAllParametersOfDeclarationOnNextLine: false 25 | AllowShortBlocksOnASingleLine: false 26 | AllowShortCaseLabelsOnASingleLine: false 27 | AllowShortFunctionsOnASingleLine: None 28 | AllowShortIfStatementsOnASingleLine: false 29 | AllowShortLoopsOnASingleLine: false 30 | AlwaysBreakAfterDefinitionReturnType: None 31 | AlwaysBreakAfterReturnType: None 32 | AlwaysBreakBeforeMultilineStrings: false 33 | AlwaysBreakTemplateDeclarations: false 34 | BinPackArguments: true 35 | BinPackParameters: true 36 | BraceWrapping: 37 | AfterClass: false 38 | AfterControlStatement: false 39 | AfterEnum: false 40 | AfterFunction: true 41 | AfterNamespace: true 42 | AfterObjCDeclaration: false 43 | AfterStruct: false 44 | AfterUnion: false 45 | AfterExternBlock: false 46 | BeforeCatch: false 47 | BeforeElse: false 48 | IndentBraces: false 49 | SplitEmptyFunction: true 50 | SplitEmptyRecord: true 51 | SplitEmptyNamespace: true 52 | BreakBeforeBinaryOperators: None 53 | BreakBeforeBraces: Custom 54 | BreakBeforeInheritanceComma: false 55 | BreakBeforeTernaryOperators: false 56 | BreakConstructorInitializersBeforeComma: false 57 | BreakConstructorInitializers: BeforeComma 58 | BreakAfterJavaFieldAnnotations: false 59 | BreakStringLiterals: false 60 | ColumnLimit: 114514 61 | CommentPragmas: '^ IWYU pragma:' 62 | CompactNamespaces: false 63 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 64 | ConstructorInitializerIndentWidth: 8 65 | ContinuationIndentWidth: 8 66 | Cpp11BracedListStyle: false 67 | DerivePointerAlignment: false 68 | DisableFormat: false 69 | ExperimentalAutoDetectBinPacking: false 70 | FixNamespaceComments: false 71 | 72 | 73 | ForEachMacros: 74 | IncludeBlocks: Preserve 75 | IncludeCategories: 76 | - Regex: '.*' 77 | Priority: 1 78 | IncludeIsMainRegex: '(Test)?$' 79 | IndentCaseLabels: false 80 | IndentGotoLabels: false 81 | IndentPPDirectives: None 82 | IndentWidth: 8 83 | IndentWrappedFunctionNames: false 84 | JavaScriptQuotes: Leave 85 | JavaScriptWrapImports: true 86 | KeepEmptyLinesAtTheStartOfBlocks: false 87 | MacroBlockBegin: '' 88 | MacroBlockEnd: '' 89 | MaxEmptyLinesToKeep: 1 90 | NamespaceIndentation: None 91 | ObjCBinPackProtocolList: Auto 92 | ObjCBlockIndentWidth: 8 93 | ObjCSpaceAfterProperty: true 94 | ObjCSpaceBeforeProtocolList: true 95 | 96 | # Taken from git's rules 97 | PenaltyBreakAssignment: 10 98 | PenaltyBreakBeforeFirstCallParameter: 30 99 | PenaltyBreakComment: 10 100 | PenaltyBreakFirstLessLess: 0 101 | PenaltyBreakString: 10 102 | PenaltyExcessCharacter: 100 103 | PenaltyReturnTypeOnItsOwnLine: 60 104 | 105 | PointerAlignment: Right 106 | ReflowComments: true 107 | SortIncludes: false 108 | SortUsingDeclarations: false 109 | SpaceAfterCStyleCast: false 110 | SpaceAfterTemplateKeyword: true 111 | SpaceBeforeAssignmentOperators: true 112 | SpaceBeforeCtorInitializerColon: true 113 | SpaceBeforeInheritanceColon: true 114 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 115 | SpaceBeforeRangeBasedForLoopColon: true 116 | SpaceInEmptyParentheses: false 117 | SpacesBeforeTrailingComments: 1 118 | SpacesInAngles: false 119 | SpacesInContainerLiterals: false 120 | SpacesInCStyleCastParentheses: false 121 | SpacesInParentheses: false 122 | SpacesInSquareBrackets: false 123 | Standard: Cpp03 124 | TabWidth: 8 125 | UseTab: Always -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | ruri_version: 7 | description: "Optional: rurima version to use as tag_name (leave blank to use the latest remote version)" 8 | required: false 9 | release_name: 10 | description: "Optional: Custom release name (rurima v* release)" 11 | required: false 12 | push: 13 | branches: [ "main" ] 14 | paths: 15 | - 'src/**' 16 | - 'build/**' 17 | - 'gen-config' 18 | - 'Makefile' 19 | pull_request: 20 | 21 | jobs: 22 | check_update: 23 | name: Check update 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Check latest version 27 | id: check_version 28 | run: | 29 | remote_version=$(curl -L https://api.github.com/repos/Moe-hacker/rurima/releases/latest | jq -r .tag_name) 30 | remote_name=$(curl -L https://api.github.com/repos/Moe-hacker/rurima/releases/latest | jq -r .name) 31 | echo "remote_version=$remote_version" | tee -a $GITHUB_OUTPUT 32 | echo "remote_name=$remote_name" | tee -a $GITHUB_OUTPUT 33 | outputs: 34 | remote_version: ${{ steps.check_version.outputs.remote_version }} 35 | remote_name: ${{ steps.check_version.outputs.remote_name }} 36 | 37 | build: 38 | name: Build 39 | needs: check_update 40 | runs-on: ubuntu-latest 41 | permissions: 42 | contents: write 43 | steps: 44 | - name: Set env 45 | run: | 46 | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then 47 | if [ -n "${{ github.event.inputs.ruri_version }}" ]; then 48 | echo "version=${{ github.event.inputs.ruri_version }}" | tee -a $GITHUB_ENV 49 | else 50 | echo "version=${{ needs.check_update.outputs.remote_version }}" | tee -a $GITHUB_ENV 51 | fi 52 | 53 | if [ -n "${{ github.event.inputs.release_name }}" ]; then 54 | echo "release_name=${{ github.event.inputs.release_name }}" | tee -a $GITHUB_ENV 55 | elif [ -n "${{ github.event.inputs.ruri_version }}" ]; then 56 | echo "release_name=${{ github.event.inputs.ruri_version }}" | tee -a $GITHUB_ENV 57 | else 58 | echo "release_name=${{ needs.check_update.outputs.remote_name }}" | tee -a $GITHUB_ENV 59 | fi 60 | else 61 | echo "version=${{ needs.check_update.outputs.remote_version }}" | tee -a $GITHUB_ENV 62 | echo "release_name=${{ needs.check_update.outputs.remote_name }}" | tee -a $GITHUB_ENV 63 | fi 64 | echo "build_time=$(TZ=Asia/Shanghai date '+%Y%m%d%H%M')" | tee -a $GITHUB_ENV 65 | 66 | - uses: actions/checkout@v4 67 | - name: Build-Release 68 | run: | 69 | cd build 70 | sudo bash build-all.sh 71 | 72 | - name: Release 73 | uses: softprops/action-gh-release@v2 74 | with: 75 | tag_name: ${{ env.version }} 76 | name: ${{ env.release_name }} 77 | body: | 78 | This is ruri binary release. 79 | Build time: ${{ env.build_time }} 80 | prerelease: false 81 | files: | 82 | ${{ github.workspace }}/x86_64.tar 83 | ${{ github.workspace }}/i386.tar 84 | ${{ github.workspace }}/s390x.tar 85 | ${{ github.workspace }}/ppc64le.tar 86 | ${{ github.workspace }}/loongarch64.tar 87 | ${{ github.workspace }}/armv7.tar 88 | ${{ github.workspace }}/armhf.tar 89 | ${{ github.workspace }}/aarch64.tar 90 | ${{ github.workspace }}/riscv64.tar 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binary 2 | rurima 3 | #ruri 4 | # Editor configuration. 5 | .vscode/ 6 | # Gdb log 7 | peda-* 8 | # Build dir. 9 | out 10 | # Tmp file. 11 | TODO 12 | *.save 13 | a.out 14 | t.c 15 | config.mk 16 | *.pyc 17 | test* 18 | test-lxc-index 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/ruri"] 2 | path = src/ruri 3 | url = https://github.com/Moe-hacker/ruri 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Moe-hacker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Moe Hacker -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # 4 | # This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | # 6 | # MIT License 7 | # 8 | # Copyright (c) 2024 Moe-hacker 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | # SOFTWARE. 27 | # 28 | # 29 | # Premature optimization is the root of evil. 30 | # 31 | CCCOLOR = \033[1;38;2;254;228;208m 32 | LDCOLOR = \033[1;38;2;254;228;208m 33 | STRIPCOLOR = \033[1;38;2;254;228;208m 34 | BINCOLOR = \033[34;1m 35 | ENDCOLOR = \033[0m 36 | CC_LOG = @printf ' $(CCCOLOR)CC$(ENDCOLOR) $(BINCOLOR)%b$(ENDCOLOR)\n' 37 | LD_LOG = @printf ' $(LDCOLOR)LD$(ENDCOLOR) $(BINCOLOR)%b$(ENDCOLOR)\n' 38 | STRIP_LOG = @printf ' $(STRIPCOLOR)STRIP$(ENDCOLOR) $(BINCOLOR)%b$(ENDCOLOR)\n' 39 | CLEAN_LOG = @printf ' $(CCCOLOR)CLEAN$(ENDCOLOR) $(BINCOLOR)%b$(ENDCOLOR)\n' 40 | # Strip. 41 | STRIP = strip 42 | # Formater. 43 | FORMATER = clang-format -i 44 | SRC = src/*.c 45 | HEADER = src/include/*.h 46 | # Checker. 47 | CHECKER = clang-tidy 48 | CHECKER_FLAGS = --checks=*,-clang-analyzer-security.insecureAPI.strcpy,-altera-unroll-loops,-cert-err33-c,-concurrency-mt-unsafe,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-readability-function-cognitive-complexity,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-bugprone-easily-swappable-parameters,-cert-err34-c,-misc-include-cleaner,-readability-identifier-length,-bugprone-signal-handler,-cert-msc54-cpp,-cert-sig30-c,-altera-id-dependent-backward-branch,-bugprone-suspicious-realloc-usage,-hicpp-signed-bitwise,-clang-analyzer-security.insecureAPI.UncheckedReturn 49 | -include config.mk 50 | # Target. 51 | objects = checkdep.o dockerhub.o config.o exec.o info.o lxcmirror.o shared.o signal.o archive.o subcommand.o main.o ruri/src/easteregg/action.o ruri/src/easteregg/nekofeng.o ruri/src/easteregg/layer.o ruri/src/easteregg/typewriter.o ruri/src/caplist.o ruri/src/chroot.o ruri/src/cprintf.o ruri/src/info.o ruri/src/rurienv.o ruri/src/rurifetch.o ruri/src/seccomp.o ruri/src/signal.o ruri/src/umount.o ruri/src/unshare.o ruri/src/rootless.o ruri/src/mount.o ruri/src/k2v.o ruri/src/elf-magic.o ruri/src/config.o ruri/src/cgroup.o ruri/src/passwd.o ruri/src/ps.o ruri/src/ruri.o 52 | O = out 53 | BIN_TARGET = rurima 54 | .NOTPARALLEL: 55 | .ONESHELL: 56 | all :build_dir $(objects) 57 | @cd $(O) 58 | @$(CC) $(CFLAGS) -o $(BIN_TARGET) $(objects) $(LD_FLAGS) 59 | $(LD_LOG) $(BIN_TARGET) 60 | @$(STRIP) $(BIN_TARGET) 61 | $(STRIP_LOG) $(BIN_TARGET) 62 | @cp -f $(BIN_TARGET) ../ 63 | @cd ..&&rm -rf $(O) 64 | dev :build_dir $(objects) 65 | @cd $(O) 66 | @$(CC) $(CFLAGS) -o $(BIN_TARGET) $(objects) $(LD_FLAGS) 67 | $(LD_LOG) $(BIN_TARGET) 68 | @cp -f $(BIN_TARGET) ../ 69 | @cd ..&&rm -rf $(O) 70 | static :all 71 | static-bionic :all 72 | build_dir: 73 | @mkdir -p $(O) 74 | @mkdir -p $(O)/easteregg 75 | @mkdir -p $(O)/ruri/src 76 | @mkdir -p $(O)/ruri/src/easteregg 77 | $(objects) :%.o:src/%.c $(build_dir) 78 | @cd $(O) 79 | @$(CC) $(CFLAGS) -c ../$< -o $@ 80 | $(CC_LOG) $@ 81 | check : 82 | @printf "\033[1;38;2;254;228;208mCheck list:\n" 83 | @sleep 1.5s 84 | @$(CHECKER) $(CHECKER_FLAGS) --list-checks $(SRC) -- $(DEV_CFLAGS) 85 | @printf ' \033[1;38;2;254;228;208mCHECK\033[0m \033[34;1m%b\033[0m\n' $(SRC) 86 | @$(CHECKER) $(CHECKER_FLAGS) $(SRC) -- $(COMMIT_ID) 87 | @printf ' \033[1;38;2;254;228;208mDONE.\n' 88 | format : 89 | $(FORMATER) $(SRC) $(HEADER) 90 | clean : 91 | $(CLEAN_LOG) $(BIN_TARGET) 92 | @rm -f $(BIN_TARGET)||true 93 | $(CLEAN_LOG) $(O) 94 | @rm -rf $(O)||true 95 | $(CLEAN_LOG) peda* 96 | @rm -f peda* 97 | upk2v : 98 | cp ../libk2v/src/k2v.c src/k2v.c 99 | cp ../libk2v/src/include/k2v.h src/include/k2v.h 100 | upcprintf : 101 | cp ../cprintf/cprintf.c src/cprintf.c 102 | cp ../cprintf/include/cprintf.h src/include/cprintf.h 103 | help : 104 | @printf "\033[1;38;2;254;228;208mUsage:\n" 105 | @echo " make all compile" 106 | @echo " make dev compile, do not strip" 107 | @echo " make clean clean" 108 | @echo "Only for developers:" 109 | @echo " make check run clang-tidy" 110 | @echo " make format format code" 111 | config : 112 | @./gen-config 113 | dbg_config : 114 | @./gen-config -d -D 115 | dev_config : 116 | @./gen-config -d 117 | static_config : 118 | @./gen-config -s 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14099730.svg)](https://doi.org/10.5281/zenodo.14099730) 2 | # WARNING: 3 | For production, I fully recommand you to use tools like [crun](https://github.com/containers/crun), [youki](https://github.com/youki-dev/youki), [containerd](https://containerd.io/), [docker](https://www.docker.com/), [podman](https://podman.io/), [LXC](https://linuxcontainers.org/), [bubblewrap](https://github.com/containers/bubblewrap), they are more secure and stable. This is a non-OCI tool and, you take your own risk using it when you really need. The whole project is experimental! 4 | # NOTE: 5 | rurima now use [jq](https://github.com/jqlang) for dockerhub support. 6 | # WARNING: 7 | ``` 8 | * Your warranty is void. 9 | * I am not responsible for anything that may happen to your device by using this program. 10 | * You do it at your own risk and take the responsibility upon yourself. 11 | * This project is open source, you can make your own fork/rewrite but not to blame the author. 12 | * Docker is a registered trademark of Docker, Inc. This program has no relationship with it. 13 | * This program has no Super Cow Powers. 14 | ``` 15 | # About: 16 | So, what is rurima? 17 | The enhanced version of ruri. 18 | ruri only focus on running container, but rurima can also provide the function of getting rootfs image and backup/restore. 19 | And it will be a more powerful container manager in the fulture. 20 | With the `docker` and `lxc` subcommand of rurima, you can search & get & unpack images from dockerhub or LXC mirror easily. 21 | # Not Only Ruri Container Manager: 22 | Rurima was planned to be the ruri container manager, but as rurima has a full integration of ruri now, you can just use it as a more powerful version of ruri, although the container manager function is still WIP. 23 | For more info about ruri, see [ruri](https://github.com/Moe-hacker/ruri) 24 | # Note & WIP: 25 | This project does not follow OCI and can only be a `PARTIAL` replacement of docker, this project is still under development. 26 | # Download: 27 | You can get rurima binary (staticly linked) for arm64, armv7, armhf, riscv64, i386, loong64, s390x, ppc64le and x86_64 from the release page. 28 | Or run the follwing command to get rurima to ./rurima and ./rurima-dbg(debug version): 29 | ```sh 30 | . <(curl -sL https://get.ruri.zip/rurima) 31 | ``` 32 | # The new pull subcommand: 33 | It's a wrap of docker/lxc pull subcommand. 34 | For example: 35 | ```sh 36 | rurima pull alpine:edge ./test 37 | ``` 38 | ```sh 39 | rurima pull whyour/qinglong ./test 40 | ``` 41 | ```sh 42 | rurima pull ubuntu ./test 43 | ``` 44 | It will search lxc image first, if not found, it will auto try to pull rootfs from dockerhub. 45 | # Example usage of docker subcommand: 46 | Get `alpine` image, use tag `edge`, save to `./test` 47 | ```sh 48 | rurima docker pull -i alpine -t edge -s ./test 49 | ``` 50 | Get `whyour/qinglong` docker image. 51 | ```sh 52 | rurima docker pull -i whyour/qinglong -s ./test 53 | ``` 54 | Get `ubuntu` docker image. 55 | ```sh 56 | rurima docker pull -i ubuntu -s ./test 57 | ``` 58 | Search for image `ubuntu`: 59 | ```sh 60 | rurima docker search -i ubuntu 61 | ``` 62 | Search tag for `ubuntu`: 63 | ```sh 64 | rurima docker tag -i ubuntu 65 | ``` 66 | Use docker mirror `dockerpull.org` 67 | ```sh 68 | rurima docker pull -m dockerpull.org -i ubuntu -s ./test 69 | ``` 70 | Try every mirrorlist: 71 | ```sh 72 | rurima docker pull -T -i ubuntu -s ./test 73 | ``` 74 | Try fallback mode: 75 | ```sh 76 | rurima docker pull -T -f -i ubuntu -s ./test 77 | ``` 78 | # Example usage of lxc subcommand: 79 | Pull `alpine` version `edge`. 80 | ```sh 81 | rurima lxc pull -o alpine -v edge -s ./test 82 | ``` 83 | Use `mirrors.bfsu.edu.cn/lxc-images` as mirror: 84 | ```sh 85 | rurima lxc pull -m mirrors.bfsu.edu.cn/lxc-images -o alpine -v edge -s ./test 86 | ``` 87 | # About suid or caps: 88 | Rurima does not allow to set any suid/sgid (with root) or capability on it, it will check it in main() and error() if detected these unsafe settings. 89 | So, please always use sudo instead. 90 | # Reporting bugs: 91 | Please use the debug version(rurima-dbg) in release to get debug logs, and please tell me the command you run to cause the unexpected behavior you think! 92 | # NOTICE: 93 | This program is not official tool of docker or dockerhub, you can report bugs here, but this program has no relation with docker. 94 | Docker is a registered trademark of Docker, Inc. 95 | # Dependent: 96 | rurima needs tar, xz, gzip, file, you can find these static binary for aarch64, armv7, x86_64, i386 or riscv64 in: 97 | [tar-static](https://github.com/Moe-sushi/tar-static) 98 | [xz-static](https://github.com/Moe-sushi/xz-static) 99 | [gzip-static](https://github.com/Moe-sushi/gzip-static) 100 | [file-static](https://github.com/Moe-sushi/file-static) 101 | rurima need `curl` for downloading images, you can find a third party build here (Thanks stunnel) [stunnel/static-curl](https://github.com/stunnel/static-curl). 102 | rurima need `sha256sum` for checking downloaded file from dockerhub, use `--fallback` option can disable this. 103 | rurima need `jq` for parsing json, you can find it in [jq](https://github.com/jqlang). 104 | rurima also need proot if you are unpacking rootfs without root privileges, you can find it by the following way: 105 | First, you know your cpu arch, then, for example for aarch64: 106 | ```sh 107 | curl -sL https://mirrors.tuna.tsinghua.edu.cn/alpine/edge/testing/aarch64/ | grep proot-static 108 | ``` 109 | You got: 110 | ``` 111 | proot-static-5.4.0-r1.apk118.8 KiB25 Oct 2024 19:10:30 +0000 112 | ``` 113 | So that you download: 114 | ``` 115 | https://mirrors.tuna.tsinghua.edu.cn/alpine/edge/testing/aarch64/proot-static-5.4.0-r1.apk 116 | ``` 117 | and finally, tar -xvf *.apk to unpack it. So you got proot.static, rename it to proot and put it in your $PATH. 118 | # TODO: 119 | Manage ruri containers and configs. 120 | Config support, a bit like Dockerfile. 121 | .... 122 | # Usage: 123 | ``` 124 | Usage: rurima [subcommand] [options] 125 | Subcommands: 126 | docker: DockerHub support. 127 | lxc: LXC mirror support. 128 | pull: Pull rootfs. 129 | unpack: Unpack rootfs. 130 | backup: Backup rootfs. 131 | ruri: Built-in ruri command. 132 | help: Show help message. 133 | Options: 134 | -h, --help: Show help message. 135 | ``` 136 | --------- 137 | 138 |

「僕らタイムフライヤー

139 |

時を駆け上がるクライマー

140 |

時のかくれんぼ

141 |

はぐれっこはもういやなんだ」

142 | -------------------------------------------------------------------------------- /build/build-all.sh: -------------------------------------------------------------------------------- 1 | bash setup.sh 2 | bash build-amd64.sh 3 | bash build-loong64.sh 4 | bash build-riscv64.sh 5 | bash build-arm64.sh 6 | bash build-armhf.sh 7 | bash build-armv7.sh 8 | bash build-x86.sh 9 | bash build-ppc64le.sh 10 | bash build-s390x.sh 11 | -------------------------------------------------------------------------------- /build/build-amd64.sh: -------------------------------------------------------------------------------- 1 | git clone https://github.com/moe-hacker/rootfstool 2 | rootfstool/rootfstool d -d alpine -v edge 3 | mkdir alpine 4 | sudo tar -xvf rootfs.tar.xz -C alpine 5 | sudo cp build.sh alpine/build.sh 6 | sudo chmod +x alpine/build.sh 7 | sudo ./ruri/ruri ./alpine /bin/sh /build.sh 8 | cp alpine/$(uname -m).tar ../ 9 | -------------------------------------------------------------------------------- /build/build-arm64.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/aarch64" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "aarch64.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir aarch64 6 | tar -xvf "$ROOTFS_URL" -C aarch64 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh aarch64/build.sh 9 | sudo chmod +x aarch64/build.sh 10 | sudo ./ruri/ruri -a aarch64 -q /usr/bin/qemu-aarch64-static ./aarch64 /bin/sh /build.sh 11 | cp aarch64/*.tar ../aarch64.tar 12 | -------------------------------------------------------------------------------- /build/build-armhf.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/armhf" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "armhf.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir armhf 6 | tar -xvf "$ROOTFS_URL" -C armhf 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh armhf/build.sh 9 | sudo chmod +x armhf/build.sh 10 | sudo ./ruri/ruri -a armhf -q /usr/bin/qemu-arm-static ./armhf /bin/sh /build.sh 11 | cp armhf/*.tar ../armhf.tar 12 | -------------------------------------------------------------------------------- /build/build-armv7.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/armv7" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "armv7.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir armv7 6 | tar -xvf "$ROOTFS_URL" -C armv7 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh armv7/build.sh 9 | sudo chmod +x armv7/build.sh 10 | sudo ./ruri/ruri -a armv7 -q /usr/bin/qemu-arm-static ./armv7 /bin/sh /build.sh 11 | cp armv7/*.tar ../armv7.tar 12 | -------------------------------------------------------------------------------- /build/build-loong64.sh: -------------------------------------------------------------------------------- 1 | sudo debootstrap --foreign --arch=loong64 --variant=buildd --include=debian-ports-archive-keyring --verbose --components=main --resolve-deps --extra-suites=unreleased unstable ./debian http://ftp.ports.debian.org/debian-ports 2 | sudo cp /usr/bin/qemu-loongarch64-static debian/usr/bin/ 3 | cat <>./debian/build.sh 4 | rm /etc/resolv.conf 5 | echo nameserver 1.1.1.1 > /etc/resolv.conf 6 | apt update 7 | apt install -y wget make clang git libseccomp-dev libcap-dev libc-dev binutils 8 | apt install -y upx 9 | git clone https://github.com/moe-hacker/rurima 10 | cd rurima 11 | git submodule update --init 12 | ./gen-config -s -D 13 | make 14 | mv rurima rurima-dbg 15 | ./gen-config -s 16 | make 17 | tar -cvf ../loong64.tar ./rurima ./LICENSE 18 | EOF 19 | sudo chmod +x ./debian/build.sh 20 | ./ruri/ruri -p -a loong64 -q /usr/bin/qemu-loongarch64-static ./debian /debootstrap/debootstrap --second-stage 21 | sudo ./ruri/ruri -p -a loong64 -q /usr/bin/qemu-loongarch64-static ./debian /bin/sh /build.sh 22 | cp ./debian/loong64.tar ../loongarch64.tar 23 | -------------------------------------------------------------------------------- /build/build-ppc64le.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/ppc64le" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "ppc64le.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir ppc64le 6 | tar -xvf "$ROOTFS_URL" -C ppc64le 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh ppc64le/build.sh 9 | sudo chmod +x ppc64le/build.sh 10 | sudo ./ruri/ruri -a ppc64le -q /usr/bin/qemu-ppc64le-static ./ppc64le /bin/sh /build.sh 11 | cp ppc64le/*.tar ../ppc64le.tar 12 | -------------------------------------------------------------------------------- /build/build-riscv64.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/riscv64" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "riscv64.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir riscv64 6 | tar -xvf "$ROOTFS_URL" -C riscv64 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh riscv64/build.sh 9 | sudo chmod +x riscv64/build.sh 10 | sudo ./ruri/ruri -a riscv64 -q /usr/bin/qemu-riscv64-static ./riscv64 /bin/sh /build.sh 11 | cp riscv64/*.tar ../riscv64.tar 12 | -------------------------------------------------------------------------------- /build/build-s390x.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/s390x" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "s390x.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir s390x 6 | tar -xvf "$ROOTFS_URL" -C s390x 7 | sudo apt install -y qemu-user-static 8 | sudo cp build.sh s390x/build.sh 9 | sudo chmod +x s390x/build.sh 10 | sudo ./ruri/ruri -a s390x -q /usr/bin/qemu-s390x-static ./s390x /bin/sh /build.sh 11 | cp s390x/*.tar ../s390x.tar 12 | -------------------------------------------------------------------------------- /build/build-x86.sh: -------------------------------------------------------------------------------- 1 | BASE_URL="https://dl-cdn.alpinelinux.org/alpine/edge/releases/x86" 2 | ROOTFS_URL=$(curl -s -L "$BASE_URL/latest-releases.yaml" | grep "alpine-minirootfs" | grep "x86.tar.gz" | head -n 1 | awk '{print $2}') 3 | FULL_URL="$BASE_URL/$ROOTFS_URL" 4 | wget "$FULL_URL" 5 | mkdir x86 6 | tar -xvf "$ROOTFS_URL" -C x86 7 | sudo cp build.sh x86/build.sh 8 | sudo chmod +x x86/build.sh 9 | sudo ./ruri/ruri ./x86 /bin/sh /build.sh 10 | cp x86/*.tar ../i386.tar 11 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | rm /etc/resolv.conf 2 | echo nameserver 1.1.1.1 >/etc/resolv.conf 3 | echo https://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories 4 | apk add wget make clang git libseccomp-dev libseccomp-static libcap-static libcap-dev xz-dev libintl libbsd-static libsemanage-dev libselinux-utils libselinux-static xz-libs zlib zlib-static libselinux-dev linux-headers libssl3 libbsd libbsd-dev gettext-libs gettext-static gettext-dev gettext python3 build-base openssl-misc openssl-libs-static openssl zlib-dev xz-dev openssl-dev automake libtool bison flex gettext autoconf gettext sqlite sqlite-dev pcre-dev wget texinfo docbook-xsl libxslt docbook2x musl-dev gettext gettext-asprintf gettext-dbg gettext-dev gettext-doc gettext-envsubst gettext-lang gettext-libs gettext-static 5 | apk add upx 6 | mkdir output 7 | 8 | git clone https://github.com/moe-hacker/rurima 9 | cd rurima 10 | git submodule update --init 11 | ./gen-config -s 12 | make 13 | strip rurima 14 | upx rurima 15 | cp rurima ../output/rurima 16 | cp LICENSE ../output/LICENSE 17 | ./gen-config -s -D 18 | make 19 | upx rurima 20 | cp rurima ../output/rurima-dbg 21 | cd .. 22 | cd output 23 | tar -cvf ../$(uname -m).tar . 24 | exit 0 25 | -------------------------------------------------------------------------------- /build/setup.sh: -------------------------------------------------------------------------------- 1 | apt update 2 | apt install -y sudo 3 | sudo apt install -y git wget 4 | yes | sudo apt install --no-install-recommends -y curl xz-utils \ 5 | make \ 6 | clang \ 7 | libseccomp-dev \ 8 | libcap-dev \ 9 | libc6-dev \ 10 | binutils 11 | # Build ruri 12 | git clone https://github.com/moe-hacker/ruri 13 | cd ruri 14 | cc -Wl,--gc-sections -static src/*.c src/easteregg/*.c -o ruri -lcap -lseccomp -lpthread 15 | cd .. 16 | sudo apt install qemu-user-static pkg-config libglib2.0-dev qemu-system-misc python3-tomli binfmt-support debootstrap ninja-build 17 | # Build qemu-loongarch64 18 | git clone https://github.com/qemu/qemu.git 19 | cd qemu 20 | ./configure --static --disable-system --target-list=loongarch64-linux-user 21 | make -j$(nproc) 22 | sudo cp build/loongarch64-linux-user/qemu-loongarch64 /usr/bin/qemu-loongarch64-static 23 | sudo cp build/qemu-loongarch64 /usr/bin/qemu-loongarch64-static 24 | sudo cp qemu-loongarch64 /usr/bin/qemu-loongarch64-static 25 | cd .. 26 | -------------------------------------------------------------------------------- /gen-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # This file is part of ruri, with ABSOLUTELY NO WARRANTY. 6 | # 7 | # MIT License 8 | # 9 | # Copyright (c) 2022-2024 Moe-hacker 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in all 19 | # copies or substantial portions of the Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | # SOFTWARE. 28 | # 29 | error() { 30 | printf "$@\n" 31 | exit 1 32 | } 33 | init() { 34 | printf "checking for make... " 35 | if ! command -v make; then 36 | error "not found" 37 | fi 38 | printf "checking for strip... " 39 | if ! command -v strip; then 40 | error "not found" 41 | fi 42 | export STRIP=$(realpath $(command -v strip)) 43 | printf "checking for compiler... " 44 | if [ ! $CC ]; then 45 | if [ ! $(command -v cc) ]; then 46 | error "not found" 47 | fi 48 | CC=$(realpath $(command -v cc)) 49 | export CC=${CC##*/} 50 | fi 51 | printf "$CC\n" 52 | printf "checking whether the compiler supports GNU C11... " 53 | (echo "int main(){}" | $CC -x c -o /dev/null -std=gnu11 -) >/dev/null 2>&1 && printf "ok\n" || error "no" 54 | if [ $STATIC_COMPILE ]; then 55 | printf "checking whether the compiler supports -static compile... " 56 | echo "int main(){}" | $CC -static -x c -o /dev/null -std=gnu11 - >/dev/null 2>&1 && printf "ok\n" || error "no" 57 | fi 58 | } 59 | test_and_add_cflag() { 60 | printf "checking whether the compiler supports $1... " 61 | if echo "int main(void){}" | $CC $1 -Werror -x c -o /dev/null - >/dev/null 2>&1; then 62 | printf "ok\n" && export CFLAGS="$CFLAGS $1" 63 | else 64 | printf "no\n" 65 | fi 66 | } 67 | test_and_add_ldflag() { 68 | printf "checking for $1... " 69 | if [ $STATIC_COMPILE ]; then 70 | if echo "int main(){}" | $CC -static -x c -o /dev/null - $1 >/dev/null 2>&1; then 71 | printf "ok\n" && export LD_FLAGS="$LD_FLAGS $1" 72 | else 73 | printf "no\n" 74 | fi 75 | else 76 | if echo "int main(){}" | $CC -x c -o /dev/null - $1 >/dev/null 2>&1; then 77 | printf "ok\n" && export LD_FLAGS="$LD_FLAGS $1" 78 | else 79 | printf "no\n" 80 | fi 81 | fi 82 | } 83 | check_header() { 84 | printf "checking for header $i... " 85 | printf "#include <$1>\nint main(){}" | $CC -x c -o /dev/null - >/dev/null 2>&1 && printf "ok\n" || error "not found" 86 | } 87 | default_cflag() { 88 | test_and_add_cflag "-ftrivial-auto-var-init=pattern" 89 | test_and_add_cflag "-fcf-protection=full" 90 | test_and_add_cflag "-flto=auto" 91 | test_and_add_cflag "-fPIE" 92 | test_and_add_cflag "-pie" 93 | test_and_add_cflag "-Wl,-z,relro" 94 | test_and_add_cflag "-Wl,-z,noexecstack" 95 | test_and_add_cflag "-Wl,-z,now" 96 | test_and_add_cflag "-fstack-protector-all" 97 | test_and_add_cflag "-fstack-clash-protection" 98 | test_and_add_cflag "-mshstk" 99 | test_and_add_cflag "-Wno-unused-result" 100 | test_and_add_cflag "-O2" 101 | test_and_add_cflag "-Wl,--build-id=sha1" 102 | test_and_add_cflag "-ffunction-sections" 103 | test_and_add_cflag "-fdata-sections" 104 | test_and_add_cflag "-Wl,--gc-sections" 105 | test_and_add_cflag "-Wl,--strip-all" 106 | if [ $STATIC_COMPILE ]; then 107 | test_and_add_cflag "-static" 108 | test_and_add_cflag "-Wl,--disable-new-dtags" 109 | fi 110 | if [ $DEBUG_BUILD ]; then 111 | test_and_add_cflag "-DRURIMA_DEBUG" 112 | fi 113 | test_and_add_cflag "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" 114 | test_and_add_ldflag "-lcap" 115 | test_and_add_ldflag "-lpthread" 116 | test_and_add_ldflag "-lseccomp" 117 | } 118 | dev_cflag() { 119 | test_and_add_cflag "-g" 120 | test_and_add_cflag "-O0" 121 | test_and_add_cflag "-fno-omit-frame-pointer" 122 | test_and_add_cflag "-Wl,-z,norelro" 123 | test_and_add_cflag "-Wl,-z,execstack" 124 | test_and_add_cflag "-fno-stack-protector" 125 | test_and_add_cflag "-Wall" 126 | test_and_add_cflag "-Wextra" 127 | test_and_add_cflag "-pedantic" 128 | test_and_add_cflag "-Wconversion" 129 | test_and_add_cflag "-Wno-newline-eof" 130 | test_and_add_cflag "-Wno-gnu-zero-variadic-macro-arguments" 131 | test_and_add_cflag "-fsanitize=address" 132 | test_and_add_cflag "-Wl,--build-id=sha1" 133 | test_and_add_cflag "-ffunction-sections" 134 | test_and_add_cflag "-fdata-sections" 135 | test_and_add_cflag "-Wl,--gc-sections" 136 | if [ $DEBUG_BUILD ]; then 137 | test_and_add_cflag "-DRURI_DEBUG" 138 | test_and_add_cflag "-DRURIMA_DEBUG" 139 | fi 140 | test_and_add_cflag "-DRURI_DEV" 141 | test_and_add_cflag "-DRURIMA_DEV" 142 | if [ $STATIC_COMPILE ]; then 143 | test_and_add_cflag "-static" 144 | fi 145 | test_and_add_ldflag "-lcap" 146 | test_and_add_ldflag "-lpthread" 147 | test_and_add_ldflag "-lseccomp" 148 | export STRIP=true 149 | } 150 | check_headers_and_libs() { 151 | # Check system headers. 152 | for i in fcntl.h sys/ioctl.h sys/mount.h sys/socket.h unistd.h; do 153 | check_header $i 154 | done 155 | # Check library headers. 156 | for i in sys/capability.h; do 157 | check_header $i 158 | done 159 | } 160 | show_help() { 161 | echo "Usage: ./gen-config [OPTION]..." 162 | echo " -h, --help show help" 163 | echo " -s, --static compile static binary" 164 | echo " -d, --dev compile dev version" 165 | echo " -D, --debug compile with debug log" 166 | } 167 | while [ $1 ]; do 168 | case $1 in 169 | "-h" | "--help") show_help && exit ;; 170 | "-s" | "--static") export STATIC_COMPILE=true ;; 171 | "-d" | "--dev") export DEV_BUILD=true ;; 172 | "-D" | "--debug") export DEBUG_BUILD=true ;; 173 | esac 174 | shift 175 | done 176 | init 177 | check_headers_and_libs 178 | if [ ! $DEV_BUILD ]; then 179 | default_cflag 180 | else 181 | dev_cflag 182 | fi 183 | printf "create config.mk... " 184 | SRC_ROOT=$(pwd) 185 | if command -v git >/dev/null; then 186 | CFLAGS="$CFLAGS -DRURIMA_COMMIT_ID=\\\"$(git rev-parse --short HEAD)\\\"" 187 | CFLAGS="$CFLAGS -DRURI_COMMIT_ID=\\\"$(cd src/ruri&&git rev-parse --short HEAD)\\\"" 188 | cd ${SRC_ROOT} 189 | fi 190 | echo "CFLAGS = $CFLAGS" >config.mk 191 | echo "LD_FLAGS = $LD_FLAGS" >>config.mk 192 | echo "CC = $CC" >>config.mk 193 | echo "STRIP = $STRIP" >>config.mk 194 | printf "ok\n" 195 | -------------------------------------------------------------------------------- /src/archive.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | static bool proot_support_link2symlink(void) 32 | { 33 | /* 34 | * Test if proot support link2symlink. 35 | * We use proot to execute ls, so that we can check if proot is really available. 36 | */ 37 | const char *cmd[] = { "proot", "--link2symlink", "ls", NULL }; 38 | char *ret = rurima_fork_execvp_get_stdout(cmd); 39 | if (ret == NULL) { 40 | rurima_log("{red}proot not support --link2symlink.\n"); 41 | return false; 42 | } 43 | free(ret); 44 | rurima_log("{green}proot support --link2symlink.\n"); 45 | return true; 46 | } 47 | static char **get_extract_command(const char *_Nonnull file, const char *_Nonnull dir) 48 | { 49 | /* 50 | * Warning: free() after use. 51 | * Only need to free **ret. 52 | * ret[] does not need free(). 53 | * Get the command to extract the archive. 54 | * Only support tar, gzip, xz. 55 | * If the file is not supported, return NULL. 56 | * 57 | * If we are not running with root, and proot exist, 58 | * we will use proot to extract the archive. 59 | */ 60 | char **ret = malloc(sizeof(char *) * 14); 61 | const char *file_command[] = { "file", "--brief", "--mime-type", file, NULL }; 62 | char *type = rurima_fork_execvp_get_stdout(file_command); 63 | if (type == NULL) { 64 | rurima_error("{red}Failed to get file type!\n"); 65 | free(ret); 66 | return NULL; 67 | } 68 | type[strlen(type) - 1] = '\0'; 69 | if (!rurima_run_with_root() && proot_exist()) { 70 | if (proot_support_link2symlink()) { 71 | if (strcmp(type, "application/gzip") == 0) { 72 | ret[0] = "proot"; 73 | ret[1] = "-0"; 74 | ret[2] = "--link2symlink"; 75 | ret[3] = "tar"; 76 | ret[4] = "-xpzf"; 77 | ret[5] = "-"; 78 | ret[6] = "-C"; 79 | ret[7] = (char *)dir; 80 | ret[8] = NULL; 81 | } else if (strcmp(type, "application/x-xz") == 0) { 82 | ret[0] = "proot"; 83 | ret[1] = "-0"; 84 | ret[2] = "--link2symlink"; 85 | ret[3] = "tar"; 86 | ret[4] = "-xpJf"; 87 | ret[5] = "-"; 88 | ret[6] = "-C"; 89 | ret[7] = (char *)dir; 90 | ret[8] = NULL; 91 | } else if (strcmp(type, "application/x-tar") == 0) { 92 | ret[0] = "proot"; 93 | ret[1] = "-0"; 94 | ret[2] = "--link2symlink"; 95 | ret[3] = "tar"; 96 | ret[4] = "-xpf"; 97 | ret[5] = "-"; 98 | ret[6] = "-C"; 99 | ret[7] = (char *)dir; 100 | ret[8] = NULL; 101 | } else { 102 | free(type); 103 | free(ret); 104 | return NULL; 105 | } 106 | } else { 107 | if (strcmp(type, "application/gzip") == 0) { 108 | ret[0] = "proot"; 109 | ret[1] = "-0"; 110 | ret[2] = "tar"; 111 | ret[3] = "-xpzf"; 112 | ret[4] = "-"; 113 | ret[5] = "-C"; 114 | ret[6] = (char *)dir; 115 | ret[7] = NULL; 116 | } else if (strcmp(type, "application/x-xz") == 0) { 117 | ret[0] = "proot"; 118 | ret[1] = "-0"; 119 | ret[2] = "tar"; 120 | ret[3] = "-xpJf"; 121 | ret[4] = "-"; 122 | ret[5] = "-C"; 123 | ret[6] = (char *)dir; 124 | ret[7] = NULL; 125 | } else if (strcmp(type, "application/x-tar") == 0) { 126 | ret[0] = "proot"; 127 | ret[1] = "-0"; 128 | ret[2] = "tar"; 129 | ret[3] = "-xpf"; 130 | ret[4] = "-"; 131 | ret[5] = "-C"; 132 | ret[6] = (char *)dir; 133 | ret[7] = NULL; 134 | } else { 135 | free(type); 136 | free(ret); 137 | return NULL; 138 | } 139 | } 140 | } else { 141 | if (strcmp(type, "application/gzip") == 0) { 142 | ret[0] = "tar"; 143 | ret[1] = "-xpzf"; 144 | ret[2] = "-"; 145 | ret[3] = "-C"; 146 | ret[4] = (char *)dir; 147 | ret[5] = NULL; 148 | } else if (strcmp(type, "application/x-xz") == 0) { 149 | ret[0] = "tar"; 150 | ret[1] = "-xpJf"; 151 | ret[2] = "-"; 152 | ret[3] = "-C"; 153 | ret[4] = (char *)dir; 154 | ret[5] = NULL; 155 | } else if (strcmp(type, "application/x-tar") == 0) { 156 | ret[0] = "tar"; 157 | ret[1] = "-xpf"; 158 | ret[2] = "-"; 159 | ret[3] = "-C"; 160 | ret[4] = (char *)dir; 161 | ret[5] = NULL; 162 | } else { 163 | free(type); 164 | free(ret); 165 | return NULL; 166 | } 167 | } 168 | free(type); 169 | return ret; 170 | } 171 | static void show_progress(double per) 172 | { 173 | /* 174 | * Show progress bar. 175 | */ 176 | if (rurima_global_config.no_process) { 177 | return; 178 | } 179 | struct winsize size; 180 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); 181 | unsigned short width = size.ws_col - 10; 182 | unsigned short pos = (unsigned short)(width * per); 183 | printf("\033[?25l"); 184 | printf("\r[\033[32m"); 185 | for (unsigned short i = 0; i < pos; i++) { 186 | printf("\033[1;38;2;254;228;208m/"); 187 | } 188 | for (unsigned short i = pos; i < width; i++) { 189 | printf("\033[0m "); 190 | } 191 | printf("\033[0m] %.2f%%", per * 100); 192 | fflush(stdout); 193 | printf("\033[?25h"); 194 | } 195 | int rurima_extract_archive(const char *_Nonnull file, const char *_Nonnull dir) 196 | { 197 | /* 198 | * Extract the archive. 199 | * The return value is not important, 200 | * because we will rurima_error() directly if failed. 201 | * 202 | * We read the file, output it to stdout, 203 | * and then pipe it to the extract command. 204 | * So that we can show a progress bar by the size we output. 205 | * 206 | */ 207 | rurima_log("{base}Extracting {cyan}%s{clear} to {cyan}%s\n", file, dir); 208 | rurima_check_dir_deny_list(dir); 209 | off_t size = rurima_get_file_size(file); 210 | if (size == 0) { 211 | rurima_error("{red}File size is 0!\n"); 212 | } 213 | char **command = get_extract_command(file, dir); 214 | if (command == NULL) { 215 | rurima_error("{red}Unsupported file type!\n"); 216 | } 217 | if (rurima_mkdirs(dir, 0755) == -1) { 218 | free(command); 219 | rurima_error("{red}Failed to create directory!\n"); 220 | } 221 | cprintf("{base}Extracting {cyan}%s\n", file); 222 | FILE *fp = fopen(file, "rb"); 223 | if (fp == NULL) { 224 | perror("fopen"); 225 | free(command); 226 | rurima_error("{red}Failed to open file!\n"); 227 | } 228 | int pipefd[2]; 229 | if (pipe(pipefd) == -1) { 230 | perror("pipe"); 231 | free(command); 232 | rurima_error("{red}Failed to create pipe!\n"); 233 | } 234 | pid_t pid = fork(); 235 | if (pid == 0) { 236 | close(pipefd[1]); 237 | dup2(pipefd[0], STDIN_FILENO); 238 | close(pipefd[0]); 239 | int fd = open("/dev/null", O_WRONLY); 240 | dup2(fd, STDOUT_FILENO); 241 | dup2(fd, STDERR_FILENO); 242 | execvp(command[0], command); 243 | free(command); 244 | rurima_error("{red}Failed to exec command!\n"); 245 | } else { 246 | close(pipefd[0]); 247 | // When buf is only 1024, it's very slow. 248 | // But when buf is 114514, it's fast enough. 249 | // So, this is the power of homo!!!!!! 250 | char *buf = malloc(114514); 251 | size_t bytes_read; 252 | size_t total_read = 0; 253 | while ((bytes_read = fread(buf, 1, 114514, fp)) > 0) { 254 | total_read += bytes_read; 255 | double progress = (double)total_read / (double)size; 256 | show_progress(progress); 257 | if (write(pipefd[1], buf, bytes_read) == -1) { 258 | perror("write"); 259 | free(command); 260 | rurima_error("{red}Failed to write to stdout!"); 261 | } 262 | } 263 | close(pipefd[1]); 264 | fclose(fp); 265 | wait(NULL); 266 | show_progress(1.0); 267 | printf("\n"); 268 | free(command); 269 | free(buf); 270 | return 0; 271 | } 272 | free(command); 273 | return 0; 274 | } 275 | static int tar_backup__(const char *_Nonnull file, const char *_Nonnull dir) 276 | { 277 | /* 278 | * Backup dir as file(.tar format). 279 | */ 280 | rurima_mkdirs(file, 0755); 281 | rmdir(file); 282 | close(open(file, O_CLOEXEC | O_CREAT, 0755)); 283 | char *file_realpath = realpath(file, NULL); 284 | char *dir_realpath = realpath(dir, NULL); 285 | int nullfd = open("/dev/null", O_RDWR); 286 | dup2(nullfd, STDOUT_FILENO); 287 | dup2(nullfd, STDERR_FILENO); 288 | int ret = 0; 289 | if (strstr(file_realpath, dir_realpath) != NULL) { 290 | chdir(dir); 291 | char exclude[PATH_MAX + 12] = { '\0' }; 292 | sprintf(exclude, "--exclude=%s", file_realpath); 293 | char exclude2[PATH_MAX + 12] = { '\0' }; 294 | sprintf(exclude2, "--exclude=%s", file); 295 | const char *command[] = { "tar", exclude, exclude2, "-cpf", file_realpath, ".", NULL }; 296 | ret = rurima_fork_execvp(command); 297 | } else { 298 | chdir(dir); 299 | const char *command[] = { "tar", "-cpf", file_realpath, ".", NULL }; 300 | ret = rurima_fork_execvp(command); 301 | } 302 | close(nullfd); 303 | free(file_realpath); 304 | free(dir_realpath); 305 | return ret; 306 | } 307 | static bool du_found(void) 308 | { 309 | const char *command[] = { "du", "--version", NULL }; 310 | char *ret = rurima_fork_execvp_get_stdout(command); 311 | if (ret == NULL) { 312 | rurima_log("{red}du not found.\n"); 313 | return false; 314 | } 315 | free(ret); 316 | rurima_log("{green}du found.\n"); 317 | return true; 318 | } 319 | int rurima_backup_dir(const char *_Nonnull file, const char *_Nonnull dir) 320 | { 321 | /* 322 | * Backup container as *.tar file. 323 | * We compare the size of file and dir to show progress. 324 | */ 325 | struct stat st; 326 | if (stat(file, &st) == 0) { 327 | rurima_error("{red}File already exist!\n"); 328 | } 329 | DIR *test = opendir(dir); 330 | if (test == NULL) { 331 | rurima_error("{red}Failed to open directory!\n"); 332 | } 333 | closedir(test); 334 | // Run ruir -U to umount the container. 335 | cprintf("{base}Unmounting container\n"); 336 | char *rexec_args[] = { "ruri", "-w", "-U", ".", NULL }; 337 | rurima_fork_rexec(rexec_args); 338 | if (!du_found()) { 339 | rurima_warning("{yellow}du not found, progress will not be shown.\n"); 340 | int exstat = tar_backup__(file, dir); 341 | return exstat; 342 | } 343 | cprintf("{base}Getting total size to backup\n"); 344 | off_t totalsize = rurima_get_dir_file_size(dir); 345 | cprintf("{base}Backing up to {cyan}%s\n", file); 346 | pid_t pid = fork(); 347 | if (pid > 0) { 348 | int status; 349 | waitpid(pid, &status, WNOHANG); 350 | off_t totalsize_bk = totalsize; 351 | while (waitpid(pid, &status, WNOHANG) == 0) { 352 | off_t currentsize = rurima_get_file_size(file); 353 | totalsize = totalsize_bk; 354 | while (totalsize > FLT_MAX) { 355 | totalsize = totalsize / 1024; 356 | currentsize = currentsize / 1024; 357 | if (totalsize < FLT_MAX) { 358 | break; 359 | } 360 | } 361 | double progress = ((double)currentsize / (double)totalsize); 362 | if (progress > 1.0) { 363 | progress = 1.0; 364 | } 365 | show_progress(progress); 366 | usleep(100); 367 | } 368 | show_progress(1.0); 369 | return status; 370 | } else { 371 | int exstat = tar_backup__(file, dir); 372 | exit(exstat); 373 | } 374 | return 0; 375 | } 376 | static int download_file__(const char *_Nonnull url, const char *_Nonnull file, const char *_Nullable token) 377 | { 378 | int status = 0; 379 | if (token != NULL) { 380 | const char *command[] = { "curl", "-sL", "-o", file, "-H", token, url, NULL }; 381 | status = rurima_fork_execvp(command); 382 | } else { 383 | const char *command[] = { "curl", "-sL", "-o", file, url, NULL }; 384 | status = rurima_fork_execvp(command); 385 | } 386 | return status; 387 | } 388 | static ssize_t get_url_file_size__(const char *_Nonnull url) 389 | { 390 | /* 391 | * Get the file size from the URL. 392 | */ 393 | const char *command[] = { "curl", "-sIL", url, NULL }; 394 | char *ret = rurima_fork_execvp_get_stdout(command); 395 | if (ret == NULL) { 396 | rurima_error("{red}Failed to get file size!\n"); 397 | } 398 | char *p = rurima_strstr_ignore_case(ret, "Content-Type"); 399 | if (p == NULL) { 400 | free(ret); 401 | return -1; 402 | } 403 | while (rurima_strstr_ignore_case(p + 1, "Content-Type") != NULL) { 404 | p = rurima_strstr_ignore_case(p + 1, "Content-Type"); 405 | } 406 | char *size = rurima_strstr_ignore_case(p, "Content-Length: "); 407 | if (size == NULL) { 408 | free(ret); 409 | return -1; 410 | } 411 | size += 16; 412 | char *end = strstr(size, "\r\n"); 413 | if (end == NULL) { 414 | free(ret); 415 | return -1; 416 | } 417 | *end = '\0'; 418 | ssize_t filesize = strtoll(size, NULL, 10); 419 | free(ret); 420 | rurima_log("{base}Get file size: {green}%ld\n", filesize); 421 | return filesize; 422 | } 423 | int rurima_download_file(const char *_Nonnull url, const char *_Nonnull file, const char *_Nullable token, ssize_t size) 424 | { 425 | /* 426 | * Download file from the specified URL. 427 | */ 428 | remove(file); 429 | unlink(file); 430 | if (!du_found()) { 431 | rurima_warning("{yellow}du not found, progress will not be shown.\n"); 432 | int exstat = download_file__(url, file, token); 433 | return exstat; 434 | } 435 | if (size <= 0) { 436 | size = get_url_file_size__(url); 437 | } 438 | if (size <= 0) { 439 | rurima_warning("{yellow}Failed to get file size, progress will not work\n"); 440 | size = 1; 441 | } 442 | pid_t pid = fork(); 443 | if (pid > 0) { 444 | int status; 445 | waitpid(pid, &status, WNOHANG); 446 | off_t size_bk = size; 447 | while (waitpid(pid, &status, WNOHANG) == 0) { 448 | off_t currentsize = rurima_get_file_size(file); 449 | size = size_bk; 450 | while (size > FLT_MAX) { 451 | size = size / 1024; 452 | currentsize = currentsize / 1024; 453 | if (size < FLT_MAX) { 454 | break; 455 | } 456 | } 457 | double progress = ((double)currentsize / (double)size); 458 | if (progress > 1.0) { 459 | progress = 1.0; 460 | } 461 | show_progress(progress); 462 | usleep(100); 463 | } 464 | show_progress(1.0); 465 | rurima_log("{green}Download complete.\n"); 466 | return status; 467 | } else { 468 | int exstat = download_file__(url, file, token); 469 | rurima_log("{green}Download complete.\n"); 470 | exit(exstat); 471 | } 472 | return 0; 473 | } -------------------------------------------------------------------------------- /src/checkdep.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | void rurima_check_dep(void) 32 | { 33 | /* 34 | * Check dependencies. 35 | * We need tar, curl, file, gzip, xz and file with --brief --mime-type support. 36 | * If not found, we will rurima_error() directly. 37 | * We also need proot, but it's only for unpacking rootfs without root privilege. 38 | * So we will not check it here. 39 | * 40 | */ 41 | const char *tar_command[] = { "tar", "--version", NULL }; 42 | const char *curl_command[] = { "curl", "--version", NULL }; 43 | const char *file_command[] = { "file", "--version", NULL }; 44 | const char *gz_command[] = { "gzip", "--version", NULL }; 45 | const char *xz_command[] = { "xz", "-V", NULL }; 46 | const char *file_command_2[] = { "file", "--brief", "--mime-type", "/proc/self/exe", NULL }; 47 | char *result = NULL; 48 | result = rurima_fork_execvp_get_stdout(tar_command); 49 | if (result == NULL) { 50 | rurima_error("{red}tar not found!\nIf you are aarch64, armv7, x86_64, i386 or riscv64 user\nYou can find it in\nhttps://github.com/Moe-sushi/tar-static\n"); 51 | } 52 | free(result); 53 | result = rurima_fork_execvp_get_stdout(curl_command); 54 | if (result == NULL) { 55 | rurima_error("{red}curl not found!\n"); 56 | } 57 | free(result); 58 | result = rurima_fork_execvp_get_stdout(file_command); 59 | if (result == NULL) { 60 | rurima_error("{red}file not found!\nIf you are aarch64, armv7, x86_64, i386 or riscv64 user\nYou can find it in\nhttps://github.com/Moe-sushi/file-static\n"); 61 | } 62 | free(result); 63 | result = rurima_fork_execvp_get_stdout(gz_command); 64 | if (result == NULL) { 65 | rurima_error("{red}gzip not found!\nIf you are aarch64, armv7, x86_64, i386 or riscv64 user\nYou can find it in\nhttps://github.com/Moe-sushi/gzip-static\n"); 66 | } 67 | free(result); 68 | result = rurima_fork_execvp_get_stdout(xz_command); 69 | if (result == NULL) { 70 | rurima_error("{red}xz not found!\nIf you are aarch64, armv7, x86_64, i386 or riscv64 user\nYou can find it in\nhttps://github.com/Moe-sushi/xz-static\n"); 71 | } 72 | free(result); 73 | result = rurima_fork_execvp_get_stdout(file_command_2); 74 | if (result == NULL) { 75 | rurima_error("{red}file does not support --brief --mime-type!\nIf you are aarch64, armv7, x86_64, i386 or riscv64 user\nYou can find a support version in\nhttps://github.com/Moe-sushi/file-static\n"); 76 | } 77 | free(result); 78 | } 79 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | struct RURIMA *rurima_init_config(void) 32 | { 33 | /* 34 | * Init an empty RURIMA struct. 35 | */ 36 | struct RURIMA *ret = malloc(sizeof(struct RURIMA)); 37 | ret->rootfs_source = NULL; 38 | ret->rootfs_image = NULL; 39 | ret->rootfs_tag = NULL; 40 | ret->rootfs_arch = NULL; 41 | ret->rootfs_mirror = NULL; 42 | ret->rootfs_os = NULL; 43 | ret->rootfs_version = NULL; 44 | ret->rootfs_type = NULL; 45 | ret->rootfs_path = NULL; 46 | ret->host_dir = NULL; 47 | ret->hook_script = NULL; 48 | ret->hook_command[0] = NULL; 49 | ruri_init_config(&ret->container); 50 | return ret; 51 | } 52 | char *dump_ruri_config(struct RURIMA *_Nonnull config) 53 | { 54 | /* 55 | * Dump ruri config from RURIMA struct. 56 | * 57 | * Warning: free() the returned value after use. 58 | */ 59 | char *ret = ruri_container_info_to_k2v(&config->container); 60 | return ret; 61 | } 62 | void rurima_read_global_config(void) 63 | { 64 | } -------------------------------------------------------------------------------- /src/dockerhub.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | void rurima_free_docker_config(struct RURIMA_DOCKER *_Nonnull config) 32 | { 33 | /* 34 | * free() the docker config. 35 | */ 36 | free(config->workdir); 37 | for (int i = 0; config->env[i] != NULL; i++) { 38 | free(config->env[i]); 39 | } 40 | for (int i = 0; config->command[i] != NULL; i++) { 41 | free(config->command[i]); 42 | } 43 | for (int i = 0; config->entrypoint[i] != NULL; i++) { 44 | free(config->entrypoint[i]); 45 | } 46 | free(config->architecture); 47 | free(config); 48 | } 49 | static void print_export_env(struct RURIMA_DOCKER *_Nonnull config) 50 | { 51 | /* 52 | * Print export env command. 53 | */ 54 | for (int i = 0; config->env[i] != NULL && config->env[i + 1] != NULL; i += 2) { 55 | printf("export %s=\"%s\"\n", config->env[i], config->env[i + 1]); 56 | } 57 | } 58 | static void print_chroot_command(struct RURIMA_DOCKER *_Nonnull config, char *_Nullable savedir) 59 | { 60 | /* 61 | * Print command to use chroot as runtime. 62 | */ 63 | print_export_env(config); 64 | printf("mount --bind %s %s\n", savedir == NULL ? "/path/to/container" : savedir, savedir == NULL ? "/path/to/container" : savedir); 65 | printf("mount --bind /dev "); 66 | printf("%s/dev\n", savedir == NULL ? "/path/to/container" : savedir); 67 | printf("mount --bind /proc "); 68 | printf("%s/proc\n", savedir == NULL ? "/path/to/container" : savedir); 69 | printf("mount --bind /sys "); 70 | printf("%s/sys\n", savedir == NULL ? "/path/to/container" : savedir); 71 | printf("chroot "); 72 | printf("%s ", savedir == NULL ? "/path/to/container" : savedir); 73 | if (config->command[0] != NULL) { 74 | for (int i = 0; config->command[i] != NULL; i++) { 75 | printf("%s ", config->command[i]); 76 | } 77 | } else if (config->entrypoint[0] != NULL) { 78 | if (config->workdir != NULL) { 79 | printf("%s/", config->workdir); 80 | } 81 | for (int i = 0; config->entrypoint[i] != NULL; i++) { 82 | printf("%s ", config->entrypoint[i]); 83 | } 84 | } 85 | printf("\n"); 86 | } 87 | static void print_proot_command(struct RURIMA_DOCKER *_Nonnull config, char *_Nullable savedir) 88 | { 89 | /* 90 | * Print command to use proot as runtime. 91 | */ 92 | print_export_env(config); 93 | printf("OTHER_ARGS=\"\"\n"); 94 | printf("QEMU_PATH=\"\"\n"); 95 | printf("proot $OTHER_ARGS -0 "); 96 | if (config->workdir != NULL) { 97 | printf("-w %s ", config->workdir); 98 | } 99 | if (strcmp(config->architecture, rurima_docker_get_host_arch()) != 0) { 100 | printf("-q $QEMU_PATH "); 101 | } 102 | printf("-r %s ", savedir == NULL ? "/path/to/container" : savedir); 103 | if (config->command[0] != NULL) { 104 | for (int i = 0; config->command[i] != NULL; i++) { 105 | printf("%s ", config->command[i]); 106 | } 107 | } else if (config->entrypoint[0] != NULL) { 108 | for (int i = 0; config->entrypoint[i] != NULL; i++) { 109 | printf("%s ", config->entrypoint[i]); 110 | } 111 | } 112 | printf("\n"); 113 | } 114 | static void print_ruri_command(struct RURIMA_DOCKER *_Nonnull config, char *_Nullable savedir) 115 | { 116 | /* 117 | * Print command to use ruri as runtime. 118 | */ 119 | printf("rurima r "); 120 | printf("-w "); 121 | if (config->workdir != NULL) { 122 | printf("-W %s ", config->workdir); 123 | } 124 | if (strcmp(config->architecture, rurima_docker_get_host_arch()) != 0) { 125 | printf("-a %s ", config->architecture); 126 | printf("-q /path/to/qemu-%s-static ", config->architecture); 127 | } 128 | for (int i = 0; config->env[i] != NULL && config->env[i + 1] != NULL; i += 2) { 129 | printf("-e \"%s\" ", config->env[i]); 130 | if (strcmp(config->env[i + 1], "") != 0) { 131 | printf("\"%s\" ", config->env[i + 1]); 132 | } else { 133 | printf("\" \" "); 134 | } 135 | } 136 | printf("%s ", savedir == NULL ? "/path/to/container" : savedir); 137 | if (config->command[0] != NULL) { 138 | for (int i = 0; config->command[i] != NULL; i++) { 139 | printf("%s ", config->command[i]); 140 | } 141 | } else if (config->entrypoint[0] != NULL) { 142 | for (int i = 0; config->entrypoint[i] != NULL; i++) { 143 | printf("%s ", config->entrypoint[i]); 144 | } 145 | } 146 | printf("\n"); 147 | } 148 | void rurima_show_docker_config(struct RURIMA_DOCKER *_Nonnull config, char *_Nullable savedir, char *_Nullable runtime, bool quiet) 149 | { 150 | /* 151 | * Show docker config. 152 | */ 153 | if (!quiet) { 154 | cprintf("{base}\nConfig:\n"); 155 | cprintf("{base} Workdir:\n {cyan}%s\n", config->workdir == NULL ? "NULL" : config->workdir); 156 | cprintf("{base} Env:\n"); 157 | if (config->env[0] == NULL) { 158 | cprintf("{cyan}NULL\n"); 159 | } else { 160 | for (int i = 0; config->env[i] != NULL; i += 2) { 161 | cprintf("{cyan} %s = ", config->env[i]); 162 | cprintf("{cyan}%s\n", config->env[i + 1]); 163 | } 164 | } 165 | cprintf("{base} Command:\n "); 166 | if (config->command[0] == NULL) { 167 | cprintf("{cyan}NULL\n"); 168 | } else { 169 | for (int i = 0; config->command[i] != NULL; i++) { 170 | cprintf("{cyan}%s ", config->command[i]); 171 | } 172 | cprintf("{clear}\n"); 173 | } 174 | cprintf("{base} Entrypoint:\n "); 175 | if (config->entrypoint[0] == NULL) { 176 | cprintf("{cyan}NULL\n"); 177 | } else { 178 | for (int i = 0; config->entrypoint[i] != NULL; i++) { 179 | cprintf("{cyan}%s ", config->entrypoint[i]); 180 | } 181 | cprintf("{clear}\n"); 182 | } 183 | } 184 | if (runtime == NULL) { 185 | runtime = "ruri"; 186 | } 187 | if (strcmp(runtime, "ruri") == 0) { 188 | if (!quiet) { 189 | cprintf("{base}Run with ruri:\n"); 190 | printf("\033[38;2;219;240;240m\n"); 191 | } 192 | print_ruri_command(config, savedir); 193 | } else if (strcmp(runtime, "proot") == 0) { 194 | if (!quiet) { 195 | cprintf("{base}Run with proot:\n"); 196 | printf("\033[38;2;219;240;240m\n"); 197 | } 198 | print_proot_command(config, savedir); 199 | if (!quiet) { 200 | rurima_warning("\n{yellow}Please replace [OTHER_ARGS] with your proot args!"); 201 | } 202 | } else if (strcmp(runtime, "chroot") == 0) { 203 | if (!quiet) { 204 | cprintf("{base}Run with chroot:\n"); 205 | printf("\033[38;2;219;240;240m\n"); 206 | } 207 | print_chroot_command(config, savedir); 208 | if (!quiet) { 209 | if (strcmp(config->architecture, rurima_docker_get_host_arch()) != 0) { 210 | rurima_warning("{yellow}For chroot, please configure binfimt_misc manually!\n"); 211 | } 212 | } 213 | } else { 214 | rurima_error("Unknown container runtime!"); 215 | } 216 | if (!quiet) { 217 | printf("\n\033[0m"); 218 | if (savedir == NULL) { 219 | rurima_warning("{yellow}Please replace /path/to/container with your container path!\n"); 220 | } 221 | if (strcmp(config->architecture, rurima_docker_get_host_arch()) != 0) { 222 | rurima_warning("{yellow}Please replace /path/to/qemu-%s-static with your qemu binary path!\n", config->architecture); 223 | } 224 | } 225 | } 226 | static char *get_auth_server_from_header(const char *_Nonnull header, bool fallback) 227 | { 228 | /* 229 | * Warning: free() the return value after use. 230 | * 231 | * Get auth server from header. 232 | * Example: 233 | * www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io" 234 | * 235 | */ 236 | // Just to show you how ugly if we don't force lowercase the header. 237 | const char *p = rurima_strstr_ignore_case(header, "wWw-aUthEntIcAtE: "); 238 | if (p == NULL) { 239 | if (fallback) { 240 | return NULL; 241 | } 242 | rurima_error("{red}No auth server found!\n"); 243 | } 244 | p = strstr(p, "realm="); 245 | if (p == NULL) { 246 | if (fallback) { 247 | return NULL; 248 | } 249 | rurima_error("{red}No auth server found!\n"); 250 | } 251 | p = strstr(p, "\""); 252 | if (p == NULL) { 253 | if (fallback) { 254 | return NULL; 255 | } 256 | rurima_error("{red}No auth server found!\n"); 257 | } 258 | p++; 259 | const char *q = strstr(p, "\""); 260 | if (q == NULL) { 261 | if (fallback) { 262 | return NULL; 263 | } 264 | rurima_error("{red}No auth server found!\n"); 265 | } 266 | char *ret = malloc((size_t)(q - p + 1)); 267 | strncpy(ret, p, (size_t)(q - p)); 268 | ret[q - p] = '\0'; 269 | return ret; 270 | } 271 | static char *get_service_from_header(const char *_Nonnull header, bool fallback) 272 | { 273 | /* 274 | * Warning: free() the return value after use. 275 | * 276 | * Get service from header. 277 | * Example: 278 | * www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io" 279 | */ 280 | const char *p = rurima_strstr_ignore_case(header, "wWw-aUthEntIcAtE: "); 281 | if (p == NULL) { 282 | if (fallback) { 283 | return NULL; 284 | } 285 | rurima_error("{red}No service found!\n"); 286 | } 287 | p = strstr(p, "service="); 288 | if (p == NULL) { 289 | if (fallback) { 290 | return NULL; 291 | } 292 | rurima_error("{red}No service found!\n"); 293 | } 294 | p = strstr(p, "\""); 295 | if (p == NULL) { 296 | if (fallback) { 297 | return NULL; 298 | } 299 | rurima_error("{red}No service found!\n"); 300 | } 301 | p++; 302 | const char *q = strstr(p, "\""); 303 | if (q == NULL) { 304 | if (fallback) { 305 | return NULL; 306 | } 307 | rurima_error("{red}No service found!\n"); 308 | } 309 | char *ret = malloc((size_t)(q - p + 1)); 310 | strncpy(ret, p, (size_t)(q - p)); 311 | ret[q - p] = '\0'; 312 | return ret; 313 | } 314 | static char *get_auth_server_url(const char *_Nullable mirror, bool fallback) 315 | { 316 | /* 317 | * Warning: free() the return value after use. 318 | * 319 | * Get auth server url. 320 | * Example: 321 | * https://auth.docker.io/token?service=registry.docker.io 322 | * 323 | */ 324 | if (mirror == NULL) { 325 | mirror = rurima_global_config.docker_mirror; 326 | } 327 | char url[4096] = { '\0' }; 328 | sprintf(url, "https://%s/v2/", mirror); 329 | const char *curl_command[] = { "curl", "--max-time", "5", "-s", "-L", "-I", url, NULL }; 330 | char *response = rurima_fork_execvp_get_stdout(curl_command); 331 | if (response == NULL) { 332 | if (fallback) { 333 | return NULL; 334 | } 335 | rurima_error("{red}Failed to get auth server!\n"); 336 | } 337 | char *server = get_auth_server_from_header(response, fallback); 338 | if (server == NULL) { 339 | if (fallback) { 340 | free(response); 341 | return NULL; 342 | } 343 | rurima_error("{red}Failed to get auth server!\n"); 344 | } 345 | char *service = get_service_from_header(response, fallback); 346 | if (service == NULL) { 347 | if (fallback) { 348 | free(response); 349 | free(server); 350 | return NULL; 351 | } 352 | rurima_error("{red}Failed to get service!\n"); 353 | } 354 | free(response); 355 | char *ret = malloc(strlen(server) + strlen(service) + 22); 356 | sprintf(ret, "%s?service=%s", server, service); 357 | free(server); 358 | free(service); 359 | rurima_log("{base}Auth server url: {cyan}%s{clear}\n", ret); 360 | return ret; 361 | } 362 | static char *get_token(const char *_Nonnull image, const char *_Nullable mirror, bool fallback) 363 | { 364 | /* 365 | * Warning: free() the return value after use. 366 | * 367 | * Get token from Docker mirror. 368 | * This token is used to pull other files of image. 369 | * 370 | */ 371 | char url[4096] = { '\0' }; 372 | if (mirror == NULL) { 373 | mirror = rurima_global_config.docker_mirror; 374 | } 375 | char *auth_server_url = get_auth_server_url(mirror, fallback); 376 | if (auth_server_url == NULL) { 377 | if (fallback) { 378 | rurima_log("{red}No auth server found, using homo magic token 1145141919810\n"); 379 | // We hope the server administrator is homo. 380 | return strdup("1145141919810"); 381 | } else { 382 | rurima_error("{red}Failed to get auth server!\n"); 383 | } 384 | } 385 | strcat(url, auth_server_url); 386 | free(auth_server_url); 387 | strcat(url, "&scope=repository%3A"); 388 | strcat(url, image); 389 | strcat(url, "%3Apull"); 390 | rurima_log("{base}url: {cyan}%s{clear}\n", url); 391 | const char *curl_command[] = { "curl", "-L", "-s", url, NULL }; 392 | char *token_json = rurima_fork_execvp_get_stdout(curl_command); 393 | if (token_json == NULL) { 394 | rurima_error("{red}Failed to get token!\n"); 395 | } 396 | char *jq_cmd_0[] = { "jq", "-r", "-j", ".token", NULL }; 397 | char *ret = rurima_call_jq(jq_cmd_0, token_json); 398 | if (ret == NULL) { 399 | if (fallback) { 400 | free(token_json); 401 | rurima_log("{red}Can not get token, using homo magic token 1145141919810\n"); 402 | // We hope the server administrator is homo. 403 | return strdup("1145141919810"); 404 | } else { 405 | rurima_error("{red}Failed to get token!"); 406 | } 407 | } 408 | free(token_json); 409 | rurima_log("{base}Token: {cyan}%s{clear}\n", ret); 410 | return ret; 411 | } 412 | static char *get_tag_manifests(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nonnull token, const char *_Nullable mirror) 413 | { 414 | /* 415 | * Warning: free() the return value after use. 416 | * 417 | * Get manifests of image. 418 | * This is used to get digest of image. 419 | * 420 | */ 421 | if (mirror == NULL) { 422 | mirror = rurima_global_config.docker_mirror; 423 | } 424 | char url[4096] = { '\0' }; 425 | sprintf(url, "https://%s/v2/%s/manifests/%s", mirror, image, tag); 426 | char *auth = malloc(strlen(token) + 114); 427 | auth[0] = '\0'; 428 | sprintf(auth, "Authorization: Bearer %s", token); 429 | const char *curl_command[] = { "curl", "-L", "-s", "-H", "Accept: application/vnd.docker.distribution.manifest.list.v1+json", "-H", auth, url, NULL }; 430 | char *ret = rurima_fork_execvp_get_stdout(curl_command); 431 | if (ret == NULL) { 432 | rurima_error("{red}Failed to get manifests!\n"); 433 | } 434 | rurima_log("{base}Manifests: \n{cyan}%s{clear}\n", ret); 435 | free(auth); 436 | return ret; 437 | } 438 | static char *get_tag_digest(const char *_Nonnull manifests, const char *_Nullable architecture) 439 | { 440 | /* 441 | * Warning: free() the return value after use. 442 | * 443 | * Get digest of image. 444 | * This is used to pull image. 445 | * 446 | */ 447 | if (architecture == NULL) { 448 | architecture = rurima_docker_get_host_arch(); 449 | } 450 | char *jq_cmd_0[] = { "jq", "-r", ".manifests", NULL }; 451 | char *tmp = rurima_call_jq(jq_cmd_0, manifests); 452 | if (tmp == NULL) { 453 | rurima_error("{red}Failed to get manifests!\n"); 454 | } 455 | char *jq_arg_0 = malloc(strlen(architecture) + 128); 456 | sprintf(jq_arg_0, ".[] | select(.platform.architecture == \"%s\")|select(.platform.os == \"linux\")|.digest", architecture); 457 | char *jq_cmd_1[] = { "jq", "-r", "-j", jq_arg_0, NULL }; 458 | char *digest = rurima_call_jq(jq_cmd_1, tmp); 459 | free(jq_arg_0); 460 | if (digest == NULL) { 461 | free(tmp); 462 | return NULL; 463 | } 464 | rurima_log("{base}Digest: %s{clear}\n", digest); 465 | free(tmp); 466 | return digest; 467 | } 468 | static char **get_blobs(const char *_Nonnull image, const char *_Nonnull digest, const char *_Nonnull token, const char *_Nullable mirror) 469 | { 470 | /* 471 | * Warning: free() the return value after use. 472 | * 473 | * Get blobs of image. 474 | * This is used to pull image. 475 | * The layers to get will be stored in the returned array. 476 | * 477 | */ 478 | char url[4096] = { '\0' }; 479 | if (mirror == NULL) { 480 | mirror = rurima_global_config.docker_mirror; 481 | } 482 | sprintf(url, "https://%s/v2/%s/manifests/%s", mirror, image, digest); 483 | rurima_log("{base}url: {cyan}%s{clear}\n", url); 484 | char *auth = malloc(strlen(token) + 114); 485 | auth[0] = '\0'; 486 | sprintf(auth, "Authorization: Bearer %s", token); 487 | const char *curl_command[] = { "curl", "-L", "-s", "-H", "Accept: application/vnd.oci.image.manifest.v1+json", "-H", auth, url, NULL }; 488 | rurima_log("{base}Command:\n{cyan}curl -L -s -H \"%s\" %s\n", auth, url); 489 | char *response = rurima_fork_execvp_get_stdout(curl_command); 490 | rurima_log("{base}Response: \n{cyan}%s{clear}\n", response); 491 | if (response == NULL) { 492 | rurima_error("{red}Failed to get blobs!\n"); 493 | } 494 | char *jq_cmd_0[] = { "jq", "-r", ".layers", NULL }; 495 | char *layers = rurima_call_jq(jq_cmd_0, response); 496 | if (layers == NULL) { 497 | rurima_error("{red}Failed to get layers!\n"); 498 | } 499 | char **ret = NULL; 500 | char *jq_cmd_1[] = { "jq", "-r", ".[] | .digest", NULL }; 501 | char *layers_orig = rurima_call_jq(jq_cmd_1, layers); 502 | size_t len = rurima_split_lines(layers_orig, &ret); 503 | free(layers_orig); 504 | if (len == 0) { 505 | rurima_error("{red}Failed to get layers!\n"); 506 | } 507 | free(layers); 508 | free(response); 509 | free(auth); 510 | return ret; 511 | } 512 | static char *get_short_sha(const char *_Nonnull sha) 513 | { 514 | /* 515 | * Warning: free() the return value after use. 516 | * 517 | * Just for displaying layer information. 518 | * 519 | */ 520 | const char *p = &sha[7]; 521 | char *ret = malloc(32); 522 | strncpy(ret, p, 16); 523 | ret[16] = '\0'; 524 | return ret; 525 | } 526 | static void pull_images(const char *_Nonnull image, char *const *_Nonnull blobs, const char *_Nonnull token, const char *_Nonnull savedir, const char *_Nullable mirror, bool fallback) 527 | { 528 | /* 529 | * Pull images. 530 | * 531 | * This function will pull all layers of image, 532 | * and extract them to savedir. 533 | * Fallback mode will get token every time pull a layer. 534 | * 535 | */ 536 | rurima_check_dir_deny_list(savedir); 537 | char *token_tmp = NULL; 538 | char url[4096] = { '\0' }; 539 | char filename[4096] = { '\0' }; 540 | if (rurima_mkdirs(savedir, 0755) != 0) { 541 | rurima_error("{red}Failed to create directory!\n"); 542 | } 543 | chdir(savedir); 544 | if (mirror == NULL) { 545 | mirror = rurima_global_config.docker_mirror; 546 | } 547 | for (int i = 0;; i++) { 548 | if (blobs[i] == NULL) { 549 | break; 550 | } 551 | char *sha = get_short_sha(blobs[i]); 552 | cprintf("{base}Pulling{cyan} %s {base}as{cyan} layer-%d\n", sha, i); 553 | free(sha); 554 | sprintf(url, "https://%s/v2/%s/blobs/%s", mirror, image, blobs[i]); 555 | sprintf(filename, "layer-%d", i); 556 | if (fallback) { 557 | token_tmp = get_token(image, mirror, fallback); 558 | } else { 559 | token_tmp = strdup(token); 560 | } 561 | char *auth = malloc(strlen(token_tmp) + 114); 562 | auth[0] = '\0'; 563 | sprintf(auth, "Authorization: Bearer %s", token_tmp); 564 | free(token_tmp); 565 | rurima_log("{base}Command:\n{cyan}curl -L -s -H \"%s\" %s -o %s\n", auth, url, filename); 566 | const char *curl_command[] = { "curl", "-L", "-s", "-H", auth, url, "-o", filename, NULL }; 567 | int ret = rurima_fork_execvp(curl_command); 568 | if (ret != 0) { 569 | rurima_error("{red}Failed to pull image!\n"); 570 | } 571 | free(auth); 572 | if (!fallback && rurima_sha256sum_exists()) { 573 | const char *sha256_command[] = { "sha256sum", filename, NULL }; 574 | char *sha256 = rurima_fork_execvp_get_stdout(sha256_command); 575 | if (sha256 == NULL) { 576 | rurima_error("{red}Failed to get sha256!\n"); 577 | } 578 | if (strchr(sha256, ' ') == NULL) { 579 | rurima_error("{red}Failed to get sha256!\n"); 580 | } 581 | *strchr(sha256, ' ') = '\0'; 582 | rurima_log("{base}SHA256: %s\n", sha256); 583 | if (strcmp(sha256, &(blobs[i][7])) != 0) { 584 | rurima_error("{red}SHA256 mismatch!\n"); 585 | } 586 | rurima_log("{base}SHA256 match!\n"); 587 | free(sha256); 588 | } 589 | ret = rurima_extract_archive(filename, "."); 590 | if (ret != 0) { 591 | rurima_error("{red}Failed to extract archive!\n"); 592 | } 593 | remove(filename); 594 | } 595 | } 596 | static char *get_config_digest_fallback(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nonnull token, const char *_Nullable mirror) 597 | { 598 | /* 599 | * Warning: free() the return value after use. 600 | * 601 | * Fallback to get config. 602 | * 603 | */ 604 | if (mirror == NULL) { 605 | mirror = rurima_global_config.docker_mirror; 606 | } 607 | char *manifests = get_tag_manifests(image, tag, token, mirror); 608 | char *jq_command_0[] = { "jq", "-r", "-j", ".config.digest", NULL }; 609 | char *ret = rurima_call_jq(jq_command_0, manifests); 610 | if (ret == NULL) { 611 | rurima_error("{red}Failed to get config!\n"); 612 | } 613 | free(manifests); 614 | return ret; 615 | } 616 | static char *get_config_digest(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nullable digest, const char *_Nonnull token, const char *_Nullable mirror, bool fallback) 617 | { 618 | /* 619 | * Warning: free() the return value after use. 620 | * 621 | * Get the digest of config of image. 622 | * 623 | */ 624 | if (mirror == NULL) { 625 | mirror = rurima_global_config.docker_mirror; 626 | } 627 | if (digest == NULL) { 628 | if (!fallback) { 629 | rurima_error("{red}No digest found!\n"); 630 | } 631 | return get_config_digest_fallback(image, tag, token, mirror); 632 | } 633 | char url[4096] = { '\0' }; 634 | sprintf(url, "https://%s/v2/%s/manifests/%s", mirror, image, digest); 635 | char *auth = malloc(strlen(token) + 114); 636 | sprintf(auth, "Authorization: Bearer %s", token); 637 | const char *curl_command[] = { "curl", "-L", "-s", "-H", "Accept: application/vnd.oci.image.manifest.v1+json", "-H", auth, url, NULL }; 638 | char *response = rurima_fork_execvp_get_stdout(curl_command); 639 | if (response == NULL) { 640 | rurima_error("{red}Failed to get config!\n"); 641 | } 642 | char *jq_command_0[] = { "jq", "-r", "-j", ".config.digest", NULL }; 643 | char *config = rurima_call_jq(jq_command_0, response); 644 | if (config == NULL) { 645 | free(response); 646 | free(auth); 647 | if (!fallback) { 648 | rurima_error("{red}Failed to get config!\n"); 649 | } 650 | return get_config_digest_fallback(image, tag, token, mirror); 651 | } 652 | rurima_log("{base}Config: %s{clear}\n", config); 653 | free(response); 654 | free(auth); 655 | return config; 656 | } 657 | static char *env_get_right(const char *_Nonnull env) 658 | { 659 | /* 660 | * Warning: free() the return value after use. 661 | * 662 | * This is used to parse env to key and value. 663 | * 664 | */ 665 | const char *p = env; 666 | for (size_t i = 0; i < strlen(env); i++) { 667 | if (env[i] == '\\') { 668 | i++; 669 | continue; 670 | } 671 | if (env[i] == '=') { 672 | p = &env[i + 1]; 673 | break; 674 | } 675 | } 676 | return strdup(p); 677 | } 678 | static char *env_get_left(const char *_Nonnull env) 679 | { 680 | /* 681 | * Warning: free() the return value after use. 682 | * 683 | * Same like above. 684 | * 685 | */ 686 | char *ret = malloc(strlen(env) + 1); 687 | strcpy(ret, env); 688 | for (size_t i = 0; i < strlen(env); i++) { 689 | if (env[i] == '\\') { 690 | i++; 691 | continue; 692 | } 693 | if (env[i] == '=') { 694 | ret[i] = '\0'; 695 | break; 696 | } 697 | } 698 | char *tmp = ret; 699 | ret = strdup(tmp); 700 | free(tmp); 701 | return ret; 702 | } 703 | static void parse_env(char *const *_Nonnull env, char **_Nonnull buf, int len) 704 | { 705 | /* 706 | * Parse env. 707 | * 708 | * Env is like KEY=VALUE, 709 | * so we need to split them. 710 | * This is because ruri use `-e ENV VALUE` to define env. 711 | * 712 | */ 713 | if (len == 0) { 714 | return; 715 | } 716 | int j = 0; 717 | for (int i = 0; i < len; i++) { 718 | buf[j] = env_get_left(env[i]); 719 | buf[j + 1] = env_get_right(env[i]); 720 | buf[j + 2] = NULL; 721 | buf[j + 3] = NULL; 722 | j += 2; 723 | } 724 | } 725 | static struct RURIMA_DOCKER *get_image_config(const char *_Nonnull image, const char *_Nonnull config, const char *_Nonnull token, const char *_Nullable mirror) 726 | { 727 | /* 728 | * Warning: free() the return value after use. 729 | * 730 | * Get the config of image. 731 | * return a struct RURIMA_DOCKER. 732 | * 733 | */ 734 | struct RURIMA_DOCKER *ret = malloc(sizeof(struct RURIMA_DOCKER)); 735 | if (mirror == NULL) { 736 | mirror = rurima_global_config.docker_mirror; 737 | } 738 | char url[4096] = { '\0' }; 739 | sprintf(url, "https://%s/v2/%s/blobs/%s", mirror, image, config); 740 | char *auth = malloc(strlen(token) + 114); 741 | auth[0] = '\0'; 742 | sprintf(auth, "Authorization: Bearer %s", token); 743 | const char *curl_command[] = { "curl", "-L", "-s", "-H", "Accept: application/vnd.oci.image.manifest.v1+json", "-H", auth, url, NULL }; 744 | char *response = rurima_fork_execvp_get_stdout(curl_command); 745 | if (response == NULL) { 746 | rurima_error("{red}Failed to get config!\n"); 747 | } 748 | rurima_log("{base}Config:\n{cyan} %s\n", response); 749 | { 750 | char *jq_command_0[] = { "jq", "-r", "-j", ".architecture", NULL }; 751 | char *architecture = rurima_call_jq(jq_command_0, response); 752 | rurima_log("{base}Arch: {cyan}%s{clear}\n", architecture == NULL ? "NULL" : architecture); 753 | if (architecture == NULL) { 754 | ret->architecture = NULL; 755 | } else { 756 | ret->architecture = architecture; 757 | } 758 | } 759 | { 760 | char *jq_command_1[] = { "jq", "-r", "-j", ".config.WorkingDir", NULL }; 761 | char *workdir = rurima_call_jq(jq_command_1, response); 762 | rurima_log("{base}Workdir: {cyan}%s{clear}\n", workdir == NULL ? "NULL" : workdir); 763 | if (workdir == NULL) { 764 | ret->workdir = NULL; 765 | } else { 766 | ret->workdir = workdir; 767 | } 768 | } 769 | { 770 | char *jq_command_2[] = { "jq", "-r", "-j", "-c", ".config.Env", NULL }; 771 | char *env_from_json = rurima_call_jq(jq_command_2, response); 772 | rurima_log("{base}Env: {cyan}%s{clear}\n", env_from_json == NULL ? "NULL" : env_from_json); 773 | if (env_from_json != NULL) { 774 | char *tmp = malloc(strlen(env_from_json) + 114); 775 | sprintf(tmp, "env=%s\n", env_from_json); 776 | char *env[RURI_MAX_ENVS / 2]; 777 | env[0] = NULL; 778 | int len = k2v_get_key(char_array, "env", tmp, env, RURI_MAX_ENVS / 2); 779 | parse_env(env, ret->env, len); 780 | for (int i = 0; i < len; i++) { 781 | rurima_log("{base}Env[%d]: {cyan}%s{clear}\n", i, env[i]); 782 | free(env[i]); 783 | } 784 | ret->env[len * 2] = NULL; 785 | free(tmp); 786 | free(env_from_json); 787 | } else { 788 | ret->env[0] = NULL; 789 | } 790 | } 791 | { 792 | char *jq_command_3[] = { "jq", "-r", "-j", "-c", ".config.Entrypoint", NULL }; 793 | char *entrypoint = rurima_call_jq(jq_command_3, response); 794 | rurima_log("{base}Entrypoint: {cyan}%s{clear}\n", entrypoint == NULL ? "NULL" : entrypoint); 795 | if (entrypoint != NULL) { 796 | char *tmp = malloc(strlen(entrypoint) + 114); 797 | sprintf(tmp, "entrypoint=%s\n", entrypoint); 798 | int len = k2v_get_key(char_array, "entrypoint", tmp, ret->entrypoint, RURI_MAX_COMMANDS); 799 | for (int i = 0; i < len; i++) { 800 | rurima_log("{base}Entrypoint[%d]: {cyan}%s{clear}\n", i, ret->entrypoint[i]); 801 | } 802 | ret->entrypoint[len] = NULL; 803 | free(tmp); 804 | free(entrypoint); 805 | } else { 806 | ret->entrypoint[0] = NULL; 807 | } 808 | } 809 | { 810 | char *jq_command_4[] = { "jq", "-r", "-j", "-c", ".config.Cmd", NULL }; 811 | char *cmdline = rurima_call_jq(jq_command_4, response); 812 | rurima_log("{base}Cmdline: {cyan}%s{clear}\n", cmdline == NULL ? "NULL" : cmdline); 813 | if (cmdline != NULL) { 814 | char *tmp = malloc(strlen(cmdline) + 114); 815 | sprintf(tmp, "cmdline=%s\n", cmdline); 816 | int len = k2v_get_key(char_array, "cmdline", tmp, ret->command, RURI_MAX_COMMANDS); 817 | for (int i = 0; i < len; i++) { 818 | rurima_log("{base}Cmdline[%d]: {cyan}%s{clear}\n", i, ret->command[i]); 819 | } 820 | ret->command[len] = NULL; 821 | free(tmp); 822 | free(cmdline); 823 | } else { 824 | ret->command[0] = NULL; 825 | } 826 | } 827 | free(response); 828 | free(auth); 829 | return ret; 830 | } 831 | struct RURIMA_DOCKER *rurima_get_docker_config(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nullable architecture, const char *_Nullable mirror, bool fallback) 832 | { 833 | /* 834 | * Warning: free() the return value after use. 835 | * 836 | * Return the config of image. 837 | * This function is called by subcommand.c 838 | * 839 | */ 840 | char *token = get_token(image, mirror, fallback); 841 | char *manifests = get_tag_manifests(image, tag, token, mirror); 842 | char *digest = get_tag_digest(manifests, architecture); 843 | // digest can be NULL, then we go to fallback. 844 | char *config = get_config_digest(image, tag, digest, token, mirror, fallback); 845 | if (config == NULL) { 846 | rurima_error("{red}Failed to get config!\n"); 847 | } 848 | struct RURIMA_DOCKER *ret = get_image_config(image, config, token, mirror); 849 | free(manifests); 850 | free(token); 851 | free(digest); 852 | free(config); 853 | return ret; 854 | } 855 | static struct RURIMA_DOCKER *docker_pull_fallback(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nonnull savedir, const char *_Nullable mirror) 856 | { 857 | /* 858 | * Warning: free() the return value after use. 859 | * 860 | * Fallback to pull image. 861 | * 862 | */ 863 | if (mirror == NULL) { 864 | mirror = rurima_global_config.docker_mirror; 865 | } 866 | char *token = get_token(image, mirror, true); 867 | char *manifests = get_tag_manifests(image, tag, token, mirror); 868 | char **blobs = NULL; 869 | char *jq_cmd_0[] = { "jq", "-r", ".layers", NULL }; 870 | char *layers = rurima_call_jq(jq_cmd_0, manifests); 871 | if (layers == NULL) { 872 | rurima_error("{red}Failed to get layers!\n"); 873 | } 874 | char *jq_cmd_1[] = { "jq", "-r", ".[] | .digest", NULL }; 875 | char *layers_orig = rurima_call_jq(jq_cmd_1, layers); 876 | size_t len = rurima_split_lines(layers_orig, &blobs); 877 | free(layers_orig); 878 | if (len == 0) { 879 | rurima_error("{red}Failed to get digest!\n"); 880 | } 881 | pull_images(image, blobs, token, savedir, mirror, true); 882 | free(token); 883 | token = get_token(image, mirror, true); 884 | char *config = get_config_digest_fallback(image, tag, token, mirror); 885 | struct RURIMA_DOCKER *ret = get_image_config(image, config, token, mirror); 886 | free(manifests); 887 | free(token); 888 | for (size_t i = 0; blobs[i] != NULL; i++) { 889 | free(blobs[i]); 890 | } 891 | free(blobs); 892 | free(config); 893 | free(layers); 894 | return ret; 895 | } 896 | struct RURIMA_DOCKER *rurima_docker_pull(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nullable architecture, const char *_Nonnull savedir, const char *_Nullable mirror, bool fallback) 897 | { 898 | /* 899 | * Warning: free() the return value after use. 900 | * 901 | * We pull all the layers of image, 902 | * and extract them to savedir. 903 | * And we return the config of image. 904 | * 905 | */ 906 | if (mirror == NULL) { 907 | mirror = rurima_global_config.docker_mirror; 908 | } 909 | char *token = get_token(image, mirror, fallback); 910 | char *manifests = get_tag_manifests(image, tag, token, mirror); 911 | char *digest = get_tag_digest(manifests, architecture); 912 | if (digest == NULL) { 913 | free(manifests); 914 | free(token); 915 | if (!fallback) { 916 | rurima_error("{red}Failed to get digest!\n"); 917 | } 918 | return docker_pull_fallback(image, tag, savedir, mirror); 919 | } 920 | char **blobs = get_blobs(image, digest, token, mirror); 921 | if (blobs == NULL) { 922 | rurima_error("{red}Failed to get blobs!\n"); 923 | } 924 | pull_images(image, blobs, token, savedir, mirror, fallback); 925 | if (fallback) { 926 | free(token); 927 | token = get_token(image, mirror, true); 928 | } 929 | char *config = get_config_digest(image, tag, digest, token, mirror, fallback); 930 | if (fallback) { 931 | free(token); 932 | token = get_token(image, mirror, true); 933 | } 934 | struct RURIMA_DOCKER *ret = get_image_config(image, config, token, mirror); 935 | free(manifests); 936 | free(token); 937 | free(digest); 938 | for (size_t i = 0; blobs[i] != NULL; i++) { 939 | free(blobs[i]); 940 | } 941 | free(blobs); 942 | free(config); 943 | return ret; 944 | } 945 | static char *docker_search__(const char *_Nonnull url) 946 | { 947 | /* 948 | * Warning: free() the return value after use. 949 | * 950 | * This is the core parse function of rurima_docker_search(). 951 | * It will read info from url, and return next url. 952 | * 953 | */ 954 | const char *curl_command[] = { "curl", "-L", "-s", url, NULL }; 955 | char *response = rurima_fork_execvp_get_stdout(curl_command); 956 | if (response == NULL) { 957 | rurima_error("{red}Failed to get response from dockerhub!\n"); 958 | } 959 | rurima_log("{base}Response from dockerhub:\n{cyan}%s{clear}\n", response); 960 | char *jq_cmd_1[] = { "jq", "-r", ".next", NULL }; 961 | char *next_url = rurima_call_jq(jq_cmd_1, response); 962 | if (next_url == NULL) { 963 | rurima_error("{red}Failed to get next url!\n"); 964 | } 965 | char *jq_cmd_2[] = { "jq", "-r", ".results", NULL }; 966 | char *results = rurima_call_jq(jq_cmd_2, response); 967 | if (results == NULL) { 968 | rurima_error("{red}No results found!\n"); 969 | } 970 | rurima_log("{base}Results:\n{cyan}%s{clear}\n", results); 971 | char **name = NULL; 972 | char *jq_cmd_3[] = { "jq", "-r", ".[].repo_name", NULL }; 973 | char *name_ori = rurima_call_jq(jq_cmd_3, results); 974 | if (name_ori == NULL) { 975 | rurima_error("{red}No results found!\n"); 976 | } 977 | size_t len = rurima_split_lines_allow_null(name_ori, &name); 978 | if (len == 0) { 979 | rurima_error("{red}No results found!\n"); 980 | } 981 | free(name_ori); 982 | char **description = NULL; 983 | char *jq_cmd_4[] = { "jq", "-r", ".[].short_description", NULL }; 984 | char *description_ori = rurima_call_jq(jq_cmd_4, results); 985 | if (description_ori == NULL) { 986 | rurima_error("{red}No results found!\n"); 987 | } 988 | size_t len2 = rurima_split_lines_allow_null(description_ori, &description); 989 | free(description_ori); 990 | if (len2 == 0) { 991 | rurima_error("{red}No results found!\n"); 992 | } 993 | if (len2 != len) { 994 | rurima_error("{red}Incorrect json!\n"); 995 | } 996 | char **is_offical = NULL; 997 | char *jq_cmd_5[] = { "jq", "-r", ".[].is_official", NULL }; 998 | char *is_offical_ori = rurima_call_jq(jq_cmd_5, results); 999 | if (is_offical_ori == NULL) { 1000 | rurima_error("{red}No results found!\n"); 1001 | } 1002 | size_t len3 = rurima_split_lines_allow_null(is_offical_ori, &is_offical); 1003 | free(is_offical_ori); 1004 | if (len3 == 0) { 1005 | rurima_error("{red}No results found!\n"); 1006 | } 1007 | if (len3 != len) { 1008 | rurima_error("{red}Incorrect json!\n"); 1009 | } 1010 | for (size_t i = 0; i < len - 1; i++) { 1011 | if (strcmp(is_offical[i], "true") == 0) { 1012 | if (!rurima_global_config.quiet) { 1013 | cprintf("{yellow}%s {green}[official]\n", name[i]); 1014 | if (description[i] != NULL && strlen(description[i]) > 0) { 1015 | cprintf(" {cyan}Description: %s\n", description[i]); 1016 | } else { 1017 | cprintf(" {cyan}Description: No description\n"); 1018 | } 1019 | } else { 1020 | printf("%s [official]\n", name[i]); 1021 | } 1022 | } else { 1023 | if (!rurima_global_config.quiet) { 1024 | cprintf("{yellow}%s\n", name[i]); 1025 | if (description[i] != NULL) { 1026 | cprintf(" {cyan}Description: %s\n", description[i]); 1027 | } else { 1028 | cprintf(" {cyan}Description: No description\n"); 1029 | } 1030 | } else { 1031 | printf("%s\n", name[i]); 1032 | } 1033 | } 1034 | } 1035 | free(response); 1036 | for (size_t i = 0; i < len; i++) { 1037 | free(name[i]); 1038 | free(description[i]); 1039 | free(is_offical[i]); 1040 | } 1041 | free(results); 1042 | free(name); 1043 | free(description); 1044 | free(is_offical); 1045 | return next_url; 1046 | } 1047 | int rurima_docker_search(const char *_Nonnull image, const char *_Nonnull page_size, bool quiet, const char *_Nullable mirror) 1048 | { 1049 | /* 1050 | * 1051 | * An implementation of docker search. 1052 | * Return value is not important here, 1053 | * because we will error() directly if failed. 1054 | * 1055 | */ 1056 | char *url = malloc(4096); 1057 | url[0] = '\0'; 1058 | if (mirror == NULL) { 1059 | mirror = "hub.docker.com"; 1060 | } 1061 | strcat(url, "https://"); 1062 | strcat(url, mirror); 1063 | strcat(url, "/v2/search/repositories/?page_size="); 1064 | strcat(url, page_size); 1065 | strcat(url, "&query="); 1066 | strcat(url, image); 1067 | while (true) { 1068 | rurima_log("{base}url: {cyan}%s{clear}\n", url); 1069 | char *next_url = docker_search__(url); 1070 | if (next_url == NULL) { 1071 | exit(EXIT_SUCCESS); 1072 | } 1073 | rurima_log("{base}nexturl: {cyan}%s{clear}\n", next_url); 1074 | if (quiet) { 1075 | free(url); 1076 | free(next_url); 1077 | break; 1078 | } 1079 | char goto_next[114] = { '\0' }; 1080 | rurima_get_input("\n{purple}Continue see more results? (y/n): ", goto_next); 1081 | rurima_log("{base}goto_next: {cyan}%s{clear}\n", goto_next); 1082 | if (strcmp(goto_next, "y") == 0) { 1083 | free(url); 1084 | url = next_url; 1085 | rurima_log("{base}next_url: {cyan}%s{clear}\n", next_url); 1086 | } else { 1087 | free(url); 1088 | free(next_url); 1089 | break; 1090 | } 1091 | } 1092 | return 0; 1093 | } 1094 | static char *docker_search_tag__(const char *_Nonnull image, const char *_Nonnull url, const char *_Nullable architecture) 1095 | { 1096 | /* 1097 | * Warning: free() the return value after use. 1098 | * 1099 | * This is the core parse function of rurima_docker_search_tag(). 1100 | * It will read info from url, and return next url. 1101 | * 1102 | */ 1103 | rurima_log("{base}url: {cyan}%s{clear}\n", url); 1104 | const char *curl_command[] = { "curl", "-L", "-s", url, NULL }; 1105 | char *response = rurima_fork_execvp_get_stdout(curl_command); 1106 | if (response == NULL) { 1107 | rurima_error("{red}Failed to get response from dockerhub!\n"); 1108 | } 1109 | rurima_log("{base}Response from dockerhub:\n{cyan}%s{clear}\n", response); 1110 | if (architecture == NULL) { 1111 | architecture = rurima_docker_get_host_arch(); 1112 | } 1113 | char *jq_cmd_0[] = { "jq", "-r", ".next", NULL }; 1114 | char *next_url = rurima_call_jq(jq_cmd_0, response); 1115 | rurima_log("{base}next_url: {cyan}%s{clear}\n", next_url); 1116 | char *jq_cmd_1[] = { "jq", "-r", "-j", ".results", NULL }; 1117 | char *results = rurima_call_jq(jq_cmd_1, response); 1118 | char *jq_arg_0 = malloc(strlen(architecture) + 1024); 1119 | sprintf(jq_arg_0, ".[]|select(.images.[].architecture==\"%s\")|.name", architecture); 1120 | if (results == NULL) { 1121 | free(jq_arg_0); 1122 | rurima_error("{red}No results found!\n"); 1123 | } 1124 | char **name = NULL; 1125 | char *jq_cmd_2[] = { "jq", "-r", jq_arg_0, NULL }; 1126 | char *name_ori = rurima_call_jq(jq_cmd_2, results); 1127 | if (name_ori == NULL) { 1128 | free(jq_arg_0); 1129 | rurima_error("{red}No results found!\n"); 1130 | } 1131 | size_t len = rurima_split_lines_allow_null(name_ori, &name); 1132 | if (len == 0) { 1133 | free(jq_arg_0); 1134 | rurima_error("{red}No results found!\n"); 1135 | } 1136 | rurima_log("{base}Results:\n{cyan}%s{clear}\n", name_ori); 1137 | for (size_t i = 0; i < len - 1; i++) { 1138 | if (!rurima_global_config.quiet) { 1139 | cprintf("{yellow}[%s]: {cyan}%s{clear}\n", image, name[i]); 1140 | } else { 1141 | printf("[%s]: %s\n", image, name[i]); 1142 | } 1143 | free(name[i]); 1144 | } 1145 | free(jq_arg_0); 1146 | free(response); 1147 | free(results); 1148 | return next_url; 1149 | } 1150 | int rurima_docker_search_tag(const char *_Nonnull image, const char *_Nonnull page_size, const char *_Nullable architecture, bool quiet, const char *_Nullable mirror) 1151 | { 1152 | /* 1153 | * An implementation of docker tag search. 1154 | * 1155 | * The return value is not important here, 1156 | * because we will rurima_error() directly if failed. 1157 | * 1158 | */ 1159 | char *url = malloc(4096); 1160 | url[0] = '\0'; 1161 | if (mirror == NULL) { 1162 | mirror = "hub.docker.com"; 1163 | } 1164 | strcat(url, "https://"); 1165 | strcat(url, mirror); 1166 | strcat(url, "/v2/repositories/"); 1167 | strcat(url, image); 1168 | strcat(url, "/tags/?page_size="); 1169 | strcat(url, page_size); 1170 | while (true) { 1171 | rurima_log("{base}url: {cyan}%s{clear}\n", url); 1172 | char *next_url = docker_search_tag__(image, url, architecture); 1173 | if (next_url == NULL) { 1174 | exit(EXIT_SUCCESS); 1175 | } 1176 | rurima_log("{base}nexturl: {cyan}%s{clear}\n", next_url); 1177 | if (quiet) { 1178 | free(url); 1179 | free(next_url); 1180 | return 0; 1181 | break; 1182 | } 1183 | char goto_next[114] = { '\0' }; 1184 | rurima_get_input("\n{purple}Continue see more results? (y/n): ", goto_next); 1185 | rurima_log("{base}goto_next: {cyan}%s{clear}\n", goto_next); 1186 | if (strcmp(goto_next, "y") == 0) { 1187 | free(url); 1188 | url = next_url; 1189 | rurima_log("{base}next_url: {cyan}%s{clear}\n", next_url); 1190 | } else { 1191 | free(url); 1192 | free(next_url); 1193 | break; 1194 | } 1195 | } 1196 | return 0; 1197 | } 1198 | static void docker_add_archlist__(char *_Nonnull arch, char ***_Nullable archlist) 1199 | { 1200 | /* 1201 | * Add arch to archlist. 1202 | * 1203 | * We will not add duplicate arch. 1204 | * 1205 | */ 1206 | if (strcmp(arch, "unknown") == 0) { 1207 | return; 1208 | } 1209 | if (*archlist == NULL) { 1210 | *archlist = malloc(sizeof(char *) * 3); 1211 | (*archlist)[0] = strdup(arch); 1212 | (*archlist)[1] = NULL; 1213 | return; 1214 | } 1215 | for (int i = 0; (*archlist)[i] != NULL; i++) { 1216 | if (strcmp((*archlist)[i], arch) == 0) { 1217 | return; 1218 | } 1219 | } 1220 | size_t j = 0; 1221 | for (; (*archlist)[j] != NULL; j++) 1222 | ; 1223 | *archlist = realloc(*archlist, sizeof(char *) * (j + 3)); 1224 | for (int i = 0;; i++) { 1225 | if ((*archlist)[i] == NULL) { 1226 | (*archlist)[i] = strdup(arch); 1227 | (*archlist)[i + 1] = NULL; 1228 | break; 1229 | } 1230 | } 1231 | } 1232 | static void docker_print_arch(const char *_Nonnull image, char *const *_Nonnull arch, size_t len) 1233 | { 1234 | /* 1235 | * Print architecture of image. 1236 | * 1237 | * We will not print unknown architecture or duplicate architecture. 1238 | * 1239 | */ 1240 | char **archlist = NULL; 1241 | for (size_t i = 0; i < len; i++) { 1242 | docker_add_archlist__(arch[i], &archlist); 1243 | } 1244 | if (archlist == NULL) { 1245 | rurima_error("{red}No results found!\n"); 1246 | } 1247 | for (int i = 0; archlist[i] != NULL; i++) { 1248 | if (!rurima_global_config.quiet) { 1249 | cprintf("{yellow}[%s]: {cyan}%s{clear}\n", image, archlist[i]); 1250 | } else { 1251 | printf("[%s]: %s\n", image, archlist[i]); 1252 | } 1253 | } 1254 | for (int i = 0; archlist[i] != NULL; i++) { 1255 | free(archlist[i]); 1256 | } 1257 | free(archlist); 1258 | } 1259 | int rurima_docker_search_arch(const char *_Nonnull image, const char *_Nonnull tag, char *_Nullable mirror, bool fallback) 1260 | { 1261 | /* 1262 | * Get architecture of image:tag. 1263 | * 1264 | * The return value is not important here, 1265 | * because we will rurima_error() directly if failed. 1266 | * 1267 | */ 1268 | if (mirror == NULL) { 1269 | mirror = rurima_global_config.docker_mirror; 1270 | } 1271 | char *token = get_token(image, mirror, fallback); 1272 | char *manifests = get_tag_manifests(image, tag, token, mirror); 1273 | char **arch = NULL; 1274 | char *jq_cmd_0[] = { "jq", "-r", ".manifests", NULL }; 1275 | char *tmp = rurima_call_jq(jq_cmd_0, manifests); 1276 | if (tmp == NULL) { 1277 | rurima_error("{red}Failed to get manifests!\n"); 1278 | } 1279 | char *jq_cmd_1[] = { "jq", "-r", ".[] | .platform.architecture", NULL }; 1280 | char *arch_ori = rurima_call_jq(jq_cmd_1, tmp); 1281 | if (arch_ori == NULL) { 1282 | free(tmp); 1283 | rurima_error("{red}Failed to get architecture!\n"); 1284 | } 1285 | size_t len = rurima_split_lines(arch_ori, &arch); 1286 | free(arch_ori); 1287 | if (len == 0) { 1288 | rurima_error("{red}No results found!\n"); 1289 | } 1290 | docker_print_arch(image, arch, len); 1291 | free(manifests); 1292 | free(token); 1293 | for (size_t i = 0; i < len; i++) { 1294 | free(arch[i]); 1295 | } 1296 | free(arch); 1297 | free(tmp); 1298 | return 0; 1299 | } -------------------------------------------------------------------------------- /src/exec.c: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | /* 4 | * 5 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2024 Moe-hacker 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | * 29 | * 30 | */ 31 | #include "include/rurima.h" 32 | static size_t get_max_pipe_size(void) 33 | { 34 | int fd = open("/proc/sys/fs/pipe-max-size", O_RDONLY); 35 | if (fd < 0) { 36 | perror("open /proc/sys/fs/pipe-max-size"); 37 | return 65536; // Default pipe size 38 | } 39 | char buffer[16]; 40 | ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); 41 | if (bytes_read < 0) { 42 | perror("read /proc/sys/fs/pipe-max-size"); 43 | close(fd); 44 | return 65536; // Default pipe size 45 | } 46 | buffer[bytes_read] = '\0'; // Null-terminate the string 47 | size_t max_size = strtoul(buffer, NULL, 10); 48 | close(fd); 49 | if (max_size == 0) { 50 | return 65536; // Default pipe size 51 | } 52 | rurima_log("{base}Maximum pipe size: {green}%zu{base} bytes\n", max_size); 53 | return max_size; 54 | } 55 | int rurima_fork_execvp(const char *_Nonnull argv[]) 56 | { 57 | /* 58 | * fork(2) and then execvp(3). 59 | * Return the exit status of the child process. 60 | */ 61 | int pid = fork(); 62 | if (pid == 0) { 63 | execvp(argv[0], (char **)argv); 64 | // If execvp(3) failed, exit as error status 114. 65 | exit(114); 66 | } 67 | int status = 0; 68 | waitpid(pid, &status, 0); 69 | return WEXITSTATUS(status); 70 | } 71 | char *rurima_fork_execvp_get_stdout_ignore_err(const char *_Nonnull argv[]) 72 | { 73 | /* 74 | * Warning: free() after use. 75 | * We will fork(2) and then execvp(3). 76 | * And then we will get the stdout of the child process. 77 | * Return the stdout of the child process. 78 | * If failed, return NULL. 79 | */ 80 | // Create a pipe. 81 | int pipefd[2]; 82 | if (pipe(pipefd) == -1) { 83 | perror("pipe"); 84 | return NULL; 85 | } 86 | // fork(2) and then execvp(3). 87 | int pid = fork(); 88 | if (pid == -1) { 89 | perror("fork"); 90 | return NULL; 91 | } 92 | if (pid == 0) { 93 | // Close the read end of the pipe. 94 | close(pipefd[0]); 95 | // Redirect stdout and stderr to the write end of the pipe. 96 | dup2(pipefd[1], STDOUT_FILENO); 97 | int nullfd = open("/dev/null", O_WRONLY); 98 | dup2(nullfd, STDERR_FILENO); 99 | close(pipefd[1]); 100 | execvp(argv[0], (char **)argv); 101 | exit(114); 102 | } else { 103 | // Close the write end of the pipe. 104 | close(pipefd[1]); 105 | // Get the output from the read end of the pipe. 106 | size_t buffer_size = 1024; 107 | size_t total_read = 0; 108 | char *output = malloc(buffer_size); 109 | output[0] = '\0'; 110 | ssize_t bytes_read; 111 | while ((bytes_read = read(pipefd[0], output + total_read, buffer_size - total_read - 1)) > 0) { 112 | total_read += (size_t)bytes_read; 113 | if (total_read >= buffer_size - 1) { 114 | buffer_size *= 2; 115 | char *new_output = realloc(output, buffer_size); 116 | output = new_output; 117 | } 118 | } 119 | if (bytes_read == -1) { 120 | perror("read"); 121 | free(output); 122 | close(pipefd[0]); 123 | return NULL; 124 | } 125 | output[total_read] = '\0'; 126 | close(pipefd[0]); 127 | int status = 0; 128 | waitpid(pid, &status, 0); 129 | rurima_log("{base}Exec {green}%s{base} result: {purple}\n%s\n", argv[0], output); 130 | return output; 131 | } 132 | return NULL; 133 | } 134 | char *rurima_fork_execvp_get_stdout(const char *_Nonnull argv[]) 135 | { 136 | /* 137 | * Warning: free() after use. 138 | * We will fork(2) and then execvp(3). 139 | * And then we will get the stdout of the child process. 140 | * Return the stdout of the child process. 141 | * If failed, return NULL. 142 | */ 143 | // Create a pipe. 144 | int pipefd[2]; 145 | if (pipe(pipefd) == -1) { 146 | perror("pipe"); 147 | return NULL; 148 | } 149 | // fork(2) and then execvp(3). 150 | int pid = fork(); 151 | if (pid == -1) { 152 | perror("fork"); 153 | return NULL; 154 | } 155 | if (pid == 0) { 156 | // Close the read end of the pipe. 157 | close(pipefd[0]); 158 | // Redirect stdout and stderr to the write end of the pipe. 159 | dup2(pipefd[1], STDOUT_FILENO); 160 | int nullfd = open("/dev/null", O_WRONLY); 161 | dup2(nullfd, STDERR_FILENO); 162 | close(pipefd[1]); 163 | execvp(argv[0], (char **)argv); 164 | exit(114); 165 | } else { 166 | // Close the write end of the pipe. 167 | close(pipefd[1]); 168 | // Get the output from the read end of the pipe. 169 | size_t buffer_size = 1024; 170 | size_t total_read = 0; 171 | char *output = malloc(buffer_size); 172 | ssize_t bytes_read; 173 | while ((bytes_read = read(pipefd[0], output + total_read, buffer_size - total_read - 1)) > 0) { 174 | total_read += (size_t)bytes_read; 175 | if (total_read >= buffer_size - 1) { 176 | buffer_size *= 2; 177 | char *new_output = realloc(output, buffer_size); 178 | output = new_output; 179 | } 180 | } 181 | if (bytes_read == -1) { 182 | perror("read"); 183 | free(output); 184 | close(pipefd[0]); 185 | return NULL; 186 | } 187 | output[total_read] = '\0'; 188 | close(pipefd[0]); 189 | int status = 0; 190 | waitpid(pid, &status, 0); 191 | if (WEXITSTATUS(status) != 0) { 192 | free(output); 193 | return NULL; 194 | } 195 | rurima_log("{base}Exec {green}%s{base} result: {purple}\n%s\n", argv[0], output); 196 | return output; 197 | } 198 | return NULL; 199 | } 200 | void rurima_add_argv(char ***_Nonnull argv, const char *_Nonnull arg) 201 | { 202 | /* 203 | * Add an argument to the argv array. 204 | * Warning: make sure length of argv is enough. 205 | */ 206 | int argc = 0; 207 | while ((*argv)[argc] != NULL) { 208 | argc++; 209 | } 210 | (*argv)[argc] = arg; 211 | (*argv)[argc + 1] = NULL; 212 | } 213 | int rurima_fork_rexec(char **_Nonnull argv) 214 | { 215 | /* 216 | * Fork and execv self with argv. 217 | */ 218 | pid_t pid = fork(); 219 | if (pid == -1) { 220 | rurima_error("{red}Fork failed!\n"); 221 | } 222 | if (pid == 0) { 223 | int argc = 0; 224 | while (argv[argc] != NULL) { 225 | argc++; 226 | } 227 | char **new_argv = malloc(sizeof(char *) * (argc + 2)); 228 | new_argv[0] = "/proc/self/exe"; 229 | for (int i = 0; i < argc; i++) { 230 | rurima_log("{base}Argv[%d]: %s\n", i, argv[i]); 231 | new_argv[i + 1] = argv[i]; 232 | } 233 | new_argv[argc + 1] = NULL; 234 | execv(new_argv[0], new_argv); 235 | rurima_error("{red}Execv() failed!\n"); 236 | free(new_argv); 237 | exit(1); 238 | } 239 | int status; 240 | waitpid(pid, &status, 0); 241 | return WEXITSTATUS(status); 242 | } 243 | char *rurima_fork_execvp_get_stdout_with_input(const char *_Nonnull argv[], const char *_Nonnull input) 244 | { 245 | /* 246 | * Warning: free() after use. 247 | * We will fork(2) and then execvp(3). 248 | * And then we will get the stdout of the child process. 249 | * Return the stdout of the child process. 250 | * If failed, return NULL. 251 | */ 252 | // Create a pipe. 253 | int pipefd_in[2]; 254 | if (pipe(pipefd_in) == -1) { 255 | perror("pipe"); 256 | return NULL; 257 | } 258 | // Set the maximum pipe size. 259 | size_t max_pipe_size = get_max_pipe_size(); 260 | fcntl(pipefd_in[1], F_SETPIPE_SZ, max_pipe_size); 261 | int pipefd_out[2]; 262 | if (pipe(pipefd_out) == -1) { 263 | perror("pipe"); 264 | return NULL; 265 | } 266 | // fork(2) and then execvp(3). 267 | int pid = fork(); 268 | if (pid == -1) { 269 | perror("fork"); 270 | return NULL; 271 | } 272 | if (pid == 0) { 273 | // Close the read end of the pipe. 274 | close(pipefd_out[0]); 275 | // Redirect stdout and stderr to the write end of the pipe. 276 | dup2(pipefd_out[1], STDOUT_FILENO); 277 | int nullfd = open("/dev/null", O_WRONLY); 278 | dup2(nullfd, STDERR_FILENO); 279 | close(pipefd_out[1]); 280 | // Close the write end of the input pipe. 281 | close(pipefd_in[1]); 282 | // Redirect stdin to the read end of the input pipe. 283 | // This will allow the child process to read from the input pipe. 284 | dup2(pipefd_in[0], STDIN_FILENO); 285 | // Close the read end of the input pipe. 286 | close(pipefd_in[0]); 287 | rurima_log("{base}Exec {green}%s{base}\n", argv[0]); 288 | execvp(argv[0], (char **)argv); 289 | exit(114); 290 | } else { 291 | usleep(100); // Give the child process some time to set up. 292 | // Close the write end of the pipe. 293 | close(pipefd_out[1]); 294 | // Close the read end of the input pipe. 295 | close(pipefd_in[0]); 296 | // Write the input to the write end of the input pipe. 297 | if (write(pipefd_in[1], input, strlen(input)) == -1) { 298 | perror("write"); 299 | close(pipefd_in[1]); 300 | close(pipefd_out[0]); 301 | return NULL; 302 | } 303 | close(pipefd_in[1]); 304 | rurima_log("{base}Input written to {green}%s{base}\n", argv[0]); 305 | // Get the output from the read end of the pipe. 306 | size_t buffer_size = 1024; 307 | size_t total_read = 0; 308 | char *output = malloc(buffer_size); 309 | ssize_t bytes_read; 310 | while ((bytes_read = read(pipefd_out[0], output + total_read, buffer_size - total_read - 1)) > 0) { 311 | total_read += (size_t)bytes_read; 312 | if (total_read >= buffer_size - 1) { 313 | buffer_size *= 2; 314 | char *new_output = realloc(output, buffer_size); 315 | output = new_output; 316 | } 317 | } 318 | if (bytes_read == -1) { 319 | perror("read"); 320 | free(output); 321 | close(pipefd_out[0]); 322 | return NULL; 323 | } 324 | output[total_read] = '\0'; 325 | close(pipefd_out[0]); 326 | int status = 0; 327 | waitpid(pid, &status, 0); 328 | if (WEXITSTATUS(status) != 0) { 329 | free(output); 330 | return NULL; 331 | } 332 | rurima_log("{base}Exec {green}%s{base} result: {purple}\n%s\n", argv[0], output); 333 | return output; 334 | } 335 | return NULL; 336 | } 337 | char *rurima_call_jq(const char *_Nonnull argv[], const char *_Nonnull input) 338 | { 339 | /* 340 | * Call jq with input. 341 | * Warning: free() after use. 342 | */ 343 | rurima_log("{base}Calling jq with argv: \n"); 344 | for (int i = 0; argv[i] != NULL; i++) { 345 | rurima_log("{cyan}argv[%d]: %s\n", i, argv[i]); 346 | } 347 | char *output = rurima_fork_execvp_get_stdout_with_input(argv, input); 348 | if (strcmp(output, "null") == 0 || output == NULL) { 349 | free(output); 350 | return NULL; 351 | } 352 | return output; 353 | } -------------------------------------------------------------------------------- /src/hook.c: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | /* 4 | * 5 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2024 Moe-hacker 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | * 29 | * 30 | */ 31 | #include "include/rurima.h" 32 | void *rurima_default_hook(const char *_Nonnull container_dir) 33 | { 34 | char *hook_script = "" 35 | "#!/bin/sh" 36 | "# Fix DNS." 37 | "rm /etc/resolv.conf" 38 | "echo \"nameserver 1.1.1.1\" > /etc/resolv.conf" 39 | "exit 0\n"; 40 | if (rurima_global_config.hook_script == NULL) { 41 | char hook_script_path[PATH_MAX] = { '\0' }; 42 | snprintf(hook_script_path, PATH_MAX, "%s/tmp/hook.sh", container_dir); 43 | chdir(container_dir); 44 | mkdir("tmp", 0666); 45 | int fd = open(hook_script_path, O_CREAT | O_WRONLY, 0777); 46 | write(fd, hook_script, strlen(hook_script)); 47 | close(fd); 48 | if (rurima_run_with_root()) { 49 | char *argv[] = { "ruri", "-j", container_dir, "sh", "/tmp/hook.sh", NULL }; 50 | rurima_fork_rexec(argv); 51 | } else { 52 | if (!rurima_rootless_supported()) { 53 | rurima_error("{red}Rootless mode is not supported on your system!\n"); 54 | } 55 | char *argv[] = { "ruri", "-j", "-r", container_dir, "sh", "/tmp/hook.sh", NULL }; 56 | rurima_fork_rexec(argv); 57 | } 58 | } else { 59 | char hook_script_path[PATH_MAX] = { '\0' }; 60 | snprintf(hook_script_path, PATH_MAX, "%s/tmp/hook.sh", container_dir); 61 | chdir(container_dir); 62 | mkdir("tmp", 0666); 63 | int fd = open(hook_script_path, O_CREAT | O_WRONLY, 0777); 64 | int fd2 = open(rurima_global_config.hook_script, O_RDONLY); 65 | sendfile(fd, fd2, NULL, rurima_get_file_size(rurima_global_config.hook_script)); 66 | close(fd); 67 | close(fd2); 68 | if (rurima_run_with_root()) { 69 | char *argv[] = { "ruri", "-j", container_dir, "sh", "/tmp/hook.sh", NULL }; 70 | rurima_fork_rexec(argv); 71 | } else { 72 | if (!rurima_rootless_supported()) { 73 | rurima_error("{red}Rootless mode is not supported on your system!\n"); 74 | } 75 | char *argv[] = { "ruri", "-j", "-r", container_dir, "sh", "/tmp/hook.sh", NULL }; 76 | rurima_fork_rexec(argv); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/include/rurima.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | // Enable Linux features. 31 | #ifndef __linux__ 32 | #error "This program is only for linux." 33 | #else 34 | #define _GNU_SOURCE 35 | #endif 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | // This program need be linked with -lcap. 53 | #include 54 | // Bool!!! 55 | #if __STDC_VERSION__ < 202000L 56 | #ifndef bool 57 | #define bool _Bool 58 | #define true ((_Bool) + 1u) 59 | #define false ((_Bool) + 0u) 60 | #endif 61 | #endif 62 | #ifndef _Nullable 63 | #define _Nullable 64 | #endif 65 | #ifndef _Nonnull 66 | #define _Nonnull 67 | #endif 68 | #include "version.h" 69 | #include "../ruri/src/include/ruri.h" 70 | struct RURIMA_CONFIG { 71 | char *_Nonnull docker_mirror; 72 | char *_Nonnull lxc_mirror; 73 | char *_Nullable hook_script; 74 | bool quiet; 75 | bool no_process; 76 | }; 77 | extern struct RURIMA_CONFIG rurima_global_config; 78 | struct RURIMA { 79 | /* 80 | * This is full rurima config. 81 | */ 82 | // rootfs source: docker, lxc, rootfs or hostdir. 83 | char *_Nonnull rootfs_source; 84 | // For docker. 85 | char *_Nullable rootfs_image; 86 | // For docker. 87 | char *_Nullable rootfs_tag; 88 | // For docker/lxc. 89 | char *_Nullable rootfs_arch; 90 | // For lxc. 91 | char *_Nullable rootfs_mirror; 92 | // For lxc. 93 | char *_Nullable rootfs_os; 94 | // For lxc. 95 | char *_Nullable rootfs_version; 96 | // For lxc. 97 | char *_Nullable rootfs_type; 98 | // For rootfs. 99 | char *_Nullable rootfs_path; 100 | // For rootfs. 101 | char *_Nullable host_dir; 102 | // Hook script, will be copied into /tmp. 103 | char *_Nullable hook_script; 104 | // Hook command. 105 | char *_Nullable hook_command[RURI_MAX_COMMANDS + 1]; 106 | // Full ruri container config. 107 | struct RURI_CONTAINER container; 108 | }; 109 | struct RURIMA_DOCKER { 110 | /* 111 | * This is part of docker config that we need. 112 | */ 113 | // Workdir. 114 | char *_Nullable workdir; 115 | // ENV. 116 | char *_Nullable env[RURI_MAX_ENVS + 2]; 117 | // Command. 118 | char *_Nullable command[RURI_MAX_COMMANDS + 1]; 119 | // Entry point. 120 | char *_Nullable entrypoint[RURI_MAX_COMMANDS + 1]; 121 | // Architecture. 122 | char *_Nullable architecture; 123 | }; 124 | // Warnings. 125 | #define rurima_warning(...) \ 126 | { \ 127 | if (!rurima_global_config.quiet) { \ 128 | cfprintf(stderr, "{yellow}In %s() in %s line %d:\n", __func__, __FILE__, __LINE__); \ 129 | cfprintf(stderr, ##__VA_ARGS__) \ 130 | } \ 131 | } 132 | // Show error msg and exit. 133 | #define rurima_error(...) \ 134 | { \ 135 | cfprintf(stderr, "{red}In %s() in %s line %d:\n", __func__, __FILE__, __LINE__); \ 136 | cfprintf(stderr, ##__VA_ARGS__); \ 137 | cfprintf(stderr, "{base}%s{clear}\n", " .^. .^."); \ 138 | cfprintf(stderr, "{base}%s{clear}\n", " /⋀\\_ノ_/⋀\\"); \ 139 | cfprintf(stderr, "{base}%s{clear}\n", " /ノソノ\\ノソ丶)|"); \ 140 | cfprintf(stderr, "{base}%s{clear}\n", " ルリリ > x )リ"); \ 141 | cfprintf(stderr, "{base}%s{clear}\n", "ノノ㇏ ^ ノ|ノ"); \ 142 | cfprintf(stderr, "{base}%s{clear}\n", " ⠁⠁"); \ 143 | cfprintf(stderr, "{base}%s{clear}\n", "RURIMA ERROR MESSAGE"); \ 144 | cfprintf(stderr, "{base}%s{clear}\n", "Hint:"); \ 145 | cfprintf(stderr, "{base}%s{clear}\n", " If you have network problems for lxc or docker subcommand,"); \ 146 | cfprintf(stderr, "{base}%s{clear}\n", " please use -m option to change the mirror."); \ 147 | cfprintf(stderr, "{base}%s{clear}\n", " For docker subcommand, try -f to enable fallback mode."); \ 148 | cfprintf(stderr, "{base}%s{clear}\n", "If you think something is wrong, please report at:"); \ 149 | cfprintf(stderr, "\033[4m{base}%s{clear}\n", "https://github.com/Moe-hacker/rurima/issues"); \ 150 | exit(114); \ 151 | } 152 | // Log system. 153 | // We use a global variable to disable log in runtime. 154 | extern bool disable_rurima_log; 155 | #if defined(RURIMA_DEBUG) 156 | #define rurima_log(...) \ 157 | { \ 158 | if (!disable_rurima_log) { \ 159 | struct timeval tv; \ 160 | gettimeofday(&tv, NULL); \ 161 | cfprintf(stderr, "{green}[%ld.%06ld] in %s() in %s line %d:\n", tv.tv_sec, tv.tv_usec, __func__, __FILE__, __LINE__); \ 162 | cfprintf(stderr, ##__VA_ARGS__) \ 163 | } \ 164 | } 165 | #else 166 | #define rurima_log(...) 167 | #endif 168 | // Functions. 169 | int rurima_fork_execvp(const char *_Nonnull argv[]); 170 | char *rurima_fork_execvp_get_stdout(const char *_Nonnull argv[]); 171 | int rurima_extract_archive(const char *_Nonnull file, const char *_Nonnull dir); 172 | off_t rurima_get_file_size(const char *_Nonnull file); 173 | char *rurima_get_prefix(void); 174 | int rurima_mkdirs(const char *_Nonnull path, mode_t mode); 175 | bool rurima_run_with_root(void); 176 | int rurima_docker_search(const char *_Nonnull image, const char *_Nonnull page_size, bool quiet, const char *_Nullable mirror); 177 | int rurima_docker_search_tag(const char *_Nonnull image, const char *_Nonnull page_size, const char *_Nullable architecture, bool quiet, const char *_Nullable mirror); 178 | struct RURIMA_DOCKER *rurima_docker_pull(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nullable architecture, const char *_Nonnull savedir, const char *_Nullable mirror, bool fallback); 179 | void rurima_register_signal(void); 180 | char *rurima_docker_get_host_arch(void); 181 | char *rurima_lxc_get_host_arch(void); 182 | void rurima_lxc_pull_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nonnull version, const char *_Nullable architecture, const char *_Nullable type, const char *_Nonnull savedir); 183 | void rurima_lxc_get_image_list(const char *_Nullable mirror, const char *_Nullable architecture); 184 | void rurima_lxc_search_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nullable architecture); 185 | void rurima_docker(int argc, char **_Nonnull argv); 186 | void rurima_lxc(int argc, char **_Nonnull argv); 187 | void rurima_unpack(int argc, char **_Nonnull argv); 188 | struct RURIMA *rurima_init_config(void); 189 | void rurima_get_input(char *_Nonnull message, char *_Nonnull buf); 190 | void rurima_check_dep(void); 191 | struct RURIMA_DOCKER *rurima_get_docker_config(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nullable architecture, const char *_Nullable mirror, bool fallback); 192 | void rurima_show_docker_config(struct RURIMA_DOCKER *_Nonnull config, char *_Nullable savedir, char *_Nullable runtime, bool quiet); 193 | void rurima_free_docker_config(struct RURIMA_DOCKER *_Nonnull config); 194 | void rurima_lxc_search_arch(const char *_Nullable mirror, const char *_Nonnull os); 195 | int rurima_docker_search_arch(const char *_Nonnull image, const char *_Nonnull tag, char *_Nullable mirror, bool fallback); 196 | void rurima_show_version_info(void); 197 | void rurima_show_version_code(void); 198 | void rurima_check_dir_deny_list(const char *_Nonnull dir); 199 | char *rurima_strstr_ignore_case(const char *_Nonnull haystack, const char *_Nonnull needle); 200 | int rurima_fork_rexec(char **_Nonnull argv); 201 | void rurima_add_argv(char ***_Nonnull argv, const char *_Nonnull arg); 202 | void *rurima_default_hook(const char *_Nonnull container_dir); 203 | void rurima_read_global_config(void); 204 | bool rurima_rootless_supported(void); 205 | off_t rurima_get_dir_file_size(const char *_Nonnull target); 206 | int rurima_backup_dir(const char *_Nonnull file, const char *_Nonnull dir); 207 | void rurima_backup(int argc, char **_Nonnull argv); 208 | char *rurima_fork_execvp_get_stdout_ignore_err(const char *_Nonnull argv[]); 209 | bool rurima_sha256sum_exists(void); 210 | void rurima_pull(int argc, char **_Nonnull argv); 211 | bool rurima_lxc_have_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nonnull version, const char *_Nullable architecture, const char *_Nullable type); 212 | int rurima_download_file(const char *_Nonnull url, const char *_Nonnull file, const char *_Nullable token, ssize_t size); 213 | bool proot_exist(void); 214 | bool rurima_jq_exists(void); 215 | char *rurima_fork_execvp_get_stdout_with_input(const char *_Nonnull argv[], const char *_Nonnull input); 216 | char *rurima_call_jq(const char *_Nonnull argv[], const char *_Nonnull input); 217 | size_t rurima_split_lines(const char *_Nonnull input, char ***_Nonnull lines); 218 | size_t rurima_split_lines_allow_null(const char *_Nonnull input, char ***_Nonnull lines); -------------------------------------------------------------------------------- /src/include/version.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2022-2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | * 30 | */ 31 | // Version info. 32 | #define RURIMA_VERSION "0.1-unreleased" 33 | #include "../ruri/src/include/version.h" -------------------------------------------------------------------------------- /src/info.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | void rurima_show_version_info(void) 32 | { 33 | /* 34 | * Just show version info and license. 35 | * Version info is defined in macro RURIMA_VERSION. 36 | * RURIMA_COMMIT_ID is defined as -D option of compiler. 37 | */ 38 | cprintf("\n"); 39 | cprintf("{base} ●●●● ● ● ●●●● ●●● ● ● ● \n"); 40 | cprintf("{base} ● ● ● ● ● ● ● ●● ●● ● ● \n"); 41 | cprintf("{base} ●●●● ● ● ●●●● ● ● ● ● ●●●●●\n"); 42 | cprintf("{base} ● ● ● ● ● ● ● ● ● ● ●\n"); 43 | cprintf("{base} ● ● ●●● ● ● ●●● ● ● ● ●\n"); 44 | cprintf("{base} Licensed under the MIT License\n"); 45 | cprintf("{base} \n"); 46 | cprintf("{base} Copyright (C) 2024 Moe-hacker\n\n"); 47 | cprintf("{base}%s%s%s", "rurima version ..................: ", RURIMA_VERSION, "\n"); 48 | #if defined(RURIMA_COMMIT_ID) 49 | cprintf("{base}%s%s%s", "rurima commit id ................: ", RURIMA_COMMIT_ID, "\n"); 50 | #endif 51 | cprintf("{base}%s%s%s", "built-in ruri version ...........: ", RURI_VERSION, "\n"); 52 | #if defined(RURI_COMMIT_ID) 53 | cprintf("{base}%s%s%s", "built-in ruri commit id .........: ", RURI_COMMIT_ID, "\n"); 54 | #endif 55 | #if defined(LIBCAP_MAJOR) && defined(LIBCAP_MINOR) 56 | cprintf("{base}%s%d%s%d%s", "libcap ..........................: ", LIBCAP_MAJOR, ".", LIBCAP_MINOR, "\n"); 57 | #endif 58 | cprintf("{base}%s%s\n", "Compiler version ................: ", __VERSION__); 59 | cprintf("{base}%s%s\n", "Build date ......................: ", __TIMESTAMP__); 60 | cprintf("{base}\nThere is NO WARRANTY, to the extent permitted by law\n"); 61 | cprintf("{base}Docker is a registered trademark of Docker, Inc\nThis program has no relationship with it\n"); 62 | cprintf("{clear}\n"); 63 | } 64 | // For `rurima -V`. 65 | void rurima_show_version_code(void) 66 | { 67 | /* 68 | * The version code is not standard now, 69 | * so in fact it's very useless. 70 | * Maybe it can be useful one day... 71 | */ 72 | cprintf("%s\n", RURIMA_VERSION); 73 | } -------------------------------------------------------------------------------- /src/lxcmirror.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | /* 32 | * /meta/1.0/index-system format: 33 | * 34 | * os;version;arch;type;time;dir 35 | * os;version;arch;type;time;dir 36 | * ........ 37 | */ 38 | static char *line_get_value(const char *_Nonnull line, int index) 39 | { 40 | /* 41 | * Get the value of the index in the line. 42 | * Warning: free() the return value after use. 43 | * 44 | * Example: 45 | * line = "a;b;c;d;e;f" 46 | * line_get_value(line, 2) will return "c" 47 | * 48 | */ 49 | char *tmp = strdup(line); 50 | char *p = tmp; 51 | // Skip index number of ';'. 52 | for (int i = 0; i < index; i++) { 53 | p = strchr(p, ';'); 54 | if (p == NULL) { 55 | free(tmp); 56 | return NULL; 57 | } 58 | p++; 59 | } 60 | if (strchr(p, ';') == NULL) { 61 | if (strchr(p, '\n') != NULL) { 62 | *strchr(p, '\n') = '\0'; 63 | } 64 | } else { 65 | *strchr(p, ';') = '\0'; 66 | } 67 | char *ret = strdup(p); 68 | free(tmp); 69 | return ret; 70 | } 71 | static const char *goto_next_line(const char *_Nonnull buf) 72 | { 73 | /* 74 | * Return the pointer to the next line. 75 | * If no next line, return NULL. 76 | */ 77 | const char *p = strchr(buf, '\n'); 78 | if (p == NULL) { 79 | return NULL; 80 | } 81 | return p + 1; 82 | } 83 | static char *get_current_line(const char *_Nonnull buf) 84 | { 85 | /* 86 | * Warning: free() the return value after use. 87 | * 88 | * Get the current line. 89 | * If no line, return NULL. 90 | * 91 | */ 92 | char *tmp = strdup(buf); 93 | char *p = strchr(tmp, '\n'); 94 | if (p == NULL) { 95 | free(tmp); 96 | return NULL; 97 | } 98 | *p = '\0'; 99 | char *ret = strdup(tmp); 100 | free(tmp); 101 | return ret; 102 | } 103 | static char *get_lxc_index(const char *_Nullable mirror) 104 | { 105 | /* 106 | * Warning: free() the return value after use. 107 | * 108 | * Get lxc index info from mirror. 109 | * 110 | * Return: The index info. 111 | * 112 | */ 113 | char url[4096]; 114 | if (mirror == NULL) { 115 | sprintf(url, "https://%s/meta/1.0/index-system", rurima_global_config.lxc_mirror); 116 | } else { 117 | sprintf(url, "https://%s/meta/1.0/index-system", mirror); 118 | } 119 | const char *command[] = { "curl", "-L", "-s", url, NULL }; 120 | return rurima_fork_execvp_get_stdout(command); 121 | } 122 | static char *lxc_get_image_dir(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nonnull version, const char *_Nullable architecture, const char *_Nullable type) 123 | { 124 | /* 125 | * Get the image dir from mirror. 126 | * 127 | * Return: The image dir. 128 | * If not found, return NULL. 129 | */ 130 | char *buf = get_lxc_index(mirror); 131 | const char *p = buf; 132 | if (p == NULL) { 133 | free(buf); 134 | return NULL; 135 | } 136 | if (architecture == NULL) { 137 | architecture = rurima_lxc_get_host_arch(); 138 | } 139 | rurima_log("arch: %s\n", architecture); 140 | if (type == NULL) { 141 | type = "default"; 142 | } 143 | char *str_to_find = malloc(strlen(os) + strlen(version) + strlen(architecture) + strlen(type) + 10); 144 | sprintf(str_to_find, "%s;%s;%s;%s", os, version, architecture, type); 145 | rurima_log("str to find: %s\n", str_to_find); 146 | if (strstr(p, str_to_find) == NULL) { 147 | free(str_to_find); 148 | free(buf); 149 | return NULL; 150 | } 151 | p = strstr(p, str_to_find); 152 | p = strchr(p, '/'); 153 | char *tmp = strdup(p); 154 | for (size_t i = 0; i < strlen(tmp); i++) { 155 | if (tmp[i] == '\n') { 156 | tmp[i] = '\0'; 157 | break; 158 | } 159 | } 160 | char *ret = strdup(tmp); 161 | free(str_to_find); 162 | free(tmp); 163 | free(buf); 164 | return ret; 165 | } 166 | void rurima_lxc_get_image_list(const char *_Nullable mirror, const char *_Nullable architecture) 167 | { 168 | /* 169 | * Get the image list from mirror, 170 | * and show them. 171 | */ 172 | if (architecture == NULL) { 173 | architecture = rurima_lxc_get_host_arch(); 174 | } 175 | if (mirror == NULL) { 176 | mirror = rurima_global_config.lxc_mirror; 177 | } 178 | char *buf = get_lxc_index(mirror); 179 | const char *p = buf; 180 | char *line = NULL; 181 | if (p == NULL) { 182 | free(buf); 183 | rurima_error("{red}Failed to get index.\n"); 184 | } 185 | bool found = false; 186 | while ((line = get_current_line(p)) != NULL) { 187 | char *os = line_get_value(line, 0); 188 | char *version = line_get_value(line, 1); 189 | char *arch = line_get_value(line, 2); 190 | char *type = line_get_value(line, 3); 191 | if (strcmp(arch, architecture) != 0) { 192 | free(os); 193 | free(version); 194 | free(arch); 195 | free(type); 196 | free(line); 197 | p = goto_next_line(p); 198 | continue; 199 | } 200 | found = true; 201 | if (!rurima_global_config.quiet) { 202 | cprintf("{yellow}%-13s {green}: {purple}%-10s {green}: {cyan}%-8s\n", os, version, type); 203 | } else { 204 | printf("%-13s : %-10s : %-8s\n", os, version, type); 205 | } 206 | free(os); 207 | free(version); 208 | free(arch); 209 | free(type); 210 | free(line); 211 | p = goto_next_line(p); 212 | } 213 | if (!found) { 214 | rurima_error("{red}No image found.\n"); 215 | } 216 | free(buf); 217 | } 218 | void rurima_lxc_search_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nullable architecture) 219 | { 220 | /* 221 | * Search the image from mirror, 222 | * and show them. 223 | */ 224 | if (architecture == NULL) { 225 | architecture = rurima_lxc_get_host_arch(); 226 | } 227 | if (mirror == NULL) { 228 | mirror = rurima_global_config.lxc_mirror; 229 | } 230 | char *buf = get_lxc_index(mirror); 231 | const char *p = buf; 232 | char *line = NULL; 233 | if (p == NULL) { 234 | free(buf); 235 | rurima_error("{red}Failed to get index.\n"); 236 | } 237 | bool found = false; 238 | while ((line = get_current_line(p)) != NULL) { 239 | char *os_cur = line_get_value(line, 0); 240 | char *version = line_get_value(line, 1); 241 | char *arch = line_get_value(line, 2); 242 | char *type = line_get_value(line, 3); 243 | if (strcmp(arch, architecture) != 0 || strcmp(os_cur, os) != 0) { 244 | free(os_cur); 245 | free(version); 246 | free(arch); 247 | free(type); 248 | free(line); 249 | p = goto_next_line(p); 250 | continue; 251 | } 252 | found = true; 253 | if (!rurima_global_config.quiet) { 254 | cprintf("{yellow}%-13s {green}: {purple}%-10s {green}: {cyan}%-8s\n", os, version, type); 255 | } else { 256 | printf("%-13s : %-10s : %-8s\n", os, version, type); 257 | } 258 | free(os_cur); 259 | free(version); 260 | free(arch); 261 | free(type); 262 | free(line); 263 | p = goto_next_line(p); 264 | } 265 | free(buf); 266 | if (!found) { 267 | rurima_error("{red}No image found.\n"); 268 | } 269 | } 270 | bool rurima_lxc_have_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nonnull version, const char *_Nullable architecture, const char *_Nullable type) 271 | { 272 | /* 273 | * Check if the image is available in mirror. 274 | * 275 | * Return: true if available, false if not. 276 | */ 277 | if (architecture == NULL) { 278 | architecture = rurima_lxc_get_host_arch(); 279 | } 280 | if (mirror == NULL) { 281 | mirror = rurima_global_config.lxc_mirror; 282 | } 283 | if (type == NULL) { 284 | type = "default"; 285 | } 286 | if (architecture == NULL) { 287 | architecture = rurima_lxc_get_host_arch(); 288 | } 289 | char *dir = lxc_get_image_dir(mirror, os, version, architecture, type); 290 | if (dir == NULL) { 291 | return false; 292 | } 293 | free(dir); 294 | return true; 295 | } 296 | void rurima_lxc_pull_image(const char *_Nullable mirror, const char *_Nonnull os, const char *_Nonnull version, const char *_Nullable architecture, const char *_Nullable type, const char *_Nonnull savedir) 297 | { 298 | /* 299 | * Pull the rootfs.tar.xz from mirror, 300 | * and extract it to savedir. 301 | */ 302 | rurima_check_dir_deny_list(savedir); 303 | char *dir = lxc_get_image_dir(mirror, os, version, architecture, type); 304 | if (dir == NULL) { 305 | rurima_error("{red}Image not found.\n"); 306 | } 307 | if (rurima_mkdirs(savedir, 0755) == -1) { 308 | rurima_error("{red}Failed to create directory.\n"); 309 | } 310 | chdir(savedir); 311 | if (mirror == NULL) { 312 | mirror = rurima_global_config.lxc_mirror; 313 | } 314 | char *url = malloc(strlen(mirror) + strlen(dir) + 114); 315 | sprintf(url, "https://%s/%srootfs.tar.xz", mirror, dir); 316 | cprintf("{base}Pulling {cyan}rootfs.tar.xz\n"); 317 | rurima_download_file(url, "rootfs.tar.xz", NULL, -1); 318 | rurima_extract_archive("rootfs.tar.xz", "."); 319 | remove("rootfs.tar.xz"); 320 | free(url); 321 | free(dir); 322 | } 323 | void rurima_lxc_search_arch(const char *_Nullable mirror, const char *_Nonnull os) 324 | { 325 | /* 326 | * Search the architecture of the image from mirror, 327 | * and show them. 328 | */ 329 | if (mirror == NULL) { 330 | mirror = rurima_global_config.lxc_mirror; 331 | } 332 | char *buf = get_lxc_index(mirror); 333 | const char *p = buf; 334 | char *line = NULL; 335 | if (p == NULL) { 336 | free(buf); 337 | rurima_error("{red}Failed to get index.\n"); 338 | } 339 | bool found = false; 340 | while ((line = get_current_line(p)) != NULL) { 341 | char *os_cur = line_get_value(line, 0); 342 | char *version = line_get_value(line, 1); 343 | char *arch = line_get_value(line, 2); 344 | char *type = line_get_value(line, 3); 345 | if (strcmp(os_cur, os) != 0) { 346 | free(os_cur); 347 | free(version); 348 | free(arch); 349 | free(type); 350 | free(line); 351 | p = goto_next_line(p); 352 | continue; 353 | } 354 | found = true; 355 | if (!rurima_global_config.quiet) { 356 | cprintf("{yellow}%-13s {green}: {purple}%-10s {green}: {cyan}%-8s\n", os, version, arch); 357 | } else { 358 | printf("%-13s : %-10s : %-8s\n", os, version, arch); 359 | } 360 | free(os_cur); 361 | free(version); 362 | free(arch); 363 | free(type); 364 | free(line); 365 | p = goto_next_line(p); 366 | } 367 | free(buf); 368 | if (!found) { 369 | rurima_error("{red}No image found.\n"); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | bool disable_rurima_log = false; 32 | // clang-format off 33 | struct RURIMA_CONFIG rurima_global_config = { 34 | .docker_mirror = "registry-1.docker.io", 35 | .lxc_mirror = "images.linuxcontainers.org", 36 | .hook_script = NULL, 37 | .quiet = false, 38 | .no_process = false, 39 | }; 40 | // clang-format on 41 | static void show_help(void) 42 | { 43 | cprintf("{base}Usage: rurima [subcommand] [options]\n"); 44 | cprintf("{base}Subcommands:\n"); 45 | cprintf("{base} docker, d : DockerHub support.\n"); 46 | cprintf("{base} lxc, l : LXC mirror support.\n"); 47 | cprintf("{base} pull, p : Pull image, as a wrap of rurima docker/lxc pull.\n"); 48 | cprintf("{base} unpack, u : Unpack rootfs.\n"); 49 | cprintf("{base} backup, b : Backup rootfs.\n"); 50 | cprintf("{base} ruri, r : Built-in ruri command.\n"); 51 | cprintf("{base} help, h : Show help message.\n"); 52 | cprintf("{base} dep : Check dependencies.\n"); 53 | cprintf("{base}Options:\n"); 54 | cprintf("{base} -h, --help: Show help message.\n"); 55 | cprintf("{base} -v, --version: Show version info.\n"); 56 | cprintf("{base} -V, --version-code: Show version code.\n"); 57 | cprintf("{base}See rurima [subcommand] help for further information.\n"); 58 | cprintf("{base}Use `rurima -q subcommand` to disable log in rurima-dbg.\n"); 59 | } 60 | static void rurima_dep_info(void) 61 | { 62 | const char *tar_command[] = { "tar", "--version", NULL }; 63 | const char *curl_command[] = { "curl", "--version", NULL }; 64 | const char *file_command[] = { "file", "--version", NULL }; 65 | const char *gz_command[] = { "gzip", "--version", NULL }; 66 | const char *xz_command[] = { "xz", "-V", NULL }; 67 | const char *file_command_2[] = { "file", "--brief", "--mime-type", "/proc/self/exe", NULL }; 68 | const char *proot_command[] = { "proot", "-V", NULL }; 69 | const char *sha256_command[] = { "sha256sum", "--version", NULL }; 70 | const char *jq_command[] = { "jq", "--version", NULL }; 71 | char *result = NULL; 72 | result = rurima_fork_execvp_get_stdout(tar_command); 73 | if (result == NULL) { 74 | cprintf("{base}tar : {red}not found\n"); 75 | } else { 76 | cprintf("{base}tar :{clear}{green} found\n{blue}%s", result); 77 | } 78 | free(result); 79 | result = rurima_fork_execvp_get_stdout(curl_command); 80 | if (result == NULL) { 81 | cprintf("{base}curl : {red}not found\n"); 82 | } else { 83 | cprintf("{base}curl :{clear}{green} found\n{blue}%s", result); 84 | } 85 | free(result); 86 | result = rurima_fork_execvp_get_stdout(file_command); 87 | if (result == NULL) { 88 | cprintf("{base}file : {red}not found\n"); 89 | } else { 90 | cprintf("{base}file :{clear}{green} found\n{blue}%s", result); 91 | } 92 | free(result); 93 | result = rurima_fork_execvp_get_stdout(file_command_2); 94 | if (result == NULL) { 95 | cprintf("{base}file : {red}not support --brief --mime-type\n"); 96 | } else { 97 | cprintf("\n{blue}file --brief --mime-type /proc/self/exe\n%s", result); 98 | } 99 | free(result); 100 | result = rurima_fork_execvp_get_stdout(gz_command); 101 | if (result == NULL) { 102 | cprintf("{base}gzip : {red}not found\n"); 103 | } else { 104 | cprintf("{base}gzip :{clear}{green} found\n{blue}%s", result); 105 | } 106 | free(result); 107 | result = rurima_fork_execvp_get_stdout(xz_command); 108 | if (result == NULL) { 109 | cprintf("{base}xz : {red}not found\n"); 110 | } else { 111 | cprintf("{base}xz :{clear}{green} found\n{blue}%s", result); 112 | } 113 | free(result); 114 | result = rurima_fork_execvp_get_stdout(proot_command); 115 | if (result == NULL) { 116 | cprintf("{base}proot : {yellow}not found\n"); 117 | } else { 118 | cprintf("{base}proot :{clear}{green} found\n{blue}%s", result); 119 | } 120 | free(result); 121 | result = rurima_fork_execvp_get_stdout(sha256_command); 122 | if (result == NULL) { 123 | cprintf("{base}sha256sum : {yellow}not found\n"); 124 | } else { 125 | cprintf("{base}sha256sum :{clear}{green} found\n{blue}%s", result); 126 | } 127 | free(result); 128 | result = rurima_fork_execvp_get_stdout(jq_command); 129 | if (result == NULL) { 130 | cprintf("{base}jq : {yellow}not found\n"); 131 | } else { 132 | cprintf("{base}jq :{clear}{green} found\n{blue}%s", result); 133 | } 134 | free(result); 135 | } 136 | static void detect_suid_or_capability(void) 137 | { 138 | struct stat st; 139 | if (stat("/proc/self/exe", &st) == 0) { 140 | if (((st.st_mode & S_ISUID) || (st.st_mode & S_ISGID)) && (geteuid() == 0 || getegid() == 0)) { 141 | rurima_error("{red}SUID or SGID bit detected on rurima, this is unsafe desu QwQ\n"); 142 | } 143 | } 144 | cap_t caps = cap_get_file("/proc/self/exe"); 145 | if (caps == NULL) { 146 | return; 147 | } 148 | char *caps_str = cap_to_text(caps, NULL); 149 | if (caps_str == NULL) { 150 | return; 151 | } 152 | if (strlen(caps_str) > 0) { 153 | rurima_error("{red}Capabilities detected on rurima, this is unsafe desu QwQ\n"); 154 | } 155 | cap_free(caps); 156 | cap_free(caps_str); 157 | } 158 | static int pmcrts(const char *s1, const char *s2) 159 | { 160 | /* 161 | * 162 | * Compare two strings, but s2 is in the end of s1. 163 | * For example, 164 | * s1 = "./ruri", s2 = "ruri", it will return 0. 165 | * s1 = "./rurima", s2 = "ruri", it will return... 'a' - 'i', 166 | * anyway, it's not 0 :) 167 | * If s1 is shorter than s2, it will return -1. 168 | * 169 | */ 170 | size_t len1 = strlen(s1); 171 | size_t len2 = strlen(s2); 172 | if (len1 < len2) { 173 | return -1; // s1 is shorter than s2 174 | } 175 | for (size_t i = len1; i > len1 - len2; i--) { 176 | if (s1[i] != s2[i - (len1 - len2)]) { 177 | return s1[i] - s2[i - (len1 - len2)]; 178 | } 179 | } 180 | return 0; // s1 ends with s2 181 | } 182 | int main(int argc, char **argv) 183 | { 184 | // Check for argv[0], if it's ruri, we will only call built-in ruri. 185 | if (argc > 0 && pmcrts(argv[0], "ruri") == 0) { 186 | rurima_log("{green}Detected ruri mode, calling built-in ruri.\n"); 187 | return ruri(argc, argv); 188 | } 189 | detect_suid_or_capability(); 190 | rurima_read_global_config(); 191 | #ifdef RURIMA_DEV 192 | rurima_warning("{red}You are using dev/debug build, if you think this is wrong, please rebuild rurima or get it from release page.\n"); 193 | #endif 194 | rurima_register_signal(); 195 | if (argc == 1) { 196 | show_help(); 197 | return 0; 198 | } 199 | for (int i = 1; i < argc; i++) { 200 | if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) { 201 | disable_rurima_log = true; 202 | continue; 203 | } 204 | if (strcmp(argv[i], "docker") == 0 || strcmp(argv[i], "d") == 0) { 205 | rurima_check_dep(); 206 | if (i + 1 >= argc) { 207 | rurima_error("{red}No subcommand specified!\n"); 208 | } 209 | rurima_docker(argc - i - 1, &argv[i + 1]); 210 | return 0; 211 | } 212 | if (strcmp(argv[i], "lxc") == 0 || strcmp(argv[i], "l") == 0) { 213 | rurima_check_dep(); 214 | if (i + 1 >= argc) { 215 | rurima_error("{red}No subcommand specified!\n"); 216 | } 217 | rurima_lxc(argc - i - 1, &argv[i + 1]); 218 | return 0; 219 | } 220 | if (strcmp(argv[i], "pull") == 0 || strcmp(argv[i], "p") == 0) { 221 | rurima_check_dep(); 222 | rurima_pull(argc - i - 1, &argv[i + 1]); 223 | return 0; 224 | } 225 | if (strcmp(argv[i], "backup") == 0 || strcmp(argv[i], "b") == 0) { 226 | rurima_check_dep(); 227 | rurima_backup(argc - i - 1, &argv[i + 1]); 228 | return 0; 229 | } 230 | if (strcmp(argv[i], "unpack") == 0 || strcmp(argv[i], "u") == 0) { 231 | rurima_check_dep(); 232 | rurima_unpack(argc - i - 1, &argv[i + 1]); 233 | return 0; 234 | } 235 | if (strcmp(argv[i], "dep") == 0) { 236 | rurima_dep_info(); 237 | return 0; 238 | } 239 | if (strcmp(argv[i], "help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 240 | show_help(); 241 | return 0; 242 | } 243 | if (strcmp(argv[i], "version") == 0 || strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) { 244 | rurima_show_version_info(); 245 | return 0; 246 | } 247 | if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version-code") == 0) { 248 | rurima_show_version_code(); 249 | return 0; 250 | } 251 | if (strcmp(argv[i], "ruri") == 0 || strcmp(argv[i], "r") == 0) { 252 | unsetenv("LD_PRELOAD"); 253 | pid_t pid = fork(); 254 | if (pid == 0) { 255 | ruri(argc - i, &argv[i]); 256 | } else { 257 | int status; 258 | waitpid(pid, &status, 0); 259 | exit(WEXITSTATUS(status)); 260 | } 261 | return 0; 262 | } 263 | show_help(); 264 | rurima_error("{red}Invalid subcommand `%s`!\n", argv[i]); 265 | return 1; 266 | } 267 | return 0; 268 | } 269 | -------------------------------------------------------------------------------- /src/shared.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | bool proot_exist(void) 32 | { 33 | /* 34 | * Test if proot exist. 35 | * We use proot to execute ls, so that we can check if proot is really available. 36 | */ 37 | const char *cmd[] = { "proot", "ls", NULL }; 38 | char *ret = rurima_fork_execvp_get_stdout(cmd); 39 | if (ret == NULL) { 40 | rurima_log("{red}proot not found.\n"); 41 | return false; 42 | } 43 | free(ret); 44 | rurima_log("{green}proot found.\n"); 45 | return true; 46 | } 47 | char *rurima_strstr_ignore_case(const char *_Nonnull haystack, const char *_Nonnull needle) 48 | { 49 | /* 50 | * An implementation of strccasestr(). 51 | */ 52 | char *haystack_lower = strdup(haystack); 53 | char *needle_lower = strdup(needle); 54 | char *ret = NULL; 55 | for (size_t i = 0; i < strlen(haystack_lower); i++) { 56 | haystack_lower[i] = tolower(haystack_lower[i]); 57 | } 58 | for (size_t i = 0; i < strlen(needle_lower); i++) { 59 | needle_lower[i] = tolower(needle_lower[i]); 60 | } 61 | if (strstr(haystack_lower, needle_lower) == NULL) { 62 | free(haystack_lower); 63 | free(needle_lower); 64 | return NULL; 65 | } 66 | ret = (char *)((strstr(haystack_lower, needle_lower) - haystack_lower) + haystack); 67 | free(haystack_lower); 68 | free(needle_lower); 69 | return ret; 70 | } 71 | static char *get_dir_realpath(const char *_Nonnull dir) 72 | { 73 | /* 74 | * Get the realpath of the savedir. 75 | * Warning: free() the return value after use. 76 | */ 77 | char *ret = malloc(PATH_MAX + strlen(dir) + 1); 78 | if (strchr(dir, '/') == dir) { 79 | sprintf(ret, "%s", dir); 80 | } else { 81 | getcwd(ret, PATH_MAX); 82 | strcat(ret, "/"); 83 | if (strstr(dir, "./") == dir) { 84 | strcat(ret, dir + 2); 85 | } else { 86 | strcat(ret, dir); 87 | } 88 | } 89 | return ret; 90 | } 91 | void rurima_check_dir_deny_list(const char *_Nonnull dir) 92 | { 93 | /* 94 | * Check if the directory is in deny list. 95 | * If it is, we will refuse to extract the archive. 96 | * To protect the host system, we refuse to extract rootfs to unsafe directories. 97 | */ 98 | char *path = get_dir_realpath(dir); 99 | rurima_log("{base}Realpath: {cyan}%s{clear}\n", path); 100 | if (strcmp(path, "/") == 0) { 101 | rurima_error("{red}Refuse to extract rootfs to /\n"); 102 | } 103 | if (strstr(path, "/usr/") == path || strcmp(path, "/usr") == 0) { 104 | rurima_error("{red}Refuse to extract rootfs to /usr/*\n"); 105 | } 106 | if (strstr(path, "/etc/") == path || strcmp(path, "/etc") == 0) { 107 | rurima_error("{red}Refuse to extract rootfs to /etc/*\n"); 108 | } 109 | if (strstr(path, "/bin/") == path || strcmp(path, "/bin") == 0) { 110 | rurima_error("{red}Refuse to extract rootfs to /bin/*\n"); 111 | } 112 | if (strstr(path, "/lib/") == path || strcmp(path, "/lib") == 0) { 113 | rurima_error("{red}Refuse to extract rootfs to /lib/*\n"); 114 | } 115 | if (strstr(path, "/lib64/") == path || strcmp(path, "/lib64") == 0) { 116 | rurima_error("{red}Refuse to extract rootfs to /lib64/*\n"); 117 | } 118 | if (strstr(path, "/sbin/") == path || strcmp(path, "/sbin") == 0) { 119 | rurima_error("{red}Refuse to extract rootfs to /sbin/*\n"); 120 | } 121 | if (strstr(path, "/boot/") == path || strcmp(path, "/boot") == 0) { 122 | rurima_error("{red}Refuse to extract rootfs to /boot/*\n"); 123 | } 124 | if (strstr(path, "/dev/") == path || strcmp(path, "/dev") == 0) { 125 | rurima_error("{red}Refuse to extract rootfs to /dev/*\n"); 126 | } 127 | if (strstr(path, "/proc/") == path || strcmp(path, "/proc") == 0) { 128 | rurima_error("{red}Refuse to extract rootfs to /proc/*\n"); 129 | } 130 | if (strstr(path, "/sys/") == path || strcmp(path, "/sys") == 0) { 131 | rurima_error("{red}Refuse to extract rootfs to /sys/*\n"); 132 | } 133 | if (strstr(path, "/snap/") == path || strcmp(path, "/snap") == 0) { 134 | rurima_error("{red}Refuse to extract rootfs to /snap/*\n"); 135 | } 136 | if (strcmp(path, "/data") == 0 || strcmp(path, "/data/") == 0 || strcmp(path, "/data/.") == 0 || strcmp(path, "/data/./") == 0) { 137 | rurima_error("{red}Refuse to extract rootfs to /data\n"); 138 | } 139 | free(path); 140 | } 141 | void rurima_get_input(char *_Nonnull message, char *_Nonnull buf) 142 | { 143 | /* 144 | * Warning: Not a safe function. 145 | */ 146 | cprintf("%s", message); 147 | fflush(stdout); 148 | fflush(stdin); 149 | scanf("%s", buf); 150 | fflush(stdout); 151 | } 152 | off_t rurima_get_file_size(const char *_Nonnull file) 153 | { 154 | rurima_log("{base}Getting file size of {cyan}%s\n", file); 155 | struct stat st; 156 | if (stat(file, &st) == -1) { 157 | rurima_log("{red}Failed to get file size!\n"); 158 | return 0; 159 | } 160 | rurima_log("{base}File size: {green}%ld\n", st.st_size); 161 | return st.st_size; 162 | } 163 | char *rurima_get_prefix(void) 164 | { 165 | /* 166 | * Warning: free() the return value after use. 167 | */ 168 | char *ret = getenv("PREFIX"); 169 | if (ret == NULL) { 170 | ret = strdup("/"); 171 | } 172 | return ret; 173 | } 174 | int rurima_mkdirs(const char *_Nonnull path, mode_t mode) 175 | { 176 | /* 177 | * A very simple implementation of mkdir -p. 178 | * I don't know why it seems that there isn't an existing function to do this... 179 | */ 180 | remove(path); 181 | unlink(path); 182 | DIR *test = opendir(path); 183 | if (test != NULL) { 184 | closedir(test); 185 | return 0; 186 | } 187 | char buf[PATH_MAX]; 188 | int ret = 0; 189 | /* If dir is path/to/mkdir 190 | * We do: 191 | * ret = mkdir("path",mode); 192 | * ret = mkdir("path/to",mode); 193 | * ret = mkdir("path/to/mkdir",mode); 194 | * return ret; 195 | */ 196 | for (size_t i = 1; i < strlen(path); i++) { 197 | if (path[i] == '/') { 198 | for (size_t j = 0; j < i; j++) { 199 | buf[j] = path[j]; 200 | buf[j + 1] = '\0'; 201 | } 202 | ret = mkdir(buf, mode); 203 | } 204 | } 205 | // If the end of `dir` is not '/', create the last level of the directory. 206 | if (path[strlen(path) - 1] != '/') { 207 | ret = mkdir(path, mode); 208 | } 209 | return ret; 210 | } 211 | bool rurima_run_with_root(void) 212 | { 213 | if (geteuid() == 0) { 214 | return true; 215 | } 216 | return false; 217 | } 218 | char *rurima_docker_get_host_arch(void) 219 | { 220 | /* 221 | * Get the cpu arch. 222 | * We just need to check the macro we have. 223 | * 224 | * Do not free() the returned value. 225 | */ 226 | char *ret = NULL; 227 | #if defined(__aarch64__) 228 | ret = "arm64"; 229 | #endif 230 | #if defined(__alpha__) 231 | ret = "alpha"; 232 | #endif 233 | #if defined(__arm__) 234 | ret = "arm"; 235 | #endif 236 | #if defined(__armeb__) 237 | ret = "arm"; 238 | #endif 239 | #if defined(__cris__) 240 | ret = "cris"; 241 | #endif 242 | #if defined(__hexagon__) 243 | ret = "hexagon"; 244 | #endif 245 | #if defined(__hppa__) 246 | ret = "hppa"; 247 | #endif 248 | #if defined(__i386__) 249 | ret = "386"; 250 | #endif 251 | #if defined(__loongarch64__) 252 | ret = "loong64"; 253 | #endif 254 | #if defined(__m68k__) 255 | ret = "m68k"; 256 | #endif 257 | #if defined(__microblaze__) 258 | ret = "microblaze"; 259 | #endif 260 | #if defined(__mips__) 261 | ret = "mips"; 262 | #endif 263 | #if defined(__mips64__) 264 | ret = "mips64"; 265 | #endif 266 | #if defined(__mips64el__) 267 | ret = "mips64el"; 268 | #endif 269 | #if defined(__mipsel__) 270 | ret = "mipsel"; 271 | #endif 272 | #if defined(__mipsn32__) 273 | ret = "mipsn32"; 274 | #endif 275 | #if defined(__mipsn32el__) 276 | ret = "mipsn32el"; 277 | #endif 278 | #if defined(__ppc__) 279 | ret = "ppc"; 280 | #endif 281 | #if defined(__ppc64__) 282 | ret = "ppc64"; 283 | #endif 284 | #if defined(__ppc64le__) 285 | ret = "ppc64le"; 286 | #endif 287 | #if defined(__riscv32__) 288 | ret = "riscv32"; 289 | #endif 290 | #if defined(__riscv64__) 291 | ret = "riscv64"; 292 | #endif 293 | #if defined(__s390x__) 294 | ret = "s390x"; 295 | #endif 296 | #if defined(__sh4__) 297 | ret = "sh4"; 298 | #endif 299 | #if defined(__sh4eb__) 300 | ret = "sh4eb"; 301 | #endif 302 | #if defined(__sparc__) 303 | ret = "sparc"; 304 | #endif 305 | #if defined(__sparc32plus__) 306 | ret = "sparc32plus"; 307 | #endif 308 | #if defined(__sparc64__) 309 | ret = "sparc64"; 310 | #endif 311 | #if defined(__x86_64__) 312 | ret = "amd64"; 313 | #endif 314 | #if defined(__xtensa__) 315 | ret = "xtensa"; 316 | #endif 317 | #if defined(__xtensaeb__) 318 | ret = "xtensaeb"; 319 | #endif 320 | if (ret == NULL) { 321 | rurima_error("{red}Unknow cpu arch!\n"); 322 | } 323 | return ret; 324 | } 325 | char *rurima_lxc_get_host_arch(void) 326 | { 327 | /* 328 | * Get the cpu arch. 329 | * We just need to check the macro we have. 330 | * 331 | * Do not free() the returned value. 332 | */ 333 | char *ret = NULL; 334 | #if defined(__aarch64__) 335 | ret = "arm64"; 336 | #endif 337 | #if defined(__alpha__) 338 | ret = "alpha"; 339 | #endif 340 | #if defined(__arm__) 341 | ret = "armhf"; 342 | #endif 343 | #if defined(__armeb__) 344 | ret = "armeabi"; 345 | #endif 346 | #if defined(__cris__) 347 | ret = "cris"; 348 | #endif 349 | #if defined(__hexagon__) 350 | ret = "hexagon"; 351 | #endif 352 | #if defined(__hppa__) 353 | ret = "hppa"; 354 | #endif 355 | #if defined(__i386__) 356 | ret = "i386"; 357 | #endif 358 | #if defined(__loongarch64__) 359 | ret = "loongarch64"; 360 | #endif 361 | #if defined(__m68k__) 362 | ret = "m68k"; 363 | #endif 364 | #if defined(__microblaze__) 365 | ret = "microblaze"; 366 | #endif 367 | #if defined(__mips__) 368 | ret = "mips"; 369 | #endif 370 | #if defined(__mips64__) 371 | ret = "mips64"; 372 | #endif 373 | #if defined(__mips64el__) 374 | ret = "mips64el"; 375 | #endif 376 | #if defined(__mipsel__) 377 | ret = "mipsel"; 378 | #endif 379 | #if defined(__mipsn32__) 380 | ret = "mipsn32"; 381 | #endif 382 | #if defined(__mipsn32el__) 383 | ret = "mipsn32el"; 384 | #endif 385 | #if defined(__ppc__) 386 | ret = "ppc"; 387 | #endif 388 | #if defined(__ppc64__) 389 | ret = "ppc64"; 390 | #endif 391 | #if defined(__ppc64le__) 392 | ret = "ppc64le"; 393 | #endif 394 | #if defined(__riscv32__) 395 | ret = "riscv32"; 396 | #endif 397 | #if defined(__riscv64__) 398 | ret = "riscv64"; 399 | #endif 400 | #if defined(__s390x__) 401 | ret = "s390x"; 402 | #endif 403 | #if defined(__sh4__) 404 | ret = "sh4"; 405 | #endif 406 | #if defined(__sh4eb__) 407 | ret = "sh4eb"; 408 | #endif 409 | #if defined(__sparc__) 410 | ret = "sparc"; 411 | #endif 412 | #if defined(__sparc32plus__) 413 | ret = "sparc32plus"; 414 | #endif 415 | #if defined(__sparc64__) 416 | ret = "sparc64"; 417 | #endif 418 | #if defined(__x86_64__) 419 | ret = "amd64"; 420 | #endif 421 | #if defined(__xtensa__) 422 | ret = "xtensa"; 423 | #endif 424 | #if defined(__xtensaeb__) 425 | ret = "xtensaeb"; 426 | #endif 427 | if (ret == NULL) { 428 | rurima_error("{red}Unknow cpu arch!\n"); 429 | } 430 | return ret; 431 | } 432 | bool rurima_rootless_supported(void) 433 | { 434 | pid_t pid = fork(); 435 | if (pid == 0) { 436 | if (getuid() == 0) { 437 | setuid(1145); 438 | } 439 | if (unshare(CLONE_NEWUSER) == -1) { 440 | exit(114); 441 | } 442 | exit(0); 443 | } 444 | int status; 445 | waitpid(pid, &status, 0); 446 | if (WEXITSTATUS(status) == 0) { 447 | return true; 448 | } 449 | return false; 450 | } 451 | off_t rurima_get_dir_file_size(const char *_Nonnull target) 452 | { 453 | /* 454 | * Get the size of the directory or file. 455 | */ 456 | const char *command[] = { "du", "-s", target, NULL }; 457 | char *result = rurima_fork_execvp_get_stdout_ignore_err(command); 458 | if (result == NULL) { 459 | return 0; 460 | } 461 | off_t ret = 0; 462 | ret = atol(result); 463 | return ret * 1024; 464 | } 465 | bool rurima_sha256sum_exists(void) 466 | { 467 | /* 468 | * Check if sha256sum exists. 469 | */ 470 | const char *command[] = { "sha256sum", "-v", NULL }; 471 | char *result = rurima_fork_execvp_get_stdout_ignore_err(command); 472 | if (result == NULL) { 473 | return false; 474 | } 475 | free(result); 476 | return true; 477 | } 478 | bool rurima_jq_exists(void) 479 | { 480 | /* 481 | * Check if jq exists. 482 | */ 483 | const char *command[] = { "jq", "--version", NULL }; 484 | char *result = rurima_fork_execvp_get_stdout(command); 485 | if (result == NULL) { 486 | return false; 487 | } 488 | free(result); 489 | return true; 490 | } 491 | size_t rurima_split_lines(const char *_Nonnull input, char ***_Nonnull lines) 492 | { 493 | /* 494 | * Split the input into lines. 495 | * Warning: free() the lines after use. 496 | */ 497 | size_t count = 0; 498 | char *input_copy = strdup(input); 499 | char *line = strtok(input_copy, "\n"); 500 | while (line != NULL) { 501 | if (line[0] == '\0') { 502 | line = strtok(NULL, "\n"); 503 | continue; 504 | } 505 | count++; 506 | line = strtok(NULL, "\n"); 507 | } 508 | free(input_copy); 509 | if (count == 0) { 510 | return 0; 511 | } 512 | *lines = malloc(sizeof(char *) * (count + 2)); 513 | input_copy = strdup(input); 514 | line = strtok(input_copy, "\n"); 515 | size_t index = 0; 516 | while (line != NULL && strlen(line) > 0) { 517 | (*lines)[index++] = strdup(line); 518 | line = strtok(NULL, "\n"); 519 | } 520 | free(input_copy); 521 | rurima_log("{base}lines count: {green}%zu{clear}\n", count); 522 | for (size_t i = 0; i < count; i++) { 523 | rurima_log("{base}lines[%zu]: {cyan}%s{clear}\n", i, (*lines)[i]); 524 | } 525 | (*lines)[count] = NULL; // Null-terminate the array 526 | return count; 527 | } 528 | size_t rurima_split_lines_allow_null(const char *_Nonnull input, char ***_Nonnull lines) 529 | { 530 | /* 531 | * Split the input into lines. 532 | * Warning: free() the lines after use. 533 | */ 534 | size_t count = 0; 535 | char *input_copy = strdup(input); 536 | char *p = input_copy; 537 | char *line = strsep(&p, "\n"); 538 | while (line != NULL) { 539 | count++; 540 | line = strsep(&p, "\n"); 541 | } 542 | free(input_copy); 543 | if (count == 0) { 544 | return 0; 545 | } 546 | *lines = malloc(sizeof(char *) * (count + 2)); 547 | input_copy = strdup(input); 548 | p = input_copy; 549 | line = strsep(&p, "\n"); 550 | size_t index = 0; 551 | while (line != NULL) { 552 | if (strlen(line) > 0) { 553 | (*lines)[index++] = strdup(line); 554 | } else { 555 | (*lines)[index++] = NULL; // Allow null lines 556 | } 557 | line = strsep(&p, "\n"); 558 | } 559 | free(input_copy); 560 | rurima_log("{base}lines count: {green}%zu{clear}\n", count); 561 | for (size_t i = 0; i < count; i++) { 562 | if ((*lines)[i] != NULL) { 563 | rurima_log("{base}lines[%zu]: {cyan}%s{clear}\n", i, (*lines)[i]); 564 | } else { 565 | rurima_log("{base}lines[%zu]: {cyan}NULL{clear}\n", i); 566 | } 567 | } 568 | (*lines)[count] = NULL; // Null-terminate the array 569 | return count; 570 | } -------------------------------------------------------------------------------- /src/signal.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | // Show some extra info when segfault. 32 | static void panic(int sig) 33 | { 34 | /* 35 | * This is very useful when bug reporting, 36 | * because we will get the cmdline that caused the error. 37 | */ 38 | signal(sig, SIG_DFL); 39 | int clifd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC); 40 | char buf[1024]; 41 | ssize_t bufsize = read(clifd, buf, sizeof(buf)); 42 | close(clifd); 43 | cfprintf(stderr, "{base}"); 44 | cfprintf(stderr, "{base}%s\n", " .^. .^."); 45 | cfprintf(stderr, "{base}%s\n", " /⋀\\_ノ_/⋀\\"); 46 | cfprintf(stderr, "{base}%s\n", " /ノソノ\\ノソ丶)|"); 47 | cfprintf(stderr, "{base}%s\n", " ルリリ > x )リ"); 48 | cfprintf(stderr, "{base}%s\n", "ノノ㇏ ^ ノ|ノ"); 49 | cfprintf(stderr, "{base}%s\n", " ⠁⠁"); 50 | cfprintf(stderr, "{base}%s\n", "RURIMA ERROR MESSAGE"); 51 | cfprintf(stderr, "{base}Seems that it's time to abort.\n"); 52 | cfprintf(stderr, "{base}SIG: %d\n", sig); 53 | cfprintf(stderr, "{base}UID: %u\n", getuid()); 54 | cfprintf(stderr, "{base}PID: %d\n", getpid()); 55 | cfprintf(stderr, "{base}CLI: "); 56 | for (ssize_t i = 0; i < bufsize - 1; i++) { 57 | if (buf[i] == '\0') { 58 | fputc(' ', stderr); 59 | } else { 60 | fputc(buf[i], stderr); 61 | } 62 | } 63 | // I'm afraid to have bugs written by myself, 64 | // but I'm more afraid that no one will report them. 65 | cfprintf(stderr, "{base}\nThis message might caused by an internal error.\n"); 66 | cfprintf(stderr, "{base}If you think something is wrong, please report at:\n"); 67 | cfprintf(stderr, "\033[4m{base}%s{clear}\n\n", "https://github.com/Moe-hacker/rurima/issues"); 68 | } 69 | // Catch coredump signal. 70 | void rurima_register_signal(void) 71 | { 72 | /* 73 | * Only SIGSEGV means segmentation fault, 74 | * but we catch all signals that might cause coredump. 75 | */ 76 | signal(SIGABRT, panic); 77 | signal(SIGBUS, panic); 78 | signal(SIGFPE, panic); 79 | signal(SIGILL, panic); 80 | signal(SIGQUIT, panic); 81 | signal(SIGSEGV, panic); 82 | signal(SIGSYS, panic); 83 | signal(SIGTRAP, panic); 84 | signal(SIGXCPU, panic); 85 | signal(SIGXFSZ, panic); 86 | } 87 | -------------------------------------------------------------------------------- /src/subcommand.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 4 | * This file is part of rurima, with ABSOLUTELY NO WARRANTY. 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2024 Moe-hacker 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * 29 | */ 30 | #include "include/rurima.h" 31 | static char *add_library_prefix(char *_Nonnull image) 32 | { 33 | /* 34 | * Warning: free() the return value after use. 35 | * 36 | * Docker image need `library` prefix when no repository is specified. 37 | * So we add it here. 38 | */ 39 | if (strchr(image, '/') != NULL) { 40 | return image; 41 | } 42 | char *ret = malloc(strlen(image) + 11); 43 | strcpy(ret, "library/"); 44 | strcat(ret, image); 45 | // image is strdup()ed, so free it. 46 | free(image); 47 | return ret; 48 | } 49 | static void docker_pull_try_mirrors(const char *_Nonnull image, const char *_Nonnull tag, const char *_Nonnull architecture, const char *_Nonnull savedir, char *_Nonnull try_mirrorlist[], bool fallback) 50 | { 51 | /* 52 | * Try mirrors. 53 | */ 54 | char **rexec_argv = malloc(128 * sizeof(char *)); 55 | rexec_argv[0] = NULL; 56 | for (int i = 0; try_mirrorlist[i] != NULL; i++) { 57 | cprintf("{base}Trying mirror: {cyan}%s\n", try_mirrorlist[i]); 58 | rurima_add_argv(&rexec_argv, "docker"); 59 | rurima_add_argv(&rexec_argv, "pull"); 60 | rurima_add_argv(&rexec_argv, "-i"); 61 | rurima_add_argv(&rexec_argv, (char *)image); 62 | rurima_add_argv(&rexec_argv, "-t"); 63 | rurima_add_argv(&rexec_argv, (char *)tag); 64 | rurima_add_argv(&rexec_argv, "-a"); 65 | rurima_add_argv(&rexec_argv, (char *)architecture); 66 | rurima_add_argv(&rexec_argv, "-s"); 67 | rurima_add_argv(&rexec_argv, (char *)savedir); 68 | rurima_add_argv(&rexec_argv, "-m"); 69 | rurima_add_argv(&rexec_argv, (char *)try_mirrorlist[i]); 70 | if (fallback) { 71 | rurima_add_argv(&rexec_argv, "-f"); 72 | if (rurima_fork_rexec(rexec_argv) == 0) { 73 | exit(0); 74 | } else { 75 | cprintf("{yellow}Mirror {cyan}%s {yellow}is not working!\n", try_mirrorlist[i]); 76 | } 77 | } else { 78 | if (rurima_fork_rexec(rexec_argv) == 0) { 79 | exit(0); 80 | } else { 81 | cprintf("{yellow}Mirror {cyan}%s {yellow}is not working!\n", try_mirrorlist[i]); 82 | } 83 | } 84 | } 85 | char *mirrorlist_builtin[] = { rurima_global_config.docker_mirror, "hub.xdark.top", "dockerpull.org", "hub.crdz.gq", "docker.1panel.live", "docker.unsee.tech", "docker.m.daocloud.io", "docker.kejinlion.pro", "registry.dockermirror.com", "hub.rat.dev", "dhub.kubesre.xyz", "docker.nastool.de", "docker.udayun.com", "docker.rainbond.cc", "hub.geekery.cn", "registry.hub.docker.com", NULL }; 86 | for (int i = 0; mirrorlist_builtin[i] != NULL; i++) { 87 | cprintf("{base}Trying mirror: {cyan}%s\n", mirrorlist_builtin[i]); 88 | rexec_argv[0] = "docker"; 89 | rexec_argv[1] = "pull"; 90 | rexec_argv[2] = "-i"; 91 | rexec_argv[3] = (char *)image; 92 | rexec_argv[4] = "-t"; 93 | rexec_argv[5] = (char *)tag; 94 | rexec_argv[6] = "-a"; 95 | rexec_argv[7] = (char *)architecture; 96 | rexec_argv[8] = "-s"; 97 | rexec_argv[9] = (char *)savedir; 98 | rexec_argv[10] = "-m"; 99 | rexec_argv[11] = (char *)mirrorlist_builtin[i]; 100 | if (fallback) { 101 | rexec_argv[12] = "-f"; 102 | rexec_argv[13] = NULL; 103 | if (rurima_fork_rexec(rexec_argv) == 0) { 104 | cprintf("\n{green}Success!\n"); 105 | exit(0); 106 | } else { 107 | cprintf("\n{yellow}Mirror {cyan}%s {yellow}is not working!\n\n", mirrorlist_builtin[i]); 108 | } 109 | } else { 110 | rexec_argv[12] = NULL; 111 | if (rurima_fork_rexec(rexec_argv) == 0) { 112 | cprintf("\n{green}Success!\n"); 113 | exit(0); 114 | } else { 115 | cprintf("\n{yellow}Mirror {cyan}%s {yellow}is not working!\n\n", mirrorlist_builtin[i]); 116 | } 117 | } 118 | } 119 | cprintf("{red}All mirrors are not working!\n"); 120 | } 121 | /* 122 | * Subcommand for rurima. 123 | */ 124 | void rurima_docker(int argc, char **_Nonnull argv) 125 | { 126 | if (!rurima_jq_exists()) { 127 | rurima_error("{red}jq is not installed!\n"); 128 | } 129 | char *image = NULL; 130 | char *tag = NULL; 131 | char *architecture = NULL; 132 | char *savedir = NULL; 133 | char *page_size = NULL; 134 | char *mirror = NULL; 135 | char *runtime = NULL; 136 | bool quiet = false; 137 | bool fallback = false; 138 | bool try_mirrors = false; 139 | char *try_mirrorlist[1024] = { NULL }; 140 | if (argc == 0) { 141 | rurima_error("{red}No subcommand specified!\n"); 142 | } 143 | for (int i = 1; i < argc; i++) { 144 | if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--image") == 0) { 145 | if (i + 1 >= argc) { 146 | rurima_error("{red}No image specified!\n"); 147 | } 148 | image = strdup(argv[i + 1]); 149 | i++; 150 | } else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--tag") == 0) { 151 | if (i + 1 >= argc) { 152 | rurima_error("{red}No tag specified!\n"); 153 | } 154 | tag = argv[i + 1]; 155 | i++; 156 | } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-process") == 0) { 157 | rurima_global_config.no_process = true; 158 | } else if (strcmp(argv[i], "-T") == 0 || strcmp(argv[i], "--try-mirrors") == 0) { 159 | try_mirrors = true; 160 | if (i + 1 < argc) { 161 | if (strchr(argv[i + 1], '-') != argv[i + 1]) { 162 | i++; 163 | for (int j = 0; j < 1024; j++) { 164 | if (try_mirrorlist[j] == NULL) { 165 | try_mirrorlist[j] = argv[i]; 166 | try_mirrorlist[j + 1] = NULL; 167 | break; 168 | } 169 | } 170 | } 171 | } 172 | } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--arch") == 0) { 173 | if (i + 1 >= argc) { 174 | rurima_error("{red}No architecture specified!\n"); 175 | } 176 | architecture = argv[i + 1]; 177 | i++; 178 | } else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--runtime") == 0) { 179 | if (i + 1 >= argc) { 180 | rurima_error("{red}No container runtime specified!\n"); 181 | } 182 | runtime = argv[i + 1]; 183 | i++; 184 | } else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--savedir") == 0) { 185 | if (i + 1 >= argc) { 186 | rurima_error("{red}No save directory specified!\n"); 187 | } 188 | rurima_check_dir_deny_list(argv[i + 1]); 189 | rurima_mkdirs(argv[i + 1], 0755); 190 | savedir = realpath(argv[i + 1], NULL); 191 | if (savedir == NULL) { 192 | rurima_error("{red}Failed to create the save directory!\n"); 193 | } 194 | i++; 195 | } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--page_size") == 0) { 196 | if (i + 1 >= argc) { 197 | rurima_error("{red}No page size specified!\n"); 198 | } 199 | page_size = argv[i + 1]; 200 | i++; 201 | } else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) { 202 | quiet = true; 203 | rurima_global_config.quiet = true; 204 | } else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fallback") == 0 || strcmp(argv[i], "--failback") == 0) { 205 | fallback = true; 206 | } else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mirror") == 0) { 207 | if (i + 1 >= argc) { 208 | rurima_error("{red}No mirror specified!\n"); 209 | } 210 | mirror = argv[i + 1]; 211 | i++; 212 | } else { 213 | rurima_error("{red}Unknown argument!\n"); 214 | } 215 | } 216 | if (architecture == NULL) { 217 | architecture = rurima_docker_get_host_arch(); 218 | } 219 | if (mirror == NULL) { 220 | mirror = rurima_global_config.docker_mirror; 221 | } 222 | if (strcmp(mirror, "registry-1.docker.io") != 0) { 223 | if (!quiet) { 224 | rurima_warning("{yellow}You are using unofficial mirror:{cyan} %s\n", mirror); 225 | rurima_warning("{yellow}You use it as your own risk.\n") 226 | } 227 | } 228 | if (strcmp(argv[0], "search") == 0) { 229 | if (image == NULL) { 230 | rurima_error("{red}No image specified!\n"); 231 | } 232 | if (page_size == NULL) { 233 | page_size = "10"; 234 | } 235 | rurima_docker_search(image, page_size, quiet, mirror); 236 | } else if (strcmp(argv[0], "tag") == 0) { 237 | if (page_size == NULL) { 238 | page_size = "10"; 239 | } 240 | if (image == NULL) { 241 | rurima_error("{red}No image specified!\n"); 242 | } 243 | image = add_library_prefix(image); 244 | rurima_docker_search_tag(image, page_size, architecture, quiet, mirror); 245 | } else if (strcmp(argv[0], "pull") == 0) { 246 | if (tag == NULL) { 247 | tag = "latest"; 248 | } 249 | if (savedir == NULL) { 250 | rurima_error("{red}No save directory specified!\n"); 251 | } 252 | if (image == NULL) { 253 | rurima_error("{red}No image specified!\n"); 254 | } 255 | if (architecture == NULL) { 256 | architecture = rurima_docker_get_host_arch(); 257 | } 258 | if (try_mirrors) { 259 | docker_pull_try_mirrors(image, tag, architecture, savedir, try_mirrorlist, fallback); 260 | exit(0); 261 | } 262 | if (!rurima_run_with_root()) { 263 | if (!proot_exist()) { 264 | rurima_warning("{yellow}You are not running with root, but proot not found, might cause bug unpacking rootfs!\n"); 265 | } 266 | } 267 | image = add_library_prefix(image); 268 | struct RURIMA_DOCKER *config = rurima_docker_pull(image, tag, architecture, savedir, mirror, fallback); 269 | if (!quiet) { 270 | rurima_show_docker_config(config, savedir, runtime, quiet); 271 | if (config->architecture != NULL) { 272 | if (strcmp(config->architecture, architecture) != 0) { 273 | rurima_warning("{yellow}\nWarning: fallback mode detected!\n"); 274 | rurima_warning("{yellow}The architecture of the image is not the same as the specified architecture!\n"); 275 | } 276 | } 277 | } 278 | rurima_free_docker_config(config); 279 | } else if (strcmp(argv[0], "config") == 0) { 280 | if (tag == NULL) { 281 | tag = "latest"; 282 | } 283 | if (image == NULL) { 284 | rurima_error("{red}No image specified!\n"); 285 | } 286 | image = add_library_prefix(image); 287 | struct RURIMA_DOCKER *config = rurima_get_docker_config(image, tag, architecture, mirror, fallback); 288 | rurima_show_docker_config(config, savedir, runtime, quiet); 289 | if (!quiet) { 290 | if (config->architecture != NULL) { 291 | if (strcmp(config->architecture, architecture) != 0) { 292 | rurima_warning("{yellow}Warning: fallback mode detected!\n"); 293 | rurima_warning("{yellow}The architecture of the image is not the same as the specified architecture!\n"); 294 | } 295 | } 296 | } 297 | rurima_free_docker_config(config); 298 | } else if (strcmp(argv[0], "arch") == 0) { 299 | if (image == NULL) { 300 | rurima_error("{red}No image specified!\n"); 301 | } 302 | image = add_library_prefix(image); 303 | if (tag == NULL) { 304 | rurima_error("{red}No tag specified!\n"); 305 | } 306 | rurima_docker_search_arch(image, tag, mirror, fallback); 307 | } else if (strcmp(argv[0], "help") == 0 || strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) { 308 | cprintf("{base}Usage: rurima docker [subcommand] [options]\n"); 309 | cprintf("{base}Subcommands:\n"); 310 | cprintf("{base} search: Search images from DockerHub.\n"); 311 | cprintf("{base} tag: Search tags from DockerHub.\n"); 312 | cprintf("{base} pull: Pull image from DockerHub.\n"); 313 | cprintf("{base} config: Get config of image from DockerHub.\n"); 314 | cprintf("{base} arch: Search architecture of image from DockerHub.\n"); 315 | cprintf("{base} help: Show help message.\n"); 316 | cprintf("{base}Options:\n"); 317 | cprintf("{base} -i, --image: Image name.\n"); 318 | cprintf("{base} -t, --tag: Tag of image.\n"); 319 | cprintf("{base} -a, --arch: Architecture of image.\n"); 320 | cprintf("{base} -s, --savedir: Save directory of image.\n"); 321 | cprintf("{base} -p, --page_size: Page size of search.\n"); 322 | cprintf("{base} -m, --mirror: Mirror of DockerHub.\n"); 323 | cprintf("{base} -r, --runtime: runtime of container, support [ruri/proot/chroot].\n"); 324 | cprintf("{base} -q, --quiet: Quiet mode.\n"); 325 | cprintf("{base} -f, --fallback: Fallback mode.\n"); 326 | cprintf("{base} -T, --try-mirrors : Try mirrors.\n"); 327 | cprintf("\n{base}Note: please remove `https://` prefix from mirror url.\n"); 328 | cprintf("{base}For example: `-m registry-1.docker.io`\n"); 329 | cprintf("{base}You can add your perfered mirrors for `-T` option to try them first, for example: `-T hub.xdark.top -T dockerpull.org`\n"); 330 | } else { 331 | rurima_error("{red}Invalid subcommand!\n"); 332 | } 333 | free(image); 334 | free(savedir); 335 | } 336 | void rurima_lxc(int argc, char **_Nonnull argv) 337 | { 338 | char *mirror = NULL; 339 | char *os = NULL; 340 | char *version = NULL; 341 | char *architecture = NULL; 342 | char *type = NULL; 343 | char *savedir = NULL; 344 | if (argc == 0) { 345 | rurima_error("{red}No subcommand specified!\n"); 346 | } 347 | for (int i = 1; i < argc; i++) { 348 | if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mirror") == 0) { 349 | if (i + 1 >= argc) { 350 | rurima_error("{red}No mirror specified!\n"); 351 | } 352 | mirror = argv[i + 1]; 353 | i++; 354 | } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-process") == 0) { 355 | rurima_global_config.no_process = true; 356 | } else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--os") == 0) { 357 | if (i + 1 >= argc) { 358 | rurima_error("{red}No os specified!\n"); 359 | } 360 | os = argv[i + 1]; 361 | i++; 362 | } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) { 363 | if (i + 1 >= argc) { 364 | rurima_error("{red}No version specified!\n"); 365 | } 366 | version = argv[i + 1]; 367 | i++; 368 | } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--arch") == 0) { 369 | if (i + 1 >= argc) { 370 | rurima_error("{red}No architecture specified!\n"); 371 | } 372 | architecture = argv[i + 1]; 373 | i++; 374 | } else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--type") == 0) { 375 | if (i + 1 >= argc) { 376 | rurima_error("{red}No type specified!\n"); 377 | } 378 | type = argv[i + 1]; 379 | i++; 380 | } else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--savedir") == 0) { 381 | if (i + 1 >= argc) { 382 | rurima_error("{red}No save directory specified!\n"); 383 | } 384 | rurima_check_dir_deny_list(argv[i + 1]); 385 | savedir = argv[i + 1]; 386 | i++; 387 | } else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) { 388 | rurima_global_config.quiet = true; 389 | } else { 390 | rurima_error("{red}Unknown argument!\n"); 391 | } 392 | } 393 | if (strcmp(argv[0], "pull") == 0) { 394 | if (os == NULL) { 395 | rurima_error("{red}No os specified!\n"); 396 | } 397 | if (version == NULL) { 398 | rurima_error("{red}No version specified!\n"); 399 | } 400 | if (savedir == NULL) { 401 | rurima_error("{red}No save directory specified!\n"); 402 | } 403 | if (!rurima_run_with_root()) { 404 | if (!proot_exist()) { 405 | rurima_warning("{yellow}You are not running as root, but proot not found, might cause bug unpacking rootfs!\n"); 406 | } 407 | } 408 | rurima_lxc_pull_image(mirror, os, version, architecture, type, savedir); 409 | } else if (strcmp(argv[0], "list") == 0) { 410 | rurima_lxc_get_image_list(mirror, architecture); 411 | } else if (strcmp(argv[0], "search") == 0) { 412 | if (os == NULL) { 413 | rurima_error("{red}No os specified!\n"); 414 | } 415 | rurima_lxc_search_image(mirror, os, architecture); 416 | } else if (strcmp(argv[0], "arch") == 0) { 417 | if (os == NULL) { 418 | rurima_error("{red}No os specified!\n"); 419 | } 420 | rurima_lxc_search_arch(mirror, os); 421 | } else if (strcmp(argv[0], "help") == 0 || strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) { 422 | cprintf("{base}Usage: rurima lxc [subcommand] [options]\n"); 423 | cprintf("{base}Subcommands:\n"); 424 | cprintf("{base} pull: Pull image from LXC image server.\n"); 425 | cprintf("{base} list: List images from LXC image server.\n"); 426 | cprintf("{base} search: Search images from LXC image server.\n"); 427 | cprintf("{base} arch: Search architecture of images from LXC image server.\n"); 428 | cprintf("{base} help: Show help message.\n"); 429 | cprintf("{base}Options:\n"); 430 | cprintf("{base} -m, --mirror: Mirror of LXC image server.\n"); 431 | cprintf("{base} -o, --os: OS of image.\n"); 432 | cprintf("{base} -v, --version: Version of image.\n"); 433 | cprintf("{base} -a, --arch: Architecture of image.\n"); 434 | cprintf("{base} -t, --type: Type of image.\n"); 435 | cprintf("{base} -s, --savedir: Save directory of image.\n"); 436 | cprintf("\n{base}Note: please remove `https://` prefix from mirror url.\n"); 437 | cprintf("{base}For example: `-m images.linuxcontainers.org`\n"); 438 | } else { 439 | rurima_error("{red}Invalid subcommand!\n"); 440 | } 441 | } 442 | void rurima_unpack(int argc, char **_Nonnull argv) 443 | { 444 | char *file = NULL; 445 | char *dir = NULL; 446 | if (argc == 0) { 447 | rurima_error("{red}Unknown argument!\n"); 448 | } 449 | for (int i = 0; i < argc; i++) { 450 | if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) { 451 | if (i + 1 >= argc) { 452 | rurima_error("{red}No file specified!\n"); 453 | } 454 | file = argv[i + 1]; 455 | i++; 456 | } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--dir") == 0) { 457 | if (i + 1 >= argc) { 458 | rurima_error("{red}No directory specified!\n"); 459 | } 460 | rurima_check_dir_deny_list(argv[i + 1]); 461 | dir = argv[i + 1]; 462 | i++; 463 | } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-process") == 0) { 464 | rurima_global_config.no_process = true; 465 | } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 466 | cprintf("{base}Usage: rurima unpack [options]\n"); 467 | cprintf("{base}Options:\n"); 468 | cprintf("{base} -f, --file: File to unpack.\n"); 469 | cprintf("{base} -d, --dir: Directory to unpack.\n"); 470 | cprintf("{base} -h, --help: Show help message.\n"); 471 | return; 472 | } else { 473 | rurima_error("{red}Unknown argument!\n"); 474 | } 475 | } 476 | if (file == NULL) { 477 | rurima_error("{red}No file specified!\n"); 478 | } 479 | if (dir == NULL) { 480 | rurima_error("{red}No directory specified!\n"); 481 | } 482 | if (!rurima_run_with_root()) { 483 | rurima_warning("{yellow}You are not running as root, might cause bug unpacking rootfs!\n"); 484 | } 485 | if (rurima_extract_archive(file, dir) != 0) { 486 | rurima_error("{red}Failed to extract archive!\n"); 487 | } 488 | } 489 | void rurima_backup(int argc, char **_Nonnull argv) 490 | { 491 | char *file = NULL; 492 | char *dir = NULL; 493 | if (argc == 0) { 494 | rurima_error("{red}Unknown argument!\n"); 495 | } 496 | for (int i = 0; i < argc; i++) { 497 | if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) { 498 | if (i + 1 >= argc) { 499 | rurima_error("{red}No file specified!\n"); 500 | } 501 | file = argv[i + 1]; 502 | i++; 503 | } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--dir") == 0) { 504 | if (i + 1 >= argc) { 505 | rurima_error("{red}No directory specified!\n"); 506 | } 507 | dir = argv[i + 1]; 508 | i++; 509 | } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-process") == 0) { 510 | rurima_global_config.no_process = true; 511 | } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 512 | cprintf("{base}Usage: rurima backup [options]\n"); 513 | cprintf("{base}Options:\n"); 514 | cprintf("{base} -f, --file: output file, tar format.\n"); 515 | cprintf("{base} -d, --dir: Directory to backup.\n"); 516 | cprintf("{base} -h, --help: Show help message.\n"); 517 | return; 518 | } else { 519 | rurima_error("{red}Unknown argument!\n"); 520 | } 521 | } 522 | if (file == NULL) { 523 | rurima_error("{red}No file specified!\n"); 524 | } 525 | if (dir == NULL) { 526 | rurima_error("{red}No directory specified!\n"); 527 | } 528 | if (rurima_backup_dir(file, dir) != 0) { 529 | rurima_warning("{yellow}tar exited with error status, but never mind, this might be fine\n"); 530 | } 531 | } 532 | void rurima_pull(int argc, char **_Nonnull argv) 533 | { 534 | if (argc == 0) { 535 | rurima_error("{red}Unknown argument!\n"); 536 | } 537 | char *mirror = NULL; 538 | char *image = NULL; 539 | char *version = NULL; 540 | char *architecture = NULL; 541 | char *type = NULL; 542 | char *savedir = NULL; 543 | char *arch = NULL; 544 | bool docker_only = false; 545 | bool fallback = false; 546 | for (int i = 0; i < argc; i++) { 547 | if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 548 | cprintf("{base}Usage: rurima pull [image]:[version] [savedir]\n"); 549 | cprintf("{base}Options:\n"); 550 | cprintf("{base} -h, --help: Show help message.\n"); 551 | cprintf("{base} -m, --mirror: Mirror URL.\n"); 552 | cprintf("{base} -a, --arch: Architecture.\n"); 553 | cprintf("{base} -d, --docker: Only search dockerhub for image.\n"); 554 | cprintf("{base} -f, --fallback: Fallback mode, only for docker image.\n"); 555 | cprintf("{base}Note: please remove `https://` prefix from mirror url.\n"); 556 | cprintf("{base}This is just a wrap of docker and lxc subcommand.\n"); 557 | cprintf("{base}It will re-exec itself to call the subcommand.\n"); 558 | cprintf("{base}If -d option is not set, it will find lxc mirror first.\n"); 559 | return; 560 | } else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mirror") == 0) { 561 | if (i + 1 >= argc) { 562 | rurima_error("{red}No mirror specified!\n"); 563 | } 564 | mirror = argv[i + 1]; 565 | i++; 566 | } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--arch") == 0) { 567 | if (i + 1 >= argc) { 568 | rurima_error("{red}No architecture specified!\n"); 569 | } 570 | architecture = argv[i + 1]; 571 | i++; 572 | } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--docker") == 0) { 573 | docker_only = true; 574 | } else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fallback") == 0) { 575 | fallback = true; 576 | } else { 577 | if (strstr(argv[i], ":") != NULL) { 578 | image = strtok(argv[i], ":"); 579 | version = strtok(NULL, ":"); 580 | } else { 581 | image = argv[i]; 582 | version = "latest"; 583 | } 584 | if (i + 1 < argc) { 585 | savedir = argv[i + 1]; 586 | } else { 587 | rurima_error("{red}No save directory specified!\n"); 588 | } 589 | char **rexec_argv = malloc(sizeof(char *) * 114); 590 | rexec_argv[0] = NULL; 591 | if (!docker_only && rurima_lxc_have_image(mirror, image, version, architecture, NULL)) { 592 | if (mirror == NULL) { 593 | mirror = rurima_global_config.lxc_mirror; 594 | } 595 | if (architecture == NULL) { 596 | architecture = rurima_lxc_get_host_arch(); 597 | } 598 | rurima_add_argv(&rexec_argv, "lxc"); 599 | rurima_add_argv(&rexec_argv, "pull"); 600 | rurima_add_argv(&rexec_argv, "-m"); 601 | rurima_add_argv(&rexec_argv, (char *)mirror); 602 | rurima_add_argv(&rexec_argv, "-o"); 603 | rurima_add_argv(&rexec_argv, (char *)image); 604 | rurima_add_argv(&rexec_argv, "-v"); 605 | rurima_add_argv(&rexec_argv, (char *)version); 606 | rurima_add_argv(&rexec_argv, "-a"); 607 | rurima_add_argv(&rexec_argv, (char *)architecture); 608 | rurima_add_argv(&rexec_argv, "-s"); 609 | rurima_add_argv(&rexec_argv, (char *)savedir); 610 | int exit_status = rurima_fork_rexec(rexec_argv); 611 | exit(exit_status); 612 | } else { 613 | if (mirror == NULL) { 614 | mirror = rurima_global_config.docker_mirror; 615 | } 616 | if (architecture == NULL) { 617 | architecture = rurima_docker_get_host_arch(); 618 | } 619 | rurima_add_argv(&rexec_argv, "docker"); 620 | rurima_add_argv(&rexec_argv, "pull"); 621 | if (fallback) { 622 | rurima_add_argv(&rexec_argv, "-f"); 623 | } 624 | rurima_add_argv(&rexec_argv, "-i"); 625 | rurima_add_argv(&rexec_argv, (char *)image); 626 | rurima_add_argv(&rexec_argv, "-t"); 627 | rurima_add_argv(&rexec_argv, (char *)version); 628 | rurima_add_argv(&rexec_argv, "-a"); 629 | rurima_add_argv(&rexec_argv, (char *)architecture); 630 | rurima_add_argv(&rexec_argv, "-s"); 631 | rurima_add_argv(&rexec_argv, (char *)savedir); 632 | rurima_add_argv(&rexec_argv, "-m"); 633 | rurima_add_argv(&rexec_argv, (char *)mirror); 634 | int exit_status = rurima_fork_rexec(rexec_argv); 635 | exit(exit_status); 636 | } 637 | } 638 | } 639 | rurima_error("Emmmm, I think it will never reach here.\n"); 640 | } --------------------------------------------------------------------------------