├── .dockerignore
├── .github
└── workflows
│ └── release.yml
├── .gitlab-ci.yml
├── LICENSE
├── README.md
├── deploy
├── common.sh
└── flatimage.sh
├── doc
├── banner.svg
├── doc.svg
├── ext2-analysis.ods
├── ext2-analyze.sh
├── flatimage-portal.png
├── flatimage-portal.svg
├── graph.flatimage.filesystem.dot
├── graph.flatimage.filesystem.svg
├── icon.png
├── icon.svg
└── toc.sh
├── docker
├── Dockerfile.boot
├── Dockerfile.bwrap_apparmor
└── Dockerfile.portal
├── examples
├── firefox.sh
└── steam.sh
├── mime
├── flatimage.xml
├── icon.png
└── icon.svg
├── sources
├── apt.list
└── arch.list
└── src
├── boot
├── CMakeLists.txt
├── boot.cpp
├── cmd
│ ├── bind.hpp
│ ├── desktop.hpp
│ ├── help.hpp
│ ├── icon.hpp
│ └── layers.hpp
├── compile_commands
│ ├── impl_setup_compile_commands.sh
│ └── setup_compile_commands.sh
├── conanfile.txt
├── config
│ ├── config.hpp
│ └── environment.hpp
├── filesystems.hpp
├── janitor.cpp
├── magic.cpp
├── parser.hpp
└── portal.hpp
├── bwrap
└── bwrap_apparmor.cpp
├── cpp
├── common.hpp
├── dependencies.sh
├── lib
│ ├── bwrap.hpp
│ ├── ciopfs.hpp
│ ├── db.hpp
│ ├── db
│ │ └── desktop.hpp
│ ├── dwarfs.hpp
│ ├── elf.hpp
│ ├── env.hpp
│ ├── fifo.hpp
│ ├── fuse.hpp
│ ├── image.hpp
│ ├── ipc.hpp
│ ├── linux.hpp
│ ├── log.hpp
│ ├── match.hpp
│ ├── overlayfs.hpp
│ ├── reserved.hpp
│ ├── reserved
│ │ ├── desktop.hpp
│ │ ├── notify.hpp
│ │ └── permissions.hpp
│ ├── squashfs.hpp
│ ├── subprocess.hpp
│ └── unionfs.hpp
├── macro.hpp
├── std
│ ├── concept.hpp
│ ├── enum.hpp
│ ├── exception.hpp
│ ├── filesystem.hpp
│ ├── functional.hpp
│ ├── string.hpp
│ ├── variant.hpp
│ └── vector.hpp
└── units.hpp
├── portal
├── portal_guest.cpp
└── portal_host.cpp
└── xdg
├── xdg-mime
└── xdg-open
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Ignore everything
2 | *
3 | build/
4 | test/
5 |
6 | # Except the Dockerfile and the necessary directories/files
7 | !sources/
8 | !src/
9 | !docker/
10 | !bin/
11 | !mime/
12 | !.git/
13 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Linux
2 |
3 | on:
4 | workflow_dispatch:
5 | release:
6 | types: [ created, edited ]
7 |
8 | jobs:
9 | #
10 | # Build
11 | #
12 | build:
13 | strategy:
14 | matrix:
15 | dist: [arch,alpine,blueprint]
16 | runs-on: ubuntu-latest
17 | steps:
18 | # Install required tools
19 | - name: Install tools
20 | run: |
21 | sudo env DEBIAN_FRONTEND=noninteractive apt install -y tar xz-utils coreutils
22 | # Download source from repository
23 | - uses: actions/checkout@v4
24 | # Build
25 | - name: Build
26 | run: |
27 | mkdir -p dist
28 | sudo ./deploy/flatimage.sh ${{ matrix.dist }}
29 | cp ./build/dist/* ./dist
30 | # Upload
31 | - name: Upload flatimage
32 | uses: actions/upload-artifact@v3
33 | with:
34 | name: flatimage
35 | path: dist
36 |
37 | #
38 | # Release
39 | #
40 | release:
41 | runs-on: ubuntu-latest
42 | needs: build
43 | steps:
44 | - uses: actions/download-artifact@v3
45 | with:
46 | name: flatimage
47 | path: dist
48 | - name: Upload to release
49 | uses: fnkr/github-action-ghr@v1
50 | env:
51 | GHR_PATH: dist/
52 | GHR_REPLACE: true
53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: "ubuntu:latest"
2 |
3 | stages:
4 | - fetch
5 | - build
6 | - package
7 | - upload
8 | - release
9 |
10 | variables:
11 | PACKAGE_REGISTRY_URL: "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/generic/fim/continuous"
12 | PROJECT_URL: "https://gitlab.com/api/v4/projects/43000137"
13 |
14 | fetch-coreutils:
15 | image: "ubuntu:focal"
16 | stage: fetch
17 | when: manual
18 | before_script:
19 | - export DEBIAN_FRONTEND=noninteractive
20 | - apt update && apt -y upgrade
21 | - apt install -y wget tar xz-utils
22 | - echo JOB_ID_FETCH_COREUTILS=$CI_JOB_ID > build.env
23 | script:
24 | - |
25 | wget "https://github.com/ruanformigoni/coreutils-static/releases/download/d7f4cd2/coreutils-x86_64.tar.xz"
26 | tar xf "coreutils-x86_64.tar.xz"
27 | artifacts:
28 | paths:
29 | - ./bin
30 | reports:
31 | dotenv: build.env
32 |
33 | fetch-magick:
34 | image: "ubuntu:focal"
35 | stage: fetch
36 | when: manual
37 | before_script:
38 | - export DEBIAN_FRONTEND=noninteractive
39 | - apt update && apt -y upgrade
40 | - apt install -y wget tar xz-utils
41 | - echo JOB_ID_FETCH_MAGICK=$CI_JOB_ID > build.env
42 | script:
43 | - |
44 | mkdir -p bin
45 | wget -O bin/magick "https://github.com/ruanformigoni/imagemagick-static-musl/releases/download/cc3f21c/magick-x86_64"
46 | wget -O bin/magick-license "https://raw.githubusercontent.com/ImageMagick/ImageMagick/main/LICENSE"
47 | chmod +x bin/magick
48 | artifacts:
49 | paths:
50 | - ./bin
51 | reports:
52 | dotenv: build.env
53 |
54 | fetch-lsof:
55 | image: "ubuntu:focal"
56 | stage: fetch
57 | when: manual
58 | before_script:
59 | - export DEBIAN_FRONTEND=noninteractive
60 | - apt update && apt -y upgrade
61 | - apt install -y wget tar xz-utils
62 | - echo JOB_ID_FETCH_LSOF=$CI_JOB_ID > build.env
63 | script:
64 | - |
65 | mkdir -p ./bin
66 | wget -O./bin/lsof "https://github.com/ruanformigoni/lsof-static-musl/releases/download/e1a88fb/lsof-x86_64"
67 | artifacts:
68 | paths:
69 | - ./bin
70 | reports:
71 | dotenv: build.env
72 |
73 | fetch-e2fsprogs:
74 | image: "ubuntu:focal"
75 | stage: fetch
76 | when: manual
77 | before_script:
78 | - export DEBIAN_FRONTEND=noninteractive
79 | - apt update && apt -y upgrade
80 | - apt install -y wget
81 | - echo JOB_ID_FETCH_E2FSPROGS=$CI_JOB_ID > build.env
82 | script:
83 | - |
84 | mkdir -p bin
85 | wget -O ./bin/fuse2fs "https://github.com/ruanformigoni/e2fsprogs/releases/download/e58f946/fuse2fs"
86 | wget -O ./bin/mke2fs "https://github.com/ruanformigoni/e2fsprogs/releases/download/e58f946/mke2fs"
87 | wget -O ./bin/e2fsck "https://github.com/ruanformigoni/e2fsprogs/releases/download/e58f946/e2fsck"
88 | wget -O ./bin/resize2fs "https://github.com/ruanformigoni/e2fsprogs/releases/download/e58f946/resize2fs"
89 | artifacts:
90 | paths:
91 | - ./bin
92 | reports:
93 | dotenv: build.env
94 |
95 | fetch-proot:
96 | image: "ubuntu:focal"
97 | stage: fetch
98 | when: manual
99 | before_script:
100 | - export DEBIAN_FRONTEND=noninteractive
101 | - apt update && apt -y upgrade
102 | - apt install -y wget
103 | - echo JOB_ID_FETCH_PROOT=$CI_JOB_ID > build.env
104 | script:
105 | - |
106 | mkdir -p ./bin
107 | wget -O ./bin/proot "https://github.com/ruanformigoni/proot/releases/download/5a4be11/proot"
108 | artifacts:
109 | paths:
110 | - ./bin
111 | reports:
112 | dotenv: build.env
113 |
114 | fetch-bwrap:
115 | image: "ubuntu:focal"
116 | stage: fetch
117 | when: manual
118 | before_script:
119 | - export DEBIAN_FRONTEND=noninteractive
120 | - apt update && apt -y upgrade
121 | - apt install -y wget
122 | - echo JOB_ID_FETCH_BWRAP=$CI_JOB_ID > build.env
123 | script:
124 | - |
125 | mkdir -p ./bin
126 | wget -O ./bin/bwrap "https://github.com/ruanformigoni/bubblewrap-musl-static/releases/download/559a725/bwrap"
127 | artifacts:
128 | paths:
129 | - ./bin
130 | reports:
131 | dotenv: build.env
132 |
133 | fetch-overlayfs:
134 | image: "ubuntu:focal"
135 | stage: fetch
136 | when: manual
137 | before_script:
138 | - export DEBIAN_FRONTEND=noninteractive
139 | - apt update && apt -y upgrade
140 | - apt install -y wget
141 | - echo JOB_ID_FETCH_OVERLAYFS=$CI_JOB_ID > build.env
142 | script:
143 | - |
144 | mkdir -p ./bin
145 | wget -O ./bin/overlayfs "https://github.com/ruanformigoni/fuse-overlayfs/releases/download/af507a2/fuse-overlayfs-x86_64"
146 | artifacts:
147 | paths:
148 | - ./bin
149 | reports:
150 | dotenv: build.env
151 |
152 | fetch-dwarfs:
153 | image: "ubuntu:jammy"
154 | stage: fetch
155 | when: manual
156 | before_script:
157 | - export DEBIAN_FRONTEND=noninteractive
158 | - apt update && apt -y upgrade
159 | - apt install -y wget xz-utils tar
160 | - echo JOB_ID_BUILD_DWARFS=$CI_JOB_ID > build.env
161 | script:
162 | - |
163 | mkdir -p ./dwarfs
164 | wget -Odwarfs.tar.xz https://github.com/mhx/dwarfs/releases/download/v0.6.2/dwarfs-0.6.2-Linux.tar.xz
165 | tar xf dwarfs.tar.xz --strip-components=1 -Cdwarfs
166 | mkdir -p ./bin
167 | mv ./dwarfs/bin/mkdwarfs ./bin
168 | mv ./dwarfs/bin/dwarfsextract ./bin
169 | mv ./dwarfs/sbin/dwarfs2 ./bin/dwarfs
170 | artifacts:
171 | paths:
172 | - ./bin
173 | reports:
174 | dotenv: build.env
175 |
176 | fetch-bash:
177 | image: "ubuntu:jammy"
178 | stage: fetch
179 | when: manual
180 | before_script:
181 | - export DEBIAN_FRONTEND=noninteractive
182 | - apt update && apt -y upgrade
183 | - apt install -y wget xz-utils tar
184 | - echo JOB_ID_BUILD_BASH=$CI_JOB_ID > build.env
185 | script:
186 | - |
187 | mkdir -p bin
188 | wget -O ./bin/bash "https://github.com/ruanformigoni/bash-static/releases/download/5355251/bash-linux-x86_64"
189 | artifacts:
190 | paths:
191 | - ./bin
192 | reports:
193 | dotenv: build.env
194 |
195 |
196 | build-elf:
197 | image: docker:latest
198 | stage: build
199 | when: manual
200 | services:
201 | - docker:dind
202 | variables:
203 | DOCKER_TLS_CERTDIR: "/certs"
204 | IMAGE_NAME: alpine:fim
205 |
206 | before_script:
207 | - echo GE_JOB_ID=$CI_JOB_ID >> build.env
208 |
209 | script:
210 | - mkdir -p bin
211 | - docker build . --build-arg FIM_DIST=ARCH -t $CI_REGISTRY/$IMAGE_NAME -f docker/Dockerfile.elf
212 | - docker run --rm -v $(pwd):/workdir $CI_REGISTRY/$IMAGE_NAME cp /fim/dist/main /workdir/bin/elf-arch
213 | - docker build . --build-arg FIM_DIST=ALPINE -t $CI_REGISTRY/$IMAGE_NAME -f docker/Dockerfile.elf
214 | - docker run --rm -v $(pwd):/workdir $CI_REGISTRY/$IMAGE_NAME cp /fim/dist/main /workdir/bin/elf-alpine
215 | - docker build . --build-arg FIM_DIST=UBUNTU -t $CI_REGISTRY/$IMAGE_NAME -f docker/Dockerfile.elf
216 | - docker run --rm -v $(pwd):/workdir $CI_REGISTRY/$IMAGE_NAME cp /fim/dist/main /workdir/bin/elf-ubuntu
217 |
218 | artifacts:
219 | paths:
220 | - ./bin/elf-arch
221 | - ./bin/elf-alpine
222 | - ./bin/elf-ubuntu
223 | reports:
224 | dotenv: build.env
225 |
226 | package-ubuntu:
227 | stage: package
228 | parallel:
229 | matrix:
230 | - DISTRO: [focal, jammy]
231 | before_script:
232 | - apt update && apt -y upgrade
233 | - apt install -y git wget curl zstd debootstrap e2fsprogs proot fuse2fs xz-utils rsync
234 | - echo "JOB_ID_BUILD_BS_$DISTRO=$CI_JOB_ID" > build.env
235 | script:
236 | # Select elf
237 | - cp ./bin/elf-ubuntu ./bin/elf
238 | # Build
239 | - ./src/scripts/_build.sh debootstrap "$DISTRO"
240 | # Set env vars
241 | - echo "${DISTRO^^}_TARBALL=${DISTRO}.tar.xz" >> build.env
242 | needs:
243 | - job: build-elf
244 | - job: fetch-coreutils
245 | - job: fetch-magick
246 | - job: fetch-lsof
247 | - job: fetch-e2fsprogs
248 | - job: fetch-bwrap
249 | - job: fetch-overlayfs
250 | - job: fetch-dwarfs
251 | - job: fetch-bash
252 | - job: fetch-proot
253 | artifacts:
254 | paths:
255 | - ./dist
256 | reports:
257 | dotenv: build.env
258 |
259 | package-arch:
260 | image: "archlinux:latest"
261 | stage: package
262 | before_script:
263 | - pacman -Syu --noconfirm
264 | - pacman -S python-pip patchelf git gawk wget curl zstd xz rsync binutils --noconfirm
265 | - echo JOB_ID_BUILD_BS_ARCH=$CI_JOB_ID > build.env
266 | script:
267 | # Select elf
268 | - cp ./bin/elf-arch ./bin/elf
269 | # Build
270 | - ./src/scripts/_build.sh archbootstrap
271 | # Set env vars
272 | - echo "ARCH_TARBALL=arch.tar.xz" >> build.env
273 | needs:
274 | - job: build-elf
275 | - job: fetch-coreutils
276 | - job: fetch-magick
277 | - job: fetch-lsof
278 | - job: fetch-e2fsprogs
279 | - job: fetch-bwrap
280 | - job: fetch-overlayfs
281 | - job: fetch-dwarfs
282 | - job: fetch-bash
283 | - job: fetch-proot
284 | artifacts:
285 | paths:
286 | - ./dist
287 | reports:
288 | dotenv: build.env
289 |
290 | package-alpine:
291 | image: "archlinux:latest"
292 | stage: package
293 | before_script:
294 | - pacman -Syu --noconfirm
295 | - pacman -S python-pip patchelf git gawk wget curl zstd xz rsync binutils --noconfirm
296 | - echo JOB_ID_BUILD_BS_ALPINE=$CI_JOB_ID > build.env
297 | script:
298 | # Select elf
299 | - cp ./bin/elf-alpine ./bin/elf
300 | # Build
301 | - ./src/scripts/_build.sh alpinebootstrap
302 | # Set env vars
303 | - echo "ALPINE_TARBALL=alpine.tar.xz" >> build.env
304 | needs:
305 | - job: build-elf
306 | - job: fetch-coreutils
307 | - job: fetch-magick
308 | - job: fetch-lsof
309 | - job: fetch-e2fsprogs
310 | - job: fetch-bwrap
311 | - job: fetch-overlayfs
312 | - job: fetch-dwarfs
313 | - job: fetch-bash
314 | - job: fetch-proot
315 | artifacts:
316 | paths:
317 | - ./dist
318 | reports:
319 | dotenv: build.env
320 |
321 | upload:
322 | stage: upload
323 | image: alpine
324 | before_script: |
325 | apk add curl
326 | script: |
327 | curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --upload-file "dist/${ALPINE_TARBALL}" "${PACKAGE_REGISTRY_URL}/${ALPINE_TARBALL}"
328 | curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --upload-file "dist/${FOCAL_TARBALL}" "${PACKAGE_REGISTRY_URL}/${FOCAL_TARBALL}"
329 | curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --upload-file "dist/${JAMMY_TARBALL}" "${PACKAGE_REGISTRY_URL}/${JAMMY_TARBALL}"
330 | curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --upload-file "dist/${ARCH_TARBALL}" "${PACKAGE_REGISTRY_URL}/${ARCH_TARBALL}"
331 | needs:
332 | - job: package-arch
333 | - job: package-ubuntu
334 | - job: package-alpine
335 |
336 | deploy:
337 | stage: release
338 | image: registry.gitlab.com/gitlab-org/release-cli:latest
339 | variables:
340 | RELEASE_DATE: $(date "+%Y-%m-%d")
341 | before_script:
342 | - apk --no-cache add curl
343 | script: |
344 | curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --request DELETE "$PROJECT_URL/releases/Continuous"
345 | echo "Deploy continuous"
346 | # Set new release
347 | release: # See https://docs.gitlab.com/ee/ci/yaml/#release for available properties
348 | tag_name: 'Continuous'
349 | description: 'Release $CI_COMMIT_SHORT_SHA : ${RELEASE_DATE}'
350 | assets:
351 | links:
352 | - name: 'focal.tar.xz'
353 | url: '${PACKAGE_REGISTRY_URL}/focal.tar.xz'
354 | - name: 'jammy.tar.xz'
355 | url: '${PACKAGE_REGISTRY_URL}/jammy.tar.xz'
356 | - name: 'arch.tar.xz'
357 | url: '${PACKAGE_REGISTRY_URL}/arch.tar.xz'
358 | - name: 'alpine.tar.xz'
359 | url: '${PACKAGE_REGISTRY_URL}/alpine.tar.xz'
360 | needs:
361 | - job: upload
362 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Ruan Formigoni
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Table of Contents
2 |
3 | - [Documentation](#documentation)
4 | - [FlatImage](#flatimage)
5 | - [Related Projects](#related-projects)
6 |
7 | # Documentation
8 |
9 |
10 |
11 |
12 |
13 | # FlatImage
14 |
15 |
16 |
17 | # Related Projects
18 |
19 | - [https://github.com/Kron4ek/Conty](https://github.com/Kron4ek/Conty)
20 | - [https://github.com/genuinetools/binctr](https://github.com/genuinetools/binctr)
21 | - [https://github.com/Intoli/exodus](https://github.com/Intoli/exodus)
22 | - [https://statifier.sourceforge.net/](https://statifier.sourceforge.net/)
23 | - [https://github.com/matthewbauer/nix-bundle](https://github.com/matthewbauer/nix-bundle)
24 | - [https://github.com/containers/bubblewrap](https://github.com/containers/bubblewrap)
25 | - [https://github.com/proot-me/proot](https://github.com/proot-me/proot)
26 |
27 |
6 |
7 |
201 |
--------------------------------------------------------------------------------
/doc/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/flatimage/817782c6025365ad706a41776b176b9cc41f7061/doc/icon.png
--------------------------------------------------------------------------------
/doc/toc.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ######################################################################
4 | # @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
5 | # @file : toc
6 | ######################################################################
7 |
8 | set -e
9 |
10 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
11 |
12 | cd "${SCRIPT_DIR}"
13 |
14 | readarray -t arr <<<"$(grep -Ei "^#+" ../README.md)"
15 |
16 | for i in "${arr[@]}"; do
17 | lead="${i//${i//\#/}/}"
18 | lead="${lead//#/" "}"
19 | text="${i#\#* }"
20 | link="${text// /-}"
21 | link="${link//\(/}"
22 | link="${link//\)/}"
23 | link="${link//,/}"
24 | link="${link//\//}"
25 | link="${link//\?/}"
26 | link="$(echo "$link" | tr "[:upper:]" "[:lower:]")"
27 | echo "${lead}- [$text](#${link})"
28 | done >> ../README.md
29 |
30 | # // cmd: !./%
31 |
--------------------------------------------------------------------------------
/docker/Dockerfile.boot:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Update repositories to edge
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install deps
9 | RUN apk update && apk upgrade
10 | RUN apk add --no-cache build-base git libbsd-dev py3-pip cmake clang clang-dev \
11 | make e2fsprogs-dev e2fsprogs-libs e2fsprogs-static libcom_err musl musl-dev \
12 | bash pcre-tools boost-dev libjpeg-turbo-dev libjpeg-turbo-static libpng-dev \
13 | libpng-static zlib-static upx
14 |
15 | # Install conan
16 | RUN python3 -m venv /conan
17 | RUN . /conan/bin/activate && pip3 install conan
18 | ENV CONAN "/conan/bin/conan"
19 |
20 | # Copy boot directory
21 | ARG FIM_DIR
22 | COPY . $FIM_DIR
23 | WORKDIR $FIM_DIR/src/boot
24 |
25 | ARG FIM_DIST
26 | ENV FIM_DIST=$FIM_DIST
27 |
28 | # Compile
29 | RUN "$CONAN" profile detect
30 | RUN "$CONAN" install . --build=missing -g CMakeDeps -g CMakeToolchain
31 | RUN cmake --preset conan-release -DCMAKE_BUILD_TYPE=Release
32 | RUN cmake --build --preset conan-release
33 | RUN strip -s ./build/Release/boot
34 |
35 | # Include magic bytes
36 | RUN ./build/Release/magic ./build/Release/boot
37 |
38 | # Compile janitor
39 | RUN g++ --std=c++23 -O3 -static -o janitor janitor.cpp
40 | RUN strip -s janitor
41 | RUN upx -6 --no-lzma janitor
42 |
--------------------------------------------------------------------------------
/docker/Dockerfile.bwrap_apparmor:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Enable community repo
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install deps
9 | RUN apk update && apk upgrade
10 | RUN apk add --no-cache build-base git libbsd-dev git cmake gcc \
11 | bash e2fsprogs xz curl zstd gawk upx nlohmann-json
12 |
13 | # Update PATH
14 | ENV PATH="/root/.local/bin:$PATH"
15 |
16 | # Copy files
17 | RUN mkdir /fim
18 | COPY . /fim/
19 |
20 | # Set workdir
21 | WORKDIR /fim
22 |
23 | # Compile
24 | WORKDIR /fim/src/bwrap
25 | RUN g++ -o fim_bwrap_apparmor bwrap_apparmor.cpp -static -Wall -Wextra -Weffc++ -Os --std=c++23
26 | RUN strip -s fim_bwrap_apparmor
27 | RUN upx -6 --no-lzma fim_bwrap_apparmor
28 |
29 | # Move to dist dir
30 | RUN mkdir -p /fim/dist
31 | RUN cp /fim/src/bwrap/fim_bwrap_apparmor /fim/dist
32 |
--------------------------------------------------------------------------------
/docker/Dockerfile.portal:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # Enable community repo
4 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/main/ > /etc/apk/repositories
5 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
6 | RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
7 |
8 | # Install deps
9 | RUN apk update && apk upgrade
10 | RUN apk add --no-cache build-base git libbsd-dev git cmake gcc \
11 | bash e2fsprogs xz curl zstd gawk upx nlohmann-json
12 |
13 | # Update PATH
14 | ENV PATH="/root/.local/bin:$PATH"
15 |
16 | # Copy files
17 | RUN mkdir /fim
18 | COPY . /fim/
19 |
20 | # Set workdir
21 | WORKDIR /fim
22 |
23 | # Compile
24 | WORKDIR /fim/src/portal
25 | RUN g++ -o fim_portal portal_guest.cpp -static -Wall -Wextra -Weffc++ -Os --std=c++23
26 | RUN g++ -o fim_portal_daemon portal_host.cpp -static -Wall -Wextra -Weffc++ -Os --std=c++23
27 | RUN strip -s fim_portal
28 | RUN strip -s fim_portal_daemon
29 | RUN upx -6 --no-lzma fim_portal
30 | RUN upx -6 --no-lzma fim_portal_daemon
31 |
32 | # Move to dist dir
33 | RUN mkdir -p /fim/dist
34 | RUN cp /fim/src/portal/fim_portal /fim/src/portal/fim_portal_daemon /fim/dist
35 |
--------------------------------------------------------------------------------
/examples/firefox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | _die()
6 | {
7 | echo "$*" >&2
8 | kill -s TERM "$$"
9 | }
10 |
11 | _requires()
12 | {
13 | for i; do
14 | command -v "$i" 1>/dev/null 2>/dev/null || _die "Dependency '$i' not found, please install it separately"
15 | done
16 | }
17 |
18 | _main()
19 | {
20 | _requires "wget"
21 |
22 | mkdir -p build && cd build
23 |
24 | # Use jq to fetch latest flatimage version from github
25 | mkdir -p bin
26 | [ -f "bin/jq" ] || wget -O bin/jq "https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux-amd64"
27 | [ -f "bin/xz" ] || wget -O bin/xz "https://github.com/ruanformigoni/xz-static-musl/releases/download/fec8a15/xz"
28 | [ -f "bin/busybox" ] || wget -O bin/busybox "https://github.com/ruanformigoni/busybox-static-musl/releases/download/7e2c5b6/busybox-x86_64"
29 | ln -sf busybox bin/tar
30 | chmod +x ./bin/*
31 | export PATH="$(pwd)/bin:$PATH"
32 |
33 | # Download flatimage
34 | IMAGE="./bin/alpine.flatimage"
35 | [ -f "$IMAGE" ] || wget -O "$IMAGE" "$(curl -H "Accept: application/vnd.github+json" \
36 | https://api.github.com/repos/ruanformigoni/flatimage/releases/latest 2>/dev/null \
37 | | jq -e -r '.assets.[].browser_download_url | match(".*alpine.flatimage$").string')"
38 | chmod +x "$IMAGE"
39 |
40 | # Enable network
41 | "$IMAGE" fim-perms set network
42 |
43 | # Update image
44 | "$IMAGE" fim-root apk update --no-cache
45 |
46 | # Install dependencies
47 | "$IMAGE" fim-root apk add --no-cache font-noto
48 |
49 | # Install firefox
50 | "$IMAGE" fim-root apk add --no-cache firefox
51 |
52 | # Set permissions
53 | "$IMAGE" fim-perms set media,audio,wayland,xorg,udev,dbus_user,usb,input,gpu,network
54 |
55 | # Configure user name and home directory
56 | "$IMAGE" fim-exec mkdir -p /home/firefox
57 | "$IMAGE" fim-env add 'USER=firefox' \
58 | 'HOME=/home/firefox' \
59 | 'XDG_CONFIG_HOME=/home/firefox/.config' \
60 | 'XDG_DATA_HOME=/home/firefox/.local/share'
61 |
62 | # Set command to run by default
63 | "$IMAGE" fim-boot /usr/bin/firefox
64 |
65 | # Configure desktop integration
66 | wget -O firefox.svg "https://upload.wikimedia.org/wikipedia/commons/a/a0/Firefox_logo%2C_2019.svg"
67 | tee desktop.json <<-EOF
68 | {
69 | "name": "firefox",
70 | "icon": "./firefox.svg",
71 | "categories": ["Network"]
72 | }
73 | EOF
74 | "$IMAGE" fim-desktop setup ./desktop.json
75 | "$IMAGE" fim-desktop enable icon,mimetype,entry
76 |
77 | # Notify the application has started
78 | "$IMAGE" fim-notify on
79 |
80 | # Compress
81 | "$IMAGE" fim-commit
82 |
83 | # Copy
84 | rm -rf ../firefox.flatimage
85 | mv "$IMAGE" ../firefox.flatimage
86 | }
87 |
88 | _main "$@"
89 |
--------------------------------------------------------------------------------
/examples/steam.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | _die()
6 | {
7 | echo "$*" >&2
8 | kill -s TERM "$$"
9 | }
10 |
11 | _requires()
12 | {
13 | for i; do
14 | command -v "$i" 1>/dev/null 2>/dev/null || _die "Dependency '$i' not found, please install it separately"
15 | done
16 | }
17 |
18 | _main()
19 | {
20 | _requires "wget"
21 |
22 | mkdir -p build && cd build
23 |
24 | # Use jq to fetch latest flatimage version from github
25 | mkdir -p bin
26 | [ -f "bin/jq" ] || wget -O bin/jq "https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux-amd64"
27 | [ -f "bin/xz" ] || wget -O bin/xz "https://github.com/ruanformigoni/xz-static-musl/releases/download/fec8a15/xz"
28 | [ -f "bin/busybox" ] || wget -O bin/busybox "https://github.com/ruanformigoni/busybox-static-musl/releases/download/7e2c5b6/busybox-x86_64"
29 | ln -sf busybox bin/tar
30 | chmod +x ./bin/*
31 | export PATH="$(pwd)/bin:$PATH"
32 |
33 | # Download flatimage
34 | IMAGE="./bin/arch.flatimage"
35 | [ -f "$IMAGE" ] || wget -O "$IMAGE" "$(curl -H "Accept: application/vnd.github+json" \
36 | https://api.github.com/repos/ruanformigoni/flatimage/releases/latest 2>/dev/null \
37 | | jq -e -r '.assets.[].browser_download_url | match(".*arch.flatimage$").string')"
38 | chmod +x "$IMAGE"
39 |
40 | # Enable network
41 | "$IMAGE" fim-perms set network
42 |
43 | # Update image
44 | "$IMAGE" fim-root pacman -Syu --noconfirm
45 |
46 | # Install dependencies
47 | ## General
48 | "$IMAGE" fim-root pacman -S --noconfirm xorg-server mesa lib32-mesa glxinfo lib32-gcc-libs \
49 | gcc-libs pcre freetype2 lib32-freetype2
50 | ## Video AMD/Intel
51 | "$IMAGE" fim-root pacman -S --noconfirm xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon vulkan-tools
52 | "$IMAGE" fim-root pacman -S --noconfirm xf86-video-intel vulkan-intel lib32-vulkan-intel vulkan-tools
53 |
54 | # Install steam
55 | ## Select the appropriated drivers for your GPU when asked
56 | "$IMAGE" fim-root pacman -S --noconfirm steam
57 |
58 | # Clear cache
59 | "$IMAGE" fim-root pacman -Scc --noconfirm
60 |
61 | # Set permissions
62 | "$IMAGE" fim-perms set media,audio,wayland,xorg,udev,dbus_user,usb,input,gpu,network
63 |
64 | # Configure user name and home directory
65 | "$IMAGE" fim-exec mkdir -p /home/steam
66 | "$IMAGE" fim-env add 'USER=steam' \
67 | 'HOME=/home/steam' \
68 | 'XDG_CONFIG_HOME=/home/steam/.config' \
69 | 'XDG_DATA_HOME=/home/steam/.local/share'
70 |
71 | # Set command to run by default
72 | "$IMAGE" fim-boot /usr/bin/steam
73 |
74 | # Configure desktop integration
75 | wget -O steam.svg https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg
76 | tee steam.json <<-EOF
77 | {
78 | "name": "Steam",
79 | "icon": "./steam.svg",
80 | "categories": ["Game"]
81 | }
82 | EOF
83 | "$IMAGE" fim-desktop setup ./steam.json
84 | "$IMAGE" fim-desktop enable icon,mimetype,entry
85 |
86 | # Notify the application has started
87 | "$IMAGE" fim-notify on
88 |
89 | # Compress
90 | "$IMAGE" fim-commit
91 |
92 | # Copy
93 | rm -rf ../steam.flatimage
94 | mv "$IMAGE" ../steam.flatimage
95 | }
96 |
97 | _main "$@"
98 |
--------------------------------------------------------------------------------
/mime/flatimage.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FlatImage Application
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/mime/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruanformigoni/flatimage/817782c6025365ad706a41776b176b9cc41f7061/mime/icon.png
--------------------------------------------------------------------------------
/sources/apt.list:
--------------------------------------------------------------------------------
1 | # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
2 | # newer versions of the distribution.
3 | deb http://archive.ubuntu.com/ubuntu/ DISTRO main restricted
4 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO main restricted
5 |
6 | ## Major bug fix updates produced after the final release of the
7 | ## distribution.
8 | deb http://archive.ubuntu.com/ubuntu/ DISTRO-updates main restricted
9 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO-updates main restricted
10 |
11 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
12 | ## team. Also, please note that software in universe WILL NOT receive any
13 | ## review or updates from the Ubuntu security team.
14 | deb http://archive.ubuntu.com/ubuntu/ DISTRO universe
15 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO universe
16 | deb http://archive.ubuntu.com/ubuntu/ DISTRO-updates universe
17 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO-updates universe
18 |
19 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
20 | ## team, and may not be under a free licence. Please satisfy yourself as to
21 | ## your rights to use the software. Also, please note that software in
22 | ## multiverse WILL NOT receive any review or updates from the Ubuntu
23 | ## security team.
24 | deb http://archive.ubuntu.com/ubuntu/ DISTRO multiverse
25 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO multiverse
26 | deb http://archive.ubuntu.com/ubuntu/ DISTRO-updates multiverse
27 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO-updates multiverse
28 |
29 | ## N.B. software from this repository may not have been tested as
30 | ## extensively as that contained in the main release, although it includes
31 | ## newer versions of some applications which may provide useful features.
32 | ## Also, please note that software in backports WILL NOT receive any review
33 | ## or updates from the Ubuntu security team.
34 | deb http://archive.ubuntu.com/ubuntu/ DISTRO-backports main restricted universe multiverse
35 | # deb-src http://archive.ubuntu.com/ubuntu/ DISTRO-backports main restricted universe multiverse
36 |
37 | ## Uncomment the following two lines to add software from Canonical's
38 | ## 'partner' repository.
39 | ## This software is not part of Ubuntu, but is offered by Canonical and the
40 | ## respective vendors as a service to Ubuntu users.
41 | # deb http://archive.canonical.com/ubuntu DISTRO partner
42 | # deb-src http://archive.canonical.com/ubuntu DISTRO partner
43 |
44 | deb http://security.ubuntu.com/ubuntu/ DISTRO-security main restricted
45 | # deb-src http://security.ubuntu.com/ubuntu/ DISTRO-security main restricted
46 | deb http://security.ubuntu.com/ubuntu/ DISTRO-security universe
47 | # deb-src http://security.ubuntu.com/ubuntu/ DISTRO-security universe
48 | deb http://security.ubuntu.com/ubuntu/ DISTRO-security multiverse
49 | # deb-src http://security.ubuntu.com/ubuntu/ DISTRO-security multiverse
50 |
--------------------------------------------------------------------------------
/sources/arch.list:
--------------------------------------------------------------------------------
1 | Server = https://mirror.osbeck.com/archlinux/$repo/os/$arch
2 | Server = https://mirrors.vectair.net/archlinux/$repo/os/$arch
3 | Server = https://arch.mirror.constant.com/$repo/os/$arch
4 | Server = https://arch-mirror.wtako.net/$repo/os/$arch
5 | Server = https://mirror.theo546.fr/archlinux/$repo/os/$arch
6 | Server = https://ftp.acc.umu.se/mirror/archlinux/$repo/os/$arch
7 | Server = https://archlinux.uk.mirror.allworldit.com/archlinux/$repo/os/$arch
8 | Server = https://archlinux.za.mirror.allworldit.com/archlinux/$repo/os/$arch
9 | Server = https://de.arch.mirror.kescher.at/$repo/os/$arch
10 | Server = https://mirrors.atviras.lt/archlinux/$repo/os/$arch
11 | Server = https://archimonde.ts.si/archlinux/$repo/os/$arch
12 | Server = https://mirror.juniorjpdj.pl/archlinux/$repo/os/$arch
13 | Server = https://mirrors.uni-plovdiv.net/archlinux/$repo/os/$arch
14 | Server = https://mirror.ubrco.de/archlinux/$repo/os/$arch
15 | Server = https://mirrors.lug.mtu.edu/archlinux/$repo/os/$arch
16 | Server = https://mirrors.abhy.me/archlinux/$repo/os/$arch
17 | Server = https://dfw.mirror.rackspace.com/archlinux/$repo/os/$arch
18 | Server = https://hkg.mirror.rackspace.com/archlinux/$repo/os/$arch
19 | Server = https://iad.mirror.rackspace.com/archlinux/$repo/os/$arch
20 | Server = https://lon.mirror.rackspace.com/archlinux/$repo/os/$arch
21 |
--------------------------------------------------------------------------------
/src/boot/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | ######################################################################
2 | # @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | # @file : CMakeLists
4 | ######################################################################
5 |
6 | cmake_minimum_required(VERSION 3.5)
7 |
8 | project(FlatImage DESCRIPTION "FlatImage" LANGUAGES CXX)
9 |
10 | # Compiler
11 | set(CMAKE_CXX_STANDARD 23)
12 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
13 | set(CMAKE_CXX_COMPILER g++)
14 | set(CMAKE_BUILD_TYPE Release)
15 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
16 | message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
17 | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
18 |
19 | # Avoid in-source builds
20 | if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
21 | message(FATAL_ERROR "In-source builds are not allowed")
22 | endif()
23 |
24 | # Tools
25 | ## XXD
26 | find_program(XXD_COMMAND xxd)
27 | if(NOT XXD_COMMAND)
28 | message(FATAL_ERROR "xxd command not found")
29 | endif()
30 | ## Git
31 | find_program(GIT_COMMAND git)
32 | if(NOT GIT_COMMAND)
33 | message(FATAL_ERROR "git command not found")
34 | endif()
35 |
36 | # Definitions
37 | ## Distribution
38 | add_definitions("-DFIM_DIST=\"$ENV{FIM_DIST}\"")
39 | message(STATUS "FIM_DIST: ${FIM_DIST}")
40 | ## Version
41 | execute_process(
42 | COMMAND ${GIT_COMMAND} describe --tags --abbrev=0
43 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
44 | OUTPUT_VARIABLE VERSION
45 | OUTPUT_STRIP_TRAILING_WHITESPACE
46 | )
47 | add_definitions("-DVERSION=\"${VERSION}\"")
48 | message(STATUS "VERSION: ${VERSION}")
49 | ## Timestamp
50 | string(TIMESTAMP TIMESTAMP "%Y%m%d%H%M%S")
51 | add_definitions("-DTIMESTAMP=\"${TIMESTAMP}\"")
52 | message(STATUS "TIMESTAMP: ${TIMESTAMP}")
53 | ## Commit
54 | execute_process(
55 | COMMAND ${GIT_COMMAND} rev-parse --short HEAD
56 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
57 | OUTPUT_VARIABLE GIT_COMMIT_HASH
58 | OUTPUT_STRIP_TRAILING_WHITESPACE
59 | )
60 | add_definitions("-DCOMMIT=\"${GIT_COMMIT_HASH}\"")
61 | message(STATUS "GIT COMMIT HASH: ${GIT_COMMIT_HASH}")
62 |
63 | # External libraries
64 | find_package(nlohmann_json REQUIRED)
65 | find_package(Boost REQUIRED)
66 | find_package(JPEG REQUIRED)
67 | find_package(PNG REQUIRED)
68 | find_package(ZLIB REQUIRED)
69 |
70 | # Main executable
71 | add_executable(boot boot.cpp)
72 | target_link_libraries(boot PRIVATE
73 | nlohmann_json::nlohmann_json
74 | /usr/lib/libturbojpeg.a
75 | /usr/lib/libpng.a
76 | /usr/lib/libcom_err.a
77 | /usr/lib/libz.a
78 | )
79 | target_compile_options(boot PRIVATE -g -rdynamic -static -Wall -Os -Wextra)
80 | target_link_options(boot PRIVATE -static)
81 |
82 | # Magic patcher
83 | add_executable(magic magic.cpp)
84 | target_compile_options(magic PRIVATE --std=c++23 -Wall -Wextra)
85 | target_link_options(magic PRIVATE -static)
86 | add_custom_target(run_magic ALL
87 | DEPENDS boot magic
88 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/magic ${CMAKE_CURRENT_BINARY_DIR}/boot
89 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
90 | COMMENT "Running magic with boot as argument"
91 | VERBATIM
92 | )
93 |
--------------------------------------------------------------------------------
/src/boot/cmd/bind.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : bind
4 | ///@created : Wednesday Sep 11, 2024 15:00:09 -03
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 | #include "../../cpp/lib/db.hpp"
11 | #include "../../cpp/std/variant.hpp"
12 |
13 | namespace ns_cmd::ns_bind
14 | {
15 | ENUM(CmdBindOp,ADD,DEL,LIST);
16 | ENUM(CmdBindType,RO,RW,DEV);
17 |
18 | using index_t = uint64_t;
19 | using bind_t = struct { CmdBindType type; std::string src; std::string dst; };
20 | using data_t = std::variant;
21 |
22 | struct CmdBind
23 | {
24 | ns_cmd::ns_bind::CmdBindOp op;
25 | data_t data;
26 | };
27 |
28 | namespace
29 | {
30 |
31 | namespace fs = std::filesystem;
32 |
33 | // fn: get_highest_index() {{{
34 | inline index_t get_highest_index(ns_db::Db const& db)
35 | {
36 | auto bindings = db.items()
37 | | std::views::transform([](auto&& e){ return e.key(); })
38 | | std::views::transform([](auto&& e){ return std::stoi(e); });
39 | auto it = std::max_element(bindings.begin()
40 | , bindings.end()
41 | , [](auto&& a, auto&& b){ return a < b; }
42 | );
43 | return ( it == bindings.end() )? 0 : *it;
44 | } // fn: get_highest_index() }}}
45 |
46 | } // namespace
47 |
48 | // fn: add() {{{
49 | inline void add(fs::path const& path_file_config, CmdBind const& cmd)
50 | {
51 | // Get src and dst paths
52 | auto binds = ns_variant::get_if_holds_alternative(cmd.data);
53 | ereturn_if(not binds, "Invalid data type for 'add' command");
54 |
55 | // Find out the highest bind index
56 | auto db = ns_db::Db(path_file_config, ns_db::Mode::UPDATE_OR_CREATE);
57 | index_t idx = get_highest_index(db) + 1;
58 | ns_log::info()("Binding index is '{}'", idx);
59 |
60 | // Include bindings
61 | db(idx)("type") = (binds->type == CmdBindType::RO)? "ro"
62 | : (binds->type == CmdBindType::RW)? "rw"
63 | : "dev";
64 | db(idx)("src") = binds->src;
65 | db(idx)("dst") = binds->dst;
66 | } // fn: add() }}}
67 |
68 | // fn: del() {{{
69 | inline decltype(auto) del(fs::path const& path_file_config, CmdBind const& cmd)
70 | {
71 | // Get index to delete
72 | auto index = ns_variant::get_if_holds_alternative(cmd.data);
73 | ereturn_if(not index, "Invalid data type for 'del' command");
74 |
75 | // Open db
76 | auto db = ns_db::Db(path_file_config, ns_db::Mode::UPDATE_OR_CREATE);
77 |
78 | // Check if it exists
79 | auto items = db.items();
80 | auto it = std::ranges::find_if(items, [&](auto&& e){ return std::stoi(e.key()) == index; });
81 | ereturn_if(it == items.end(), "Specified index does not exist");
82 |
83 | // Erase value by overwriting
84 | while( std::next(it) != items.end() )
85 | {
86 | it.value() = std::next(it).value();
87 | ++it;
88 | } // while
89 |
90 | // Erase last index
91 | db.obj_erase(get_highest_index(db));
92 | } // fn: del() }}}
93 |
94 | // fn: list() {{{
95 | inline decltype(auto) list(fs::path const& path_file_config)
96 | {
97 | // Open db
98 | auto db = ns_db::Db(path_file_config, ns_db::Mode::UPDATE_OR_CREATE);
99 | // Print entries to stdout
100 | println(db.dump(2));
101 | } // fn: list() }}}
102 |
103 | } // namespace ns_cmd::ns_bind
104 |
105 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
106 |
--------------------------------------------------------------------------------
/src/boot/cmd/help.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : help
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | namespace ns_cmd::ns_help
13 | {
14 |
15 | class HelpEntry
16 | {
17 | private:
18 | std::string m_msg;
19 | std::string m_name;
20 | public:
21 | HelpEntry(std::string const& name)
22 | : m_msg("Flatimage - Portable Linux Applications\n")
23 | , m_name(name)
24 | {};
25 | HelpEntry& with_usage(std::string_view usage)
26 | {
27 | m_msg.append("Usage: ").append(usage).append("\n");
28 | return *this;
29 | }
30 | HelpEntry& with_example(std::string_view example)
31 | {
32 | m_msg.append("Example: ").append(example).append("\n");
33 | return *this;
34 | }
35 | HelpEntry& with_note(std::string_view note)
36 | {
37 | m_msg.append("Note: ").append(note).append("\n");
38 | return *this;
39 | }
40 | HelpEntry& with_description(std::string_view description)
41 | {
42 | m_msg.append(m_name).append(" : ").append(description).append("\n");
43 | return *this;
44 | }
45 | HelpEntry& with_commands(std::vector> commands)
46 | {
47 | m_msg.append("Commands:\n ");
48 | std::ranges::for_each(commands, [&](auto&& e){ m_msg += e.first + ","; } );
49 | m_msg.append("\n");
50 | std::ranges::for_each(commands, [&](auto&& e)
51 | {
52 | m_msg += std::string{" "} + e.first + " : " + e.second + '\n';
53 | });
54 | return *this;
55 | }
56 | HelpEntry& with_args(std::vector> args)
57 | {
58 | std::ranges::for_each(args, [&](auto&& e)
59 | {
60 | m_msg += std::string{" <"} + e.first + "> : " + e.second + '\n';
61 | });
62 | return *this;
63 | }
64 | std::string get()
65 | {
66 | return m_msg;
67 | }
68 | };
69 |
70 | inline std::string help_usage()
71 | {
72 | return HelpEntry{"fim-help"}
73 | .with_description("See usage details for specified command")
74 | .with_usage("fim-help ")
75 | .with_args({
76 | { "cmd", "Name of the command to display help details" },
77 | })
78 | .with_note("Available commands: fim-{exec,root,perms,env,desktop,layer,bind,commit,boot}")
79 | .with_example(R"(fim-help bind")")
80 | .get();
81 | }
82 |
83 | inline std::string root_usage()
84 | {
85 | return HelpEntry{"fim-root"}
86 | .with_description("Executes a command as the root user")
87 | .with_usage("fim-root program-name [program-args...]")
88 | .with_args({
89 | { "program-name", "Name of the program to execute, it can be the name of a binary or the full path" },
90 | { "program-args...", "Arguments for the executed program" },
91 | })
92 | .with_example(R"(fim-root id -u")")
93 | .get();
94 | }
95 |
96 | inline std::string exec_usage()
97 | {
98 | return HelpEntry{"fim-exec"}
99 | .with_description("Executes a command as a regular user")
100 | .with_usage("fim-exec program-name [program-args...]")
101 | .with_args({
102 | { "program-name", "Name of the program to execute, it can be the name of a binary or the full path" },
103 | { "program-args...", "Arguments for the executed program" },
104 | })
105 | .with_example(R"(fim-exec echo -e "hello\nworld")")
106 | .get();
107 | }
108 |
109 | inline std::string perms_usage()
110 | {
111 | return HelpEntry{"fim-perms"}
112 | .with_description("Edit current permissions for the flatimage")
113 | .with_commands({
114 | { "add", "Allow one or more permissions" },
115 | { "del", "Delete one or more permissions" },
116 | { "list", "List current permissions" },
117 | })
118 | .with_note("Permissions: home,media,audio,wayland,xorg,dbus_user,dbus_system,udev,usb,input,gpu,network")
119 | .with_usage("fim-perms add ")
120 | .with_args({
121 | { "perms", "One or more permissions" },
122 | })
123 | .with_usage("fim-perms del ")
124 | .with_args({
125 | { "perms", "One or more permissions" },
126 | })
127 | .with_usage("fim-perms list")
128 | .with_example("fim-perms add home,network,gpu")
129 | .get();
130 | }
131 |
132 | inline std::string env_usage()
133 | {
134 | return HelpEntry{"fim-env"}
135 | .with_description("Edit current permissions for the flatimage")
136 | .with_commands({
137 | { "set", "Redefines the environment variables as the input arguments" },
138 | { "add", "Include a novel environment variable" },
139 | })
140 | .with_usage("fim-env add <'key=value'...>")
141 | .with_example("fim-env add 'APP_NAME=hello-world' 'HOME=/home/my-app'")
142 | .with_usage("fim-env set <'key=value'...>")
143 | .with_example("fim-env set 'APP_NAME=hello-world' 'HOME=/home/my-app'")
144 | .get();
145 | }
146 |
147 | inline std::string desktop_usage()
148 | {
149 | return HelpEntry{"fim-desktop"}
150 | .with_description("Configure the desktop integration")
151 | .with_commands({
152 | { "setup", "Sets up the desktop integration with an input json file" },
153 | { "enable", "Enables the desktop integration selectively" },
154 | })
155 | .with_usage("fim-desktop setup ")
156 | .with_args({
157 | { "json-file", "Path to the json file with the desktop configuration"},
158 | })
159 | .with_usage("fim-desktop enable [entry,mimetype,icon]")
160 | .with_args({
161 | { "entry", "Enable the start menu desktop entry"},
162 | { "mimetype", "Enable the mimetype"},
163 | { "icon", "Enable the icon for the file manager and desktop entry"},
164 | })
165 | .get();
166 | }
167 |
168 | inline std::string layer_usage()
169 | {
170 | return HelpEntry{"fim-layer"}
171 | .with_description("Manage the layers of the current FlatImage")
172 | .with_commands({
173 | { "create", "Creates a novel layer from and save in " },
174 | { "add", "Includes the novel layer in the image in the top of the layer stack" },
175 | })
176 | .with_usage("fim-layer create ")
177 | .with_args({
178 | { "in-dir", "Input directory to create a novel layer from"},
179 | { "out-file" , "Output file name of the layer file"},
180 | })
181 | .with_usage("fim-layer add ")
182 | .with_args({
183 | { "in-file", "Path to the layer file to include in the FlatImage"},
184 | })
185 | .get();
186 | }
187 |
188 | inline std::string bind_usage()
189 | {
190 | return HelpEntry{"fim-bind"}
191 | .with_description("Bind paths from the host to inside the container")
192 | .with_commands({
193 | { "add", "Create a novel binding of type from to " },
194 | { "del", "Deletes a binding with the specified index" },
195 | { "list", "List current bindings"}
196 | })
197 | .with_usage("fim-bind add ")
198 | .with_args({
199 | { "type", "ro, rw, dev" },
200 | { "src" , "A file, directory, or device" },
201 | { "dst" , "A file, directory, or device" },
202 | })
203 | .with_usage("fim-bind del ")
204 | .with_args({
205 | { "index" , "Index of the binding to erase" },
206 | })
207 | .with_usage("fim-bind list")
208 | .get();
209 | }
210 |
211 | inline std::string commit_usage()
212 | {
213 | return HelpEntry{"fim-commit"}
214 | .with_description("Compresses current changes and inserts them into the FlatImage")
215 | .with_commands({
216 | { "commit", "Compress and include changes in the image" },
217 | })
218 | .with_usage("fim-commit")
219 | .get();
220 | }
221 |
222 | inline std::string notify_usage()
223 | {
224 | return HelpEntry{"fim-notify"}
225 | .with_description("Notify with 'notify-send' when the program starts")
226 | .with_usage("fim-notify ")
227 | .with_args({
228 | { "switch", "on, off" },
229 | })
230 | .get();
231 | }
232 |
233 | inline std::string casefold_usage()
234 | {
235 | return HelpEntry{"fim-casefold"}
236 | .with_description("Enables casefolding for the filesystem (ignore case)")
237 | .with_usage("fim-casefold ")
238 | .with_args({
239 | { "switch", "on, off" },
240 | })
241 | .get();
242 | }
243 |
244 | inline std::string boot_usage()
245 | {
246 | return HelpEntry{"fim-boot"}
247 | .with_description("Configure the default startup command")
248 | .with_commands({
249 | { "boot", "Execute with optional [args] when FlatImage is launched" },
250 | })
251 | .with_usage("fim-boot [args...]")
252 | .with_args({
253 | { "command" , "Startup command" },
254 | { "args" , "Arguments for the startup command" },
255 | })
256 | .with_example("fim-boot echo test")
257 | .with_note("To restore the default behavior use `fim-boot bash`")
258 | .get();
259 | }
260 |
261 |
262 | } // namespace ns_cmd::ns_help
263 |
264 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
265 |
--------------------------------------------------------------------------------
/src/boot/cmd/layers.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : layers
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "../../cpp/lib/subprocess.hpp"
12 |
13 | namespace
14 | {
15 |
16 | namespace fs = std::filesystem;
17 |
18 | }
19 |
20 | namespace ns_layers
21 | {
22 |
23 | // fn: create() {{{
24 | inline void create(fs::path const& path_dir_src, fs::path const& path_file_dst, uint64_t compression_level)
25 | {
26 | // Find mkdwarfs binary
27 | auto opt_path_file_mkdwarfs = ns_subprocess::search_path("mkdwarfs");
28 | ethrow_if(not opt_path_file_mkdwarfs, "Could not find 'mkdwarfs' binary");
29 |
30 | // Compression level must be at least 1 and less or equal to 10
31 | compression_level = std::clamp(compression_level, uint64_t{0}, uint64_t{9});
32 |
33 | // // Convert to non-percentual compression level
34 | // compression_level = std::ceil(22 * (static_cast(compression_level) / 10));
35 |
36 | // Compress filesystem
37 | ns_log::info()("Compression level: '{}'", compression_level);
38 | ns_log::info()("Compress filesystem to '{}'", path_file_dst);
39 | auto ret = ns_subprocess::Subprocess(*opt_path_file_mkdwarfs)
40 | .with_args("-f")
41 | .with_args("-i", path_dir_src, "-o", path_file_dst)
42 | .with_args("-l", compression_level)
43 | .spawn()
44 | .wait();
45 | ethrow_if(not ret, "mkdwarfs process exited abnormally");
46 | ethrow_if(*ret != 0, "mkdwarfs process exited with error code '{}'"_fmt(*ret));
47 | } // fn: create() }}}
48 |
49 | // fn: add() {{{
50 | inline void add(fs::path const& path_file_binary, fs::path const& path_file_layer)
51 | {
52 | // Open binary file for writing
53 | std::ofstream file_binary(path_file_binary, std::ios::app | std::ios::binary);
54 | std::ifstream file_layer(path_file_layer, std::ios::in | std::ios::binary);
55 | ereturn_if(not file_binary.is_open(), "Failed to open output file '{}'"_fmt(path_file_binary))
56 | ereturn_if(not file_layer.is_open(), "Failed to open input file '{}'"_fmt(path_file_layer))
57 | // Get byte size
58 | uint64_t file_size = fs::file_size(path_file_layer);
59 | // Write byte size
60 | file_binary.write(reinterpret_cast(&file_size), sizeof(file_size));
61 | char buff[8192];
62 | while( file_layer.read(buff, sizeof(buff)) or file_layer.gcount() > 0 )
63 | {
64 | file_binary.write(buff, file_layer.gcount());
65 | ereturn_if(not file_binary, "Error writing data to file");
66 | } // while
67 | ns_log::info()("Included novel layer from file '{}'", path_file_layer);
68 | } // fn: add() }}}
69 |
70 | } // namespace ns_layers
71 |
72 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
73 |
--------------------------------------------------------------------------------
/src/boot/compile_commands/impl_setup_compile_commands.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | # Find used libraries
8 | readarray -t LIB_NAMES < <(pcregrep -o2 "isystem (.*?/p/(.*?)/p/include)" ./build/Release/compile_commands.json)
9 | readarray -t LIB_PATHS < <(pcregrep -o1 "isystem (.*?/p/(.*?)/p/include)" ./build/Release/compile_commands.json)
10 |
11 | # Create libs directory
12 | DIR_LIB="/host/conan-libs"
13 | echo "DIR_LIB: $DIR_LIB"
14 | mkdir -p "$DIR_LIB"
15 |
16 | # Copy compile_commands.json
17 | COMPILE_COMMANDS_JSON=/host/compile_commands.json
18 | echo "COMPILE_COMMANDS_JSON: $COMPILE_COMMANDS_JSON"
19 | cp ./build/Release/compile_commands.json "$COMPILE_COMMANDS_JSON"
20 |
21 | for (( i=0; i < "${#LIB_NAMES[@]}"; i++ )); do
22 | # Copy the library to the create directory
23 | cp -r "${LIB_PATHS[$i]}" "$DIR_LIB/${LIB_NAMES[$i]}"
24 | # Replace path in compile_commands.json
25 | sed -i "s|${LIB_PATHS[$i]}|$(dirname "$(dirname "$SCRIPT_DIR")")/conan-libs/${LIB_NAMES[$i]}|" "$COMPILE_COMMANDS_JSON"
26 | done
27 |
--------------------------------------------------------------------------------
/src/boot/compile_commands/setup_compile_commands.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | # goto parent dir
8 | cd "$(dirname "$(dirname "$SCRIPT_DIR")")"
9 |
10 | docker run -it --rm -v"$(pwd)":/host flatimage-boot ./compile_commands/impl_setup_compile_commands.sh
11 |
--------------------------------------------------------------------------------
/src/boot/conanfile.txt:
--------------------------------------------------------------------------------
1 | [requires]
2 | nlohmann_json/3.11.3
3 |
4 | [generators]
5 | CMakeDeps
6 | CMakeToolchain
7 |
8 | [layout]
9 | cmake_layout
10 |
--------------------------------------------------------------------------------
/src/boot/config/config.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : config
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "../../cpp/lib/env.hpp"
12 |
13 | #ifndef FIM_DIST
14 | #define FIM_DIST "TRUNK"
15 | #endif
16 |
17 | namespace ns_config
18 | {
19 |
20 | namespace
21 | {
22 |
23 | namespace fs = std::filesystem;
24 |
25 | struct Offset
26 | {
27 | uint64_t offset;
28 | uint64_t size;
29 | };
30 |
31 | // impl_update_get_config_files() {{{
32 | inline decltype(auto) impl_update_get_config_files(std::vector const& vec_path_dir_layer
33 | , fs::path const& path_dir_upper
34 | , fs::path const& path_file_config)
35 | {
36 | // Check if configuration exists in upperdir
37 | dreturn_if(fs::exists(path_dir_upper / path_file_config), "Configuration file '{}' exists"_fmt(path_file_config));
38 | // Try to find configuration file in layer stack with descending order
39 | auto it = std::ranges::find_if(vec_path_dir_layer, [&](auto&& e){ return fs::exists(e / path_file_config); });
40 | dreturn_if(it == std::ranges::end(vec_path_dir_layer), "Could not find '{}' in layer stack"_fmt(path_file_config));
41 | // Copy to upperdir
42 | fs::copy_file(*it / path_file_config, path_dir_upper / path_file_config, fs::copy_options::skip_existing);
43 | } // impl_update_get_config_files() }}}
44 |
45 | } // namespace
46 |
47 | constexpr int64_t const SIZE_RESERVED_TOTAL = 2097152;
48 | constexpr int64_t const SIZE_RESERVED_IMAGE = 1048576;
49 |
50 | // enum class OverlayType {{{
51 | enum class OverlayType
52 | {
53 | BWRAP,
54 | FUSE_OVERLAYFS,
55 | FUSE_UNIONFS,
56 | }; // }}}
57 |
58 | // struct FlatimageConfig {{{
59 | struct FlatimageConfig
60 | {
61 | std::string str_dist;
62 | bool is_root;
63 | bool is_readonly;
64 | bool is_debug;
65 |
66 | OverlayType overlay_type;
67 | uint64_t offset_reserved;
68 | Offset offset_permissions;
69 | Offset offset_notify;
70 | Offset offset_desktop;
71 | Offset offset_desktop_image;
72 | uint64_t offset_filesystem;
73 | fs::path path_dir_global;
74 | fs::path path_dir_mount;
75 | fs::path path_dir_app;
76 | fs::path path_dir_app_bin;
77 | fs::path path_dir_busybox;
78 | fs::path path_dir_instance;
79 | fs::path path_file_binary;
80 | fs::path path_dir_binary;
81 | fs::path path_file_bashrc;
82 | fs::path path_file_bash;
83 | fs::path path_dir_mount_layers;
84 | fs::path path_dir_runtime;
85 | fs::path path_dir_runtime_host;
86 | fs::path path_dir_host_home;
87 | fs::path path_dir_host_config;
88 | fs::path path_dir_data_overlayfs;
89 | fs::path path_dir_upper_overlayfs;
90 | fs::path path_dir_work_overlayfs;
91 | fs::path path_dir_mount_overlayfs;
92 |
93 | fs::path path_dir_config;
94 | fs::path path_file_config_boot;
95 | fs::path path_file_config_environment;
96 | fs::path path_file_config_bindings;
97 | fs::path path_file_config_casefold;
98 |
99 | uint32_t layer_compression_level;
100 |
101 | std::string env_path;
102 | }; // }}}
103 |
104 | // config() {{{
105 | inline FlatimageConfig config()
106 | {
107 | FlatimageConfig config;
108 |
109 | ns_env::set("PID", std::to_string(getpid()), ns_env::Replace::Y);
110 | ns_env::set("FIM_DIST", FIM_DIST, ns_env::Replace::Y);
111 |
112 | // Distribution
113 | config.str_dist = FIM_DIST;
114 |
115 | // Flags
116 | config.is_root = ns_env::exists("FIM_ROOT", "1");
117 | config.is_readonly = ns_env::exists("FIM_RO", "1");
118 | config.is_debug = ns_env::exists("FIM_DEBUG", "1");
119 | config.overlay_type = ns_env::exists("FIM_FUSE_UNIONFS", "1")? OverlayType::FUSE_UNIONFS
120 | : ns_env::exists("FIM_FUSE_OVERLAYFS", "1")? OverlayType::FUSE_OVERLAYFS
121 | : OverlayType::BWRAP;
122 | // Paths in /tmp
123 | config.offset_reserved = std::stoll(ns_env::get_or_throw("FIM_OFFSET"));
124 | // Reserve 8 first bytes for permission data
125 | config.offset_permissions = { config.offset_reserved, 8 };
126 | // Reserve next byte to check if notify is enabled
127 | config.offset_notify = { config.offset_permissions.offset + config.offset_permissions.size, 1 };
128 | // Desktop entry information, reserve 4096 bytes for json data
129 | config.offset_desktop = { config.offset_notify.offset + config.offset_notify.size, 4096 };
130 | // Space reserved for desktop icon
131 | config.offset_desktop_image = { config.offset_reserved + SIZE_RESERVED_TOTAL - SIZE_RESERVED_IMAGE, SIZE_RESERVED_IMAGE};
132 | config.offset_filesystem = config.offset_reserved + SIZE_RESERVED_TOTAL;
133 | config.path_dir_global = ns_env::get_or_throw("FIM_DIR_GLOBAL");
134 | config.path_file_binary = ns_env::get_or_throw("FIM_FILE_BINARY");
135 | config.path_dir_binary = config.path_file_binary.parent_path();
136 | config.path_dir_app = ns_env::get_or_throw("FIM_DIR_APP");
137 | config.path_dir_app_bin = ns_env::get_or_throw("FIM_DIR_APP_BIN");
138 | config.path_dir_busybox = ns_env::get_or_throw("FIM_DIR_BUSYBOX");
139 | config.path_dir_instance = ns_env::get_or_throw("FIM_DIR_INSTANCE");
140 | config.path_dir_mount = ns_env::get_or_throw("FIM_DIR_MOUNT");
141 | config.path_file_bashrc = config.path_dir_app / ".bashrc";
142 | config.path_file_bash = config.path_dir_app_bin / "bash";
143 | config.path_dir_mount_layers = config.path_dir_mount / "layers";
144 | config.path_dir_mount_overlayfs = config.path_dir_mount / "overlayfs";
145 |
146 | // Paths only available inside the container (runtime)
147 | config.path_dir_runtime = "/tmp/fim/run";
148 | config.path_dir_runtime_host = config.path_dir_runtime / "host";
149 | ns_env::set("FIM_DIR_RUNTIME", config.path_dir_runtime, ns_env::Replace::Y);
150 | ns_env::set("FIM_DIR_RUNTIME_HOST", config.path_dir_runtime_host, ns_env::Replace::Y);
151 |
152 | // Home directory
153 | config.path_dir_host_home = fs::path{ns_env::get_or_throw("HOME")}.relative_path();
154 |
155 | // Create host config directory
156 | config.path_dir_host_config = config.path_file_binary.parent_path() / ".{}.config"_fmt(config.path_file_binary.filename());
157 | ethrow_if(not fs::exists(config.path_dir_host_config) and not fs::create_directories(config.path_dir_host_config)
158 | , "Could not create configuration directory in '{}'"_fmt(config.path_dir_host_config)
159 | );
160 | ns_env::set("FIM_DIR_CONFIG", config.path_dir_host_config, ns_env::Replace::Y);
161 |
162 | // Overlayfs write data to remain on the host
163 | config.path_dir_data_overlayfs = config.path_dir_host_config / "overlays";
164 | config.path_dir_upper_overlayfs = config.path_dir_data_overlayfs / "upperdir";
165 | config.path_dir_work_overlayfs = config.path_dir_data_overlayfs / "workdir";
166 | fs::create_directories(config.path_dir_upper_overlayfs);
167 | fs::create_directories(config.path_dir_work_overlayfs);
168 |
169 | // Configuration files directory
170 | config.path_dir_config = config.path_dir_upper_overlayfs / "fim/config";
171 | fs::create_directories(config.path_dir_config);
172 |
173 | // Bwrap
174 | ns_env::set("BWRAP_LOG", config.path_dir_mount.string() + ".bwrap.log", ns_env::Replace::Y);
175 |
176 | // Environment
177 | config.env_path = config.path_dir_app_bin.string() + ":" + ns_env::get_or_else("PATH", "");
178 | config.env_path += ":/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin";
179 | config.env_path += ":{}"_fmt(config.path_dir_busybox.string());
180 | ns_env::set("PATH", config.env_path, ns_env::Replace::Y);
181 |
182 | // Compression level configuration (goes from 0 to 10, default is 7)
183 | config.layer_compression_level = ns_exception::to_expected([]{ return std::stoi(ns_env::get_or_else("FIM_COMPRESSION_LEVEL", "7")); })
184 | .value_or(7);
185 | config.layer_compression_level = std::clamp(config.layer_compression_level, uint32_t{0}, uint32_t{10});
186 |
187 | // Paths to the configuration files
188 | config.path_file_config_boot = config.path_dir_config / "boot.json";
189 | config.path_file_config_environment = config.path_dir_config / "environment.json";
190 | config.path_file_config_bindings = config.path_dir_config / "bindings.json";
191 | config.path_file_config_casefold = config.path_dir_config / "casefold.json";
192 |
193 | // PID
194 | ns_env::set("FIM_PID", getpid(), ns_env::Replace::Y);
195 |
196 | // LD_LIBRARY_PATH
197 | if ( ns_env::exists("LD_LIBRARY_PATH") )
198 | {
199 | ns_env::prepend("LD_LIBRARY_PATH", "/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu:");
200 | }
201 | else
202 | {
203 | ns_env::set("LD_LIBRARY_PATH", "/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu", ns_env::Replace::Y);
204 | } // else
205 |
206 | return config;
207 | } // config() }}}
208 |
209 | // get_mounted_layers() {{{
210 | inline decltype(auto) get_mounted_layers(fs::path const& path_dir_layers)
211 | {
212 | std::vector vec_path_dir_layer = fs::directory_iterator(path_dir_layers)
213 | | std::views::filter([](auto&& e){ return fs::is_directory(e.path()); })
214 | | std::views::transform([](auto&& e){ return e.path(); })
215 | | std::ranges::to>();
216 | // Reverse sort
217 | std::ranges::sort(vec_path_dir_layer, std::greater<>{});
218 | return vec_path_dir_layer;
219 | } // get_mounted_layers() }}}
220 |
221 | // push_config_files() {{{
222 | inline decltype(auto) push_config_files(fs::path const& path_dir_layers, fs::path const& path_dir_upper)
223 | {
224 | auto vec_path_dir_layer = get_mounted_layers(path_dir_layers);
225 | // Write configuration files to upper directory
226 | impl_update_get_config_files(vec_path_dir_layer, path_dir_upper, "fim/config/boot.json");
227 | impl_update_get_config_files(vec_path_dir_layer, path_dir_upper, "fim/config/environment.json");
228 | impl_update_get_config_files(vec_path_dir_layer, path_dir_upper, "fim/config/bindings.json");
229 | impl_update_get_config_files(vec_path_dir_layer, path_dir_upper, "fim/config/casefold.json");
230 | } // push_config_files() }}}
231 |
232 | } // namespace ns_config
233 |
234 |
235 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
236 |
--------------------------------------------------------------------------------
/src/boot/config/environment.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : environment
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "../../cpp/std/functional.hpp"
12 | #include "../../cpp/lib/db.hpp"
13 | #include "../../cpp/lib/subprocess.hpp"
14 |
15 | namespace ns_config::ns_environment
16 | {
17 |
18 | namespace
19 | {
20 |
21 | namespace fs = std::filesystem;
22 |
23 | inline std::vector keys(std::vector const& entries)
24 | {
25 | return entries
26 | | std::views::transform([](auto&& e){ return e.substr(0, e.find('=')); })
27 | | std::ranges::to>();
28 | } // keys
29 |
30 | inline std::vector validate(std::vector const& entries)
31 | {
32 | return entries
33 | | std::views::filter([](auto&& e){ return std::ranges::count_if(e, [](char c){ return c == '='; }) > 0; })
34 | | std::ranges::to>();
35 | } // validate
36 |
37 | } // namespace
38 |
39 | inline void del(fs::path const& path_file_config_environment, std::vector const& entries)
40 | {
41 | for(auto&& entry : entries)
42 | {
43 | ns_db::Db(path_file_config_environment, ns_db::Mode::UPDATE).array_erase_if(ns_functional::StartsWith(entry + "="));
44 | } // for
45 | }
46 |
47 | inline void set(fs::path const& path_file_config_environment, std::vector entries)
48 | {
49 | entries = validate(entries);
50 | if ( fs::exists(path_file_config_environment) )
51 | {
52 | ns_exception::ignore([&]{ del(path_file_config_environment, keys(entries)); });
53 | } // if
54 | ns_db::Db(path_file_config_environment, ns_db::Mode::CREATE).set_insert(entries);
55 | }
56 |
57 | inline void add(fs::path const& path_file_config_environment, std::vector entries)
58 | {
59 | entries = validate(entries);
60 | ns_exception::ignore([&]{ del(path_file_config_environment, keys(entries)); });
61 | ns_db::Db(path_file_config_environment, ns_db::Mode::UPDATE_OR_CREATE).set_insert(entries);
62 | }
63 |
64 | inline std::vector get(fs::path const& path_file_config_environment)
65 | {
66 | std::vector environment = ns_db::Db(path_file_config_environment, ns_db::Mode::READ).as_vector();
67 | // Expand variables
68 | for(auto& variable : environment)
69 | {
70 | if(auto expanded = ns_env::expand(variable))
71 | {
72 | variable = *expanded;
73 | } // if
74 | else
75 | {
76 | ns_log::error()("Failed to expand variable: {}", expanded.error());
77 | } // else
78 | } // for
79 | return environment;
80 | }
81 |
82 | } // namespace ns_config::ns_environment
83 |
84 |
85 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
86 |
--------------------------------------------------------------------------------
/src/boot/filesystems.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : filesystems
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "../cpp/lib/overlayfs.hpp"
12 | #include "../cpp/lib/unionfs.hpp"
13 | #include "../cpp/lib/squashfs.hpp"
14 | #include "../cpp/lib/dwarfs.hpp"
15 | #include "../cpp/lib/ciopfs.hpp"
16 | #include "./config/config.hpp"
17 |
18 | #include "config/config.hpp"
19 |
20 | namespace ns_filesystems
21 | {
22 |
23 | // class Filesystems {{{
24 | class Filesystems
25 | {
26 | private:
27 | fs::path m_path_dir_mount;
28 | std::vector m_vec_path_dir_mountpoints;
29 | std::vector> m_layers;
30 | std::unique_ptr m_ciopfs;
31 | std::unique_ptr m_overlayfs;
32 | std::unique_ptr m_unionfs;
33 | std::optional m_opt_pid_janitor;
34 | uint64_t mount_dwarfs(fs::path const& path_dir_mount, fs::path const& path_file_binary, uint64_t offset);
35 | void mount_ciopfs(fs::path const& path_dir_lower, fs::path const& path_dir_upper);
36 | void mount_unionfs(std::vector const& vec_path_dir_layer
37 | , fs::path const& path_dir_data
38 | , fs::path const& path_dir_mount
39 | , fs::path const& path_dir_workdir
40 | );
41 | void mount_overlayfs(std::vector const& vec_path_dir_layer
42 | , fs::path const& path_dir_data
43 | , fs::path const& path_dir_mount
44 | , fs::path const& path_dir_workdir
45 | );
46 | // In case the parent process fails to clean the mountpoints, this child does it
47 | void spawn_janitor();
48 |
49 | public:
50 | Filesystems(ns_config::FlatimageConfig const& config);
51 | ~Filesystems();
52 | Filesystems(Filesystems const&) = delete;
53 | Filesystems(Filesystems&&) = delete;
54 | Filesystems& operator=(Filesystems const&) = delete;
55 | Filesystems& operator=(Filesystems&&) = delete;
56 | }; // class Filesystems }}}
57 |
58 | // fn: Filesystems::Filesystems {{{
59 | inline Filesystems::Filesystems(ns_config::FlatimageConfig const& config)
60 | : m_path_dir_mount(config.path_dir_mount)
61 | {
62 | // Mount compressed layers
63 | uint64_t index_fs = mount_dwarfs(config.path_dir_mount_layers, config.path_file_binary, config.offset_filesystem);
64 | // Push config files to upper directories if they do not exist in it
65 | ns_config::push_config_files(config.path_dir_mount_layers, config.path_dir_upper_overlayfs);
66 | // Check if should mount ciopfs
67 | if ( ns_env::exists("FIM_CASEFOLD", "1") )
68 | {
69 | mount_ciopfs(config.path_dir_mount_layers / std::to_string(index_fs-1)
70 | , config.path_dir_mount_layers / std::to_string(index_fs)
71 | );
72 | ns_log::debug()("ciopfs is enabled");
73 | } // if
74 | if ( config.overlay_type == ns_config::OverlayType::FUSE_UNIONFS )
75 | {
76 | // Mount overlayfs
77 | mount_unionfs(ns_config::get_mounted_layers(config.path_dir_mount_layers)
78 | , config.path_dir_upper_overlayfs
79 | , config.path_dir_mount_overlayfs
80 | , config.path_dir_work_overlayfs
81 | );
82 | }
83 | // Use fuse-overlayfs
84 | else if ( config.overlay_type == ns_config::OverlayType::FUSE_OVERLAYFS )
85 | {
86 | // Mount overlayfs
87 | mount_overlayfs(ns_config::get_mounted_layers(config.path_dir_mount_layers)
88 | , config.path_dir_upper_overlayfs
89 | , config.path_dir_mount_overlayfs
90 | , config.path_dir_work_overlayfs
91 | );
92 | } // if
93 | // Spawn janitor
94 | spawn_janitor();
95 | } // fn Filesystems::Filesystems }}}
96 |
97 | // fn: Filesystems::Filesystems {{{
98 | inline Filesystems::~Filesystems()
99 | {
100 | if ( m_opt_pid_janitor and *m_opt_pid_janitor > 0)
101 | {
102 | // Stop janitor loop & wait for cleanup
103 | kill(*m_opt_pid_janitor, SIGTERM);
104 | // Wait for janitor to finish execution
105 | int status;
106 | waitpid(*m_opt_pid_janitor, &status, 0);
107 | dreturn_if(not WIFEXITED(status), "Janitor exited abnormally");
108 | int code = WEXITSTATUS(status);
109 | dreturn_if(code != 0, "Janitor exited with code '{}'"_fmt(code));
110 | } // if
111 | else
112 | {
113 | ns_log::error()("Janitor is not running");
114 | } // else
115 | } // fn Filesystems::Filesystems }}}
116 |
117 | // fn: spawn_janitor {{{
118 | inline void Filesystems::spawn_janitor()
119 | {
120 | // Find janitor binary
121 | fs::path path_file_janitor = fs::path{ns_env::get_or_throw("FIM_DIR_APP_BIN")} / "janitor";
122 |
123 | // Fork and execve into the janitor process
124 | pid_t pid_parent = getpid();
125 | m_opt_pid_janitor = fork();
126 | ethrow_if(m_opt_pid_janitor < 0, "Failed to fork janitor");
127 |
128 | // Is parent
129 | dreturn_if(m_opt_pid_janitor > 0, "Spawned janitor with PID '{}'"_fmt(*m_opt_pid_janitor));
130 |
131 | // Redirect stdout/stderr to a log file
132 | fs::path path_stdout = std::string{ns_env::get_or_throw("FIM_DIR_MOUNT")} + ".janitor.stdout.log";
133 | fs::path path_stderr = std::string{ns_env::get_or_throw("FIM_DIR_MOUNT")} + ".janitor.stderr.log";
134 | int fd_stdout = open(path_stdout.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
135 | int fd_stderr = open(path_stderr.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
136 | eabort_if(fd_stdout < 0, "Failed to open stdout janitor file");
137 | eabort_if(fd_stderr < 0, "Failed to open stderr janitor file");
138 | dup2(fd_stdout, STDOUT_FILENO);
139 | dup2(fd_stderr, STDERR_FILENO);
140 | close(fd_stdout);
141 | close(fd_stderr);
142 | close(STDIN_FILENO);
143 |
144 | // Keep parent pid in a variable
145 | ns_env::set("PID_PARENT", pid_parent, ns_env::Replace::Y);
146 |
147 | // Create args to janitor
148 | std::vector vec_argv_custom;
149 | vec_argv_custom.push_back(path_file_janitor);
150 | std::copy(m_vec_path_dir_mountpoints.rbegin(), m_vec_path_dir_mountpoints.rend(), std::back_inserter(vec_argv_custom));
151 | auto argv_custom = std::make_unique(vec_argv_custom.size() + 1);
152 | argv_custom[vec_argv_custom.size()] = nullptr;
153 | std::ranges::transform(vec_argv_custom, argv_custom.get(), [](auto&& e) { return e.c_str(); });
154 |
155 | // Execve to janitor
156 | execve(path_file_janitor.c_str(), (char**) argv_custom.get(), environ);
157 |
158 | // Exit process in case of an error
159 | std::abort();
160 | } // fn: spawn_janitor }}}
161 |
162 | // fn: mount_dwarfs {{{
163 | inline uint64_t Filesystems::mount_dwarfs(fs::path const& path_dir_mount, fs::path const& path_file_binary, uint64_t offset)
164 | {
165 | // Open the main binary
166 | std::ifstream file_binary(path_file_binary, std::ios::binary);
167 |
168 | // Filesystem index
169 | uint64_t index_fs{};
170 |
171 | auto f_mount = [this](fs::path path_file_binary, fs::path const& path_dir_mount, uint64_t index_fs, uint64_t offset, uint64_t size_fs)
172 | {
173 | // Create mountpoint
174 | fs::path path_dir_mount_index = path_dir_mount / std::to_string(index_fs);
175 | lec(fs::create_directories,path_dir_mount_index);
176 | // Mount filesystem
177 | ns_log::debug()("Offset to filesystem is '{}'", offset);
178 | this->m_layers.emplace_back(std::make_unique(path_file_binary
179 | , path_dir_mount_index
180 | , offset
181 | , size_fs
182 | , getpid()
183 | ));
184 | // Include in mountpoints vector
185 | m_vec_path_dir_mountpoints.push_back(path_dir_mount_index);
186 | };
187 |
188 | // Advance offset
189 | file_binary.seekg(offset);
190 |
191 | // Mount filesystem concatenated in the image itself
192 | while (true)
193 | {
194 | // Read filesystem size
195 | int64_t size_fs;
196 | dbreak_if(not file_binary.read(reinterpret_cast(&size_fs), sizeof(size_fs)), "Stopped reading at index {}"_fmt(index_fs));
197 | ns_log::debug()("Filesystem size is '{}'", size_fs);
198 | // Skip size bytes
199 | offset += 8;
200 | // Check if filesystem is of type 'DWARFS'
201 | ebreak_if(not ns_dwarfs::is_dwarfs(path_file_binary, offset), "Invalid dwarfs filesystem appended on the image");
202 | // Mount filesystem
203 | f_mount(path_file_binary, path_dir_mount, index_fs, offset, size_fs);
204 | // Go to next filesystem if exists
205 | index_fs += 1;
206 | offset += size_fs;
207 | file_binary.seekg(offset);
208 | } // while
209 | file_binary.close();
210 |
211 | // Get layers from layer directories
212 | std::vector vec_path_file_layer = ns_env::get_optional("FIM_DIRS_LAYER")
213 | // Expand variable, allow expansion to fail to be non-fatal
214 | .transform([](auto&& e){ return ns_env::expand(e).value_or(std::string{e}); })
215 | // Split directories by the char ':'
216 | .transform([](auto&& e){ return ns_vector::from_string(e, ':'); })
217 | // Get all files from each directory into a single vector
218 | .transform([](auto&& e)
219 | {
220 | return e
221 | // Each directory expands to a file list
222 | | std::views::transform([](auto&& f){ return ns_filesystem::ns_path::list_files(f); })
223 | // Filter and transform into a vector of vectors
224 | | std::views::filter([](auto&& f){ return f.has_value(); })
225 | | std::views::transform([](auto&& f){ return f.value(); })
226 | // Joins into a single vector
227 | | std::views::join
228 | // Collect
229 | | std::ranges::to>();
230 | }).value_or(std::vector{});
231 | // Get layers from file paths
232 | ns_vector::append_range(vec_path_file_layer, ns_env::get_optional("FIM_FILES_LAYER")
233 | // Expand variable, allow expansion to fail to be non-fatal
234 | .transform([](auto&& e){ return ns_env::expand(e).value_or(std::string{e}); })
235 | // Split files by the char ':'
236 | .transform([](auto&& e){ return ns_vector::from_string(e, ':') | std::ranges::to>(); })
237 | .value_or(std::vector{})
238 | );
239 | // Mount external filesystems
240 | for (fs::path const& path_file_layer : vec_path_file_layer)
241 | {
242 | // Check if filesystem is of type 'DWARFS'
243 | econtinue_if(not ns_dwarfs::is_dwarfs(path_file_layer, 0), "Invalid dwarfs filesystem appended on the image");
244 | // Mount file as a filesystem
245 | f_mount(path_file_layer, path_dir_mount, index_fs, 0, fs::file_size(path_file_layer));
246 | // Go to next filesystem if exists
247 | index_fs += 1;
248 | } // for
249 |
250 | return index_fs;
251 | } // fn: mount_dwarfs }}}
252 |
253 | // fn: mount_unionfs {{{
254 | inline void Filesystems::mount_unionfs(std::vector const& vec_path_dir_layer
255 | , fs::path const& path_dir_data
256 | , fs::path const& path_dir_mount
257 | , fs::path const& path_dir_workdir)
258 | {
259 | m_unionfs = std::make_unique(vec_path_dir_layer
260 | , path_dir_data
261 | , path_dir_mount
262 | , getpid()
263 | );
264 | m_vec_path_dir_mountpoints.push_back(path_dir_mount);
265 | } // fn: mount_unionfs }}}
266 |
267 | // fn: mount_overlayfs {{{
268 | inline void Filesystems::mount_overlayfs(std::vector const& vec_path_dir_layer
269 | , fs::path const& path_dir_data
270 | , fs::path const& path_dir_mount
271 | , fs::path const& path_dir_workdir)
272 | {
273 | m_overlayfs = std::make_unique(vec_path_dir_layer
274 | , path_dir_data
275 | , path_dir_mount
276 | , path_dir_workdir
277 | , getpid()
278 | );
279 | m_vec_path_dir_mountpoints.push_back(path_dir_mount);
280 | } // fn: mount_overlayfs }}}
281 |
282 | // fn: mount_ciopfs {{{
283 | inline void Filesystems::mount_ciopfs(fs::path const& path_dir_lower, fs::path const& path_dir_upper)
284 | {
285 | this->m_ciopfs = std::make_unique(path_dir_lower, path_dir_upper);
286 | m_vec_path_dir_mountpoints.push_back(path_dir_upper);
287 | } // fn: mount_ciopfs }}}
288 |
289 | } // namespace ns_filesystems
290 |
291 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
292 |
--------------------------------------------------------------------------------
/src/boot/janitor.cpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : janitor
4 | ///
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "../cpp/lib/log.hpp"
12 | #include "../cpp/lib/env.hpp"
13 | #include "../cpp/lib/fuse.hpp"
14 | #include "../cpp/macro.hpp"
15 | #include "../cpp/common.hpp"
16 |
17 | namespace fs = std::filesystem;
18 |
19 | std::atomic_bool G_CONTINUE(true);
20 |
21 | void cleanup(int)
22 | {
23 | G_CONTINUE = false;
24 | } // cleanup
25 |
26 | int main(int argc, char const* argv[])
27 | {
28 | // Register signal handler
29 | signal(SIGTERM, cleanup);
30 |
31 | // Initialize logger
32 | fs::path path_file_log = std::string{ns_env::get_or_throw("FIM_DIR_MOUNT")} + ".janitor.log";
33 | ns_log::set_sink_file(path_file_log);
34 |
35 | ereturn_if(argc < 2, "Incorrect usage", EXIT_FAILURE);
36 |
37 | pid_t pid_parent = std::stoi(ns_env::get_or_throw("PID_PARENT"));
38 |
39 | // Create a novel session for the child process
40 | pid_t pid_session = setsid();
41 | ereturn_if(pid_session < 0, "Failed to create a novel session for janitor", EXIT_FAILURE);
42 | ns_log::info()("Session id is '{}'", pid_session);
43 |
44 | // Wait for parent process to exit
45 | while ( kill(pid_parent, 0) == 0 and G_CONTINUE )
46 | {
47 | using namespace std::chrono_literals;
48 | std::this_thread::sleep_for(100ms);
49 | } // while
50 | ns_log::info()("Parent process with pid '{}' finished", pid_parent);
51 |
52 | // Cleanup mountpoints
53 | for (auto&& path_dir_mountpoint : std::vector(argv+1, argv+argc))
54 | {
55 | ns_log::info()("Un-mount '{}'", path_dir_mountpoint);
56 | ns_fuse::unmount(path_dir_mountpoint);
57 | } // for
58 |
59 | // Exit child
60 | exit(0);
61 |
62 | } // main
63 |
64 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
65 |
--------------------------------------------------------------------------------
/src/boot/magic.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : magic
4 | // @created : Monday Dec 25, 2023 23:37:09 -03
5 | //
6 | // @description : Patch magic bytes to identify file as a flatimage
7 | //
8 |
9 | #include
10 | #include
11 |
12 | int main(int argc, char** argv)
13 | {
14 | // Check arg count
15 | if (argc != 2)
16 | {
17 | std::cerr << "Invalid parameters" << std::endl;
18 | return 1;
19 | }
20 |
21 | // Define magic
22 | unsigned char arr_magic[] = {'F', 'I', 0x01};
23 |
24 | // Open the file in binary mode for rw
25 | std::fstream file (argv[1], std::ios::in | std::ios::out | std::ios::binary);
26 | if (!file)
27 | {
28 | std::cerr << "Error opening file" << std::endl;
29 | return -1;
30 | }
31 |
32 | // Seek
33 | file.seekp(8);
34 |
35 | // Check for seek errors
36 | if (!file)
37 | {
38 | std::cerr << "Error seeking position" << std::endl;
39 | file.close();
40 | return -1;
41 | }
42 |
43 | // Write the new bytes to the specified position
44 | file.write(reinterpret_cast(arr_magic), sizeof(arr_magic));
45 |
46 | // Check for errors after writing
47 | if (!file)
48 | {
49 | std::cerr << "Error writing to file" << std::endl;
50 | file.close();
51 | return -1;
52 | }
53 |
54 | // Close the file
55 | file.close();
56 |
57 | std::cout << "Patched file " << argv[1] << std::endl;
58 |
59 | return 0;
60 | }
61 |
62 | // cmd: !g++ %:p -o %:p:r
63 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
64 |
--------------------------------------------------------------------------------
/src/boot/portal.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | /// @file : portal
4 | ///
5 |
6 | #include
7 | #include
8 |
9 | #include "../cpp/lib/env.hpp"
10 | #include "../cpp/lib/subprocess.hpp"
11 |
12 | namespace ns_portal
13 | {
14 |
15 | namespace
16 | {
17 |
18 | namespace fs = std::filesystem;
19 |
20 | } // anonymous namespace
21 |
22 | // struct Portal {{{
23 | struct Portal
24 | {
25 | std::unique_ptr m_process;
26 | fs::path m_path_file_daemon;
27 | fs::path m_path_file_guest;
28 |
29 | Portal(fs::path const& path_file_reference)
30 | {
31 | // This is read by the guest to send commands to the daemon
32 | ns_env::set("FIM_PORTAL_FILE", path_file_reference, ns_env::Replace::Y);
33 |
34 | // Path to flatimage binaries
35 | const char* str_dir_app_bin = ns_env::get("FIM_DIR_APP_BIN");
36 | ethrow_if(not str_dir_app_bin, "FIM_DIR_APP_BIN is undefined");
37 |
38 | // Create paths to daemon and portal
39 | m_path_file_daemon = fs::path{str_dir_app_bin} / "fim_portal_daemon";
40 | m_path_file_guest = fs::path{str_dir_app_bin} / "fim_portal";
41 | ethrow_if(not fs::exists(m_path_file_daemon), "Daemon not found in {}"_fmt(m_path_file_daemon));
42 | ethrow_if(not fs::exists(m_path_file_guest), "Guest not found in {}"_fmt(m_path_file_guest));
43 |
44 | // Create a portal that uses the reference file to create an unique communication key
45 | m_process = std::make_unique(m_path_file_daemon);
46 |
47 | // Spawn process to background
48 | std::ignore = m_process->with_piped_outputs()
49 | .with_die_on_pid(getpid())
50 | .with_args(path_file_reference)
51 | .spawn();
52 | } // Portal
53 |
54 | ~Portal()
55 | {
56 | m_process->kill(SIGTERM);
57 | std::ignore = m_process->wait();
58 | } // ~Portal()
59 | }; // struct Portal }}}
60 |
61 | } // namespace ns_portal
62 |
63 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
64 |
--------------------------------------------------------------------------------
/src/bwrap/bwrap_apparmor.cpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : bwrap_apparmor
4 | ///
5 |
6 | #include
7 |
8 | #include "../cpp/lib/env.hpp"
9 | #include "../cpp/lib/log.hpp"
10 | #include "../cpp/lib/subprocess.hpp"
11 | #include "../cpp/common.hpp"
12 | #include "../cpp/macro.hpp"
13 |
14 | constexpr std::string_view const profile_bwrap =
15 | R"(abi ,
16 | include
17 | profile bwrap /opt/flatimage/bwrap flags=(unconfined) {
18 | userns,
19 | }
20 | )";
21 |
22 | namespace fs = std::filesystem;
23 |
24 | int main(int argc, char const* argv[])
25 | {
26 | // Check arguments
27 | ereturn_if(argc != 3, "Incorrect # of arguments for bwrap-apparmor", EXIT_FAILURE);
28 | // Set log file location
29 | fs::path path_file_log = std::string{argv[1]} + ".bwrap-apparmor.log";
30 | ns_log::set_sink_file(path_file_log);
31 | // Find apparmor_parser
32 | auto opt_path_file_apparmor_parser = ns_subprocess::search_path("apparmor_parser");
33 | ereturn_if(not opt_path_file_apparmor_parser, "Could not find apparmor_parser", EXIT_FAILURE);
34 | // Define paths
35 | fs::path path_file_bwrap_src{argv[2]};
36 | fs::path path_dir_bwrap{"/opt/flatimage"};
37 | fs::path path_file_bwrap_dst{path_dir_bwrap / "bwrap"};
38 | fs::path path_file_profile{"/etc/apparmor.d/flatimage"};
39 | // Try to create /opt/bwrap directory
40 | qreturn_if(not lec(fs::exists, path_dir_bwrap) and not lec(fs::create_directories, path_dir_bwrap), EXIT_FAILURE);
41 | // Try copy bwrap to /opt/bwrap
42 | qreturn_if(not lec(fs::copy_file, path_file_bwrap_src, path_file_bwrap_dst, fs::copy_options::overwrite_existing), EXIT_FAILURE);
43 | // Try to set permissions for bwrap binary ( chmod 755 )
44 | lec(fs::permissions
45 | , path_file_bwrap_dst
46 | , fs::perms::owner_all
47 | | fs::perms::group_read | fs::perms::group_exec
48 | | fs::perms::others_read | fs::perms::others_exec
49 | , fs::perm_options::replace
50 | );
51 | // Try to create profile
52 | std::ofstream file_profile(path_file_profile);
53 | ereturn_if(not file_profile.is_open(), "Could not open profile file", EXIT_FAILURE);
54 | file_profile << profile_bwrap;
55 | file_profile.close();
56 | // Reload profile
57 | auto ret = ns_subprocess::Subprocess(*opt_path_file_apparmor_parser)
58 | .with_args("-r", path_file_profile)
59 | .spawn()
60 | .wait();
61 | // Exit code same as from call to opt_path_file_apparmor_parser
62 | return (ret)? *ret : EXIT_FAILURE;
63 | } // main
64 |
--------------------------------------------------------------------------------
/src/cpp/common.hpp:
--------------------------------------------------------------------------------
1 |
2 | ///
3 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
4 | // @file : common
5 | ///
6 |
7 | #pragma once
8 |
9 | #include
10 | #include
11 | #include
12 | #include "std/string.hpp"
13 |
14 | namespace
15 | {
16 |
17 | // struct format_args {{{
18 | template
19 | struct format_args
20 | {
21 | // Create a tuple where each type is the result of ns_string::to_string(Args...)
22 | std::tuple()))...> m_tuple_args;
23 |
24 | // Initializes the tuple elements to be string representations of the arguments
25 | format_args(Args&&... args)
26 | : m_tuple_args(ns_string::to_string(std::forward(args))...)
27 | {} // format_args
28 |
29 | // Get underlying arguments
30 | auto operator*()
31 | {
32 | return std::apply([](auto&&... e) { return std::make_format_args(e...); }, m_tuple_args);
33 | } // operator*
34 | }; // struct format_args }}}
35 |
36 | } // namespace
37 |
38 | namespace std
39 | {
40 |
41 | template
42 | using error = optional;
43 |
44 | } // namespace std
45 |
46 | // User defined literals {{{
47 |
48 | // Print to stdout
49 | inline auto operator""_print(const char* c_str, std::size_t)
50 | {
51 | return [=](Args&&... args)
52 | {
53 | std::cout << std::vformat(c_str, *format_args(std::forward(args)...));
54 | };
55 | }
56 |
57 | // Format strings with user-defined literals
58 | inline decltype(auto) operator ""_fmt(const char* str, size_t)
59 | {
60 | return [str](Args&&... args)
61 | {
62 | return std::vformat(str, *format_args(std::forward(args)...)) ;
63 | };
64 | } //
65 |
66 | // Throw with message
67 | inline decltype(auto) operator ""_throw(const char* str, size_t)
68 | {
69 | return [str](Args&&... args)
70 | {
71 | throw std::runtime_error(std::vformat(str, *format_args(std::forward(args)...)));
72 | };
73 | }
74 |
75 | // }}}
76 |
77 | // print() {{{
78 | template
79 | inline void print(std::ostream& os, T&& t, Args&&... args)
80 | {
81 | if constexpr ( sizeof...(args) > 0 )
82 | {
83 | os << std::vformat(std::forward(t), *format_args(std::forward(args)...));
84 | } // if
85 | else
86 | {
87 | os << t;
88 | } // if
89 | } // print() }}}
90 |
91 | // print() {{{
92 | template
93 | requires ( ( ns_concept::StringRepresentable or ns_concept::IterableConst ) and ... )
94 | inline void print(T&& t, Args&&... args)
95 | {
96 | if constexpr ( sizeof...(args) > 0 )
97 | {
98 | std::cout << std::vformat(std::forward(t), *format_args(std::forward(args)...));
99 | } // if
100 | else
101 | {
102 | std::cout << t;
103 | } // if
104 | } // print() }}}
105 |
106 | // print_if() {{{
107 | template
108 | inline void print_if(B&& cond, Args&&... args)
109 | {
110 | if ( cond )
111 | {
112 | print(std::forward(args)...);
113 | } // if
114 | } // print_if() }}}
115 |
116 | // println() {{{
117 | template
118 | inline void println(std::ostream& os, T&& t, Args&&... args)
119 | {
120 | print(os, ns_string::to_string(std::forward(t)) + "\n", std::forward(args)...);
121 | } // println() }}}
122 |
123 | // println() {{{
124 | template
125 | inline void println(T&& t, Args&&... args)
126 | {
127 | print(ns_string::to_string(std::forward(t)) + "\n", std::forward(args)...);
128 | } // println() }}}
129 |
130 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
131 |
--------------------------------------------------------------------------------
/src/cpp/dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ######################################################################
4 | # @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
5 | # @file : dependencies
6 | ######################################################################
7 |
8 | set -e
9 |
10 | echo "digraph G {"
11 |
12 | for i; do
13 | name_file="$i"
14 | name_base="${name_file%%.*}"
15 |
16 | for j in $(pcregrep -o1 "(ns_[a-zA-z0-9]+)" "$name_file" | grep -v "ns_$name_base" | sort -u); do
17 | echo "ns_$name_base -> $j"
18 | done
19 | done
20 |
21 | echo "}"
22 |
--------------------------------------------------------------------------------
/src/cpp/lib/ciopfs.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : ciopfs
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "subprocess.hpp"
12 | #include "fuse.hpp"
13 |
14 | namespace ns_ciopfs
15 | {
16 |
17 |
18 | namespace
19 | {
20 |
21 | namespace fs = std::filesystem;
22 |
23 | } // namespace
24 |
25 | class Ciopfs
26 | {
27 | private:
28 | std::unique_ptr m_subprocess;
29 | fs::path m_path_dir_upper;
30 |
31 | public:
32 | Ciopfs( fs::path const& path_dir_lower , fs::path const& path_dir_upper)
33 | : m_path_dir_upper(path_dir_upper)
34 | {
35 | ethrow_if(not fs::exists(path_dir_lower), "Lowerdir does not exist for ciopfs");
36 |
37 | std::error_code ec;
38 | ethrow_if(not fs::exists(path_dir_upper) and not fs::create_directories(path_dir_upper, ec)
39 | , "Upperdir does not exist for ciopfs: {}"_fmt(ec.message())
40 | );
41 |
42 | // Find Ciopfs
43 | auto opt_path_file_ciopfs = ns_subprocess::search_path("ciopfs");
44 | ethrow_if (not opt_path_file_ciopfs, "Could not find 'ciopfs' in PATH");
45 |
46 | // Create subprocess
47 | m_subprocess = std::make_unique(*opt_path_file_ciopfs);
48 |
49 |
50 | // Include arguments and spawn process
51 | std::ignore = m_subprocess->
52 | with_args(path_dir_lower, path_dir_upper)
53 | .spawn()
54 | .wait();
55 | } // ciopfs
56 |
57 | ~Ciopfs()
58 | {
59 | ns_fuse::unmount(m_path_dir_upper);
60 | } // ~ciopfs
61 | }; // class: ciopfs
62 |
63 | } // namespace ns_ciopfs
64 |
65 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
66 |
--------------------------------------------------------------------------------
/src/cpp/lib/db/desktop.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : desktop
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include "../db.hpp"
10 | #include "../../std/string.hpp"
11 |
12 | namespace ns_db::ns_desktop
13 | {
14 |
15 | ENUM(IntegrationItem, ENTRY, MIMETYPE, ICON);
16 |
17 | // struct Desktop {{{
18 | struct Desktop
19 | {
20 | private:
21 | std::string m_name;
22 | fs::path m_path_file_icon;
23 | std::set m_set_integrations;
24 | std::set m_set_categories;
25 | Desktop(std::string_view raw_json)
26 | {
27 | // Open DB
28 | auto db = ns_db::Db(raw_json);
29 | // Parse name (required)
30 | m_name = db["name"];
31 | // Parse enabled integrations (optional)
32 | if (db.contains("integrations"))
33 | {
34 | std::ranges::for_each(db["integrations"].values(), [&](auto&& e){ m_set_integrations.insert(e); });
35 | } // if
36 | // Parse icon path (required)
37 | m_path_file_icon = db["icon"];
38 | // Parse categories (required)
39 | ns_db::Db const& db_categories = db["categories"];
40 | std::ranges::for_each(db_categories.values(), [&](auto&& e){ m_set_categories.insert(e); });
41 | } // Desktop
42 | public:
43 | std::string const& get_name() const { return m_name; }
44 | fs::path const& get_path_file_icon() const { return m_path_file_icon; }
45 | std::set const& get_integrations() const { return m_set_integrations; }
46 | std::set const& get_categories() const { return m_set_categories; }
47 | void set_name(std::string_view str_name) { m_name = str_name; }
48 | void set_integrations(std::set const& set_integrations) { m_set_integrations = set_integrations; }
49 | void set_categories(std::set const& set_categories) { m_set_categories = set_categories; }
50 | friend std::expected deserialize(std::string_view raw_json) noexcept;
51 | friend std::expected deserialize(std::ifstream& stream_raw_json) noexcept;
52 | friend std::expected serialize(Desktop const& desktop) noexcept;
53 | }; // Desktop }}}
54 |
55 | // deserialize() {{{
56 | inline std::expected deserialize(std::string_view str_raw_json) noexcept
57 | {
58 | return ns_exception::to_expected([&]{ return Desktop(str_raw_json); });
59 | }
60 | // deserialize() }}}
61 |
62 | // deserialize() {{{
63 | inline std::expected deserialize(std::ifstream& stream_raw_json) noexcept
64 | {
65 | std::stringstream ss;
66 | ss << stream_raw_json.rdbuf();
67 | return ns_exception::to_expected([&] { return Desktop(ss.str()); });
68 | }
69 | // deserialize() }}}
70 |
71 | // serialize() {{{
72 | inline std::expected serialize(Desktop const& desktop) noexcept
73 | {
74 | return ns_exception::to_expected([&]
75 | {
76 | auto db = ns_db::Db("{}");
77 | db("name") = desktop.m_name;
78 | db("integrations") = desktop.m_set_integrations;
79 | db("icon") = desktop.m_path_file_icon;
80 | db("categories") = desktop.m_set_categories;
81 | return db.dump();
82 | });
83 | } // serialize() }}}
84 |
85 | } // namespace ns_db::ns_desktop
86 |
87 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
88 |
--------------------------------------------------------------------------------
/src/cpp/lib/dwarfs.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : dwarfs
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include "log.hpp"
10 | #include "fuse.hpp"
11 | #include "subprocess.hpp"
12 | #include "../macro.hpp"
13 |
14 | namespace ns_dwarfs
15 | {
16 |
17 | namespace
18 | {
19 |
20 | namespace fs = std::filesystem;
21 |
22 | };
23 |
24 | // class Dwarfs {{{
25 | class Dwarfs
26 | {
27 | private:
28 | std::unique_ptr m_subprocess;
29 | fs::path m_path_dir_mountpoint;
30 |
31 | public:
32 | Dwarfs(Dwarfs const&) = delete;
33 | Dwarfs(Dwarfs&&) = delete;
34 | Dwarfs& operator=(Dwarfs const&) = delete;
35 | Dwarfs& operator=(Dwarfs&&) = delete;
36 |
37 | Dwarfs(fs::path const& path_file_image, fs::path const& path_dir_mount, uint64_t offset, uint64_t size_image, pid_t pid_to_die_for)
38 | : m_path_dir_mountpoint(path_dir_mount)
39 | {
40 | // Check if image exists and is a regular file
41 | ethrow_if(not fs::is_regular_file(path_file_image)
42 | , "'{}' does not exist or is not a regular file"_fmt(path_file_image)
43 | );
44 |
45 | // Check if mountpoint exists and is directory
46 | ethrow_if(not fs::is_directory(path_dir_mount)
47 | , "'{}' does not exist or is not a directory"_fmt(path_dir_mount)
48 | );
49 |
50 | // Find command in PATH
51 | auto opt_file_dwarfs = ns_subprocess::search_path("dwarfs");
52 | ethrow_if(not opt_file_dwarfs.has_value(), "Could not find dwarfs");
53 |
54 | // Create command
55 | m_subprocess = std::make_unique(*opt_file_dwarfs);
56 |
57 | // Spawn command
58 | std::ignore = m_subprocess->with_piped_outputs()
59 | .with_args(path_file_image, path_dir_mount, "-f", "-o", "auto_unmount,offset={},imagesize={}"_fmt(offset, size_image))
60 | .with_die_on_pid(pid_to_die_for)
61 | .spawn();
62 | // Wait for mount
63 | ns_fuse::wait_fuse(path_dir_mount);
64 | } // Dwarfs
65 |
66 | ~Dwarfs()
67 | {
68 | // Un-mount
69 | ns_fuse::unmount(m_path_dir_mountpoint);
70 | // Tell process to exit with SIGTERM
71 | if ( auto opt_pid = m_subprocess->get_pid() )
72 | {
73 | kill(*opt_pid, SIGTERM);
74 | } // if
75 | // Wait for process to exit
76 | auto ret = m_subprocess->wait();
77 | dreturn_if(not ret, "Mount '{}' exited unexpectedly"_fmt(m_path_dir_mountpoint));
78 | dreturn_if(ret and *ret != 0, "Mount '{}' exited with non-zero exit code '{}'"_fmt(m_path_dir_mountpoint, *ret));
79 | } // Dwarfs
80 |
81 | fs::path const& get_dir_mountpoint()
82 | {
83 | return m_path_dir_mountpoint;
84 | }
85 | }; // class Dwarfs }}}
86 |
87 | // is_dwarfs() {{{
88 | inline bool is_dwarfs(fs::path const& path_file_dwarfs, uint64_t offset = 0)
89 | {
90 | // Open file
91 | std::ifstream file_dwarfs(path_file_dwarfs, std::ios::binary | std::ios::in);
92 | ereturn_if(not file_dwarfs.is_open(), "Could not open file '{}'"_fmt(path_file_dwarfs), false);
93 | // Adjust offset
94 | file_dwarfs.seekg(offset);
95 | ereturn_if(not file_dwarfs, "Failed to seek offset '{}' in file '{}'"_fmt(offset, path_file_dwarfs), false);
96 | // Read initial 'DWARFS' identifier
97 | std::array header;
98 | ereturn_if(not file_dwarfs.read(header.data(), header.size()), "Could not read bytes from file '{}'"_fmt(path_file_dwarfs), false);
99 | // Check for a successful read
100 | ereturn_if(file_dwarfs.gcount() != header.size(), "Short read for file '{}'"_fmt(path_file_dwarfs), false);
101 | // Check for match
102 | return std::ranges::equal(header, std::string_view("DWARFS"));
103 | } // is_dwarfs() }}}
104 |
105 |
106 | } // namespace ns_dwarfs
107 |
108 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
109 |
--------------------------------------------------------------------------------
/src/cpp/lib/elf.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : elf
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | #include "log.hpp"
19 |
20 | #include "../macro.hpp"
21 | #include "../common.hpp"
22 |
23 | namespace ns_elf
24 | {
25 |
26 | namespace fs = std::filesystem;
27 |
28 | #if defined(__LP64__)
29 | #define ElfW(type) Elf64_ ## type
30 | #else
31 | #define ElfW(type) Elf32_ ## type
32 | #endif
33 |
34 | // copy_binary() {{{
35 | // Copies the binary data between [offset.first, offset.second] from path_file_input to path_file_output
36 | inline void copy_binary(fs::path const& path_file_input, fs::path const& path_file_output, std::pair offset)
37 | {
38 | std::ifstream f_in{path_file_input, std::ios::binary};
39 | std::ofstream f_out{path_file_output, std::ios::binary};
40 |
41 | ereturn_if(not f_in.good() , "Failed to open in file {}\n"_fmt(path_file_input));
42 | ereturn_if(not f_out.good(), "Failed to open out file {}\n"_fmt(path_file_output));
43 |
44 | // Calculate the size of the data to read
45 | uint64_t size = offset.second - offset.first;
46 |
47 | // Seek to the start offset in the input file
48 | f_in.seekg(offset.first, std::ios::beg);
49 |
50 | // Read in chunks
51 | const size_t size_buf = 4096;
52 | char buffer[size_buf];
53 |
54 | while( size > 0 )
55 | {
56 | std::streamsize read_size = static_cast(std::min(static_cast(size_buf), size));
57 |
58 | f_in.read(buffer, read_size);
59 | f_out.write(buffer, read_size);
60 |
61 | size -= read_size;
62 | } // while
63 | } // function: copy_binary
64 |
65 | // }}}
66 |
67 | // skip_elf_header() {{{
68 | // Skips the elf header starting from 'offset' and returns the offset to the first byte afterwards
69 | inline uint64_t skip_elf_header(fs::path const& path_file_elf, uint64_t offset = 0) {
70 | // Either Elf64_Ehdr or Elf32_Ehdr depending on architecture.
71 | ElfW(Ehdr) header;
72 |
73 | FILE* file = fopen(path_file_elf.c_str(), "rb");
74 | fseek(file, offset, SEEK_SET);
75 | if (file)
76 | {
77 | // read the header
78 | fread(&header, sizeof(header), 1, file);
79 | // check so its really an elf file
80 | if (std::memcmp(header.e_ident, ELFMAG, SELFMAG) == 0) {
81 | fclose(file);
82 | offset = header.e_shoff + (header.e_ehsize * header.e_shnum);
83 | return offset;
84 | }
85 | // finally close the file
86 | fclose(file);
87 | } // if
88 |
89 | ns_log::error()("Could not read elf header from {}", path_file_elf);
90 |
91 | exit(1);
92 | } // }}}
93 |
94 | } // namespace ns_elf
95 |
96 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
97 |
--------------------------------------------------------------------------------
/src/cpp/lib/env.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : env
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #include "../std/string.hpp"
13 | #include "../common.hpp"
14 | #include "../macro.hpp"
15 | #include "log.hpp"
16 | #include "subprocess.hpp"
17 |
18 | // Environment variable handling {{{
19 | namespace ns_env
20 | {
21 |
22 | namespace fs = std::filesystem;
23 |
24 | // enum class Replace {{{
25 | enum class Replace
26 | {
27 | Y,
28 | N,
29 | }; // enum class Replace }}}
30 |
31 | // dir() {{{
32 | // Fetches a directory path from an environment variable
33 | // Tries to create if not exists
34 | inline fs::path dir(const char* name)
35 | {
36 | // Get environment variable
37 | const char * value = std::getenv(name) ;
38 |
39 | // Check if variable exists
40 | ethrow_if(not value, "Variable '{}' not set"_fmt(name));
41 |
42 | // Create if not exists
43 | ethrow_if(not fs::create_directory(value), "Could not create directory {}"_fmt(value));
44 |
45 | return fs::canonical(value);
46 | } // dir() }}}
47 |
48 | // file() {{{
49 | // Fetches a file path from an environment variable
50 | // Checks if file exists
51 | inline fs::path file(const char* name)
52 | {
53 | // Get environment variable
54 | const char * value = std::getenv(name) ;
55 |
56 | // Check if variable exists
57 | ethrow_if(not value, "Variable '{}' not set"_fmt(name));
58 |
59 | // Create if not exists
60 | ethrow_if(not fs::exists(value), "File '{}' does not exist"_fmt(value));
61 |
62 | return fs::canonical(value);
63 | } // file() }}}
64 |
65 | // set() {{{
66 | // Sets an environment variable
67 | template
68 | void set(T&& name, U&& value, Replace replace)
69 | {
70 | // ns_log::debug()("ENV: {} -> {}", name , value);
71 | setenv(ns_string::to_string(name).c_str(), ns_string::to_string(value).c_str(), (replace == Replace::Y));
72 | } // set() }}}
73 |
74 | // prepend() {{{
75 | // Prepends 'extra' to an environment variable 'name'
76 | inline void prepend(const char* name, std::string const& extra)
77 | {
78 | // Append to var
79 | if ( const char* var_curr = std::getenv(name); var_curr )
80 | {
81 | // ns_log::debug()("ENV: {} -> {}", name, extra + var_curr);
82 | setenv(name, std::string{extra + var_curr}.c_str(), 1);
83 | } // if
84 | else
85 | {
86 | ns_log::error()("Variable '{}' is not set"_fmt(name));
87 | } // else
88 | } // prepend() }}}
89 |
90 | // concat() {{{
91 | // Appends 'extra' to an environment variable 'name'
92 | inline void concat(const char* name, std::string const& extra)
93 | {
94 | // Append to var
95 | if ( const char* var_curr = std::getenv(name); var_curr )
96 | {
97 | setenv(name, std::string{var_curr + extra}.c_str(), 1);
98 | } // if
99 | else
100 | {
101 | ns_log::error()("Variable '{}' is not set"_fmt(name));
102 | } // else
103 | } // concat() }}}
104 |
105 | // set_mutual_exclusion() {{{
106 | // If first exists, sets first to value and unsets second
107 | // Else sets second to value
108 | inline void set_mutual_exclusion(const char* name1, const char* name2, const char* value)
109 | {
110 | if ( std::getenv(name1) != nullptr )
111 | {
112 | setenv(name1, value, true);
113 | unsetenv(name2);
114 | return;
115 | } // if
116 |
117 | setenv(name2, value, true);
118 | } // set_mutual_exclusion() }}}
119 |
120 | // print() {{{
121 | // print an env variable
122 | inline void print(const char* name, std::ostream& os = std::cout)
123 | {
124 | if ( const char* var = std::getenv(name); var )
125 | {
126 | os << var;
127 | } // if
128 | } // print() }}}
129 |
130 | // get_or_throw() {{{
131 | // Get an env variable
132 | template
133 | inline T get_or_throw(const char* name)
134 | {
135 | const char* value = std::getenv(name);
136 | ethrow_if(not value, "Variable '{}' is undefined"_fmt(name));
137 | return value;
138 | } // get_or_throw() }}}
139 |
140 | // get_or_else() {{{
141 | // Get an env variable
142 | inline std::string get_or_else(std::string_view name, std::string_view alternative)
143 | {
144 | const char* value = std::getenv(name.data());
145 | return_if_else(value != nullptr, value, alternative.data());
146 | } // get_or_else() }}}
147 |
148 | // get() {{{
149 | // Get an env variable
150 | inline const char* get(const char* name)
151 | {
152 | return std::getenv(name);
153 | } // get() }}}
154 |
155 | // get_optional() {{{
156 | // get_optional an env variable
157 | template
158 | inline std::optional get_optional(std::string_view name)
159 | {
160 | const char* var = std::getenv(name.data());
161 | return (var)? std::make_optional(var) : std::nullopt;
162 | } // get_optional() }}}
163 |
164 | // exists() {{{
165 | // Checks if variable exists
166 | inline bool exists(const char* var)
167 | {
168 | return get(var) != nullptr;
169 | } // exists() }}}
170 |
171 | // exists() {{{
172 | // Checks if variable exists and equals value
173 | inline bool exists(const char* var, std::string_view target)
174 | {
175 | const char* value = get(var);
176 | qreturn_if(not value, false);
177 | return std::string_view{value} == target;
178 | } // exists() }}}
179 |
180 | // expand() {{{
181 | inline std::expected expand(ns_concept::StringRepresentable auto&& var)
182 | {
183 | std::string expanded = ns_string::to_string(var);
184 |
185 | // Perform word expansion
186 | wordexp_t data;
187 | if (int ret = wordexp(expanded.c_str(), &data, 0); ret == 0)
188 | {
189 | if (data.we_wordc > 0)
190 | {
191 | expanded = data.we_wordv[0];
192 | } // if
193 | wordfree(&data);
194 | } // if
195 | else
196 | {
197 | std::string error;
198 | switch(ret)
199 | {
200 | case WRDE_BADCHAR: error = "WRDE_BADCHAR"; break;
201 | case WRDE_BADVAL: error = "WRDE_BADVAL"; break;
202 | case WRDE_CMDSUB: error = "WRDE_CMDSUB"; break;
203 | case WRDE_NOSPACE: error = "WRDE_NOSPACE"; break;
204 | case WRDE_SYNTAX: error = "WRDE_SYNTAX"; break;
205 | default: error = "unknown";
206 | } // switch
207 | return std::unexpected(error);
208 | } // else
209 |
210 | return expanded;
211 | } // expand() }}}
212 |
213 | // xdg_data_home() {{{
214 | template
215 | std::optional xdg_data_home()
216 | {
217 | const char* var = std::getenv("XDG_DATA_HOME");
218 | qreturn_if(var, std::make_optional(var));
219 | const char* home = std::getenv("HOME");
220 | qreturn_if(not home, std::nullopt);
221 | return std::make_optional(std::string{home} + "/.local/share");
222 | } // xdg_data_home() }}}
223 |
224 | } // namespace ns_env }}}
225 |
226 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
227 |
--------------------------------------------------------------------------------
/src/cpp/lib/fifo.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : fifo
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "log.hpp"
14 | #include "../macro.hpp"
15 |
16 | namespace ns_fifo
17 | {
18 |
19 | namespace
20 | {
21 |
22 | namespace fs = std::filesystem;
23 |
24 | } // namespace
25 |
26 | template
27 | [[nodiscard]] std::expected open_and_write(fs::path const& path_file_fifo, std::span data)
28 | {
29 | // Open fifo or return error
30 | int fd_pid = open(path_file_fifo.c_str(), O_WRONLY);
31 | qreturn_if (fd_pid < 0, std::unexpected("Could not open fifo: '{}'"_fmt(strerror(errno))));
32 |
33 | // Close file & return error on write fail
34 | auto count_bytes = write(fd_pid, data.data(), data.size());
35 | qreturn_if (count_bytes <= 0, (close(fd_pid), std::unexpected("Could not write pid to fifo: '{}'"_fmt(strerror(errno)))));
36 |
37 | // Close fifo and return no error
38 | close(fd_pid);
39 | return count_bytes;
40 | } // function: open_and_read
41 |
42 | template
43 | [[nodiscard]] std::expected open_and_read(fs::path const& path_file_fifo, std::span data)
44 | {
45 | // Open fifo or return error
46 | int fd_pid = open(path_file_fifo.c_str(), O_RDONLY);
47 | qreturn_if (fd_pid < 0, std::unexpected("Could not open fifo: '{}'"_fmt(strerror(errno))));
48 |
49 | // Close file & return error on write fail
50 | int count_bytes = read(fd_pid, data.data(), data.size());
51 | qreturn_if (count_bytes <= 0, (close(fd_pid), std::unexpected("Could not write pid to fifo: '{}'"_fmt(strerror(errno)))));
52 |
53 | // Close fifo and return no error
54 | close(fd_pid);
55 | return count_bytes;
56 | } // function: open_and_read
57 |
58 | } // namespace ns_fifo
59 |
60 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
61 |
--------------------------------------------------------------------------------
/src/cpp/lib/fuse.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : fuse
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "subprocess.hpp"
16 |
17 | // Other codes available here:
18 | // https://man7.org/linux/man-pages/man2/statfs.2.html
19 | #define FUSE_SUPER_MAGIC 0x65735546
20 |
21 | namespace ns_fuse
22 | {
23 |
24 | namespace
25 | {
26 |
27 | namespace fs = std::filesystem;
28 |
29 | } // namespace
30 |
31 | // Check if a directory is mounted with fuse
32 | inline std::expected is_fuse(fs::path const& path_dir_mount)
33 | {
34 | struct statfs buf;
35 |
36 | if ( statfs(path_dir_mount.c_str(), &buf) < 0 )
37 | {
38 | return std::unexpected(strerror(errno));
39 | } // if
40 |
41 | return buf.f_type == FUSE_SUPER_MAGIC;
42 | } // function: mountpoint
43 |
44 | inline void wait_fuse(fs::path const& path_dir_filesystem)
45 | {
46 | using namespace std::chrono_literals;
47 | auto time_beg = std::chrono::system_clock::now();
48 | while ( true )
49 | {
50 | auto expected_is_fuse = ns_fuse::is_fuse(path_dir_filesystem);
51 | ebreak_if(not expected_is_fuse, "Could not check if filesystem is fuse");
52 | dbreak_if( *expected_is_fuse, "Filesystem '{}' is fuse"_fmt(path_dir_filesystem));
53 | auto time_cur = std::chrono::system_clock::now();
54 | auto elapsed = std::chrono::duration_cast(time_cur - time_beg);
55 | ebreak_if(elapsed.count() > 60, "Reached timeout to wait for fuse filesystems");
56 | } // while
57 | } // function: wait_fuse
58 |
59 |
60 | inline void unmount(fs::path const& path_dir_mountpoint)
61 | {
62 | using namespace std::chrono_literals;
63 |
64 | // Find fusermount
65 | auto opt_path_file_fusermount = ns_subprocess::search_path("fusermount");
66 | ereturn_if (not opt_path_file_fusermount, "Could not find 'fusermount' in PATH");
67 |
68 | // Un-mount filesystem
69 | auto ret = ns_subprocess::Subprocess(*opt_path_file_fusermount)
70 | .with_piped_outputs()
71 | .with_args("-zu", path_dir_mountpoint)
72 | .spawn()
73 | .wait();
74 |
75 | // Check for successful un-mount
76 | if(ret and *ret == 0)
77 | {
78 | ns_log::debug()("Un-mounted filesystem '{}'"_fmt(path_dir_mountpoint));
79 | } // if
80 |
81 | // Filesystem could be busy for a bit after un-mount
82 | std::expected expected_is_fuse = ns_fuse::is_fuse(*opt_path_file_fusermount);
83 | while( expected_is_fuse and *expected_is_fuse )
84 | {
85 | std::this_thread::sleep_for(100ms);
86 | expected_is_fuse = ns_fuse::is_fuse(*opt_path_file_fusermount);
87 | } // while
88 |
89 | // Check for query errors
90 | ereturn_if(not expected_is_fuse, expected_is_fuse.error());
91 | } // function: unmount
92 |
93 | } // namespace ns_fuse
94 |
95 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
96 |
--------------------------------------------------------------------------------
/src/cpp/lib/image.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : image
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "log.hpp"
16 | #include "match.hpp"
17 | #include "../std/exception.hpp"
18 |
19 | namespace ns_image
20 | {
21 |
22 | namespace
23 | {
24 |
25 | namespace fs = std::filesystem;
26 |
27 | // resize_impl() {{{
28 | inline void resize_impl(fs::path const& path_file_src
29 | , fs::path const& path_file_dst
30 | , uint32_t width
31 | , uint32_t height
32 | , bool should_preserve_aspect_ratio)
33 | {
34 | namespace gil = boost::gil;
35 |
36 | // Create icon directory and set file name
37 | ns_log::info()("Reading image {}", path_file_src);
38 | ethrow_if(not fs::is_regular_file(path_file_src), "File '{}' does not exist or is not a regular file"_fmt(path_file_src));
39 |
40 | gil::rgba8_image_t img;
41 | std::ignore = ns_match::match(path_file_src.extension()
42 | , ns_match::equal(".jpg", ".jpeg") >>= [&]{ gil::read_and_convert_image(path_file_src, img, gil::jpeg_tag()); }
43 | , ns_match::equal(".png") >>= [&]{ gil::read_and_convert_image(path_file_src, img, gil::png_tag()); }
44 | );
45 |
46 | ns_log::info()("Image size is {}x{}", std::to_string(img.width()), std::to_string(img.height()));
47 |
48 | if ( should_preserve_aspect_ratio )
49 | {
50 | // Calculate desired and current aspected ratios
51 | double src_aspect = static_cast(img.width()) / img.height();
52 | double dst_aspect = static_cast(width) / height;
53 |
54 | // Calculate novel dimensions that preserve the aspect ratio
55 | int width_new = (src_aspect > dst_aspect)? static_cast(src_aspect * height) : width;
56 | int height_new = (src_aspect <= dst_aspect)? static_cast(width / src_aspect ) : height;
57 |
58 | // Resize
59 | gil::rgba8_image_t img_resized(width_new, height_new);
60 | ns_log::info()("Image aspect ratio is {}", std::to_string(src_aspect));
61 | ns_log::info()("Target aspect ratio is {}", std::to_string(dst_aspect));
62 | ns_log::info()("Resizing image to {}x{}", std::to_string(width_new), std::to_string(height_new));
63 | gil::resize_view(gil::const_view(img), gil::view(img_resized), gil::bilinear_sampler());
64 |
65 | // Write to disk
66 | ns_log::info()("Saving image to {}", path_file_dst);
67 | gil::write_view(path_file_dst, gil::const_view(img_resized), gil::png_tag());
68 | } // if
69 | else
70 | {
71 | // Resize
72 | gil::rgba8_image_t img_resized(width, height);
73 | ns_log::info()("Resizing image to {}x{}", std::to_string(width), std::to_string(height));
74 | gil::resize_view(gil::const_view(img), gil::view(img_resized), gil::bilinear_sampler());
75 | // Write to disk
76 | ns_log::info()("Saving image to {}", path_file_dst);
77 | gil::write_view(path_file_dst, gil::const_view(img_resized), gil::png_tag());
78 |
79 | } // else
80 | } // resize_impl() }}}
81 |
82 | } // namespace
83 |
84 | // resize() {{{
85 | inline decltype(auto) resize(fs::path const& path_file_src
86 | , fs::path const& path_file_dst
87 | , uint32_t width
88 | , uint32_t height
89 | , bool should_preserve_aspect_ratio = false)
90 | {
91 | return ns_exception::to_expected([&]{ resize_impl(path_file_src, path_file_dst, width, height, should_preserve_aspect_ratio); });
92 | } // resize() }}}
93 |
94 | } // namespace ns_image
95 |
96 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
97 |
--------------------------------------------------------------------------------
/src/cpp/lib/ipc.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : ipc
4 | ///
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "../common.hpp"
12 | #include "../std/concept.hpp"
13 | #include "../std/string.hpp"
14 | #include "log.hpp"
15 |
16 | namespace ns_ipc
17 | {
18 |
19 | namespace fs = std::filesystem;
20 |
21 | struct message_buffer
22 | {
23 | long message_type;
24 | char message_text[2048];
25 | };
26 |
27 | // class Ipc {{{
28 | class Ipc
29 | {
30 | private:
31 | key_t m_key;
32 | int m_message_queue_id;
33 | message_buffer m_buffer;
34 | bool m_is_guest;
35 | public:
36 | // Constructors
37 | Ipc(key_t key, int message_queue_id, message_buffer buffer, bool is_guest)
38 | : m_key(key)
39 | , m_message_queue_id(message_queue_id)
40 | , m_buffer(buffer)
41 | , m_is_guest(is_guest)
42 | {}
43 | ~Ipc();
44 | // Factory methods
45 | static Ipc guest(fs::path path_file);
46 | static Ipc host(fs::path path_file);
47 | // Send/Recv Operations
48 | template
49 | void send(T&& t);
50 | std::optional recv();
51 |
52 | }; // class Ipc }}}
53 |
54 | // Ipc::guest() {{{
55 | inline Ipc Ipc::guest(fs::path path_file)
56 | {
57 | std::string identifier = ns_string::to_string(fs::canonical(path_file));
58 | ns_log::info()("key identifier: {}", identifier);
59 |
60 | // Use a unique key for the message queue.
61 | key_t key;
62 | if(key = ftok(identifier.c_str(), 65); key == -1 )
63 | {
64 | perror("Could not generate token for message queue");
65 | "Could not generate key for message queue with identifier '{}': {}"_throw(identifier, strerror(errno));
66 | } // if
67 | ns_log::info()("Generated message_queue key: {}", key);
68 |
69 | // Connect to the message queue
70 | int message_queue_id = -1;
71 | if (message_queue_id = msgget(key, 0666); message_queue_id == -1 )
72 | {
73 | perror("Could not create message queue");
74 | "msgget failed, could not create message queue for identifier '{}': {}"_throw(identifier, strerror(errno));
75 | } // if
76 | ns_log::info()("Message queue id: {}", message_queue_id);
77 |
78 | return Ipc(key, message_queue_id, message_buffer{.message_type=1, .message_text=""}, true);
79 | } // Ipc::guest() }}}
80 |
81 | // Ipc::host() {{{
82 | inline Ipc Ipc::host(fs::path path_file)
83 | {
84 | std::string identifier = ns_string::to_string(fs::canonical(path_file));
85 | ns_log::info()("key identifier: {}", identifier);
86 |
87 | // Use a unique key for the message queue.
88 | key_t key;
89 | if(key = ftok(identifier.c_str(), 65); key == -1 )
90 | {
91 | perror("Could not generate token for message queue");
92 | "Could not generate key for message queue with identifier '{}': {}"_throw(identifier, strerror(errno));
93 | } // if
94 | ns_log::info()("Generated message_queue key: {}", key);
95 |
96 | // Close existing queue
97 | if ( int message_queue_id = msgget(key, 0666); message_queue_id != -1 and msgctl(message_queue_id, IPC_RMID, NULL) == -1)
98 | {
99 | ns_log::error()("Could not remove existing message queue");
100 | perror("Could not remove existing message queue");
101 | } // if
102 | else
103 | {
104 | ns_log::info()("Closed existing message queue");
105 | } // else
106 |
107 | // Connect to the message queue
108 | int message_queue_id = -1;
109 | if (message_queue_id = msgget(key, 0666 | IPC_CREAT); message_queue_id == -1 )
110 | {
111 | perror("Could not create message queue");
112 | "msgget failed, could not create message queue for identifier '{}': {}"_throw(identifier, strerror(errno));
113 | } // if
114 | ns_log::info()("Message queue id: {}", message_queue_id);
115 |
116 | return Ipc(key, message_queue_id, message_buffer{.message_type=1, .message_text=""}, false);
117 | } // Ipc::host() }}}
118 |
119 | // Ipc::~Ipc() {{{
120 | inline Ipc::~Ipc()
121 | {
122 | // No managed by guest
123 | if ( m_is_guest ) { return; } // if
124 |
125 | // Close
126 | if ( msgctl(m_message_queue_id, IPC_RMID, NULL) == -1 )
127 | {
128 | ns_log::error()("Could not remove the message queue");
129 | perror("Could not remove message queue");
130 | } // if
131 | } // Ipc::~Ipc() }}}
132 |
133 | // Ipc::send() {{{
134 | template
135 | void Ipc::send(T&& t)
136 | {
137 | std::string data = ns_string::to_string(t);
138 | ns_log::info()("Sending message '{}'", data);
139 | // Copy the contents of std::string to the message_text buffer
140 | strncpy(m_buffer.message_text, data.c_str(), sizeof(m_buffer.message_text));
141 | // Ensure null termination
142 | m_buffer.message_text[sizeof(m_buffer.message_text) - 1] = '\0';
143 | // Send message
144 | if ( msgsnd(m_message_queue_id, &m_buffer, sizeof(m_buffer), 0) == -1 )
145 | {
146 | perror("Failure to send message");
147 | } // if
148 | } // Ipc::send() }}}
149 |
150 | // Ipc::recv() {{{
151 | inline std::optional Ipc::recv()
152 | {
153 | int length = msgrcv(m_message_queue_id, &m_buffer, sizeof(m_buffer.message_text), 0, MSG_NOERROR);
154 |
155 | // Read message
156 | if (length == -1)
157 | {
158 | perror("Failure to receive message");
159 | ns_log::error()("Failed to receive message: {}", strerror(errno));
160 | return std::nullopt;
161 | } // if
162 |
163 | // Ensure null termination
164 | m_buffer.message_text[sizeof(m_buffer.message_text) - 1] = '\0';
165 |
166 | // Convert message text to std::string
167 | return std::make_optional(m_buffer.message_text);
168 | } // Ipc::recv() }}}
169 |
170 | } // namespace ns_ipc
171 |
172 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
173 |
--------------------------------------------------------------------------------
/src/cpp/lib/linux.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : linux
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "log.hpp"
14 | #include "../common.hpp"
15 | #include "../macro.hpp"
16 |
17 | namespace ns_linux
18 | {
19 |
20 | namespace
21 | {
22 |
23 | namespace fs = std::filesystem;
24 |
25 | }
26 |
27 | // mkdtemp() {{{
28 | // Creates a temporary directory in path_dir_parent with the template provided by 'dir_template'
29 | [[nodiscard]] inline fs::path mkdtemp(fs::path const& path_dir_parent, std::string dir_template = "XXXXXX")
30 | {
31 | std::error_code ec;
32 | fs::create_directories(path_dir_parent, ec);
33 | ethrow_if(ec, "Failed to create temporary dir {}: '{}'"_fmt(path_dir_parent, ec.message()));
34 | fs::path path_dir_template = path_dir_parent / dir_template;
35 | const char* cstr_path_dir_temp = ::mkdtemp(path_dir_template.string().data());
36 | ethrow_if(cstr_path_dir_temp == nullptr, "Failed to create temporary dir {}"_fmt(path_dir_template));
37 | return cstr_path_dir_temp;
38 | } // function: mkdtemp }}}
39 |
40 | // mkstemp() {{{
41 | [[nodiscard]] inline std::expected mkstemps(fs::path const& path_dir_parent
42 | , std::string file_template = "XXXXXX"
43 | , int suffixlen = 0
44 | )
45 | {
46 | std::string str_template = path_dir_parent / file_template;
47 | int fd = ::mkstemps(str_template.data(), suffixlen);
48 | qreturn_if(fd < 0, std::unexpected(strerror(errno)));
49 | close(fd);
50 | return fs::path{str_template};
51 | }
52 | // mkstemp() }}}
53 |
54 | // module_check() {{{
55 | inline std::expected module_check(std::string_view str_name)
56 | {
57 | std::ifstream file_modules("/proc/modules");
58 | qreturn_if(not file_modules.is_open(), std::unexpected("Could not open modules file"));
59 |
60 | std::string line;
61 | while ( std::getline(file_modules, line) )
62 | {
63 | qreturn_if(line.contains(str_name), true);
64 | } // while
65 |
66 | return false;
67 | } // function: module_check() }}}
68 |
69 | } // namespace ns_linux
70 |
71 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
72 |
--------------------------------------------------------------------------------
/src/cpp/lib/log.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : log
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "../common.hpp"
12 | #include "../std/concept.hpp"
13 | #include "../std/filesystem.hpp"
14 | #include "../std/exception.hpp"
15 |
16 | namespace ns_log
17 | {
18 |
19 | enum class Level : int
20 | {
21 | QUIET,
22 | ERROR,
23 | INFO,
24 | DEBUG,
25 | };
26 |
27 | namespace
28 | {
29 |
30 | namespace fs = std::filesystem;
31 |
32 | // class Logger {{{
33 | class Logger
34 | {
35 | private:
36 | std::optional m_opt_os;
37 | Level m_level;
38 | public:
39 | Logger();
40 | Logger(Logger const&) = delete;
41 | Logger(Logger&&) = delete;
42 | Logger& operator=(Logger const&) = delete;
43 | Logger& operator=(Logger&&) = delete;
44 | void set_level(Level level);
45 | Level get_level() const;
46 | void set_sink_file(fs::path path_file_sink);
47 | std::optional& get_sink_file();
48 | }; // class Logger }}}
49 |
50 | // fn: Logger::Logger {{{
51 | inline Logger::Logger()
52 | : m_level(Level::QUIET)
53 | {
54 | } // fn: Logger::Logger }}}
55 |
56 | // fn: Logger::set_sink_file {{{
57 | inline void Logger::set_sink_file(fs::path path_file_sink)
58 | {
59 | // File to save logs into
60 | if ( const char* var = std::getenv("FIM_DEBUG"); var && std::string_view{var} == "1" )
61 | {
62 | "Logger file: {}\n"_print(path_file_sink);
63 | } // if
64 |
65 | // File output stream
66 | m_opt_os = std::ofstream{path_file_sink};
67 |
68 | if( m_opt_os->bad() ) { std::runtime_error("Could not open file '{}'"_fmt(path_file_sink)); };
69 | } // fn: Logger::set_sink_file }}}
70 |
71 | // fn: Logger::get_sink_file {{{
72 | inline std::optional& Logger::get_sink_file()
73 | {
74 | return m_opt_os;
75 | } // fn: Logger::get_sink_file }}}
76 |
77 | // fn: Logger::set_level {{{
78 | inline void Logger::set_level(Level level)
79 | {
80 | m_level = level;
81 | } // fn: Logger::set_level }}}
82 |
83 | // fn: Logger::get_level {{{
84 | inline Level Logger::get_level() const
85 | {
86 | return m_level;
87 | } // fn: Logger::get_level }}}
88 |
89 | static Logger logger;
90 |
91 | } // namespace
92 |
93 | // fn: set_level {{{
94 | inline void set_level(Level level)
95 | {
96 | logger.set_level(level);
97 | } // fn: set_level }}}
98 |
99 | // fn: get_level {{{
100 | inline Level get_level()
101 | {
102 | return logger.get_level();
103 | } // fn: get_level }}}
104 |
105 | // fn: set_sink_file {{{
106 | inline void set_sink_file(fs::path path_file_sink)
107 | {
108 | logger.set_sink_file(path_file_sink);
109 | } // fn: set_sink_file }}}
110 |
111 | // class: Location {{{
112 | class Location
113 | {
114 | private:
115 | fs::path m_str_file;
116 | uint32_t m_line;
117 |
118 | public:
119 | Location(
120 | char const* str_file = __builtin_FILE()
121 | , uint32_t line = __builtin_LINE()
122 | )
123 | : m_str_file(fs::path(str_file).filename())
124 | , m_line(line)
125 | {}
126 |
127 | auto get() const
128 | {
129 | return "{}::{}"_fmt(m_str_file, m_line);
130 | } // get
131 | }; // class: Location }}}
132 |
133 | // class info {{{
134 | class info
135 | {
136 | private:
137 | Location m_loc;
138 |
139 | public:
140 | info(Location location = {}) : m_loc(location) {}
141 | template
142 | requires ( ( ns_concept::StringRepresentable or ns_concept::IterableConst ) and ... )
143 | void operator()(T&& format, Args&&... args)
144 | {
145 | auto& opt_ostream_sink = logger.get_sink_file();
146 | print_if(opt_ostream_sink, *opt_ostream_sink, "I::{}::{}\n"_fmt(m_loc.get(), format), args...);
147 | print_if((logger.get_level() >= Level::INFO), std::cout, "I::{}::{}\n"_fmt(m_loc.get(), format), std::forward(args)...);
148 | } // info
149 | }; // class info }}}
150 |
151 | // class error {{{
152 | class error
153 | {
154 | private:
155 | Location m_loc;
156 |
157 | public:
158 | error(Location location = {}) : m_loc(location) {}
159 | template
160 | requires ( ( ns_concept::StringRepresentable or ns_concept::IterableConst ) and ... )
161 | void operator()(T&& format, Args&&... args)
162 | {
163 | auto& opt_ostream_sink = logger.get_sink_file();
164 | print_if(opt_ostream_sink, *opt_ostream_sink, "E::{}::{}\n"_fmt(m_loc.get(), format), args...);
165 | print_if((logger.get_level() >= Level::ERROR), std::cerr, "E::{}::{}\n"_fmt(m_loc.get(), format), std::forward(args)...);
166 | } // error
167 | }; // class error }}}
168 |
169 | // class debug {{{
170 | class debug
171 | {
172 | private:
173 | Location m_loc;
174 |
175 | public:
176 | debug(Location location = {}) : m_loc(location) {}
177 | template
178 | requires ( ( ns_concept::StringRepresentable or ns_concept::IterableConst ) and ... )
179 | void operator()(T&& format, Args&&... args)
180 | {
181 | auto& opt_ostream_sink = logger.get_sink_file();
182 | print_if(opt_ostream_sink, *opt_ostream_sink, "D::{}::{}\n"_fmt(m_loc.get(), format), args...);
183 | print_if((logger.get_level() >= Level::DEBUG), std::cerr, "D::{}::{}\n"_fmt(m_loc.get(), format), std::forward(args)...);
184 | } // debug
185 | }; // class debug }}}
186 |
187 | // fn: exception {{{
188 | inline void exception(auto&& fn)
189 | {
190 | if (auto expected = ns_exception::to_expected(fn); not expected)
191 | {
192 | ns_log::error()(expected.error());
193 | } // if
194 | } // }}}
195 |
196 | // fn: ec {{{
197 | template
198 | inline auto ec(F&& fn, Args&&... args) -> std::invoke_result_t
199 | {
200 | std::error_code ec;
201 | if constexpr ( std::same_as> )
202 | {
203 | fn(std::forward(args)..., ec);
204 | if ( ec ) { ns_log::error()(ec.message()); } // if
205 | }
206 | else
207 | {
208 | auto ret = fn(std::forward(args)..., ec);
209 | if ( ec ) { ns_log::error()(ec.message()); } // if
210 | return ret;
211 | } // else
212 | } // }}}
213 |
214 | } // namespace ns_log
215 |
216 | /* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
217 |
--------------------------------------------------------------------------------
/src/cpp/lib/match.hpp:
--------------------------------------------------------------------------------
1 | ///
2 | // @author : Ruan E. Formigoni (ruanformigoni@gmail.com)
3 | // @file : match
4 | ///
5 |
6 | #pragma once
7 |
8 | #include
9 | #include
10 |
11 | #include "log.hpp"
12 | #include "../macro.hpp"
13 | #include "../std/concept.hpp"
14 |
15 | namespace ns_match
16 | {
17 |
18 | // class compare {{{
19 | template
20 | requires std::is_default_constructible_v
21 | class compare
22 | {
23 | private:
24 | std::tuple...> m_tuple;
25 | public:
26 | // Constructor
27 | compare(Args&&... args)
28 | : m_tuple(std::forward(args)...)
29 | {}
30 | // Comparison
31 | template
32 | requires ( std::predicate and ... )
33 | bool operator()(U&& u) const
34 | {
35 | return std::apply([&](auto&&... e){ return (Comp{}(e, u) or ...); }, m_tuple);
36 | } // operator()
37 | }; // }}}
38 |
39 | // fn: equal() {{{
40 | template
41 | requires ns_concept::Uniform...>
42 | and (not ns_concept::SameAs...>)
43 | [[nodiscard]] decltype(auto) equal(Args&&... args)
44 | {
45 | return compare,Args...>(std::forward(args)...);
46 | }; // fn: equal() }}}
47 |
48 | // fn: equal() {{{
49 | template
50 | requires ns_concept::SameAs...>
51 | [[nodiscard]] decltype(auto) equal(Args&&... args)
52 | {
53 | auto to_string = [](auto&& e){ return std::string(e); };
54 | return compare, std::invoke_result_t...>(std::string(args)...);
55 | }; // fn: equal() }}}
56 |
57 | // operator>>= {{{
58 | template
59 | [[nodiscard]] decltype(auto) operator>>=(compare const& partial_comp, U const& u)
60 | {
61 | // Make it lazy
62 | return [&](auto&& e)
63 | {
64 | if constexpr ( std::regular_invocable )
65 | {
66 | if constexpr ( std::is_void_v