├── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── scorecard.yml ├── LICENSE ├── README.md ├── RELEASE ├── SECURITY.md ├── contrib ├── ci │ ├── Dockerfile-debian │ ├── Dockerfile-fedora │ ├── build-debian.sh │ └── build-fedora.sh └── passim.spec.in ├── data ├── a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447-HELLO.md ├── favicon.ico ├── meson.build ├── org.freedesktop.Passim.conf ├── org.freedesktop.Passim.metainfo.xml ├── org.freedesktop.Passim.png ├── org.freedesktop.Passim.service.in ├── org.freedesktop.Passim.svg ├── passim.conf ├── passim.service.in ├── passim.sysusers.conf └── style.css ├── libpassim ├── generate-version-script.py ├── meson.build ├── passim-client.c ├── passim-client.h ├── passim-item.c ├── passim-item.h ├── passim-version.c ├── passim-version.h.in ├── passim.h └── passim.map ├── meson.build ├── meson_options.txt ├── po ├── LINGUAS ├── POTFILES.in ├── cs.po ├── de.po ├── en_GB.po ├── fi.po ├── fix_translations.py ├── fr.po ├── ka.po ├── meson.build ├── nb_NO.po ├── passim.pot ├── ru.po ├── ta.po └── tr.po └── src ├── meson.build ├── org.freedesktop.Passim.xml ├── passim-avahi-service-browser.c ├── passim-avahi-service-browser.h ├── passim-avahi-service-resolver.c ├── passim-avahi-service-resolver.h ├── passim-avahi-service.c ├── passim-avahi-service.h ├── passim-avahi.c ├── passim-avahi.h ├── passim-cli.c ├── passim-common.c ├── passim-common.h ├── passim-gnutls.c ├── passim-gnutls.h ├── passim-self-test.c ├── passim-server.c └── passim.1 /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: 'Align' 3 | AlignConsecutiveAssignments: 'false' 4 | AlignConsecutiveDeclarations: 'false' 5 | AlignConsecutiveMacros: 'true' 6 | AlignOperands: 'true' 7 | AlignTrailingComments: 'true' 8 | AllowAllArgumentsOnNextLine: 'false' 9 | AllowAllParametersOfDeclarationOnNextLine: 'false' 10 | AllowShortBlocksOnASingleLine: 'false' 11 | AllowShortCaseLabelsOnASingleLine: 'false' 12 | AllowShortFunctionsOnASingleLine: 'Inline' 13 | AllowShortIfStatementsOnASingleLine: 'false' 14 | AlwaysBreakAfterReturnType: 'All' 15 | BinPackParameters: 'false' 16 | BinPackArguments: 'false' 17 | BreakBeforeBraces: 'Linux' 18 | ColumnLimit: '100' 19 | DerivePointerAlignment: 'false' 20 | IndentCaseLabels: 'false' 21 | IndentWidth: '8' 22 | IncludeBlocks: 'Regroup' 23 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 24 | MaxEmptyLinesToKeep: '1' 25 | PointerAlignment: 'Right' 26 | SortIncludes: 'true' 27 | SpaceAfterCStyleCast: 'false' 28 | SpaceBeforeAssignmentOperators : 'true' 29 | SpaceBeforeParens: 'ControlStatements' 30 | SpaceInEmptyParentheses: 'false' 31 | SpacesInSquareBrackets: 'false' 32 | TabWidth: '8' 33 | UseTab: 'Always' 34 | PenaltyBreakAssignment: '3' 35 | PenaltyBreakBeforeFirstCallParameter: '15' 36 | --- 37 | Language: 'Proto' 38 | --- 39 | Language: 'Cpp' 40 | IncludeCategories: 41 | - Regex: '^"config.h"$' 42 | Priority: '0' 43 | - Regex: '^<' 44 | Priority: '2' 45 | - Regex: '.*' 46 | Priority: '4' 47 | ... 48 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build-linux: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | distro: 18 | - fedora 19 | - debian 20 | fail-fast: false 21 | steps: 22 | - uses: actions/checkout@v4 23 | - run: docker build -t passim-${{ matrix.distro }} -f contrib/ci/Dockerfile-${{ matrix.distro }} . 24 | - run: docker run -t -v `pwd`:/build passim-${{ matrix.distro }} ./contrib/ci/build-${{ matrix.distro }}.sh 25 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '16 18 * * 0' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard (optional). 69 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 70 | - name: "Upload to code-scanning" 71 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 72 | with: 73 | sarif_file: results.sarif 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passim 2 | 3 | [![Translation status](https://hosted.weblate.org/widget/passim/svg-badge.svg)](https://hosted.weblate.org/engage/passim/) 4 | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/hughsie/passim/badge)](https://securityscorecards.dev/viewer/?uri=github.com/hughsie/passim) 5 | [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8814/badge)](https://www.bestpractices.dev/projects/8814) 6 | 7 | A local caching server. Named after the Latin word for “here, there and everywhere”. 8 | 9 | ## Introduction 10 | 11 | Much of the software running on your computer that connects to other systems over the Internet needs 12 | to periodically download *metadata* or information needed to perform other requests. 13 | 14 | As part of running the passim/LVFS projects I've seen how *download this small file once per 24h* 15 | turns into tens of millions of requests per day. Everybody downloads the same file from a CDN, and 16 | although a CDN is not super-expensive, it's certainly not free. Everybody on your current network 17 | (perhaps thousands of users) has to download the same 1MB blob of metadata from a CDN over a 18 | perhaps-expensive internet link. 19 | 20 | What if we could download the file from the CDN on one machine, and the next machine on the local 21 | network that needs it instead downloads it from the first machine? We could put a limit on the 22 | number of times it can be shared, and the maximum age so that we don't store yesterdays metadata 23 | forever, and so that we don't turn a ThinkPad X220 into a machine distributing 1Gb/s to every other 24 | machine in the office. We could cut the CDN traffic by at least one order of magnitude, but possibly 25 | much more. This is better for the person paying the cloud bill, the person paying for the internet 26 | connection, and the planet as a whole. 27 | 28 | This is what `passim` tries to be. You add automatically or manually add files to the daemon which 29 | stores them in `/var/lib/passim/data` with attributes set on each file for the `max-age` and 30 | `share-limit`. When the file has been shared more than the share limit number of times, or is older 31 | than the max age it is deleted and not advertised to other clients. 32 | 33 | The daemon then advertises the availability of the file as a mDNS service subtype and provides a 34 | tiny single-threaded webserver that supplies the file using HTTP using a self-signed TLS certificate. 35 | 36 | The file is sent when requested from a URL like `https://192.168.1.1:27500/filename.xml.gz?sha256=hash` 37 | -- any file requested without the checksum will not be supplied. Although this is a chicken-and-egg 38 | problem where you don't know the payload checksum until you've checked the remote server, this is 39 | easy solved using a tiny <100 byte request to the CDN for the payload checksum (or a .jcat file) 40 | and then the multi-megabyte (or multi-gigabyte!) payload can be requested over mDNS. 41 | 42 | ## Sharing Considerations 43 | 44 | Here we've assuming your local network (aka LAN) is a nice and friendly place, without evil people 45 | trying to overwhelm your system or feed you fake files. Although we request files by their hash 46 | (and thus can detect tampering) it still uses resources to send a file over the network. 47 | 48 | We'll assume that any network with working mDNS (as implemented in Avahi) is good enough to get 49 | metadata from other peers. If Avahi is not running, or mDNS is turned off on the firewall then 50 | no files will be shared. 51 | 52 | The cached index is available locally without any kind of authentication -- both over mDNS and 53 | as a webpage on `https://localhost:27500/`. 54 | 55 | So, **NEVER ADD FILES TO THE CACHE THAT YOU DO NOT WANT TO SHARE**. Even the filename may give more 56 | clues than you wanted to provide, e.g. sharing a file might get you in trouble. 57 | This can be subtle; if you download a security update for a Lenovo P1 Gen 3 laptop and share it with 58 | other laptops on your LAN -- it also tells the attacker your laptop model and also that you're 59 | running a system firmware that isn't patched against the latest firmware bug. 60 | 61 | My recommendation here is only to advertise files that are common to all machines. For instance: 62 | 63 | * AdBlocker metadata 64 | * Firmware update metadata 65 | * Remote metadata for update frameworks, e.g. apt-get/dnf etc. 66 | 67 | ## Implementation Considerations 68 | 69 | Any client **MUST** (and I'll go as far as to say it again, **MUST**) calculate the checksum of the 70 | supplied file and verify that it matches. There is no authentication or signing verification done 71 | so this step is non-optional. A malicious server could advertise the hash of `firmware.xml.gz` but 72 | actually supply `evil-payload.exe` -- and you do not want that. 73 | 74 | ## Static Data 75 | 76 | Passim can also add the contents of static directories, and offer those to clients. This might be 77 | useful if you have a big directory of thousands of files (an LVFS mirror, for example) that you 78 | want to distribute from a dedicated machine. 79 | 80 | To set this up, create a keyfile with a unique name and extension `.conf`, something like 81 | `/etc/passim.d/lvfs.conf` with the contents: 82 | 83 | [passim] 84 | Path=/srv/lvfs/downloads/ 85 | 86 | The running daemon will be notified this file has been created, scan the contents, and publish them 87 | for other users. If `passimd` has write permissions on the directory, it will also write an xattr 88 | of `user.checksum.sha256` which will speed up the next daemon restart considerably. 89 | 90 | ## Firewall Configuration 91 | 92 | Port 27500 should be open by default, but if downloading files fails you can open the port using 93 | firewalld: 94 | 95 | $ firewall-cmd --permanent --zone=public --add-port=27500/tcp 96 | 97 | ## Comparisons 98 | 99 | The obvious comparison to make is IPFS. I'll try to make this as fair as possible, although I'm 100 | obviously somewhat biased. 101 | 102 | IPFS: 103 | 104 | * existing project that's existed for many years tested by many people 105 | * allows sharing with other users not on your local network 106 | * not packaged in many distributions and not trivial to install correctly 107 | * requires a significant time to find resources 108 | * does not prioritize local clients over remote clients 109 | * requires a internet<->IPFS "gateway" which cost $$$ for a large number of files 110 | 111 | Passim: 112 | 113 | * new project that's not even finished 114 | * only allowed sharing with computers on your local network 115 | * returns results within 2s 116 | 117 | One concern we had specifically with IPFS with firmware were ITAR/EAR legal considerations. e.g. 118 | we can't share firmware containing strong encryption with users in some countries. From an ITAR/EAR 119 | point of view Passim would be compliant (as it only shares locally) and IPFS would not be. 120 | 121 | ## Debugging 122 | 123 | $ curl -v -k https://localhost:27500/HELLO.md?sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 -L 124 | * Trying 127.0.0.1:27500... 125 | * Connected to localhost (127.0.0.1) port 27500 (#0) 126 | * ALPN: offers h2,http/1.1 127 | * TLSv1.3 (OUT), TLS handshake, Client hello (1): 128 | * TLSv1.3 (IN), TLS handshake, Server hello (2): 129 | * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): 130 | * TLSv1.3 (IN), TLS handshake, Certificate (11): 131 | * TLSv1.3 (IN), TLS handshake, CERT verify (15): 132 | * TLSv1.3 (IN), TLS handshake, Finished (20): 133 | * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): 134 | * TLSv1.3 (OUT), TLS handshake, Finished (20): 135 | * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 136 | * ALPN: server accepted http/1.1 137 | * Server certificate: 138 | * subject: [NONE] 139 | * start date: Aug 15 20:13:03 2023 GMT 140 | * expire date: Dec 31 23:59:59 9999 GMT 141 | * using HTTP/1.1 142 | > GET /HELLO.md?sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 HTTP/1.1 143 | > Host: localhost:27500 144 | > User-Agent: curl/8.0.1 145 | > Accept: */* 146 | > 147 | < HTTP/1.1 302 Found 148 | < Server: passim libsoup/3.4.2 149 | < Date: Tue, 15 Aug 2023 20:37:10 GMT 150 | < Location: https://192.168.122.39:27500/sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447?sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 151 | < Content-Type: text/html 152 | < Content-Length: 227 153 | < 154 | * Ignoring the response-body 155 | * Connection #0 to host localhost left intact 156 | * Issue another request to this URL: 'https://192.168.122.39:27500/sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447?sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447' 157 | * Trying 192.168.122.39:27500... 158 | * Connected to 192.168.122.39 (192.168.122.39) port 27500 (#1) 159 | * ALPN: offers h2,http/1.1 160 | * TLSv1.3 (OUT), TLS handshake, Client hello (1): 161 | * TLSv1.3 (IN), TLS handshake, Server hello (2): 162 | * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): 163 | * TLSv1.3 (IN), TLS handshake, Certificate (11): 164 | * TLSv1.3 (IN), TLS handshake, CERT verify (15): 165 | * TLSv1.3 (IN), TLS handshake, Finished (20): 166 | * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): 167 | * TLSv1.3 (OUT), TLS handshake, Finished (20): 168 | * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 169 | * ALPN: server accepted http/1.1 170 | * Server certificate: 171 | * subject: [NONE] 172 | * start date: Aug 15 20:27:28 2023 GMT 173 | * expire date: Dec 31 23:59:59 9999 GMT 174 | * using HTTP/1.1 175 | > GET /sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447?sha256=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 HTTP/1.1 176 | > Host: 192.168.122.39:27500 177 | > User-Agent: curl/8.0.1 178 | > Accept: */* 179 | > 180 | < HTTP/1.1 200 OK 181 | < Server: passim libsoup/3.4.2 182 | < Date: Tue, 15 Aug 2023 20:37:11 GMT 183 | < Content-Disposition: attachment; filename="a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447-HELLO.md" 184 | < Content-Type: text/markdown 185 | < Content-Length: 12 186 | < 187 | hello world 188 | * Connection #1 to host 192.168.122.39 left intact 189 | 190 | Using the CLI: 191 | 192 | $ passim dump 193 | 7ea83bf1f9505f1e846cddef0d8ee49f6fd19361e2eb2f2e4f371b86382eacc2 HELLO.md (max-age: 86400, share-limit: 5) 194 | $ sudo passim publish /var/lib/passim/metadata/lvfs/metadata.xml.xz 60 44 195 | 7ea83bf1f9505f1e846cddef0d8ee49f6fd19361e2eb2f2e4f371b86382eacc2 HELLO.md (max-age: 86400, share-limit: 5) 196 | 0157efe3cdab369a17b68facb187df1c559c91e2771c9094880ff2019ad84eaf metadata.xml.xz (max-age: 60, share-count: 0, share-limit: 44) 197 | 198 | # TODO: 199 | 200 | - Self tests 201 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | Passim Release Notes 2 | 3 | Write release entries: 4 | 5 | git log --format="%s" --cherry-pick --right-only 0.1.10... | grep -i -v trivial | grep -v Merge | sort | uniq 6 | Add any user visible changes into ../data/org.freedesktop.Passim.metainfo.xml 7 | appstream-util appdata-to-news ../data/org.freedesktop.Passim.metainfo.xml > NEWS 8 | 9 | Update translations: 10 | 11 | ninja-build passim-pot 12 | git commit -a -m "trivial: Update translations for Weblate" 13 | 14 | # MAKE SURE THIS IS CORRECT 15 | export release_ver="0.1.11" 16 | 17 | git commit -a -m "Release ${release_ver}" --no-verify 18 | git tag -s -f -m "Release ${release_ver}" "${release_ver}" 19 | ninja dist 20 | git push --tags 21 | git push 22 | gpg -b -a meson-dist/passim-${release_ver}.tar.xz 23 | 24 | Create release and upload tarball to https://github.com/hughsie/passim/tags 25 | 26 | Do post release version bump in meson.build 27 | 28 | git commit -a -m "trivial: post release version bump" --no-verify 29 | git push 30 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.1.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | We have enabled private reporting in GitHub, so please [follow these steps](https://github.com/hughsie/passim/security) to report vulnerabilities. 12 | -------------------------------------------------------------------------------- /contrib/ci/Dockerfile-debian: -------------------------------------------------------------------------------- 1 | FROM debian:unstable 2 | 3 | RUN apt-get update -qq 4 | RUN apt-get install -yq --no-install-recommends \ 5 | gnutls-dev \ 6 | gobject-introspection \ 7 | libgirepository1.0-dev \ 8 | libglib2.0-bin \ 9 | libglib2.0-dev \ 10 | libsoup-3.0-dev \ 11 | libsystemd-dev \ 12 | meson \ 13 | ninja-build \ 14 | pkg-config \ 15 | systemd-dev \ 16 | shared-mime-info 17 | 18 | # Meson is too old in unstable, and that won't change until Buster is released 19 | # RUN pip3 install meson --break-system-packages 20 | 21 | WORKDIR /build 22 | -------------------------------------------------------------------------------- /contrib/ci/Dockerfile-fedora: -------------------------------------------------------------------------------- 1 | FROM fedora:38 2 | 3 | RUN dnf -y update 4 | RUN dnf -y install \ 5 | git-core \ 6 | gnutls-devel \ 7 | gobject-introspection-devel \ 8 | libsoup3-devel \ 9 | meson \ 10 | redhat-rpm-config \ 11 | shared-mime-info \ 12 | systemd 13 | 14 | WORKDIR /build 15 | -------------------------------------------------------------------------------- /contrib/ci/build-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | export LC_ALL=C.UTF-8 4 | mkdir -p build && cd build 5 | rm -rf * 6 | meson .. 7 | ninja -v || bash 8 | ninja test -v 9 | DESTDIR=/tmp/install-ninja ninja install 10 | cd .. 11 | -------------------------------------------------------------------------------- /contrib/ci/build-fedora.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | export LC_ALL=C.UTF-8 4 | mkdir -p build && cd build 5 | rm -rf * 6 | meson .. 7 | ninja -v || bash 8 | ninja test -v 9 | DESTDIR=/tmp/install-ninja ninja install 10 | cd .. 11 | -------------------------------------------------------------------------------- /contrib/passim.spec.in: -------------------------------------------------------------------------------- 1 | %global glib2_version 2.45.8 2 | %global systemd_version 231 3 | 4 | %define alphatag #ALPHATAG# 5 | 6 | %global __meson_wrap_mode nodownload 7 | 8 | Summary: Local caching server 9 | Name: passim 10 | Version: #VERSION# 11 | Release: 0.#BUILD#%{?alphatag}%{?dist} 12 | License: LGPL-2.1-or-later 13 | URL: https://github.com/hughsie/%{name} 14 | Source0: https://github.com/hughsie/%{name}/releases/download/%{version}/%{name}-%{version}.tar.xz 15 | 16 | BuildRequires: gcc 17 | BuildRequires: gettext 18 | BuildRequires: git-core 19 | BuildRequires: glib2-devel >= %{glib2_version} 20 | BuildRequires: gnutls-devel 21 | BuildRequires: gobject-introspection-devel 22 | BuildRequires: libappstream-glib 23 | BuildRequires: libsoup3-devel 24 | BuildRequires: meson 25 | BuildRequires: systemd-rpm-macros 26 | BuildRequires: systemd >= %{systemd_version} 27 | 28 | Recommends: avahi 29 | 30 | Requires: glib2%{?_isa} >= %{glib2_version} 31 | Requires: %{name}-libs%{?_isa} = %{version}-%{release} 32 | 33 | # Obsolete versions from before the subpackage split 34 | Obsoletes: %{name} < 0.1.1-3 35 | 36 | %description 37 | Passim is a daemon that allows software to share files on your local network. 38 | 39 | %package libs 40 | Summary: Local caching server library 41 | # Obsolete versions from before the subpackage split 42 | Obsoletes: %{name} < 0.1.1-3 43 | 44 | %description libs 45 | libpassim is a library that allows software to share files on your local network 46 | using the passimd daemon. 47 | 48 | %package devel 49 | Summary: Development package for %{name} 50 | Requires: %{name}%{?_isa} = %{version}-%{release} 51 | 52 | %description devel 53 | Files for development with %{name}. 54 | 55 | %prep 56 | %autosetup -p1 57 | 58 | %build 59 | %meson 60 | %meson_build 61 | 62 | %install 63 | %meson_install 64 | rm %{buildroot}/var/lib/passim/data/* 65 | %find_lang %{name} 66 | 67 | %check 68 | %meson_test 69 | appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/*.metainfo.xml 70 | 71 | %post 72 | %systemd_post passim.service 73 | 74 | %preun 75 | %systemd_preun passim.service 76 | 77 | %postun 78 | %systemd_postun_with_restart passim.service 79 | 80 | %files -f %{name}.lang 81 | %doc README.md 82 | %license LICENSE 83 | %{_bindir}/passim 84 | %config(noreplace)%{_sysconfdir}/passim.conf 85 | %dir %{_datadir}/passim 86 | %{_datadir}/passim/*.ico 87 | %{_datadir}/passim/*.css 88 | %{_datadir}/dbus-1/system.d/org.freedesktop.Passim.conf 89 | %{_datadir}/dbus-1/interfaces/org.freedesktop.Passim.xml 90 | %{_datadir}/dbus-1/system-services/org.freedesktop.Passim.service 91 | %{_datadir}/icons/hicolor/scalable/apps/org.freedesktop.Passim.svg 92 | %{_datadir}/icons/hicolor/256x256/apps/org.freedesktop.Passim.png 93 | %{_datadir}/metainfo/org.freedesktop.Passim.metainfo.xml 94 | %{_libdir}/girepository-1.0/Passim-1.0.typelib 95 | %{_libexecdir}/passimd 96 | %{_mandir}/man1/passim.1* 97 | %{_unitdir}/passim.service 98 | /usr/lib/sysusers.d/passim.conf 99 | 100 | %files libs 101 | %license LICENSE 102 | %{_libdir}/libpassim.so.1* 103 | 104 | %files devel 105 | %{_datadir}/gir-1.0/Passim-1.0.gir 106 | %dir %{_includedir}/passim-1 107 | %{_includedir}/passim-1/passim*.h 108 | %{_libdir}/libpassim*.so 109 | %{_libdir}/pkgconfig/passim.pc 110 | 111 | %changelog 112 | %autochangelog 113 | -------------------------------------------------------------------------------- /data/a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447-HELLO.md: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hughsie/passim/541e8e1a580a6415e9b27dfc3af94be5b1167d3e/data/favicon.ico -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | 'passim.conf', 3 | install_dir: sysconfdir, 4 | ) 5 | install_data( 6 | 'passim.sysusers.conf', 7 | rename: ['passim.conf'], 8 | install_dir: sysusersdir, 9 | ) 10 | install_data( 11 | 'favicon.ico', 12 | 'style.css', 13 | install_dir: datadir / meson.project_name(), 14 | ) 15 | install_data( 16 | 'org.freedesktop.Passim.svg', 17 | install_dir: datadir / 'icons/hicolor/scalable/apps', 18 | ) 19 | install_data( 20 | 'org.freedesktop.Passim.png', 21 | install_dir: datadir / 'icons/hicolor/256x256/apps', 22 | ) 23 | 24 | con2 = configuration_data() 25 | con2.set('libexecdir', libexecdir) 26 | con2.set('localstatedir', localstatedir) 27 | configure_file( 28 | input: 'passim.service.in', 29 | output: 'passim.service', 30 | configuration: con2, 31 | install: true, 32 | install_dir: systemdunitdir, 33 | ) 34 | configure_file( 35 | input: 'org.freedesktop.Passim.service.in', 36 | output: 'org.freedesktop.Passim.service', 37 | configuration: con2, 38 | install: true, 39 | install_dir: datadir / 'dbus-1/system-services', 40 | ) 41 | install_data( 42 | 'org.freedesktop.Passim.conf', 43 | install_dir: datadir / 'dbus-1/system.d', 44 | ) 45 | install_data( 46 | 'org.freedesktop.Passim.metainfo.xml', 47 | install_dir: datadir / 'metainfo', 48 | ) 49 | install_data( 50 | 'a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447-HELLO.md', 51 | install_dir: localstatedir / 'lib' / meson.project_name() / 'data', 52 | ) 53 | -------------------------------------------------------------------------------- /data/org.freedesktop.Passim.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 25 | 27 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /data/org.freedesktop.Passim.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.freedesktop.Passim 5 | CC0-1.0 6 | LGPL-2.1-or-later 7 | Passim 8 | Local caching server 9 | 10 |

11 | Passim is a daemon that allows other software to share metadata on your local network. 12 |

13 |
14 | https://github.com/hughsie/passim/issues 15 | https://github.com/hughsie/passim 16 | 17 | moderate 18 | 19 | 20 | passim 21 | 22 | 23 | 24 | 25 |

This release fixes the following bugs:

26 |
    27 |
  • Adjust man page to parse properly in tools like lexgrog
  • 28 |
  • Fix a crash when reaching the share limit
  • 29 |
  • Fix the location the icon is installed to match the correct size
  • 30 |
  • Restart service after failure
  • 31 |
32 |
33 |
34 | 35 | 36 |

This release fixes the following bugs:

37 |
    38 |
  • Install the icons to the correct location
  • 39 |
40 |
41 |
42 | 43 | 44 |

This release fixes the following bugs:

45 |
    46 |
  • Fix an almost-impossible resource leak when adding files
  • 47 |
48 |
49 |
50 | 51 | 52 |

This release fixes the following bugs:

53 |
    54 |
  • Fix a crash when deleting items
  • 55 |
  • Fix a small memory leak when parsing a request with duplicate arguments
  • 56 |
  • Lock the systemd service down some more
  • 57 |
58 |
59 |
60 | 61 | 62 |

63 | This release adds the following features: 64 |

65 |
    66 |
  • Add a 'download' command to the CLI, and allow ignoring the localhost scan
  • 67 |
  • Log to an audit log when publishing, unpublishing and sharing
  • 68 |
  • Show the URI, auto-generated name, network and carbon saving in the CLI
  • 69 |
70 |

This release fixes the following bugs:

71 |
    72 |
  • Add translation support for the CLI tool
  • 73 |
  • Properly encode socket addresses, but disable IPv6 by default
  • 74 |
  • Redirect with the basename set correctly
  • 75 |
  • Reduce some log-spam when checking item ages
  • 76 |
77 |
78 |
79 | 80 | 81 |

This release adds some new API for fwupd to use.

82 |
    83 |
  • Add passim_item_set_stream()
  • 84 |
85 |
86 |
87 | 88 | 89 |

This release fixes the following bugs:

90 |
    91 |
  • Allow setting MaxItemSize bigger than 4GB
  • 92 |
  • Do not abort with a critical warning when no query is used
  • 93 |
  • Do not follow symlinks in libdir and sysconfpkgdir
  • 94 |
  • Fix a harmless assertion warning when serving a zero-length file
  • 95 |
  • Properly escape the Content-Disposition filename
  • 96 |
  • Reduce RSS when reloading the daemon
  • 97 |
  • Show a better message when publishing a file that is too large
  • 98 |
99 |

Many thanks to Matthias Gerstner from the SUSE Security team for the code review.

100 |
101 |
102 | 103 | 104 |

This release fixes the following bug:

105 |
    106 |
  • Use a dedicated user to run the server
  • 107 |
108 |
109 |
110 | 111 | 112 |

113 | This release adds the following features: 114 |

115 |
    116 |
  • Add file size information into the exported item
  • 117 |
  • Allow admins to add a directory of static contents
  • 118 |
119 |

This release fixes the following bugs:

120 |
    121 |
  • Do not advertise files when on a metered network connection
  • 122 |
  • Do not fail to start the service if /var/lib/passim/data does not exist
  • 123 |
  • Explicitly depend on avahi-daemon in passim.service
  • 124 |
125 |
126 |
127 | 128 | 129 |

This release fixes the following bugs:

130 |
    131 |
  • Sanity check share-count is less than share-limit
  • 132 |
  • Show the correct age in the 'passim dump' CLI
  • 133 |
134 |
135 |
136 | 137 | 138 |

Initial release.

139 |
140 |
141 |
142 |
143 | -------------------------------------------------------------------------------- /data/org.freedesktop.Passim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hughsie/passim/541e8e1a580a6415e9b27dfc3af94be5b1167d3e/data/org.freedesktop.Passim.png -------------------------------------------------------------------------------- /data/org.freedesktop.Passim.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.freedesktop.Passim 3 | Documentation=https://github.com/hughsie/passim 4 | Exec=@libexecdir@/passimd 5 | User=passim 6 | SystemdService=passim.service 7 | AssumedAppArmorLabel=unconfined 8 | -------------------------------------------------------------------------------- /data/org.freedesktop.Passim.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 24 | 28 | 33 | 37 | 38 | 45 | 49 | 53 | 54 | 64 | 65 | 83 | 91 | 95 | 99 | 100 | 108 | 112 | 117 | 118 | 126 | 131 | 135 | 139 | 144 | 145 | 153 | 157 | 161 | 165 | 166 | 174 | 178 | 182 | 186 | 190 | 194 | 198 | 199 | 206 | 210 | 214 | 215 | 219 | 223 | 227 | 231 | 235 | 239 | 243 | Passim 259 | 260 | -------------------------------------------------------------------------------- /data/passim.conf: -------------------------------------------------------------------------------- 1 | [daemon] 2 | # Port = 27500 3 | # Path = /some/other/place 4 | -------------------------------------------------------------------------------- /data/passim.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Local Caching Server 3 | Documentation=https://github.com/hughsie/passim 4 | After=avahi-daemon.service 5 | Before=display-manager.service 6 | Wants=avahi-daemon.service 7 | 8 | [Service] 9 | Type=dbus 10 | TimeoutSec=180 11 | BusName=org.freedesktop.Passim 12 | ExecStart=@libexecdir@/passimd 13 | User=passim 14 | DevicePolicy=closed 15 | LockPersonality=yes 16 | MemoryDenyWriteExecute=yes 17 | NoNewPrivileges=yes 18 | PrivateDevices=yes 19 | PrivateMounts=yes 20 | PrivateTmp=yes 21 | ProtectClock=yes 22 | ProtectControlGroups=yes 23 | ProtectHome=yes 24 | ProtectHostname=yes 25 | ProtectKernelLogs=yes 26 | ProtectKernelModules=yes 27 | ProtectKernelTunables=yes 28 | ProtectSystem=full 29 | RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK 30 | RestrictNamespaces=yes 31 | RestrictRealtime=yes 32 | RestrictSUIDSGID=yes 33 | SystemCallFilter=@system-service 34 | SystemCallFilter=~@resources 35 | SystemCallErrorNumber=EPERM 36 | SystemCallArchitectures=native 37 | StateDirectory=passim passim/data 38 | LogsDirectory=passim 39 | Restart=on-failure 40 | -------------------------------------------------------------------------------- /data/passim.sysusers.conf: -------------------------------------------------------------------------------- 1 | #Type Name ID GECOS Home directory Shell 2 | u passim - "Local Caching Server" /usr/share/empty - 3 | -------------------------------------------------------------------------------- /data/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 20px 20px 20px 20px; 4 | } 5 | th { 6 | text-align: left; 7 | } 8 | tr:nth-child(even) { 9 | background-color: #f2f2f2; 10 | } 11 | table, th, td { 12 | border: 1px solid #f2f2f2; 13 | border-collapse: collapse; 14 | } 15 | th, td { 16 | padding: 10px; 17 | } 18 | -------------------------------------------------------------------------------- /libpassim/generate-version-script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # pylint: disable=invalid-name,missing-docstring 3 | # 4 | # Copyright 2017 Richard Hughes 5 | # 6 | # SPDX-License-Identifier: LGPL-2.1-or-later 7 | 8 | import sys 9 | import argparse 10 | import xml.etree.ElementTree as ET 11 | 12 | XMLNS = "{http://www.gtk.org/introspection/core/1.0}" 13 | XMLNS_C = "{http://www.gtk.org/introspection/c/1.0}" 14 | 15 | 16 | def parse_version(ver): 17 | return tuple(map(int, ver.split("."))) 18 | 19 | 20 | def usage(return_code): 21 | """print usage and exit with the supplied return code""" 22 | if return_code == 0: 23 | out = sys.stdout 24 | else: 25 | out = sys.stderr 26 | out.write("usage: %s \n" % sys.argv[0]) 27 | sys.exit(return_code) 28 | 29 | 30 | class LdVersionScript: 31 | """Rasterize some text""" 32 | 33 | def __init__(self, library_name): 34 | self.library_name = library_name 35 | self.releases = {} 36 | self.overrides = {} 37 | 38 | def _add_node(self, node): 39 | identifier = node.attrib[XMLNS_C + "identifier"] 40 | introspectable = int(node.get("introspectable", 1)) 41 | version = node.get("version", None) 42 | if introspectable and not version: 43 | print("No version for", identifier) 44 | sys.exit(1) 45 | if not version: 46 | return None 47 | version = node.attrib["version"] 48 | if version not in self.releases: 49 | self.releases[version] = [] 50 | release = self.releases[version] 51 | if identifier not in release: 52 | release.append(identifier) 53 | return version 54 | 55 | def _add_cls(self, cls): 56 | 57 | # add all class functions 58 | for node in cls.findall(XMLNS + "function"): 59 | self._add_node(node) 60 | 61 | # choose the lowest version method for the _get_type symbol 62 | version_lowest = None 63 | 64 | # add all class methods 65 | for node in cls.findall(XMLNS + "method"): 66 | version_tmp = self._add_node(node) 67 | if version_tmp: 68 | if not version_lowest or parse_version(version_tmp) < parse_version( 69 | version_lowest 70 | ): 71 | version_lowest = version_tmp 72 | 73 | # add the constructor 74 | for node in cls.findall(XMLNS + "constructor"): 75 | version_tmp = self._add_node(node) 76 | if version_tmp: 77 | if not version_lowest or parse_version(version_tmp) < parse_version( 78 | version_lowest 79 | ): 80 | version_lowest = version_tmp 81 | 82 | if "{http://www.gtk.org/introspection/glib/1.0}get-type" not in cls.attrib: 83 | return 84 | type_name = cls.attrib["{http://www.gtk.org/introspection/glib/1.0}get-type"] 85 | 86 | # finally add the get_type symbol 87 | version = self.overrides.get(type_name, version_lowest) 88 | if version: 89 | self.releases[version].append(type_name) 90 | 91 | def import_gir(self, filename): 92 | tree = ET.parse(filename) 93 | root = tree.getroot() 94 | for ns in root.findall(XMLNS + "namespace"): 95 | for node in ns.findall(XMLNS + "function"): 96 | self._add_node(node) 97 | for cls in ns.findall(XMLNS + "record"): 98 | self._add_cls(cls) 99 | for cls in ns.findall(XMLNS + "class"): 100 | self._add_cls(cls) 101 | 102 | def render(self): 103 | 104 | # get a sorted list of all the versions 105 | versions = [] 106 | for version in self.releases: 107 | versions.append(version) 108 | 109 | # output the version data to a file 110 | verout = "# generated automatically, do not edit!\n" 111 | oldversion = None 112 | for version in sorted(versions, key=parse_version): 113 | symbols = sorted(self.releases[version]) 114 | verout += "\n%s_%s {\n" % (self.library_name, version) 115 | verout += " global:\n" 116 | for symbol in symbols: 117 | verout += " %s;\n" % symbol 118 | verout += " local: *;\n" 119 | if oldversion: 120 | verout += "} %s_%s;\n" % (self.library_name, oldversion) 121 | else: 122 | verout += "};\n" 123 | oldversion = version 124 | return verout 125 | 126 | 127 | if __name__ == "__main__": 128 | 129 | parser = argparse.ArgumentParser() 130 | parser.add_argument( 131 | "-r", "--override", action="append", nargs=2, metavar=("symbol", "version") 132 | ) 133 | args, argv = parser.parse_known_args() 134 | if len(argv) != 3: 135 | usage(1) 136 | 137 | ld = LdVersionScript(library_name=argv[0]) 138 | if args.override: 139 | for override_symbol, override_version in args.override: 140 | ld.overrides[override_symbol] = override_version 141 | ld.import_gir(argv[1]) 142 | open(argv[2], "w").write(ld.render()) 143 | -------------------------------------------------------------------------------- /libpassim/meson.build: -------------------------------------------------------------------------------- 1 | gnome = import('gnome') 2 | 3 | passim_version_h = configure_file( 4 | input: 'passim-version.h.in', 5 | output: 'passim-version.h', 6 | configuration: conf 7 | ) 8 | 9 | install_headers([ 10 | 'passim.h', 11 | 'passim-client.h', 12 | 'passim-item.h', 13 | passim_version_h, 14 | ], 15 | subdir: 'passim-1', 16 | ) 17 | 18 | libpassim_deps = [ 19 | libgio, 20 | ] 21 | 22 | libpassim_src = [ 23 | 'passim-client.c', 24 | 'passim-item.c', 25 | 'passim-version.c', 26 | ] 27 | 28 | passim_mapfile = 'passim.map' 29 | vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), passim_mapfile) 30 | passim = library( 31 | 'passim', 32 | sources: libpassim_src, 33 | soversion: libpassim_lt_current, 34 | version: libpassim_lt_version, 35 | dependencies: libpassim_deps, 36 | c_args: [ 37 | '-DG_LOG_DOMAIN="Passim"', 38 | '-DLOCALSTATEDIR="' + localstatedir + '"', 39 | ], 40 | include_directories: root_incdir, 41 | link_args: cc.get_supported_link_arguments([vflag]), 42 | link_depends: passim_mapfile, 43 | install: true 44 | ) 45 | 46 | passim_dep = declare_dependency( 47 | link_with: passim, 48 | include_directories: [root_incdir, include_directories('.')], 49 | dependencies: libpassim_deps 50 | ) 51 | 52 | pkgg = import('pkgconfig') 53 | pkgg.generate( 54 | passim, 55 | requires: [ 'gio-2.0' ], 56 | subdirs: 'passim-1', 57 | version: meson.project_version(), 58 | name: 'passim', 59 | filebase: 'passim', 60 | description: 'passim is a system daemon for installing device firmware', 61 | ) 62 | 63 | gir_dep = dependency('gobject-introspection-1.0', required: get_option('introspection')) 64 | introspection = get_option('introspection').disable_auto_if(host_machine.system() != 'linux').disable_auto_if(not gir_dep.found()) 65 | 66 | if introspection.allowed() 67 | passim_gir_deps = [ 68 | libgio, 69 | ] 70 | passim_gir = gnome.generate_gir(passim, 71 | sources: [ 72 | 'passim-client.c', 73 | 'passim-client.h', 74 | 'passim-item.c', 75 | 'passim-item.h', 76 | 'passim-version.c', 77 | passim_version_h, 78 | ], 79 | nsversion: '1.0', 80 | namespace: 'Passim', 81 | symbol_prefix: 'passim', 82 | identifier_prefix: ['Passim', 'passim'], 83 | export_packages: 'passim', 84 | header: 'passim.h', 85 | dependencies: passim_gir_deps, 86 | includes: [ 87 | 'Gio-2.0', 88 | 'GObject-2.0', 89 | ], 90 | install: true 91 | ) 92 | 93 | # Verify the map file is correct -- note we can't actually use the generated 94 | # file for two reasons: 95 | # 96 | # 1. We don't hard depend on GObject Introspection 97 | # 2. The map file is required to build the lib that the GIR is built from 98 | # 99 | # To avoid the circular dep, and to ensure we don't change exported API 100 | # accidentally actually check in a version of the version script to git. 101 | generate_version_script = [python3, files('generate-version-script.py')] 102 | mapfile_target = custom_target('passim_mapfile', 103 | input: passim_gir[0], 104 | output: 'passim.map', 105 | command: [ 106 | generate_version_script, 107 | 'LIBPASSIM', 108 | '@INPUT@', 109 | '@OUTPUT@', 110 | ], 111 | ) 112 | diffcmd = find_program('diff') 113 | test('passim-exported-api', diffcmd, 114 | args: [ 115 | '-urNp', 116 | files('passim.map'), 117 | mapfile_target, 118 | ], 119 | ) 120 | endif 121 | 122 | passim_incdir = include_directories('.') 123 | -------------------------------------------------------------------------------- /libpassim/passim-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef HAVE_MEMFD_CREATE 17 | #include 18 | #endif 19 | 20 | #include "passim-client.h" 21 | 22 | /** 23 | * PassimClient: 24 | * 25 | * A shared client. 26 | */ 27 | 28 | typedef struct { 29 | GDBusProxy *proxy; 30 | gchar *version; 31 | gchar *name; 32 | gchar *uri; 33 | PassimStatus status; 34 | guint64 download_saving; 35 | gdouble carbon_saving; 36 | } PassimClientPrivate; 37 | 38 | G_DEFINE_TYPE_WITH_PRIVATE(PassimClient, passim_client, G_TYPE_OBJECT) 39 | #define GET_PRIVATE(o) (passim_client_get_instance_private(o)) 40 | 41 | /** 42 | * passim_client_get_version: 43 | * @self: a #PassimClient 44 | * 45 | * Gets the daemon version. 46 | * 47 | * Returns: the version string, or %NULL if unset 48 | * 49 | * Since: 0.1.0 50 | **/ 51 | const gchar * 52 | passim_client_get_version(PassimClient *self) 53 | { 54 | PassimClientPrivate *priv = GET_PRIVATE(self); 55 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), NULL); 56 | return priv->version; 57 | } 58 | 59 | /** 60 | * passim_client_get_name: 61 | * @self: a #PassimClient 62 | * 63 | * Gets the daemon name. 64 | * 65 | * Returns: the name string, or %NULL if unset 66 | * 67 | * Since: 0.1.6 68 | **/ 69 | const gchar * 70 | passim_client_get_name(PassimClient *self) 71 | { 72 | PassimClientPrivate *priv = GET_PRIVATE(self); 73 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), NULL); 74 | return priv->name; 75 | } 76 | 77 | /** 78 | * passim_client_get_uri: 79 | * @self: a #PassimClient 80 | * 81 | * Gets the daemon URI. 82 | * 83 | * Returns: the URI string, or %NULL if unset 84 | * 85 | * Since: 0.1.6 86 | **/ 87 | const gchar * 88 | passim_client_get_uri(PassimClient *self) 89 | { 90 | PassimClientPrivate *priv = GET_PRIVATE(self); 91 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), NULL); 92 | return priv->uri; 93 | } 94 | 95 | /** 96 | * passim_client_get_status: 97 | * @self: a #PassimClient 98 | * 99 | * Gets the daemon status. 100 | * 101 | * Returns: the #PassimStatus 102 | * 103 | * Since: 0.1.2 104 | **/ 105 | PassimStatus 106 | passim_client_get_status(PassimClient *self) 107 | { 108 | PassimClientPrivate *priv = GET_PRIVATE(self); 109 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), PASSIM_STATUS_UNKNOWN); 110 | return priv->status; 111 | } 112 | 113 | /** 114 | * passim_client_get_download_saving: 115 | * @self: a #PassimClient 116 | * 117 | * Gets the total number of bytes saved from using this project. 118 | * 119 | * Returns: bytes 120 | * 121 | * Since: 0.1.6 122 | **/ 123 | guint64 124 | passim_client_get_download_saving(PassimClient *self) 125 | { 126 | PassimClientPrivate *priv = GET_PRIVATE(self); 127 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), G_MAXUINT64); 128 | return priv->download_saving; 129 | } 130 | 131 | /** 132 | * passim_client_get_carbon_saving: 133 | * @self: a #PassimClient 134 | * 135 | * Gets the carbon saving from using this project. 136 | * 137 | * Returns: kgs of CO₂e 138 | * 139 | * Since: 0.1.6 140 | **/ 141 | gdouble 142 | passim_client_get_carbon_saving(PassimClient *self) 143 | { 144 | PassimClientPrivate *priv = GET_PRIVATE(self); 145 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), PASSIM_STATUS_UNKNOWN); 146 | return priv->carbon_saving; 147 | } 148 | 149 | static void 150 | passim_client_load_proxy_properties(PassimClient *self) 151 | { 152 | PassimClientPrivate *priv = GET_PRIVATE(self); 153 | g_autoptr(GVariant) download_saving = NULL; 154 | g_autoptr(GVariant) carbon_saving = NULL; 155 | g_autoptr(GVariant) name = NULL; 156 | g_autoptr(GVariant) status = NULL; 157 | g_autoptr(GVariant) version = NULL; 158 | g_autoptr(GVariant) uri = NULL; 159 | 160 | version = g_dbus_proxy_get_cached_property(priv->proxy, "DaemonVersion"); 161 | if (version != NULL) { 162 | g_free(priv->version); 163 | priv->version = g_variant_dup_string(version, NULL); 164 | } 165 | name = g_dbus_proxy_get_cached_property(priv->proxy, "Name"); 166 | if (name != NULL) { 167 | g_free(priv->name); 168 | priv->name = g_variant_dup_string(name, NULL); 169 | } 170 | uri = g_dbus_proxy_get_cached_property(priv->proxy, "Uri"); 171 | if (uri != NULL) { 172 | g_free(priv->uri); 173 | priv->uri = g_variant_dup_string(uri, NULL); 174 | } 175 | status = g_dbus_proxy_get_cached_property(priv->proxy, "Status"); 176 | if (status != NULL) 177 | priv->status = g_variant_get_uint32(status); 178 | download_saving = g_dbus_proxy_get_cached_property(priv->proxy, "DownloadSaving"); 179 | if (download_saving != NULL) 180 | priv->download_saving = g_variant_get_uint64(download_saving); 181 | carbon_saving = g_dbus_proxy_get_cached_property(priv->proxy, "CarbonSaving"); 182 | if (carbon_saving != NULL) 183 | priv->carbon_saving = g_variant_get_double(carbon_saving); 184 | } 185 | 186 | static void 187 | passim_client_proxy_signal_cb(GDBusProxy *proxy, 188 | const gchar *sender_name, 189 | const gchar *signal_name, 190 | GVariant *parameters, 191 | gpointer user_data) 192 | { 193 | PassimClient *self = PASSIM_CLIENT(user_data); 194 | if (g_strcmp0(signal_name, "Changed") == 0) 195 | passim_client_load_proxy_properties(self); 196 | } 197 | 198 | /** 199 | * passim_client_load: 200 | * @self: a #PassimClient 201 | * @error: (nullable): optional return location for an error 202 | * 203 | * Loads properties from the passim daemon. 204 | * 205 | * Returns: %TRUE for success 206 | * 207 | * Since: 0.1.0 208 | **/ 209 | gboolean 210 | passim_client_load(PassimClient *self, GError **error) 211 | { 212 | PassimClientPrivate *priv = GET_PRIVATE(self); 213 | 214 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), FALSE); 215 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 216 | 217 | if (priv->proxy != NULL) 218 | return TRUE; 219 | priv->proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, 220 | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, 221 | NULL, 222 | PASSIM_DBUS_SERVICE, 223 | PASSIM_DBUS_PATH, 224 | PASSIM_DBUS_INTERFACE, 225 | NULL, 226 | error); 227 | if (priv->proxy == NULL) { 228 | if (error != NULL) 229 | g_dbus_error_strip_remote_error(*error); 230 | return FALSE; 231 | } 232 | g_signal_connect(G_DBUS_PROXY(priv->proxy), 233 | "g-signal", 234 | G_CALLBACK(passim_client_proxy_signal_cb), 235 | self); 236 | passim_client_load_proxy_properties(self); 237 | 238 | /* success */ 239 | return TRUE; 240 | } 241 | 242 | static GPtrArray * 243 | passim_item_array_from_variant(GVariant *value) 244 | { 245 | GPtrArray *items = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); 246 | g_autoptr(GVariant) untuple = g_variant_get_child_value(value, 0); 247 | gsize sz = g_variant_n_children(untuple); 248 | for (guint i = 0; i < sz; i++) { 249 | g_autoptr(GVariant) data = g_variant_get_child_value(untuple, i); 250 | g_ptr_array_add(items, passim_item_from_variant(data)); 251 | } 252 | return items; 253 | } 254 | 255 | /** 256 | * passim_client_get_items: 257 | * @self: a #PassimClient 258 | * @error: (nullable): optional return location for an error 259 | * 260 | * Get items currently published by the daemon. 261 | * 262 | * Returns: (element-type PassimItem) (transfer container): items, or %NULL for error 263 | * 264 | * Since: 0.1.0 265 | **/ 266 | GPtrArray * 267 | passim_client_get_items(PassimClient *self, GError **error) 268 | { 269 | PassimClientPrivate *priv = GET_PRIVATE(self); 270 | g_autoptr(GVariant) val = NULL; 271 | 272 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), NULL); 273 | g_return_val_if_fail(priv->proxy != NULL, NULL); 274 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 275 | 276 | val = g_dbus_proxy_call_sync(priv->proxy, 277 | "GetItems", 278 | NULL, 279 | G_DBUS_CALL_FLAGS_NONE, 280 | 1500, 281 | NULL, 282 | error); 283 | if (val == NULL) { 284 | if (error != NULL) 285 | g_dbus_error_strip_remote_error(*error); 286 | return FALSE; 287 | } 288 | 289 | /* success */ 290 | return passim_item_array_from_variant(val); 291 | } 292 | 293 | /** 294 | * passim_client_unpublish: 295 | * @self: a #PassimClient 296 | * @hash: (not nullable): an item hash value 297 | * @error: (nullable): optional return location for an error 298 | * 299 | * Unpublish a file from the index. 300 | * 301 | * Returns: %TRUE for success 302 | * 303 | * Since: 0.1.0 304 | **/ 305 | gboolean 306 | passim_client_unpublish(PassimClient *self, const gchar *hash, GError **error) 307 | { 308 | PassimClientPrivate *priv = GET_PRIVATE(self); 309 | g_autoptr(GVariant) val = NULL; 310 | 311 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), FALSE); 312 | g_return_val_if_fail(priv->proxy != NULL, FALSE); 313 | g_return_val_if_fail(hash != NULL, FALSE); 314 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 315 | 316 | val = g_dbus_proxy_call_sync(priv->proxy, 317 | "Unpublish", 318 | g_variant_new("(s)", hash), 319 | G_DBUS_CALL_FLAGS_NONE, 320 | 1500, 321 | NULL, 322 | error); 323 | if (val == NULL) { 324 | if (error != NULL) 325 | g_dbus_error_strip_remote_error(*error); 326 | return FALSE; 327 | } 328 | 329 | /* success */ 330 | return TRUE; 331 | } 332 | 333 | static GUnixInputStream * 334 | passim_client_input_stream_from_bytes(GBytes *bytes, GError **error) 335 | { 336 | gint fd; 337 | gssize rc; 338 | #ifndef HAVE_MEMFD_CREATE 339 | gchar tmp_file[] = "/tmp/passim.XXXXXX"; 340 | #endif 341 | 342 | #ifdef HAVE_MEMFD_CREATE 343 | fd = memfd_create("passim", MFD_CLOEXEC); 344 | #else 345 | /* emulate in-memory file by an unlinked temporary file */ 346 | fd = g_mkstemp(tmp_file); 347 | if (fd != -1) { 348 | rc = g_unlink(tmp_file); 349 | if (rc != 0) { 350 | if (!g_close(fd, error)) { 351 | g_prefix_error(error, "failed to close temporary file: "); 352 | return NULL; 353 | } 354 | g_set_error_literal(error, 355 | G_IO_ERROR, 356 | G_IO_ERROR_INVALID_DATA, 357 | "failed to unlink temporary file"); 358 | return NULL; 359 | } 360 | } 361 | #endif 362 | 363 | if (fd < 0) { 364 | g_set_error_literal(error, 365 | G_IO_ERROR, 366 | G_IO_ERROR_INVALID_DATA, 367 | "failed to create memfd"); 368 | return NULL; 369 | } 370 | rc = write(fd, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); 371 | if (rc < 0) { 372 | g_close(fd, NULL); 373 | g_set_error(error, 374 | G_IO_ERROR, 375 | G_IO_ERROR_INVALID_DATA, 376 | "failed to write %" G_GSSIZE_FORMAT, 377 | rc); 378 | return NULL; 379 | } 380 | if (lseek(fd, 0, SEEK_SET) < 0) { 381 | g_close(fd, NULL); 382 | g_set_error(error, 383 | G_IO_ERROR, 384 | G_IO_ERROR_INVALID_DATA, 385 | "failed to seek: %s", 386 | g_strerror(errno)); 387 | return NULL; 388 | } 389 | return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE)); 390 | } 391 | 392 | static GUnixInputStream * 393 | passim_client_input_stream_from_filename(const gchar *fn, GError **error) 394 | { 395 | gint fd = open(fn, O_RDONLY); 396 | if (fd < 0) { 397 | g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to open %s", fn); 398 | return NULL; 399 | } 400 | return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE)); 401 | } 402 | 403 | /** 404 | * passim_client_publish: 405 | * @self: a #PassimClient 406 | * @item: (not nullable): a #PassimItem 407 | * @error: (nullable): optional return location for an error 408 | * 409 | * Connects to the remote server. 410 | * 411 | * Returns: %TRUE for success 412 | * 413 | * Since: 0.1.0 414 | **/ 415 | gboolean 416 | passim_client_publish(PassimClient *self, PassimItem *item, GError **error) 417 | { 418 | PassimClientPrivate *priv = GET_PRIVATE(self); 419 | g_autoptr(GDBusMessage) reply = NULL; 420 | g_autoptr(GDBusMessage) request = NULL; 421 | g_autoptr(GUnixFDList) fd_list = g_unix_fd_list_new(); 422 | g_autoptr(GUnixInputStream) istream = NULL; 423 | 424 | g_return_val_if_fail(PASSIM_IS_CLIENT(self), FALSE); 425 | g_return_val_if_fail(PASSIM_IS_ITEM(item), FALSE); 426 | g_return_val_if_fail(priv->proxy != NULL, FALSE); 427 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 428 | 429 | /* set out of band file descriptor */ 430 | if (passim_item_get_stream(item) != NULL) { 431 | istream = g_object_ref(G_UNIX_INPUT_STREAM(passim_item_get_stream(item))); 432 | } else if (passim_item_get_file(item) != NULL) { 433 | g_autofree gchar *filename = g_file_get_path(passim_item_get_file(item)); 434 | istream = passim_client_input_stream_from_filename(filename, error); 435 | if (istream == NULL) 436 | return FALSE; 437 | } else if (passim_item_get_bytes(item) != NULL) { 438 | istream = passim_client_input_stream_from_bytes(passim_item_get_bytes(item), error); 439 | if (istream == NULL) 440 | return FALSE; 441 | } else { 442 | g_set_error_literal(error, 443 | G_IO_ERROR, 444 | G_IO_ERROR_INVALID_DATA, 445 | "no PassimItem bytes or file set"); 446 | return FALSE; 447 | } 448 | g_unix_fd_list_append(fd_list, g_unix_input_stream_get_fd(istream), NULL); 449 | request = g_dbus_message_new_method_call(g_dbus_proxy_get_name(priv->proxy), 450 | g_dbus_proxy_get_object_path(priv->proxy), 451 | g_dbus_proxy_get_interface_name(priv->proxy), 452 | "Publish"); 453 | g_dbus_message_set_unix_fd_list(request, fd_list); 454 | 455 | /* call into daemon */ 456 | g_dbus_message_set_body(request, 457 | g_variant_new("(h@a{sv})", 458 | g_unix_input_stream_get_fd(istream), 459 | passim_item_to_variant(item))); 460 | reply = 461 | g_dbus_connection_send_message_with_reply_sync(g_dbus_proxy_get_connection(priv->proxy), 462 | request, 463 | G_DBUS_SEND_MESSAGE_FLAGS_NONE, 464 | G_MAXINT, 465 | NULL, 466 | NULL, /* cancellable */ 467 | error); 468 | if (reply == NULL) { 469 | if (error != NULL) 470 | g_dbus_error_strip_remote_error(*error); 471 | return FALSE; 472 | } 473 | if (g_dbus_message_to_gerror(reply, error)) 474 | return FALSE; 475 | 476 | /* success */ 477 | return TRUE; 478 | } 479 | 480 | static void 481 | passim_client_init(PassimClient *self) 482 | { 483 | } 484 | 485 | static void 486 | passim_client_finalize(GObject *object) 487 | { 488 | PassimClient *self = PASSIM_CLIENT(object); 489 | PassimClientPrivate *priv = GET_PRIVATE(self); 490 | 491 | if (priv->proxy != NULL) 492 | g_object_unref(priv->proxy); 493 | g_free(priv->version); 494 | g_free(priv->name); 495 | g_free(priv->uri); 496 | 497 | G_OBJECT_CLASS(passim_client_parent_class)->finalize(object); 498 | } 499 | 500 | static void 501 | passim_client_class_init(PassimClientClass *klass) 502 | { 503 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 504 | object_class->finalize = passim_client_finalize; 505 | } 506 | 507 | /** 508 | * passim_client_new: 509 | * 510 | * Creates a new client. 511 | * 512 | * Returns: a new #PassimClient 513 | * 514 | * Since: 0.1.0 515 | **/ 516 | PassimClient * 517 | passim_client_new(void) 518 | { 519 | PassimClient *self; 520 | self = g_object_new(PASSIM_TYPE_CLIENT, NULL); 521 | return PASSIM_CLIENT(self); 522 | } 523 | -------------------------------------------------------------------------------- /libpassim/passim-client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "passim-item.h" 10 | 11 | G_BEGIN_DECLS 12 | 13 | #define PASSIM_TYPE_CLIENT (passim_client_get_type()) 14 | G_DECLARE_DERIVABLE_TYPE(PassimClient, passim_client, PASSIM, CLIENT, GObject) 15 | 16 | struct _PassimClientClass { 17 | GObjectClass parent_class; 18 | /*< private >*/ 19 | void (*_passim_reserved1)(void); 20 | void (*_passim_reserved2)(void); 21 | void (*_passim_reserved3)(void); 22 | void (*_passim_reserved4)(void); 23 | void (*_passim_reserved5)(void); 24 | void (*_passim_reserved6)(void); 25 | void (*_passim_reserved7)(void); 26 | }; 27 | 28 | #define PASSIM_DBUS_SERVICE "org.freedesktop.Passim" 29 | #define PASSIM_DBUS_INTERFACE "org.freedesktop.Passim" 30 | #define PASSIM_DBUS_PATH "/" 31 | 32 | typedef enum { 33 | PASSIM_STATUS_UNKNOWN, 34 | PASSIM_STATUS_STARTING, 35 | PASSIM_STATUS_LOADING, 36 | PASSIM_STATUS_RUNNING, 37 | PASSIM_STATUS_DISABLED_METERED, 38 | } PassimStatus; 39 | 40 | PassimClient * 41 | passim_client_new(void); 42 | const gchar * 43 | passim_client_get_version(PassimClient *self); 44 | const gchar * 45 | passim_client_get_name(PassimClient *self); 46 | const gchar * 47 | passim_client_get_uri(PassimClient *self); 48 | PassimStatus 49 | passim_client_get_status(PassimClient *self); 50 | guint64 51 | passim_client_get_download_saving(PassimClient *self); 52 | gdouble 53 | passim_client_get_carbon_saving(PassimClient *self); 54 | gboolean 55 | passim_client_load(PassimClient *self, GError **error); 56 | GPtrArray * 57 | passim_client_get_items(PassimClient *self, GError **error); 58 | gboolean 59 | passim_client_publish(PassimClient *self, PassimItem *item, GError **error); 60 | gboolean 61 | passim_client_unpublish(PassimClient *self, const gchar *hash, GError **error); 62 | 63 | G_END_DECLS 64 | -------------------------------------------------------------------------------- /libpassim/passim-item.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | G_BEGIN_DECLS 12 | 13 | #define PASSIM_TYPE_ITEM (passim_item_get_type()) 14 | G_DECLARE_DERIVABLE_TYPE(PassimItem, passim_item, PASSIM, ITEM, GObject) 15 | 16 | struct _PassimItemClass { 17 | GObjectClass parent_class; 18 | /*< private >*/ 19 | void (*_passim_reserved1)(void); 20 | void (*_passim_reserved2)(void); 21 | void (*_passim_reserved3)(void); 22 | void (*_passim_reserved4)(void); 23 | void (*_passim_reserved5)(void); 24 | void (*_passim_reserved6)(void); 25 | void (*_passim_reserved7)(void); 26 | }; 27 | 28 | /** 29 | * PASSIM_ITEM_FLAG_NONE: 30 | * 31 | * No item flags are set. 32 | * 33 | * Since: 0.1.0 34 | */ 35 | #define PASSIM_ITEM_FLAG_NONE 0u 36 | 37 | /** 38 | * PASSIM_ITEM_FLAG_DISABLED: 39 | * 40 | * The item is not active for some reason. 41 | * 42 | * Since: 0.1.0 43 | */ 44 | #define PASSIM_ITEM_FLAG_DISABLED (1llu << 0) 45 | 46 | /** 47 | * PASSIM_ITEM_FLAG_NEXT_REBOOT: 48 | * 49 | * Only register the item when the machine has been rebooted. 50 | * 51 | * Since: 0.1.0 52 | */ 53 | #define PASSIM_ITEM_FLAG_NEXT_REBOOT (1llu << 1) 54 | 55 | /** 56 | * PASSIM_ITEM_FLAG_UNKNOWN: 57 | * 58 | * The item flag is unknown. 59 | * 60 | * This is usually caused by a mismatched libpassimplugin and daemon. 61 | * 62 | * Since: 0.1.0 63 | */ 64 | #define PASSIM_ITEM_FLAG_UNKNOWN G_MAXUINT64 65 | 66 | /** 67 | * PassimItemFlags: 68 | * 69 | * Flags used to represent item attributes 70 | */ 71 | typedef guint64 PassimItemFlags; 72 | 73 | PassimItem * 74 | passim_item_new(void); 75 | gchar * 76 | passim_item_to_string(PassimItem *self); 77 | 78 | const gchar * 79 | passim_item_get_hash(PassimItem *self); 80 | void 81 | passim_item_set_hash(PassimItem *self, const gchar *hash); 82 | const gchar * 83 | passim_item_get_basename(PassimItem *self); 84 | void 85 | passim_item_set_basename(PassimItem *self, const gchar *basename); 86 | const gchar * 87 | passim_item_get_cmdline(PassimItem *self); 88 | void 89 | passim_item_set_cmdline(PassimItem *self, const gchar *cmdline); 90 | guint32 91 | passim_item_get_age(PassimItem *self); 92 | guint32 93 | passim_item_get_max_age(PassimItem *self); 94 | void 95 | passim_item_set_max_age(PassimItem *self, guint32 max_age); 96 | guint32 97 | passim_item_get_share_limit(PassimItem *self); 98 | void 99 | passim_item_set_share_limit(PassimItem *self, guint32 share_limit); 100 | guint64 101 | passim_item_get_size(PassimItem *self); 102 | void 103 | passim_item_set_size(PassimItem *self, guint64 size); 104 | guint32 105 | passim_item_get_share_count(PassimItem *self); 106 | void 107 | passim_item_set_share_count(PassimItem *self, guint32 share_count); 108 | GFile * 109 | passim_item_get_file(PassimItem *self); 110 | void 111 | passim_item_set_file(PassimItem *self, GFile *file); 112 | GBytes * 113 | passim_item_get_bytes(PassimItem *self); 114 | void 115 | passim_item_set_bytes(PassimItem *self, GBytes *bytes); 116 | GInputStream * 117 | passim_item_get_stream(PassimItem *self); 118 | void 119 | passim_item_set_stream(PassimItem *self, GInputStream *stream); 120 | GDateTime * 121 | passim_item_get_ctime(PassimItem *self); 122 | void 123 | passim_item_set_ctime(PassimItem *self, GDateTime *ctime); 124 | 125 | guint64 126 | passim_item_get_flags(PassimItem *self); 127 | gchar * 128 | passim_item_get_flags_as_string(PassimItem *self); 129 | void 130 | passim_item_set_flags(PassimItem *self, guint64 flags); 131 | void 132 | passim_item_add_flag(PassimItem *self, PassimItemFlags flag); 133 | void 134 | passim_item_remove_flag(PassimItem *self, PassimItemFlags flag); 135 | gboolean 136 | passim_item_has_flag(PassimItem *self, PassimItemFlags flag); 137 | 138 | const gchar * 139 | passim_item_flag_to_string(PassimItemFlags item_flag); 140 | PassimItemFlags 141 | passim_item_flag_from_string(const gchar *item_flag); 142 | 143 | gboolean 144 | passim_item_load_filename(PassimItem *self, const gchar *filename, GError **error); 145 | 146 | PassimItem * 147 | passim_item_from_variant(GVariant *value); 148 | GVariant * 149 | passim_item_to_variant(PassimItem *self); 150 | 151 | G_END_DECLS 152 | -------------------------------------------------------------------------------- /libpassim/passim-version.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-version.h" 10 | 11 | /** 12 | * passim_version_string: 13 | * 14 | * Gets the libpassim installed runtime version. 15 | * 16 | * This may be different to the *build-time* version if the daemon and library 17 | * objects somehow get out of sync. 18 | * 19 | * Returns: version string 20 | * 21 | * Since: 0.1.0 22 | **/ 23 | const gchar * 24 | passim_version_string(void) 25 | { 26 | return G_STRINGIFY(PASSIM_MAJOR_VERSION) "." G_STRINGIFY( 27 | PASSIM_MINOR_VERSION) "." G_STRINGIFY(PASSIM_MICRO_VERSION); 28 | } 29 | -------------------------------------------------------------------------------- /libpassim/passim-version.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #if !defined(__PASSIM_H_INSIDE__) && !defined(PASSIM_COMPILATION) 12 | #error "Only can be included directly." 13 | #endif 14 | 15 | /* clang-format off */ 16 | /** 17 | * PASSIM_MAJOR_VERSION: 18 | * 19 | * The compile-time major version 20 | */ 21 | #define PASSIM_MAJOR_VERSION @MAJOR_VERSION@ 22 | 23 | /** 24 | * PASSIM_MINOR_VERSION: 25 | * 26 | * The compile-time minor version 27 | */ 28 | #define PASSIM_MINOR_VERSION @MINOR_VERSION@ 29 | 30 | /** 31 | * PASSIM_MICRO_VERSION: 32 | * 33 | * The compile-time micro version 34 | */ 35 | #define PASSIM_MICRO_VERSION @MICRO_VERSION@ 36 | /* clang-format on */ 37 | 38 | /** 39 | * PASSIM_CHECK_VERSION: 40 | * @major: Major version number 41 | * @minor: Minor version number 42 | * @micro: Micro version number 43 | * 44 | * Check whether a passim version equal to or greater than 45 | * major.minor.micro. 46 | * 47 | * These compile time macros allow the user to enable parts of client code 48 | * depending on the version of libpassim installed. 49 | */ 50 | #define PASSIM_CHECK_VERSION(major, minor, micro) \ 51 | (PASSIM_MAJOR_VERSION > major || \ 52 | (PASSIM_MAJOR_VERSION == major && PASSIM_MINOR_VERSION > minor) || \ 53 | (PASSIM_MAJOR_VERSION == major && PASSIM_MINOR_VERSION == minor && \ 54 | PASSIM_MICRO_VERSION >= micro)) 55 | 56 | const gchar * 57 | passim_version_string(void); 58 | -------------------------------------------------------------------------------- /libpassim/passim.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #define __PASSIM_H_INSIDE__ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #undef __PASSIM_H_INSIDE__ 16 | -------------------------------------------------------------------------------- /libpassim/passim.map: -------------------------------------------------------------------------------- 1 | # generated automatically, do not edit! 2 | 3 | LIBPASSIM_0.1.0 { 4 | global: 5 | passim_client_get_items; 6 | passim_client_get_type; 7 | passim_client_get_version; 8 | passim_client_load; 9 | passim_client_new; 10 | passim_client_publish; 11 | passim_client_unpublish; 12 | passim_item_add_flag; 13 | passim_item_flag_from_string; 14 | passim_item_flag_to_string; 15 | passim_item_from_variant; 16 | passim_item_get_age; 17 | passim_item_get_basename; 18 | passim_item_get_bytes; 19 | passim_item_get_cmdline; 20 | passim_item_get_ctime; 21 | passim_item_get_file; 22 | passim_item_get_flags; 23 | passim_item_get_flags_as_string; 24 | passim_item_get_hash; 25 | passim_item_get_max_age; 26 | passim_item_get_share_count; 27 | passim_item_get_share_limit; 28 | passim_item_get_type; 29 | passim_item_has_flag; 30 | passim_item_load_filename; 31 | passim_item_new; 32 | passim_item_remove_flag; 33 | passim_item_set_basename; 34 | passim_item_set_bytes; 35 | passim_item_set_cmdline; 36 | passim_item_set_ctime; 37 | passim_item_set_file; 38 | passim_item_set_flags; 39 | passim_item_set_hash; 40 | passim_item_set_max_age; 41 | passim_item_set_share_count; 42 | passim_item_set_share_limit; 43 | passim_item_to_string; 44 | passim_item_to_variant; 45 | passim_version_string; 46 | local: *; 47 | }; 48 | 49 | LIBPASSIM_0.1.2 { 50 | global: 51 | passim_client_get_status; 52 | passim_item_get_size; 53 | passim_item_set_size; 54 | local: *; 55 | } LIBPASSIM_0.1.0; 56 | 57 | LIBPASSIM_0.1.5 { 58 | global: 59 | passim_item_get_stream; 60 | passim_item_set_stream; 61 | local: *; 62 | } LIBPASSIM_0.1.2; 63 | 64 | LIBPASSIM_0.1.6 { 65 | global: 66 | passim_client_get_carbon_saving; 67 | passim_client_get_download_saving; 68 | passim_client_get_name; 69 | passim_client_get_uri; 70 | local: *; 71 | } LIBPASSIM_0.1.5; 72 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('passim', 'c', 2 | version: '0.1.11', 3 | license: 'LGPL-2.1-or-later', 4 | meson_version: '>=0.61.0', 5 | default_options: ['warning_level=2', 'c_std=c11'], 6 | ) 7 | 8 | # libtool versioning - this applies to libpassim 9 | libpassim_lt_current = '1' 10 | libpassim_lt_revision = '0' 11 | libpassim_lt_age = '0' 12 | libpassim_lt_version = '@0@.@1@.@2@'.format(libpassim_lt_current, libpassim_lt_age, libpassim_lt_revision) 13 | 14 | warning_flags = [ 15 | '-Waggregate-return', 16 | '-Wunused', 17 | '-Warray-bounds', 18 | '-Wcast-align', 19 | '-Wclobbered', 20 | '-Wdeclaration-after-statement', 21 | '-Wdiscarded-qualifiers', 22 | '-Wduplicated-branches', 23 | '-Wduplicated-cond', 24 | '-Wempty-body', 25 | '-Wformat=2', 26 | '-Wformat-nonliteral', 27 | '-Wformat-security', 28 | '-Wformat-signedness', 29 | '-Wignored-qualifiers', 30 | '-Wimplicit-function-declaration', 31 | '-Wimplicit-int', 32 | '-Winit-self', 33 | '-Wint-conversion', 34 | '-Wlogical-op', 35 | '-Wmaybe-uninitialized', 36 | '-Wmissing-declarations', 37 | '-Wmissing-format-attribute', 38 | '-Wmissing-include-dirs', 39 | '-Wmissing-noreturn', 40 | '-Wmissing-parameter-type', 41 | '-Wmissing-prototypes', 42 | '-Wnested-externs', 43 | '-Wno-cast-function-type', 44 | '-Wno-address-of-packed-member', # incompatible with g_autoptr() 45 | '-Wno-unknown-pragmas', 46 | '-Wno-missing-field-initializers', 47 | '-Wno-strict-aliasing', 48 | '-Wno-suggest-attribute=format', 49 | '-Wno-typedef-redefinition', 50 | '-Wno-unknown-warning-option', 51 | '-Wno-unused-parameter', 52 | '-Wold-style-definition', 53 | '-Woverride-init', 54 | '-Wpointer-arith', 55 | '-Wredundant-decls', 56 | '-Wreturn-type', 57 | '-Wshadow', 58 | '-Wsign-compare', 59 | '-Wstrict-aliasing', 60 | '-Wstrict-prototypes', 61 | '-Wswitch-default', 62 | '-Wtype-limits', 63 | '-Wundef', 64 | '-Wuninitialized', 65 | '-Wunused-but-set-variable', 66 | '-Wunused-variable', 67 | '-Wvla', 68 | '-Wwrite-strings' 69 | ] 70 | cc = meson.get_compiler('c') 71 | add_project_arguments(cc.get_supported_arguments(warning_flags), language: 'c') 72 | add_project_arguments('-DPASSIM_COMPILATION', language: 'c') 73 | 74 | # needed for memfd_create() 75 | add_project_arguments('-D_GNU_SOURCE', language: 'c') 76 | 77 | prefix = get_option('prefix') 78 | sysconfdir = prefix / get_option('sysconfdir') 79 | bindir = prefix / get_option('bindir') 80 | localstatedir = prefix / get_option('localstatedir') 81 | libexecdir = prefix / get_option('libexecdir') 82 | datadir = prefix / get_option('datadir') 83 | localedir = prefix / get_option('localedir') 84 | 85 | conf = configuration_data() 86 | conf.set_quoted('PACKAGE_NAME', meson.project_name()) 87 | conf.set_quoted('PACKAGE_DATADIR', datadir) 88 | conf.set_quoted('PACKAGE_SYSCONFDIR', sysconfdir) 89 | conf.set_quoted('PACKAGE_LOCALEDIR', localedir) 90 | conf.set_quoted('PACKAGE_LOCALSTATEDIR', localstatedir) 91 | conf.set_quoted('VERSION', meson.project_version()) 92 | conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) 93 | 94 | varr = meson.project_version().split('.') 95 | conf.set('MAJOR_VERSION', varr[0]) 96 | conf.set('MINOR_VERSION', varr[1]) 97 | conf.set('MICRO_VERSION', varr[2]) 98 | 99 | systemd = dependency('systemd', version: '>= 211') 100 | systemd_root_prefix = get_option('systemd_root_prefix') 101 | if systemd_root_prefix == '' 102 | pkgconfig_kwargs = {} 103 | else 104 | pkgconfig_kwargs = { 105 | 'pkgconfig_define': ['rootprefix', systemd_root_prefix], 106 | } 107 | endif 108 | 109 | systemdunitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir', kwargs: pkgconfig_kwargs) 110 | sysusersdir = systemd.get_variable(pkgconfig: 'sysusersdir', kwargs: pkgconfig_kwargs) 111 | 112 | # get source version, falling back to package version 113 | git = find_program('git', required: false) 114 | tag = false 115 | if git.found() 116 | source_version = run_command([git, 'describe'], check: false).stdout().strip() 117 | if source_version == '' 118 | source_version = meson.project_version() 119 | endif 120 | tag = run_command([git, 'describe', '--exact-match'], check: false).returncode() == 0 121 | else 122 | source_version = meson.project_version() 123 | endif 124 | conf.set_quoted('SOURCE_VERSION', source_version) 125 | 126 | root_incdir = include_directories('.') 127 | 128 | libgio = dependency('gio-unix-2.0', version: '>= 2.68.0') 129 | libsoup = dependency('libsoup-3.0', version: '>= 3.4.0') 130 | libgnutls = dependency('gnutls', version: '>= 3.6.0') 131 | 132 | if cc.has_function('memfd_create') 133 | conf.set('HAVE_MEMFD_CREATE', '1') 134 | endif 135 | 136 | configure_file( 137 | output: 'config.h', 138 | configuration: conf 139 | ) 140 | 141 | python3 = import('python').find_installation('python3') 142 | 143 | subdir('libpassim') 144 | subdir('src') 145 | subdir('data') 146 | subdir('po') 147 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('systemd_root_prefix', type: 'string', value: '', description: 'Directory to base systemd’s installation directories on') 2 | option('introspection', type : 'feature', description : 'generate GObject Introspection data') 3 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | en_GB 2 | nb_NO 3 | cs 4 | de 5 | fr 6 | tr 7 | ka 8 | ru 9 | ta 10 | fi 11 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | src/passim-cli.c 2 | -------------------------------------------------------------------------------- /po/cs.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-03-19 15:36+0000\n" 11 | "PO-Revision-Date: 2024-03-22 19:02+0000\n" 12 | "Last-Translator: Matej Cepl \n" 13 | "Language-Team: Czech " 14 | "\n" 15 | "Language: cs\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:179 24 | #, fuzzy 25 | msgid "Disabled" 26 | msgstr "Zakázané" 27 | 28 | #. TRANSLATORS: only begin sharing the item after the next restart 29 | #: src/passim-cli.c:183 30 | msgid "Next Reboot" 31 | msgstr "Příští restart" 32 | 33 | #. TRANSLATORS: item file basename 34 | #: src/passim-cli.c:196 35 | msgid "Filename" 36 | msgstr "Jméno souboru" 37 | 38 | #. TRANSLATORS: item flags 39 | #: src/passim-cli.c:203 40 | msgid "Flags" 41 | msgstr "Značky" 42 | 43 | #. TRANSLATORS: basename of the thing that published the item 44 | #: src/passim-cli.c:210 45 | msgid "Command Line" 46 | msgstr "Příkazová řádka" 47 | 48 | #. TRANSLATORS: age of the published item 49 | #: src/passim-cli.c:217 50 | #, fuzzy 51 | msgid "Age" 52 | msgstr "Věk" 53 | 54 | #. TRANSLATORS: number of times we can share the item 55 | #: src/passim-cli.c:226 56 | #, fuzzy 57 | msgid "Share Limit" 58 | msgstr "Limit sdílení" 59 | 60 | #. TRANSLATORS: size of the published item 61 | #: src/passim-cli.c:235 62 | #, fuzzy 63 | msgid "Size" 64 | msgstr "Velikost" 65 | 66 | #. TRANSLATORS: daemon is starting up 67 | #: src/passim-cli.c:267 68 | #, fuzzy 69 | msgid "Loading…" 70 | msgstr "Nahrávání .." 71 | 72 | #. TRANSLATORS: daemon is scared to publish files 73 | #: src/passim-cli.c:270 74 | #, fuzzy 75 | msgid "Disabled (metered network)" 76 | msgstr "Zakázané (měřená síť)" 77 | 78 | #. TRANSLATORS: daemon is offering files like normal 79 | #: src/passim-cli.c:273 80 | msgid "Running" 81 | msgstr "Běží" 82 | 83 | #: src/passim-cli.c:277 84 | #, fuzzy 85 | msgid "Status" 86 | msgstr "Stav" 87 | 88 | #. TRANSLATORS: full https://whatever of the daemon 89 | #: src/passim-cli.c:284 90 | msgid "URI" 91 | msgstr "URI" 92 | 93 | #. TRANSLATORS: user mistyped the command 94 | #: src/passim-cli.c:323 src/passim-cli.c:353 95 | #, fuzzy 96 | msgid "Invalid arguments" 97 | msgstr "Neplatný argument" 98 | 99 | #. TRANSLATORS: now sharing to the world 100 | #: src/passim-cli.c:340 101 | msgid "Published" 102 | msgstr "Zveřejňuje" 103 | 104 | #. TRANSLATORS: no longer sharing with the world 105 | #: src/passim-cli.c:360 106 | msgid "Unpublished" 107 | msgstr "Nezveřejňuje" 108 | 109 | #. TRANSLATORS: --version 110 | #: src/passim-cli.c:375 111 | #, fuzzy 112 | msgid "Show project version" 113 | msgstr "Zobrazit verzi projektu" 114 | 115 | #: src/passim-cli.c:382 116 | #, fuzzy 117 | msgid "Next reboot" 118 | msgstr "Příští restart" 119 | 120 | #. TRANSLATORS: CLI action description 121 | #: src/passim-cli.c:397 122 | #, fuzzy 123 | msgid "Show daemon status" 124 | msgstr "Zobrazit stav daemonu" 125 | 126 | #. TRANSLATORS: CLI option example 127 | #: src/passim-cli.c:402 128 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 129 | msgstr "JMÉNO-SOUBORU [NEJSTARŠÍ] [NEJDÉLE VEŘEJNÝ]" 130 | 131 | #. TRANSLATORS: CLI action description 132 | #: src/passim-cli.c:404 133 | #, fuzzy 134 | msgid "Publish an additional file" 135 | msgstr "Publikovat další soubor" 136 | 137 | #. TRANSLATORS: CLI option example 138 | #: src/passim-cli.c:409 139 | msgid "HASH" 140 | msgstr "HASH" 141 | 142 | #. TRANSLATORS: CLI action description 143 | #: src/passim-cli.c:411 144 | msgid "Unpublish an existing file" 145 | msgstr "Nezveřejnit existující soubor" 146 | 147 | #. TRANSLATORS: CLI tool description 148 | #: src/passim-cli.c:418 149 | #, fuzzy 150 | msgid "Interact with the local passimd process." 151 | msgstr "Interakce s lokálním procesem." 152 | 153 | #. TRANSLATORS: CLI tool name 154 | #: src/passim-cli.c:420 155 | #, fuzzy 156 | msgid "Passim CLI" 157 | msgstr "Passim CLI" 158 | 159 | #. TRANSLATORS: we don't know what to do 160 | #: src/passim-cli.c:424 161 | msgid "Failed to parse arguments" 162 | msgstr "Analýza argumentů selhala" 163 | 164 | #. TRANSLATORS: daemon failed to start 165 | #: src/passim-cli.c:432 166 | msgid "Failed to connect to daemon" 167 | msgstr "Nepodařilo se připojit k daemon" 168 | 169 | #. TRANSLATORS: CLI tool 170 | #: src/passim-cli.c:439 171 | msgid "client version" 172 | msgstr "verze klientu" 173 | 174 | #. TRANSLATORS: server 175 | #: src/passim-cli.c:441 176 | msgid "daemon version" 177 | msgstr "verze démonu" 178 | -------------------------------------------------------------------------------- /po/de.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-03-19 15:36+0000\n" 11 | "PO-Revision-Date: 2024-03-22 19:02+0000\n" 12 | "Last-Translator: Paul Lettich \n" 13 | "Language-Team: German " 14 | "\n" 15 | "Language: de\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:179 24 | msgid "Disabled" 25 | msgstr "Deaktiviert" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:183 29 | msgid "Next Reboot" 30 | msgstr "Nächster Neustart" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:196 34 | msgid "Filename" 35 | msgstr "Dateiname" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:203 39 | msgid "Flags" 40 | msgstr "Optionen" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:210 44 | msgid "Command Line" 45 | msgstr "Kommandozeile" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:217 49 | msgid "Age" 50 | msgstr "Alter" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:226 54 | msgid "Share Limit" 55 | msgstr "" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:235 59 | msgid "Size" 60 | msgstr "Größe" 61 | 62 | #. TRANSLATORS: daemon is starting up 63 | #: src/passim-cli.c:267 64 | msgid "Loading…" 65 | msgstr "Laden…" 66 | 67 | #. TRANSLATORS: daemon is scared to publish files 68 | #: src/passim-cli.c:270 69 | #, fuzzy 70 | msgid "Disabled (metered network)" 71 | msgstr "Deaktiviert (getaktetes Netzwerk)" 72 | 73 | #. TRANSLATORS: daemon is offering files like normal 74 | #: src/passim-cli.c:273 75 | msgid "Running" 76 | msgstr "" 77 | 78 | #: src/passim-cli.c:277 79 | msgid "Status" 80 | msgstr "Status" 81 | 82 | #. TRANSLATORS: full https://whatever of the daemon 83 | #: src/passim-cli.c:284 84 | msgid "URI" 85 | msgstr "URI" 86 | 87 | #. TRANSLATORS: user mistyped the command 88 | #: src/passim-cli.c:323 src/passim-cli.c:353 89 | msgid "Invalid arguments" 90 | msgstr "Ungültige Parameter" 91 | 92 | #. TRANSLATORS: now sharing to the world 93 | #: src/passim-cli.c:340 94 | msgid "Published" 95 | msgstr "Veröffentlicht" 96 | 97 | #. TRANSLATORS: no longer sharing with the world 98 | #: src/passim-cli.c:360 99 | msgid "Unpublished" 100 | msgstr "Veröffentlichung gestoppt" 101 | 102 | #. TRANSLATORS: --version 103 | #: src/passim-cli.c:375 104 | msgid "Show project version" 105 | msgstr "Projektversion anzeigen" 106 | 107 | #: src/passim-cli.c:382 108 | msgid "Next reboot" 109 | msgstr "Nächster Neustart" 110 | 111 | #. TRANSLATORS: CLI action description 112 | #: src/passim-cli.c:397 113 | msgid "Show daemon status" 114 | msgstr "Status des Daemons anzeigen" 115 | 116 | #. TRANSLATORS: CLI option example 117 | #: src/passim-cli.c:402 118 | #, fuzzy 119 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 120 | msgstr "DATEINAME [MAX-ALTER] [MAX-VERTEILUNG]" 121 | 122 | #. TRANSLATORS: CLI action description 123 | #: src/passim-cli.c:404 124 | msgid "Publish an additional file" 125 | msgstr "Eine weitere Datei veröffentlichen" 126 | 127 | #. TRANSLATORS: CLI option example 128 | #: src/passim-cli.c:409 129 | msgid "HASH" 130 | msgstr "HASH" 131 | 132 | #. TRANSLATORS: CLI action description 133 | #: src/passim-cli.c:411 134 | msgid "Unpublish an existing file" 135 | msgstr "Veröffentlichung einer existierenden Datei stoppen" 136 | 137 | #. TRANSLATORS: CLI tool description 138 | #: src/passim-cli.c:418 139 | msgid "Interact with the local passimd process." 140 | msgstr "Mit dem lokalen passimd-Prozess interagieren." 141 | 142 | #. TRANSLATORS: CLI tool name 143 | #: src/passim-cli.c:420 144 | msgid "Passim CLI" 145 | msgstr "Passim CLI" 146 | 147 | #. TRANSLATORS: we don't know what to do 148 | #: src/passim-cli.c:424 149 | msgid "Failed to parse arguments" 150 | msgstr "Verarbeiten der Parameter fehlgeschlagen" 151 | 152 | #. TRANSLATORS: daemon failed to start 153 | #: src/passim-cli.c:432 154 | msgid "Failed to connect to daemon" 155 | msgstr "Verbindung zum Daemon fehlgeschlagen" 156 | 157 | #. TRANSLATORS: CLI tool 158 | #: src/passim-cli.c:439 159 | msgid "client version" 160 | msgstr "Version des Clients" 161 | 162 | #. TRANSLATORS: server 163 | #: src/passim-cli.c:441 164 | msgid "daemon version" 165 | msgstr "Version des Daemons" 166 | -------------------------------------------------------------------------------- /po/en_GB.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-03-19 15:36+0000\n" 11 | "PO-Revision-Date: 2024-03-19 16:51+0000\n" 12 | "Last-Translator: Richard Hughes \n" 13 | "Language-Team: English (United Kingdom) \n" 15 | "Language: en_GB\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:179 24 | msgid "Disabled" 25 | msgstr "Disabled" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:183 29 | msgid "Next Reboot" 30 | msgstr "Next Reboot" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:196 34 | msgid "Filename" 35 | msgstr "Filename" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:203 39 | msgid "Flags" 40 | msgstr "Flags" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:210 44 | msgid "Command Line" 45 | msgstr "Command Line" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:217 49 | msgid "Age" 50 | msgstr "Age" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:226 54 | msgid "Share Limit" 55 | msgstr "Share Limit" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:235 59 | msgid "Size" 60 | msgstr "Size" 61 | 62 | #. TRANSLATORS: daemon is starting up 63 | #: src/passim-cli.c:267 64 | msgid "Loading…" 65 | msgstr "Loading…" 66 | 67 | #. TRANSLATORS: daemon is scared to publish files 68 | #: src/passim-cli.c:270 69 | msgid "Disabled (metered network)" 70 | msgstr "Disabled (metered network)" 71 | 72 | #. TRANSLATORS: daemon is offering files like normal 73 | #: src/passim-cli.c:273 74 | msgid "Running" 75 | msgstr "Running" 76 | 77 | #: src/passim-cli.c:277 78 | msgid "Status" 79 | msgstr "Status" 80 | 81 | #. TRANSLATORS: full https://whatever of the daemon 82 | #: src/passim-cli.c:284 83 | msgid "URI" 84 | msgstr "URI" 85 | 86 | #. TRANSLATORS: user mistyped the command 87 | #: src/passim-cli.c:323 src/passim-cli.c:353 88 | msgid "Invalid arguments" 89 | msgstr "Invalid arguments" 90 | 91 | #. TRANSLATORS: now sharing to the world 92 | #: src/passim-cli.c:340 93 | msgid "Published" 94 | msgstr "Published" 95 | 96 | #. TRANSLATORS: no longer sharing with the world 97 | #: src/passim-cli.c:360 98 | msgid "Unpublished" 99 | msgstr "Unpublished" 100 | 101 | #. TRANSLATORS: --version 102 | #: src/passim-cli.c:375 103 | msgid "Show project version" 104 | msgstr "Show project version" 105 | 106 | #: src/passim-cli.c:382 107 | msgid "Next reboot" 108 | msgstr "Next reboot" 109 | 110 | #. TRANSLATORS: CLI action description 111 | #: src/passim-cli.c:397 112 | msgid "Show daemon status" 113 | msgstr "Show daemon status" 114 | 115 | #. TRANSLATORS: CLI option example 116 | #: src/passim-cli.c:402 117 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 118 | msgstr "FILENAME [MAX-AGE] [MAX-SHARE]" 119 | 120 | #. TRANSLATORS: CLI action description 121 | #: src/passim-cli.c:404 122 | msgid "Publish an additional file" 123 | msgstr "Publish an additional file" 124 | 125 | #. TRANSLATORS: CLI option example 126 | #: src/passim-cli.c:409 127 | msgid "HASH" 128 | msgstr "HASH" 129 | 130 | #. TRANSLATORS: CLI action description 131 | #: src/passim-cli.c:411 132 | msgid "Unpublish an existing file" 133 | msgstr "Unpublish an existing file" 134 | 135 | #. TRANSLATORS: CLI tool description 136 | #: src/passim-cli.c:418 137 | msgid "Interact with the local passimd process." 138 | msgstr "Interact with the local passimd process." 139 | 140 | #. TRANSLATORS: CLI tool name 141 | #: src/passim-cli.c:420 142 | msgid "Passim CLI" 143 | msgstr "Passim CLI" 144 | 145 | #. TRANSLATORS: we don't know what to do 146 | #: src/passim-cli.c:424 147 | msgid "Failed to parse arguments" 148 | msgstr "Failed to parse arguments" 149 | 150 | #. TRANSLATORS: daemon failed to start 151 | #: src/passim-cli.c:432 152 | msgid "Failed to connect to daemon" 153 | msgstr "Failed to connect to daemon" 154 | 155 | #. TRANSLATORS: CLI tool 156 | #: src/passim-cli.c:439 157 | msgid "client version" 158 | msgstr "client version" 159 | 160 | #. TRANSLATORS: server 161 | #: src/passim-cli.c:441 162 | msgid "daemon version" 163 | msgstr "daemon version" 164 | -------------------------------------------------------------------------------- /po/fi.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 11 | "PO-Revision-Date: 2025-03-02 11:02+0000\n" 12 | "Last-Translator: Ricky Tigg \n" 13 | "Language-Team: Finnish \n" 15 | "Language: fi\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.10.3-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:180 24 | msgid "Disabled" 25 | msgstr "Poistettu käytöstä" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:184 29 | msgid "Next Reboot" 30 | msgstr "Seuraava uudelleenkäynnistys" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:197 34 | msgid "Filename" 35 | msgstr "Tiedoston nimi" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:204 39 | msgid "Flags" 40 | msgstr "Liput" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:211 44 | msgid "Command Line" 45 | msgstr "Komentorivi" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:218 49 | msgid "Age" 50 | msgstr "Ikä" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:227 54 | msgid "Share Limit" 55 | msgstr "Jaa raja" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:236 59 | msgid "Size" 60 | msgstr "Koko" 61 | 62 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 63 | #: src/passim-cli.c:271 64 | msgid "Name" 65 | msgstr "Nimi" 66 | 67 | #. TRANSLATORS: daemon is starting up 68 | #: src/passim-cli.c:278 69 | msgid "Loading…" 70 | msgstr "Ladataan…" 71 | 72 | #. TRANSLATORS: daemon is scared to publish files 73 | #: src/passim-cli.c:281 74 | msgid "Disabled (metered network)" 75 | msgstr "Poissa käytöstä (mitattu verkko)" 76 | 77 | #. TRANSLATORS: daemon is offering files like normal 78 | #: src/passim-cli.c:284 79 | msgid "Running" 80 | msgstr "Käynnissä" 81 | 82 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 83 | #: src/passim-cli.c:289 84 | msgid "Status" 85 | msgstr "Tila" 86 | 87 | #. TRANSLATORS: how many bytes we did not download from the internet 88 | #: src/passim-cli.c:297 89 | msgid "Network Saving" 90 | msgstr "Verkkosäästö" 91 | 92 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 93 | #: src/passim-cli.c:304 94 | msgid "Carbon Saving" 95 | msgstr "Hiilisäästö" 96 | 97 | #. TRANSLATORS: full https://whatever of the daemon 98 | #: src/passim-cli.c:312 99 | msgid "URI" 100 | msgstr "URI" 101 | 102 | #. TRANSLATORS: user mistyped the command 103 | #: src/passim-cli.c:351 src/passim-cli.c:381 104 | msgid "Invalid arguments" 105 | msgstr "Epäkelvolliset argumentit" 106 | 107 | #. TRANSLATORS: now sharing to the world 108 | #: src/passim-cli.c:368 109 | msgid "Published" 110 | msgstr "Julkaistu" 111 | 112 | #. TRANSLATORS: no longer sharing with the world 113 | #: src/passim-cli.c:388 114 | msgid "Unpublished" 115 | msgstr "Julkaisematon" 116 | 117 | #. TRANSLATORS: user mistyped the command 118 | #: src/passim-cli.c:426 119 | msgid "Invalid arguments, expected BASENAME HASH" 120 | msgstr "Epäkelvolliset argumentit; odotettu ALUSTANIMI HAJAUTUS" 121 | 122 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 123 | #. the untranslated error phrase 124 | #: src/passim-cli.c:469 125 | #, c-format 126 | msgid "Failed to download %s from %s: %s" 127 | msgstr "%s:n lataaminen %s:sta epäonnistui: %s" 128 | 129 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 130 | #: src/passim-cli.c:483 131 | #, c-format 132 | msgid "Failed to download %s from %s as file checksum does not match" 133 | msgstr "" 134 | "%s:n lataaminen %s:sta epäonnistui, koska tiedoston tarkistussumma ei täsmää" 135 | 136 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 137 | #. * 'application/zstd') 138 | #: src/passim-cli.c:502 139 | #, c-format 140 | msgid "Saved %s (%s of %s)" 141 | msgstr "Säästetty %s (%s/%s)" 142 | 143 | #. TRANSLATORS: --version 144 | #: src/passim-cli.c:518 145 | msgid "Show project version" 146 | msgstr "Näytä projektin versio" 147 | 148 | #: src/passim-cli.c:525 149 | msgid "Next reboot" 150 | msgstr "Seuraava uudelleenkäynnistys" 151 | 152 | #. TRANSLATORS: CLI action description 153 | #: src/passim-cli.c:540 154 | msgid "Show daemon status" 155 | msgstr "Näytä demonin tila" 156 | 157 | #. TRANSLATORS: CLI option example 158 | #: src/passim-cli.c:545 159 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 160 | msgstr "TIEDOSTON_NIMI [MAKS-IKÄ] [MAKS-JAKO]" 161 | 162 | #. TRANSLATORS: CLI action description 163 | #: src/passim-cli.c:547 164 | msgid "Publish an additional file" 165 | msgstr "Julkaise lisätiedosto" 166 | 167 | #. TRANSLATORS: CLI option example 168 | #: src/passim-cli.c:552 169 | msgid "HASH" 170 | msgstr "HAJAUTUS" 171 | 172 | #. TRANSLATORS: CLI action description 173 | #: src/passim-cli.c:554 174 | msgid "Unpublish an existing file" 175 | msgstr "Peruuta olemassa olevan tiedoston julkaisu" 176 | 177 | #. TRANSLATORS: CLI option example 178 | #: src/passim-cli.c:559 179 | msgid "BASENAME HASH" 180 | msgstr "ALUSTANIMI HAJAUTUS" 181 | 182 | #. TRANSLATORS: CLI action description 183 | #: src/passim-cli.c:561 184 | msgid "Download a file from a remote machine" 185 | msgstr "Lataa tiedosto etäkoneelta" 186 | 187 | #. TRANSLATORS: CLI tool description 188 | #: src/passim-cli.c:568 189 | msgid "Interact with the local passimd process." 190 | msgstr "Ole vuorovaikutuksessa paikallisen passimd-prosessin kanssa." 191 | 192 | #. TRANSLATORS: CLI tool name 193 | #: src/passim-cli.c:570 194 | msgid "Passim CLI" 195 | msgstr "Passim-CLI" 196 | 197 | #. TRANSLATORS: we don't know what to do 198 | #: src/passim-cli.c:574 199 | msgid "Failed to parse arguments" 200 | msgstr "Argumenttien jäsentäminen epäonnistui" 201 | 202 | #. TRANSLATORS: daemon failed to start 203 | #: src/passim-cli.c:582 204 | msgid "Failed to connect to daemon" 205 | msgstr "Yhteyden muodostaminen demoniin epäonnistui" 206 | 207 | #. TRANSLATORS: CLI tool 208 | #: src/passim-cli.c:589 209 | msgid "client version" 210 | msgstr "asiakasversio" 211 | 212 | #. TRANSLATORS: server 213 | #: src/passim-cli.c:591 214 | msgid "daemon version" 215 | msgstr "demoniversio" 216 | -------------------------------------------------------------------------------- /po/fix_translations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | import sys 5 | import os 6 | import subprocess 7 | 8 | 9 | def _do_msgattrib(fn): 10 | argv = [ 11 | "msgattrib", 12 | "--no-location", 13 | "--translated", 14 | "--no-wrap", 15 | "--sort-output", 16 | fn, 17 | "--output-file=" + fn, 18 | ] 19 | ret = subprocess.run(argv) 20 | if ret.returncode != 0: 21 | return 22 | 23 | 24 | def _do_nukeheader(fn): 25 | clean_lines = [] 26 | with open(fn) as f: 27 | lines = f.readlines() 28 | for line in lines: 29 | if line.startswith('"POT-Creation-Date:'): 30 | continue 31 | if line.startswith('"PO-Revision-Date:'): 32 | continue 33 | if line.startswith('"Last-Translator:'): 34 | continue 35 | clean_lines.append(line) 36 | with open(fn, "w") as f: 37 | f.writelines(clean_lines) 38 | 39 | 40 | def _process_file(fn): 41 | _do_msgattrib(fn) 42 | _do_nukeheader(fn) 43 | 44 | 45 | if __name__ == "__main__": 46 | if len(sys.argv) == 1: 47 | print("path required") 48 | sys.exit(1) 49 | try: 50 | dirname = sys.argv[1] 51 | for fn in os.listdir(dirname): 52 | if fn.endswith(".po"): 53 | _process_file(os.path.join(dirname, fn)) 54 | except NotADirectoryError: 55 | print("path required") 56 | sys.exit(2) 57 | -------------------------------------------------------------------------------- /po/fr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-03-19 15:36+0000\n" 11 | "PO-Revision-Date: 2024-03-27 10:01+0000\n" 12 | "Last-Translator: Michael S \n" 13 | "Language-Team: French " 14 | "\n" 15 | "Language: fr\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n > 1;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:179 24 | msgid "Disabled" 25 | msgstr "Désactivé" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:183 29 | msgid "Next Reboot" 30 | msgstr "Lors du prochain redémarrage" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:196 34 | msgid "Filename" 35 | msgstr "Nom du fichier" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:203 39 | msgid "Flags" 40 | msgstr "Drapeaux" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:210 44 | msgid "Command Line" 45 | msgstr "Ligne de commande" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:217 49 | msgid "Age" 50 | msgstr "Âge" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:226 54 | msgid "Share Limit" 55 | msgstr "Limite de partage" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:235 59 | msgid "Size" 60 | msgstr "Taille" 61 | 62 | #. TRANSLATORS: daemon is starting up 63 | #: src/passim-cli.c:267 64 | msgid "Loading…" 65 | msgstr "Chargement…" 66 | 67 | #. TRANSLATORS: daemon is scared to publish files 68 | #: src/passim-cli.c:270 69 | msgid "Disabled (metered network)" 70 | msgstr "Désactivé (réseau limité)" 71 | 72 | #. TRANSLATORS: daemon is offering files like normal 73 | #: src/passim-cli.c:273 74 | msgid "Running" 75 | msgstr "En cours d’exécution" 76 | 77 | #: src/passim-cli.c:277 78 | msgid "Status" 79 | msgstr "Statut" 80 | 81 | #. TRANSLATORS: full https://whatever of the daemon 82 | #: src/passim-cli.c:284 83 | msgid "URI" 84 | msgstr "URI" 85 | 86 | #. TRANSLATORS: user mistyped the command 87 | #: src/passim-cli.c:323 src/passim-cli.c:353 88 | msgid "Invalid arguments" 89 | msgstr "Arguments invalides" 90 | 91 | #. TRANSLATORS: now sharing to the world 92 | #: src/passim-cli.c:340 93 | msgid "Published" 94 | msgstr "Publié" 95 | 96 | #. TRANSLATORS: no longer sharing with the world 97 | #: src/passim-cli.c:360 98 | msgid "Unpublished" 99 | msgstr "Dépublié" 100 | 101 | #. TRANSLATORS: --version 102 | #: src/passim-cli.c:375 103 | msgid "Show project version" 104 | msgstr "Affiche la version du projet" 105 | 106 | #: src/passim-cli.c:382 107 | msgid "Next reboot" 108 | msgstr "Prochain redémarrage" 109 | 110 | #. TRANSLATORS: CLI action description 111 | #: src/passim-cli.c:397 112 | msgid "Show daemon status" 113 | msgstr "Affiche le statut du daemon" 114 | 115 | #. TRANSLATORS: CLI option example 116 | #: src/passim-cli.c:402 117 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 118 | msgstr "NOM DU FICHIER [AGE MAXIMUM] [NOMBRE DE PARTAGE]" 119 | 120 | #. TRANSLATORS: CLI action description 121 | #: src/passim-cli.c:404 122 | msgid "Publish an additional file" 123 | msgstr "Publier un fichier additionnel" 124 | 125 | #. TRANSLATORS: CLI option example 126 | #: src/passim-cli.c:409 127 | msgid "HASH" 128 | msgstr "HASH" 129 | 130 | #. TRANSLATORS: CLI action description 131 | #: src/passim-cli.c:411 132 | msgid "Unpublish an existing file" 133 | msgstr "Dépublier un fichier existant" 134 | 135 | #. TRANSLATORS: CLI tool description 136 | #: src/passim-cli.c:418 137 | msgid "Interact with the local passimd process." 138 | msgstr "Interagit avec le processus passimd local." 139 | 140 | #. TRANSLATORS: CLI tool name 141 | #: src/passim-cli.c:420 142 | msgid "Passim CLI" 143 | msgstr "Ligne de commande Passim" 144 | 145 | #. TRANSLATORS: we don't know what to do 146 | #: src/passim-cli.c:424 147 | msgid "Failed to parse arguments" 148 | msgstr "Impossible d'analyser les arguments" 149 | 150 | #. TRANSLATORS: daemon failed to start 151 | #: src/passim-cli.c:432 152 | msgid "Failed to connect to daemon" 153 | msgstr "Contact avec le daemon impossible" 154 | 155 | #. TRANSLATORS: CLI tool 156 | #: src/passim-cli.c:439 157 | msgid "client version" 158 | msgstr "version du client" 159 | 160 | #. TRANSLATORS: server 161 | #: src/passim-cli.c:441 162 | msgid "daemon version" 163 | msgstr "version du daemon" 164 | -------------------------------------------------------------------------------- /po/ka.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 11 | "PO-Revision-Date: 2024-08-10 04:09+0000\n" 12 | "Last-Translator: Temuri Doghonadze \n" 13 | "Language-Team: Georgian \n" 15 | "Language: ka\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.7-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:180 24 | msgid "Disabled" 25 | msgstr "გამორთულია" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:184 29 | msgid "Next Reboot" 30 | msgstr "შემდეგი გადატვირთვა" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:197 34 | msgid "Filename" 35 | msgstr "ფაილის სახელი" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:204 39 | msgid "Flags" 40 | msgstr "ალმები" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:211 44 | msgid "Command Line" 45 | msgstr "ბრძანების ველი" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:218 49 | msgid "Age" 50 | msgstr "ასაკი" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:227 54 | msgid "Share Limit" 55 | msgstr "გაზიარებების შეზღუდვა" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:236 59 | msgid "Size" 60 | msgstr "ზომა" 61 | 62 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 63 | #: src/passim-cli.c:271 64 | msgid "Name" 65 | msgstr "სახელი" 66 | 67 | #. TRANSLATORS: daemon is starting up 68 | #: src/passim-cli.c:278 69 | msgid "Loading…" 70 | msgstr "ჩატვირთვა…" 71 | 72 | #. TRANSLATORS: daemon is scared to publish files 73 | #: src/passim-cli.c:281 74 | msgid "Disabled (metered network)" 75 | msgstr "გათიშულია (გაზომვადი ქსელი)" 76 | 77 | #. TRANSLATORS: daemon is offering files like normal 78 | #: src/passim-cli.c:284 79 | msgid "Running" 80 | msgstr "გაშვებულია" 81 | 82 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 83 | #: src/passim-cli.c:289 84 | msgid "Status" 85 | msgstr "სტატუსი" 86 | 87 | #. TRANSLATORS: how many bytes we did not download from the internet 88 | #: src/passim-cli.c:297 89 | msgid "Network Saving" 90 | msgstr "ქსელის დაზოგვა" 91 | 92 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 93 | #: src/passim-cli.c:304 94 | msgid "Carbon Saving" 95 | msgstr "გაზოგილი ნახშირბადი" 96 | 97 | #. TRANSLATORS: full https://whatever of the daemon 98 | #: src/passim-cli.c:312 99 | msgid "URI" 100 | msgstr "URI" 101 | 102 | #. TRANSLATORS: user mistyped the command 103 | #: src/passim-cli.c:351 src/passim-cli.c:381 104 | msgid "Invalid arguments" 105 | msgstr "არასწორი არგუმენტები" 106 | 107 | #. TRANSLATORS: now sharing to the world 108 | #: src/passim-cli.c:368 109 | msgid "Published" 110 | msgstr "გამოქვეყნებულია" 111 | 112 | #. TRANSLATORS: no longer sharing with the world 113 | #: src/passim-cli.c:388 114 | msgid "Unpublished" 115 | msgstr "გამოუქვეყნებელი" 116 | 117 | #. TRANSLATORS: user mistyped the command 118 | #: src/passim-cli.c:426 119 | msgid "Invalid arguments, expected BASENAME HASH" 120 | msgstr "არასწორი არგუმენტები. მოველოდი საბაზისოსახელი ჰეში" 121 | 122 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 123 | #. the untranslated error phrase 124 | #: src/passim-cli.c:469 125 | #, c-format 126 | msgid "Failed to download %s from %s: %s" 127 | msgstr "%s-ის გადმოწერა %s-დან ჩავარდა: %s" 128 | 129 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 130 | #: src/passim-cli.c:483 131 | #, c-format 132 | msgid "Failed to download %s from %s as file checksum does not match" 133 | msgstr "%s-ის გადმოწერა %s-დან ჩავარდა, რადგან საკონტროლო ჯამი არ ემთხვევა" 134 | 135 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 136 | #. * 'application/zstd') 137 | #: src/passim-cli.c:502 138 | #, c-format 139 | msgid "Saved %s (%s of %s)" 140 | msgstr "შენახულია %s (%s %s-დან)" 141 | 142 | #. TRANSLATORS: --version 143 | #: src/passim-cli.c:518 144 | msgid "Show project version" 145 | msgstr "პროექტის ვერსიის ჩვენება" 146 | 147 | #: src/passim-cli.c:525 148 | msgid "Next reboot" 149 | msgstr "შემდეგი გადატვირთვის შემდეგ" 150 | 151 | #. TRANSLATORS: CLI action description 152 | #: src/passim-cli.c:540 153 | msgid "Show daemon status" 154 | msgstr "დემონის სტატუსის ჩვენება" 155 | 156 | #. TRANSLATORS: CLI option example 157 | #: src/passim-cli.c:545 158 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 159 | msgstr "ფაილისსახელი [მაქს-ასაკი] [მაქს-გაზიარება]" 160 | 161 | #. TRANSLATORS: CLI action description 162 | #: src/passim-cli.c:547 163 | msgid "Publish an additional file" 164 | msgstr "დამატებითი ფაილის გამოქვეყნება" 165 | 166 | #. TRANSLATORS: CLI option example 167 | #: src/passim-cli.c:552 168 | msgid "HASH" 169 | msgstr "ჰეში" 170 | 171 | #. TRANSLATORS: CLI action description 172 | #: src/passim-cli.c:554 173 | msgid "Unpublish an existing file" 174 | msgstr "არსებული ფაილის გამოქვეყნების მოხსნა" 175 | 176 | #. TRANSLATORS: CLI option example 177 | #: src/passim-cli.c:559 178 | msgid "BASENAME HASH" 179 | msgstr "საბაზისოსახელი ჰეში" 180 | 181 | #. TRANSLATORS: CLI action description 182 | #: src/passim-cli.c:561 183 | msgid "Download a file from a remote machine" 184 | msgstr "ფაილის გადმოწერა დაშორებული მანქანიდან" 185 | 186 | #. TRANSLATORS: CLI tool description 187 | #: src/passim-cli.c:568 188 | msgid "Interact with the local passimd process." 189 | msgstr "მიმართვა passimd-ის ლოკალურ პროცესზე." 190 | 191 | #. TRANSLATORS: CLI tool name 192 | #: src/passim-cli.c:570 193 | msgid "Passim CLI" 194 | msgstr "Passim CLI" 195 | 196 | #. TRANSLATORS: we don't know what to do 197 | #: src/passim-cli.c:574 198 | msgid "Failed to parse arguments" 199 | msgstr "არგუმენტების დამუშავების შეცდომა" 200 | 201 | #. TRANSLATORS: daemon failed to start 202 | #: src/passim-cli.c:582 203 | msgid "Failed to connect to daemon" 204 | msgstr "დემონთან მიერთება ჩავარდა" 205 | 206 | #. TRANSLATORS: CLI tool 207 | #: src/passim-cli.c:589 208 | msgid "client version" 209 | msgstr "კლიენტის ვერსია" 210 | 211 | #. TRANSLATORS: server 212 | #: src/passim-cli.c:591 213 | msgid "daemon version" 214 | msgstr "დემონის ვერსია" 215 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n = import('i18n') 2 | i18n.gettext(meson.project_name(), 3 | preset: 'glib', 4 | args: [ 5 | '--default-domain=' + meson.project_name(), 6 | ] 7 | ) 8 | 9 | run_target('fix-translations', 10 | command: [ 11 | [python3, files('fix_translations.py')], 12 | join_paths(meson.project_source_root(), 'po') 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /po/nb_NO.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-03-19 15:36+0000\n" 11 | "PO-Revision-Date: 2024-03-26 02:01+0000\n" 12 | "Last-Translator: Allan Nordhøy \n" 13 | "Language-Team: Norwegian Bokmål \n" 15 | "Language: nb_NO\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:179 24 | msgid "Disabled" 25 | msgstr "Deaktivert" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:183 29 | msgid "Next Reboot" 30 | msgstr "Neste omstart" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:196 34 | msgid "Filename" 35 | msgstr "Filnavn" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:203 39 | msgid "Flags" 40 | msgstr "Flagg" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:210 44 | msgid "Command Line" 45 | msgstr "Kommandolinje" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:217 49 | msgid "Age" 50 | msgstr "Alder" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:226 54 | msgid "Share Limit" 55 | msgstr "Delingsgrense" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:235 59 | msgid "Size" 60 | msgstr "Størrelse" 61 | 62 | #. TRANSLATORS: daemon is starting up 63 | #: src/passim-cli.c:267 64 | msgid "Loading…" 65 | msgstr "Laster …" 66 | 67 | #. TRANSLATORS: daemon is scared to publish files 68 | #: src/passim-cli.c:270 69 | msgid "Disabled (metered network)" 70 | msgstr "Deaktivert (betalt dataforbindelse)" 71 | 72 | #. TRANSLATORS: daemon is offering files like normal 73 | #: src/passim-cli.c:273 74 | msgid "Running" 75 | msgstr "Kjører" 76 | 77 | #: src/passim-cli.c:277 78 | msgid "Status" 79 | msgstr "Status" 80 | 81 | #. TRANSLATORS: full https://whatever of the daemon 82 | #: src/passim-cli.c:284 83 | msgid "URI" 84 | msgstr "URI" 85 | 86 | #. TRANSLATORS: user mistyped the command 87 | #: src/passim-cli.c:323 src/passim-cli.c:353 88 | msgid "Invalid arguments" 89 | msgstr "Ugyldige argumenter" 90 | 91 | #. TRANSLATORS: now sharing to the world 92 | #: src/passim-cli.c:340 93 | msgid "Published" 94 | msgstr "Publisert" 95 | 96 | #. TRANSLATORS: no longer sharing with the world 97 | #: src/passim-cli.c:360 98 | msgid "Unpublished" 99 | msgstr "Upublisert" 100 | 101 | #. TRANSLATORS: --version 102 | #: src/passim-cli.c:375 103 | msgid "Show project version" 104 | msgstr "Vis prosjektversjon" 105 | 106 | #: src/passim-cli.c:382 107 | msgid "Next reboot" 108 | msgstr "Neste omstart" 109 | 110 | #. TRANSLATORS: CLI action description 111 | #: src/passim-cli.c:397 112 | msgid "Show daemon status" 113 | msgstr "Vis tjenestens status" 114 | 115 | #. TRANSLATORS: CLI option example 116 | #: src/passim-cli.c:402 117 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 118 | msgstr "FILNAVN [MAKS-ALDER] [MAX-DELING]" 119 | 120 | #. TRANSLATORS: CLI action description 121 | #: src/passim-cli.c:404 122 | msgid "Publish an additional file" 123 | msgstr "Publiser en fil i tillegg" 124 | 125 | #. TRANSLATORS: CLI option example 126 | #: src/passim-cli.c:409 127 | msgid "HASH" 128 | msgstr "HASH" 129 | 130 | #. TRANSLATORS: CLI action description 131 | #: src/passim-cli.c:411 132 | msgid "Unpublish an existing file" 133 | msgstr "Av-publiser en eksisterende fil" 134 | 135 | #. TRANSLATORS: CLI tool description 136 | #: src/passim-cli.c:418 137 | msgid "Interact with the local passimd process." 138 | msgstr "Samhandle med den lokale passimd-prosessen." 139 | 140 | #. TRANSLATORS: CLI tool name 141 | #: src/passim-cli.c:420 142 | msgid "Passim CLI" 143 | msgstr "Passim-CLI" 144 | 145 | #. TRANSLATORS: we don't know what to do 146 | #: src/passim-cli.c:424 147 | msgid "Failed to parse arguments" 148 | msgstr "Kunne ikke tolke argumentene" 149 | 150 | #. TRANSLATORS: daemon failed to start 151 | #: src/passim-cli.c:432 152 | msgid "Failed to connect to daemon" 153 | msgstr "FIkk ikke kontakt med tjenesten" 154 | 155 | #. TRANSLATORS: CLI tool 156 | #: src/passim-cli.c:439 157 | msgid "client version" 158 | msgstr "klientversjon" 159 | 160 | #. TRANSLATORS: server 161 | #: src/passim-cli.c:441 162 | msgid "daemon version" 163 | msgstr "nisse-versjon" 164 | -------------------------------------------------------------------------------- /po/passim.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: passim\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #. TRANSLATORS: the item is not enabled 21 | #: src/passim-cli.c:180 22 | msgid "Disabled" 23 | msgstr "" 24 | 25 | #. TRANSLATORS: only begin sharing the item after the next restart 26 | #: src/passim-cli.c:184 27 | msgid "Next Reboot" 28 | msgstr "" 29 | 30 | #. TRANSLATORS: item file basename 31 | #: src/passim-cli.c:197 32 | msgid "Filename" 33 | msgstr "" 34 | 35 | #. TRANSLATORS: item flags 36 | #: src/passim-cli.c:204 37 | msgid "Flags" 38 | msgstr "" 39 | 40 | #. TRANSLATORS: basename of the thing that published the item 41 | #: src/passim-cli.c:211 42 | msgid "Command Line" 43 | msgstr "" 44 | 45 | #. TRANSLATORS: age of the published item 46 | #: src/passim-cli.c:218 47 | msgid "Age" 48 | msgstr "" 49 | 50 | #. TRANSLATORS: number of times we can share the item 51 | #: src/passim-cli.c:227 52 | msgid "Share Limit" 53 | msgstr "" 54 | 55 | #. TRANSLATORS: size of the published item 56 | #: src/passim-cli.c:236 57 | msgid "Size" 58 | msgstr "" 59 | 60 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 61 | #: src/passim-cli.c:271 62 | msgid "Name" 63 | msgstr "" 64 | 65 | #. TRANSLATORS: daemon is starting up 66 | #: src/passim-cli.c:278 67 | msgid "Loading…" 68 | msgstr "" 69 | 70 | #. TRANSLATORS: daemon is scared to publish files 71 | #: src/passim-cli.c:281 72 | msgid "Disabled (metered network)" 73 | msgstr "" 74 | 75 | #. TRANSLATORS: daemon is offering files like normal 76 | #: src/passim-cli.c:284 77 | msgid "Running" 78 | msgstr "" 79 | 80 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 81 | #: src/passim-cli.c:289 82 | msgid "Status" 83 | msgstr "" 84 | 85 | #. TRANSLATORS: how many bytes we did not download from the internet 86 | #: src/passim-cli.c:297 87 | msgid "Network Saving" 88 | msgstr "" 89 | 90 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 91 | #: src/passim-cli.c:304 92 | msgid "Carbon Saving" 93 | msgstr "" 94 | 95 | #. TRANSLATORS: full https://whatever of the daemon 96 | #: src/passim-cli.c:312 97 | msgid "URI" 98 | msgstr "" 99 | 100 | #. TRANSLATORS: user mistyped the command 101 | #: src/passim-cli.c:351 src/passim-cli.c:381 102 | msgid "Invalid arguments" 103 | msgstr "" 104 | 105 | #. TRANSLATORS: now sharing to the world 106 | #: src/passim-cli.c:368 107 | msgid "Published" 108 | msgstr "" 109 | 110 | #. TRANSLATORS: no longer sharing with the world 111 | #: src/passim-cli.c:388 112 | msgid "Unpublished" 113 | msgstr "" 114 | 115 | #. TRANSLATORS: user mistyped the command 116 | #: src/passim-cli.c:426 117 | msgid "Invalid arguments, expected BASENAME HASH" 118 | msgstr "" 119 | 120 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 121 | #. the untranslated error phrase 122 | #: src/passim-cli.c:469 123 | #, c-format 124 | msgid "Failed to download %s from %s: %s" 125 | msgstr "" 126 | 127 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 128 | #: src/passim-cli.c:483 129 | #, c-format 130 | msgid "Failed to download %s from %s as file checksum does not match" 131 | msgstr "" 132 | 133 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 134 | #. * 'application/zstd') 135 | #: src/passim-cli.c:502 136 | #, c-format 137 | msgid "Saved %s (%s of %s)" 138 | msgstr "" 139 | 140 | #. TRANSLATORS: --version 141 | #: src/passim-cli.c:518 142 | msgid "Show project version" 143 | msgstr "" 144 | 145 | #: src/passim-cli.c:525 146 | msgid "Next reboot" 147 | msgstr "" 148 | 149 | #. TRANSLATORS: CLI action description 150 | #: src/passim-cli.c:540 151 | msgid "Show daemon status" 152 | msgstr "" 153 | 154 | #. TRANSLATORS: CLI option example 155 | #: src/passim-cli.c:545 156 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 157 | msgstr "" 158 | 159 | #. TRANSLATORS: CLI action description 160 | #: src/passim-cli.c:547 161 | msgid "Publish an additional file" 162 | msgstr "" 163 | 164 | #. TRANSLATORS: CLI option example 165 | #: src/passim-cli.c:552 166 | msgid "HASH" 167 | msgstr "" 168 | 169 | #. TRANSLATORS: CLI action description 170 | #: src/passim-cli.c:554 171 | msgid "Unpublish an existing file" 172 | msgstr "" 173 | 174 | #. TRANSLATORS: CLI option example 175 | #: src/passim-cli.c:559 176 | msgid "BASENAME HASH" 177 | msgstr "" 178 | 179 | #. TRANSLATORS: CLI action description 180 | #: src/passim-cli.c:561 181 | msgid "Download a file from a remote machine" 182 | msgstr "" 183 | 184 | #. TRANSLATORS: CLI tool description 185 | #: src/passim-cli.c:568 186 | msgid "Interact with the local passimd process." 187 | msgstr "" 188 | 189 | #. TRANSLATORS: CLI tool name 190 | #: src/passim-cli.c:570 191 | msgid "Passim CLI" 192 | msgstr "" 193 | 194 | #. TRANSLATORS: we don't know what to do 195 | #: src/passim-cli.c:574 196 | msgid "Failed to parse arguments" 197 | msgstr "" 198 | 199 | #. TRANSLATORS: daemon failed to start 200 | #: src/passim-cli.c:582 201 | msgid "Failed to connect to daemon" 202 | msgstr "" 203 | 204 | #. TRANSLATORS: CLI tool 205 | #: src/passim-cli.c:589 206 | msgid "client version" 207 | msgstr "" 208 | 209 | #. TRANSLATORS: server 210 | #: src/passim-cli.c:591 211 | msgid "daemon version" 212 | msgstr "" 213 | -------------------------------------------------------------------------------- /po/ru.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 11 | "PO-Revision-Date: 2024-06-28 16:09+0000\n" 12 | "Last-Translator: Airat Makhmutov \n" 13 | "Language-Team: Russian \n" 15 | "Language: ru\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " 20 | "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 21 | "X-Generator: Weblate 5.7-dev\n" 22 | 23 | #. TRANSLATORS: the item is not enabled 24 | #: src/passim-cli.c:180 25 | msgid "Disabled" 26 | msgstr "Выключен" 27 | 28 | #. TRANSLATORS: only begin sharing the item after the next restart 29 | #: src/passim-cli.c:184 30 | msgid "Next Reboot" 31 | msgstr "Следующая Перезагрузка" 32 | 33 | #. TRANSLATORS: item file basename 34 | #: src/passim-cli.c:197 35 | msgid "Filename" 36 | msgstr "Имя файла" 37 | 38 | #. TRANSLATORS: item flags 39 | #: src/passim-cli.c:204 40 | msgid "Flags" 41 | msgstr "Флаги" 42 | 43 | #. TRANSLATORS: basename of the thing that published the item 44 | #: src/passim-cli.c:211 45 | msgid "Command Line" 46 | msgstr "Командная строка" 47 | 48 | #. TRANSLATORS: age of the published item 49 | #: src/passim-cli.c:218 50 | msgid "Age" 51 | msgstr "Возраст" 52 | 53 | #. TRANSLATORS: number of times we can share the item 54 | #: src/passim-cli.c:227 55 | msgid "Share Limit" 56 | msgstr "Лимит Раздачи" 57 | 58 | #. TRANSLATORS: size of the published item 59 | #: src/passim-cli.c:236 60 | msgid "Size" 61 | msgstr "Размер" 62 | 63 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 64 | #: src/passim-cli.c:271 65 | msgid "Name" 66 | msgstr "Имя" 67 | 68 | #. TRANSLATORS: daemon is starting up 69 | #: src/passim-cli.c:278 70 | msgid "Loading…" 71 | msgstr "Загрузка…" 72 | 73 | #. TRANSLATORS: daemon is scared to publish files 74 | #: src/passim-cli.c:281 75 | msgid "Disabled (metered network)" 76 | msgstr "Выключен (ограниченный интернет)" 77 | 78 | #. TRANSLATORS: daemon is offering files like normal 79 | #: src/passim-cli.c:284 80 | msgid "Running" 81 | msgstr "Работает" 82 | 83 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 84 | #: src/passim-cli.c:289 85 | msgid "Status" 86 | msgstr "Статус" 87 | 88 | #. TRANSLATORS: how many bytes we did not download from the internet 89 | #: src/passim-cli.c:297 90 | msgid "Network Saving" 91 | msgstr "Экономия Интернета" 92 | 93 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 94 | #: src/passim-cli.c:304 95 | msgid "Carbon Saving" 96 | msgstr "Экономия Углерода" 97 | 98 | #. TRANSLATORS: full https://whatever of the daemon 99 | #: src/passim-cli.c:312 100 | msgid "URI" 101 | msgstr "URI" 102 | 103 | #. TRANSLATORS: user mistyped the command 104 | #: src/passim-cli.c:351 src/passim-cli.c:381 105 | msgid "Invalid arguments" 106 | msgstr "Недопустимые аргументы" 107 | 108 | #. TRANSLATORS: now sharing to the world 109 | #: src/passim-cli.c:368 110 | msgid "Published" 111 | msgstr "Опубликовано" 112 | 113 | #. TRANSLATORS: no longer sharing with the world 114 | #: src/passim-cli.c:388 115 | msgid "Unpublished" 116 | msgstr "Не опубликовано" 117 | 118 | #. TRANSLATORS: user mistyped the command 119 | #: src/passim-cli.c:426 120 | msgid "Invalid arguments, expected BASENAME HASH" 121 | msgstr "Недопустимые аргументы, ожидалось BASENAME HASH" 122 | 123 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 124 | #. the untranslated error phrase 125 | #: src/passim-cli.c:469 126 | #, c-format 127 | msgid "Failed to download %s from %s: %s" 128 | msgstr "Не удалось скачать %s из %s:%s" 129 | 130 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 131 | #: src/passim-cli.c:483 132 | #, c-format 133 | msgid "Failed to download %s from %s as file checksum does not match" 134 | msgstr "" 135 | "Не удалось скачать %s из %s так как контрольная сумма файла не совпадает" 136 | 137 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 138 | #. * 'application/zstd') 139 | #: src/passim-cli.c:502 140 | #, c-format 141 | msgid "Saved %s (%s of %s)" 142 | msgstr "Сохранено %s (%s %s)" 143 | 144 | #. TRANSLATORS: --version 145 | #: src/passim-cli.c:518 146 | msgid "Show project version" 147 | msgstr "Показать версию проекта" 148 | 149 | #: src/passim-cli.c:525 150 | msgid "Next reboot" 151 | msgstr "Следующая перезагрузка" 152 | 153 | #. TRANSLATORS: CLI action description 154 | #: src/passim-cli.c:540 155 | msgid "Show daemon status" 156 | msgstr "Показать статус демона" 157 | 158 | #. TRANSLATORS: CLI option example 159 | #: src/passim-cli.c:545 160 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 161 | msgstr "ИМЯ-ФАЙЛА [МАКСИМАЛЬНЫЙ-ВОЗРАСТ] [МАКСИМАЛЬНАЯ-РАЗДАЧА]" 162 | 163 | #. TRANSLATORS: CLI action description 164 | #: src/passim-cli.c:547 165 | msgid "Publish an additional file" 166 | msgstr "Опубликовать дополнительный файл" 167 | 168 | #. TRANSLATORS: CLI option example 169 | #: src/passim-cli.c:552 170 | msgid "HASH" 171 | msgstr "HASH" 172 | 173 | #. TRANSLATORS: CLI action description 174 | #: src/passim-cli.c:554 175 | msgid "Unpublish an existing file" 176 | msgstr "Отменить публикацию существующего файла" 177 | 178 | #. TRANSLATORS: CLI option example 179 | #: src/passim-cli.c:559 180 | msgid "BASENAME HASH" 181 | msgstr "BASENAME HASH" 182 | 183 | #. TRANSLATORS: CLI action description 184 | #: src/passim-cli.c:561 185 | msgid "Download a file from a remote machine" 186 | msgstr "Загрузите файл с удаленной машины" 187 | 188 | #. TRANSLATORS: CLI tool description 189 | #: src/passim-cli.c:568 190 | msgid "Interact with the local passimd process." 191 | msgstr "Взаимодействие с локальным процессом passimd." 192 | 193 | #. TRANSLATORS: CLI tool name 194 | #: src/passim-cli.c:570 195 | msgid "Passim CLI" 196 | msgstr "Passim CLI" 197 | 198 | #. TRANSLATORS: we don't know what to do 199 | #: src/passim-cli.c:574 200 | msgid "Failed to parse arguments" 201 | msgstr "Не удалось проанализировать аргументы" 202 | 203 | #. TRANSLATORS: daemon failed to start 204 | #: src/passim-cli.c:582 205 | msgid "Failed to connect to daemon" 206 | msgstr "Не удалось подключиться к демону" 207 | 208 | #. TRANSLATORS: CLI tool 209 | #: src/passim-cli.c:589 210 | msgid "client version" 211 | msgstr "версия клиента" 212 | 213 | #. TRANSLATORS: server 214 | #: src/passim-cli.c:591 215 | msgid "daemon version" 216 | msgstr "версия демона" 217 | -------------------------------------------------------------------------------- /po/ta.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 11 | "PO-Revision-Date: 2025-01-27 13:13+0000\n" 12 | "Last-Translator: தமிழ்நேரம் \n" 13 | "Language-Team: Tamil \n" 15 | "Language: ta\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.10-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:180 24 | msgid "Disabled" 25 | msgstr "முடக்கப்பட்டது" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:184 29 | msgid "Next Reboot" 30 | msgstr "அடுத்த மறுதொடக்கம்" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:197 34 | msgid "Filename" 35 | msgstr "கோப்புப்பெயர்" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:204 39 | msgid "Flags" 40 | msgstr "கொடிகள்" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:211 44 | msgid "Command Line" 45 | msgstr "கட்டளை வரி" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:218 49 | msgid "Age" 50 | msgstr "அகவை" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:227 54 | msgid "Share Limit" 55 | msgstr "பகிர்வு வரம்பு" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:236 59 | msgid "Size" 60 | msgstr "அளவு" 61 | 62 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 63 | #: src/passim-cli.c:271 64 | msgid "Name" 65 | msgstr "பெயர்" 66 | 67 | #. TRANSLATORS: daemon is starting up 68 | #: src/passim-cli.c:278 69 | msgid "Loading…" 70 | msgstr "ஏற்றுகிறது…" 71 | 72 | #. TRANSLATORS: daemon is scared to publish files 73 | #: src/passim-cli.c:281 74 | msgid "Disabled (metered network)" 75 | msgstr "முடக்கப்பட்ட (மீட்டர் நெட்வொர்க்)" 76 | 77 | #. TRANSLATORS: daemon is offering files like normal 78 | #: src/passim-cli.c:284 79 | msgid "Running" 80 | msgstr "இயங்கும்" 81 | 82 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 83 | #: src/passim-cli.c:289 84 | msgid "Status" 85 | msgstr "நிலை" 86 | 87 | #. TRANSLATORS: how many bytes we did not download from the internet 88 | #: src/passim-cli.c:297 89 | msgid "Network Saving" 90 | msgstr "பிணைய சேமிப்பு" 91 | 92 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 93 | #: src/passim-cli.c:304 94 | msgid "Carbon Saving" 95 | msgstr "கார்பன் சேமிப்பு" 96 | 97 | #. TRANSLATORS: full https://whatever of the daemon 98 | #: src/passim-cli.c:312 99 | msgid "URI" 100 | msgstr "யூரி" 101 | 102 | #. TRANSLATORS: user mistyped the command 103 | #: src/passim-cli.c:351 src/passim-cli.c:381 104 | msgid "Invalid arguments" 105 | msgstr "தவறான வாதங்கள்" 106 | 107 | #. TRANSLATORS: now sharing to the world 108 | #: src/passim-cli.c:368 109 | msgid "Published" 110 | msgstr "வெளியிடப்பட்டது" 111 | 112 | #. TRANSLATORS: no longer sharing with the world 113 | #: src/passim-cli.c:388 114 | msgid "Unpublished" 115 | msgstr "வெளியிடப்படாதது" 116 | 117 | #. TRANSLATORS: user mistyped the command 118 | #: src/passim-cli.c:426 119 | msgid "Invalid arguments, expected BASENAME HASH" 120 | msgstr "தவறான வாதங்கள், எதிர்பார்க்கப்படும் பேசன் பெயர் ஆச்" 121 | 122 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 123 | #. the untranslated error phrase 124 | #: src/passim-cli.c:469 125 | #, c-format 126 | msgid "Failed to download %s from %s: %s" 127 | msgstr "%s: %s இலிருந்து %s ஐ பதிவிறக்கத் தவறிவிட்டது" 128 | 129 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 130 | #: src/passim-cli.c:483 131 | #, c-format 132 | msgid "Failed to download %s from %s as file checksum does not match" 133 | msgstr "கோப்பு செக்சம் பொருந்தாததால் %s இலிருந்து %s ஐ பதிவிறக்குவதில் தோல்வி" 134 | 135 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 136 | #. * 'application/zstd') 137 | #: src/passim-cli.c:502 138 | #, c-format 139 | msgid "Saved %s (%s of %s)" 140 | msgstr "%s ( %s %s) சேமிக்கப்பட்டது" 141 | 142 | #. TRANSLATORS: --version 143 | #: src/passim-cli.c:518 144 | msgid "Show project version" 145 | msgstr "திட்ட பதிப்பைக் காட்டு" 146 | 147 | #: src/passim-cli.c:525 148 | msgid "Next reboot" 149 | msgstr "அடுத்த மறுதொடக்கம்" 150 | 151 | #. TRANSLATORS: CLI action description 152 | #: src/passim-cli.c:540 153 | msgid "Show daemon status" 154 | msgstr "டீமான் நிலையைக் காட்டு" 155 | 156 | #. TRANSLATORS: CLI option example 157 | #: src/passim-cli.c:545 158 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 159 | msgstr "கோப்பு பெயர் [மேக்ச்-ஏச்] [மேக்ச்-சேர்]" 160 | 161 | #. TRANSLATORS: CLI action description 162 | #: src/passim-cli.c:547 163 | msgid "Publish an additional file" 164 | msgstr "கூடுதல் கோப்பை வெளியிடுங்கள்" 165 | 166 | #. TRANSLATORS: CLI option example 167 | #: src/passim-cli.c:552 168 | msgid "HASH" 169 | msgstr "ஆச்" 170 | 171 | #. TRANSLATORS: CLI action description 172 | #: src/passim-cli.c:554 173 | msgid "Unpublish an existing file" 174 | msgstr "ஏற்கனவே உள்ள கோப்பை வெளியிடுங்கள்" 175 | 176 | #. TRANSLATORS: CLI option example 177 | #: src/passim-cli.c:559 178 | msgid "BASENAME HASH" 179 | msgstr "பேசன்ம் ஆச்" 180 | 181 | #. TRANSLATORS: CLI action description 182 | #: src/passim-cli.c:561 183 | msgid "Download a file from a remote machine" 184 | msgstr "தொலைநிலை இயந்திரத்திலிருந்து ஒரு கோப்பைப் பதிவிறக்கவும்" 185 | 186 | #. TRANSLATORS: CLI tool description 187 | #: src/passim-cli.c:568 188 | msgid "Interact with the local passimd process." 189 | msgstr "உள்ளக பாசிம்ட் செயல்முறையுடன் தொடர்பு கொள்ளுங்கள்." 190 | 191 | #. TRANSLATORS: CLI tool name 192 | #: src/passim-cli.c:570 193 | msgid "Passim CLI" 194 | msgstr "பாசிம் கிளி" 195 | 196 | #. TRANSLATORS: we don't know what to do 197 | #: src/passim-cli.c:574 198 | msgid "Failed to parse arguments" 199 | msgstr "வாதங்களை அலசத் தவறிவிட்டது" 200 | 201 | #. TRANSLATORS: daemon failed to start 202 | #: src/passim-cli.c:582 203 | msgid "Failed to connect to daemon" 204 | msgstr "டீமனுடன் இணைக்கத் தவறிவிட்டது" 205 | 206 | #. TRANSLATORS: CLI tool 207 | #: src/passim-cli.c:589 208 | msgid "client version" 209 | msgstr "கிளீன் பதிப்பு" 210 | 211 | #. TRANSLATORS: server 212 | #: src/passim-cli.c:591 213 | msgid "daemon version" 214 | msgstr "டீமான் பதிப்பு" 215 | -------------------------------------------------------------------------------- /po/tr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the passim package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: passim\n" 9 | "Report-Msgid-Bugs-To: richard@hughsie.com\n" 10 | "POT-Creation-Date: 2024-04-15 14:10+0100\n" 11 | "PO-Revision-Date: 2024-04-24 17:07+0000\n" 12 | "Last-Translator: Oğuz Ersen \n" 13 | "Language-Team: Turkish \n" 15 | "Language: tr\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.5.1-dev\n" 21 | 22 | #. TRANSLATORS: the item is not enabled 23 | #: src/passim-cli.c:180 24 | msgid "Disabled" 25 | msgstr "Devre dışı" 26 | 27 | #. TRANSLATORS: only begin sharing the item after the next restart 28 | #: src/passim-cli.c:184 29 | msgid "Next Reboot" 30 | msgstr "Sonraki Yeniden Başlatmada" 31 | 32 | #. TRANSLATORS: item file basename 33 | #: src/passim-cli.c:197 34 | msgid "Filename" 35 | msgstr "Dosya Adı" 36 | 37 | #. TRANSLATORS: item flags 38 | #: src/passim-cli.c:204 39 | msgid "Flags" 40 | msgstr "Bayraklar" 41 | 42 | #. TRANSLATORS: basename of the thing that published the item 43 | #: src/passim-cli.c:211 44 | msgid "Command Line" 45 | msgstr "Komut Satırı" 46 | 47 | #. TRANSLATORS: age of the published item 48 | #: src/passim-cli.c:218 49 | msgid "Age" 50 | msgstr "Yaş" 51 | 52 | #. TRANSLATORS: number of times we can share the item 53 | #: src/passim-cli.c:227 54 | msgid "Share Limit" 55 | msgstr "Paylaşım Sınırı" 56 | 57 | #. TRANSLATORS: size of the published item 58 | #: src/passim-cli.c:236 59 | msgid "Size" 60 | msgstr "Boyut" 61 | 62 | #. TRANSLATORS: the daemon autogenerated name, e.g. "Passim-ABCD" 63 | #: src/passim-cli.c:271 64 | msgid "Name" 65 | msgstr "Ad" 66 | 67 | #. TRANSLATORS: daemon is starting up 68 | #: src/passim-cli.c:278 69 | msgid "Loading…" 70 | msgstr "Yükleniyor…" 71 | 72 | #. TRANSLATORS: daemon is scared to publish files 73 | #: src/passim-cli.c:281 74 | msgid "Disabled (metered network)" 75 | msgstr "Devre dışı (kotalı ağ)" 76 | 77 | #. TRANSLATORS: daemon is offering files like normal 78 | #: src/passim-cli.c:284 79 | msgid "Running" 80 | msgstr "Çalışıyor" 81 | 82 | #. TRANSLATORS: what the daemon is doing right now, e.g. 'Running' 83 | #: src/passim-cli.c:289 84 | msgid "Status" 85 | msgstr "Durum" 86 | 87 | #. TRANSLATORS: how many bytes we did not download from the internet 88 | #: src/passim-cli.c:297 89 | msgid "Network Saving" 90 | msgstr "Ağ Tasarrufu" 91 | 92 | #. TRANSLATORS: how much carbon we did not *burn* by using local data 93 | #: src/passim-cli.c:304 94 | msgid "Carbon Saving" 95 | msgstr "Karbon Tasarrufu" 96 | 97 | #. TRANSLATORS: full https://whatever of the daemon 98 | #: src/passim-cli.c:312 99 | msgid "URI" 100 | msgstr "URI" 101 | 102 | #. TRANSLATORS: user mistyped the command 103 | #: src/passim-cli.c:351 src/passim-cli.c:381 104 | msgid "Invalid arguments" 105 | msgstr "Geçersiz argümanlar" 106 | 107 | #. TRANSLATORS: now sharing to the world 108 | #: src/passim-cli.c:368 109 | msgid "Published" 110 | msgstr "Yayınlanıyor" 111 | 112 | #. TRANSLATORS: no longer sharing with the world 113 | #: src/passim-cli.c:388 114 | msgid "Unpublished" 115 | msgstr "Yayınlanmıyor" 116 | 117 | #. TRANSLATORS: user mistyped the command 118 | #: src/passim-cli.c:426 119 | msgid "Invalid arguments, expected BASENAME HASH" 120 | msgstr "Geçersiz argümanlar, TEMEL-AD SAĞLAMA-TOPLAMI bekleniyor" 121 | 122 | #. TRANSLATORS: %1 is a filename, %2 is a internet address and %3 is 123 | #. the untranslated error phrase 124 | #: src/passim-cli.c:469 125 | #, c-format 126 | msgid "Failed to download %s from %s: %s" 127 | msgstr "%s indirilemedi (%s adresinden): %s" 128 | 129 | #. TRANSLATORS: %1 is a filename, %2 is a internet address 130 | #: src/passim-cli.c:483 131 | #, c-format 132 | msgid "Failed to download %s from %s as file checksum does not match" 133 | msgstr "Dosya sağlama toplamı eşleşmediği için %s indirilemedi (%s adresinden)" 134 | 135 | #. TRANSLATORS: %1 is a filename, %2 is size (e.g. '1.3 MB') and %3 is a type, (e.g. 136 | #. * 'application/zstd') 137 | #: src/passim-cli.c:502 138 | #, c-format 139 | msgid "Saved %s (%s of %s)" 140 | msgstr "%s kaydedildi (%s - %s)" 141 | 142 | #. TRANSLATORS: --version 143 | #: src/passim-cli.c:518 144 | msgid "Show project version" 145 | msgstr "Proje sürümünü göster" 146 | 147 | #: src/passim-cli.c:525 148 | msgid "Next reboot" 149 | msgstr "Sonraki yeniden başlatmada" 150 | 151 | #. TRANSLATORS: CLI action description 152 | #: src/passim-cli.c:540 153 | msgid "Show daemon status" 154 | msgstr "Sunucu durumunu göster" 155 | 156 | #. TRANSLATORS: CLI option example 157 | #: src/passim-cli.c:545 158 | msgid "FILENAME [MAX-AGE] [MAX-SHARE]" 159 | msgstr "DOSYA-ADI [AZAMİ-YAŞ] [AZAMİ-PAYLAŞIM]" 160 | 161 | #. TRANSLATORS: CLI action description 162 | #: src/passim-cli.c:547 163 | msgid "Publish an additional file" 164 | msgstr "Ek bir dosya yayınlayın" 165 | 166 | #. TRANSLATORS: CLI option example 167 | #: src/passim-cli.c:552 168 | msgid "HASH" 169 | msgstr "SAĞLAMA-TOPLAMI" 170 | 171 | #. TRANSLATORS: CLI action description 172 | #: src/passim-cli.c:554 173 | msgid "Unpublish an existing file" 174 | msgstr "Var olan bir dosyayı yayından kaldırın" 175 | 176 | #. TRANSLATORS: CLI option example 177 | #: src/passim-cli.c:559 178 | msgid "BASENAME HASH" 179 | msgstr "TEMEL-AD SAĞLAMA-TOPLAMI" 180 | 181 | #. TRANSLATORS: CLI action description 182 | #: src/passim-cli.c:561 183 | msgid "Download a file from a remote machine" 184 | msgstr "Uzak makineden bir dosya indirin" 185 | 186 | #. TRANSLATORS: CLI tool description 187 | #: src/passim-cli.c:568 188 | msgid "Interact with the local passimd process." 189 | msgstr "Yerel passimd programıyla etkileşim kurun." 190 | 191 | #. TRANSLATORS: CLI tool name 192 | #: src/passim-cli.c:570 193 | msgid "Passim CLI" 194 | msgstr "Passim komut satırı arayüzü" 195 | 196 | #. TRANSLATORS: we don't know what to do 197 | #: src/passim-cli.c:574 198 | msgid "Failed to parse arguments" 199 | msgstr "Argümanlar ayrıştırılamadı" 200 | 201 | #. TRANSLATORS: daemon failed to start 202 | #: src/passim-cli.c:582 203 | msgid "Failed to connect to daemon" 204 | msgstr "Sunucuya bağlanılamadı" 205 | 206 | #. TRANSLATORS: CLI tool 207 | #: src/passim-cli.c:589 208 | msgid "client version" 209 | msgstr "istemci sürümü" 210 | 211 | #. TRANSLATORS: server 212 | #: src/passim-cli.c:591 213 | msgid "daemon version" 214 | msgstr "sunucu sürümü" 215 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | 'org.freedesktop.Passim.xml', 3 | install_dir: datadir / 'dbus-1/interfaces', 4 | ) 5 | install_data( 6 | 'passim.1', 7 | install_dir: datadir / 'man/man1', 8 | ) 9 | 10 | executable( 11 | 'passimd', 12 | sources: [ 13 | 'passim-avahi.c', 14 | 'passim-avahi-service-browser.c', 15 | 'passim-avahi-service.c', 16 | 'passim-avahi-service-resolver.c', 17 | 'passim-common.c', 18 | 'passim-gnutls.c', 19 | 'passim-server.c', 20 | ], 21 | include_directories: [ 22 | root_incdir, 23 | passim_incdir, 24 | ], 25 | dependencies: [ 26 | libgio, 27 | libsoup, 28 | libgnutls, 29 | ], 30 | link_with: [ 31 | passim, 32 | ], 33 | install: true, 34 | install_dir: libexecdir, 35 | ) 36 | 37 | executable( 38 | 'passim', 39 | sources: [ 40 | 'passim-cli.c', 41 | 'passim-common.c', 42 | ], 43 | include_directories: [ 44 | root_incdir, 45 | passim_incdir, 46 | ], 47 | dependencies: [ 48 | libgio, 49 | libsoup, 50 | ], 51 | link_with: [ 52 | passim, 53 | ], 54 | install: true, 55 | install_dir: bindir, 56 | ) 57 | 58 | env = environment() 59 | env.set('G_TEST_SRCDIR', meson.current_source_dir()) 60 | env.set('G_TEST_BUILDDIR', meson.current_build_dir()) 61 | e = executable( 62 | 'passim-self-test', 63 | sources: [ 64 | 'passim-common.c', 65 | 'passim-self-test.c', 66 | ], 67 | include_directories: [ 68 | root_incdir, 69 | passim_incdir, 70 | ], 71 | dependencies: [ 72 | libgio 73 | ], 74 | link_with: [ 75 | passim 76 | ], 77 | c_args: [ 78 | '-DSRCDIR="' + meson.current_source_dir() + '"', 79 | '-DBUILDDIR="' + meson.current_build_dir() + '"', 80 | ], 81 | ) 82 | test('passim-self-test', e, is_parallel: false, timeout: 180, env: env) 83 | -------------------------------------------------------------------------------- /src/org.freedesktop.Passim.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | The interface used for interacting with Passim. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | The daemon version. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | The daemon web URI. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | The daemon auto-generated name, e.g. "Passim-0801". 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | The daemon current status. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | The total number of bytes saved by using this project. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | The carbon saving in kg CO₂e by using this project. 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Gets the hashes of index. 72 | NOTE: This can only be called by any user. 73 | 74 | 75 | 76 | 77 | 78 | 79 | An array of vardict. 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Adds a file to the index. 89 | NOTE: This can only be called by the root user. 90 | 91 | 92 | 93 | 94 | 95 | 96 | The file descriptor to read. 97 | 98 | 99 | 100 | 101 | 102 | 103 | The attributes in a dictionary. 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | Unpublish a file from the index. 113 | NOTE: This can only be called by the root user. 114 | 115 | 116 | 117 | 118 | 119 | 120 | The hash of the file to unpublish. 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | Some value on the interface has changed. 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/passim-avahi-service-browser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-avahi-service-browser.h" 10 | #include "passim-avahi-service.h" 11 | #include "passim-avahi.h" 12 | 13 | typedef struct { 14 | GDBusProxy *proxy; 15 | GPtrArray *items; /* of PassimAvahiService */ 16 | gchar *hash; 17 | gchar *object_path; 18 | gulong signal_id; 19 | } PassimAvahiServiceBrowserHelper; 20 | 21 | static void 22 | passim_avahi_service_browser_helper_free(PassimAvahiServiceBrowserHelper *helper) 23 | { 24 | if (helper->signal_id > 0) 25 | g_signal_handler_disconnect(helper->proxy, helper->signal_id); 26 | if (helper->proxy != NULL) 27 | g_object_unref(helper->proxy); 28 | g_ptr_array_unref(helper->items); 29 | g_free(helper->hash); 30 | g_free(helper->object_path); 31 | g_free(helper); 32 | } 33 | 34 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(PassimAvahiServiceBrowserHelper, 35 | passim_avahi_service_browser_helper_free) 36 | 37 | static void 38 | passim_avahi_service_browser_free_cb(GObject *source, GAsyncResult *res, gpointer user_data) 39 | { 40 | g_autoptr(GError) error = NULL; 41 | g_autoptr(GTask) task = G_TASK(user_data); 42 | g_autoptr(GVariant) val = NULL; 43 | PassimAvahiServiceBrowserHelper *helper = g_task_get_task_data(task); 44 | 45 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 46 | if (val == NULL) { 47 | g_task_return_error(task, g_steal_pointer(&error)); 48 | return; 49 | } 50 | if (helper->items->len == 0) { 51 | g_task_return_new_error(task, 52 | G_IO_ERROR, 53 | G_IO_ERROR_FAILED, 54 | "failed to find %s", 55 | helper->hash); 56 | return; 57 | } 58 | g_task_return_pointer(task, 59 | g_ptr_array_ref(helper->items), 60 | (GDestroyNotify)g_ptr_array_unref); 61 | } 62 | 63 | static void 64 | passim_avahi_service_browser_free(GTask *task) 65 | { 66 | PassimAvahiServiceBrowserHelper *helper = g_task_get_task_data(task); 67 | 68 | /* needed? */ 69 | if (helper->signal_id > 0) { 70 | g_signal_handler_disconnect(helper->proxy, helper->signal_id); 71 | helper->signal_id = 0; 72 | } 73 | g_dbus_proxy_call(helper->proxy, 74 | "Free", 75 | NULL, 76 | G_DBUS_CALL_FLAGS_NONE, 77 | PASSIM_SERVER_TIMEOUT, 78 | g_task_get_cancellable(task), 79 | passim_avahi_service_browser_free_cb, 80 | task); 81 | } 82 | 83 | static void 84 | passim_avahi_service_browser_signal_cb(GDBusProxy *proxy, 85 | const gchar *sender_name, 86 | const gchar *signal_name, 87 | GVariant *parameters, 88 | gpointer user_data) 89 | { 90 | GTask *task = G_TASK(user_data); 91 | PassimAvahiServiceBrowserHelper *helper = g_task_get_task_data(task); 92 | 93 | if (g_strcmp0(signal_name, "CacheExhausted") == 0) 94 | return; 95 | if (g_strcmp0(signal_name, "AllForNow") == 0) { 96 | passim_avahi_service_browser_free(task); 97 | return; 98 | } 99 | if (g_strcmp0(signal_name, "Failure") == 0) { 100 | const gchar *errmsg = NULL; 101 | g_variant_get(parameters, "(&s)", &errmsg); 102 | g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", errmsg); 103 | return; 104 | } 105 | if (g_strcmp0(signal_name, "ItemNew") == 0) { 106 | g_autoptr(PassimAvahiService) item = g_new0(PassimAvahiService, 1); 107 | g_variant_get(parameters, 108 | "(iisssu)", 109 | &item->interface, 110 | &item->protocol, 111 | &item->name, 112 | &item->type, 113 | &item->domain, 114 | &item->flags); 115 | if (item->flags & AVAHI_LOOKUP_RESULT_LOCAL) { 116 | g_debug("ignoring local result on interface %i", item->interface); 117 | return; 118 | } 119 | g_ptr_array_add(helper->items, g_steal_pointer(&item)); 120 | return; 121 | } 122 | g_warning("unhandled ServiceBrowser signal: %s %s", 123 | signal_name, 124 | g_variant_get_type_string(parameters)); 125 | } 126 | 127 | static void 128 | passim_avahi_service_browser_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) 129 | { 130 | GTask *task = G_TASK(user_data); /* unref when we get the signal */ 131 | g_autoptr(GError) error = NULL; 132 | g_autoptr(GVariant) val = NULL; 133 | 134 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 135 | if (val == NULL) { 136 | g_task_return_error(task, g_steal_pointer(&error)); 137 | g_object_unref(task); 138 | return; 139 | } 140 | } 141 | 142 | static void 143 | passim_avahi_service_browser_new_cb(GObject *source, GAsyncResult *res, gpointer user_data) 144 | { 145 | g_autoptr(GTask) task = G_TASK(user_data); 146 | g_autoptr(GError) error = NULL; 147 | PassimAvahiServiceBrowserHelper *helper = g_task_get_task_data(task); 148 | 149 | helper->proxy = g_dbus_proxy_new_for_bus_finish(res, &error); 150 | if (helper->proxy == NULL) { 151 | g_prefix_error(&error, "failed to use ServiceBrowser %s: ", helper->object_path); 152 | g_task_return_error(task, g_steal_pointer(&error)); 153 | return; 154 | } 155 | helper->signal_id = g_signal_connect(helper->proxy, 156 | "g-signal", 157 | G_CALLBACK(passim_avahi_service_browser_signal_cb), 158 | task); 159 | g_dbus_proxy_call(helper->proxy, 160 | "Start", 161 | NULL, 162 | G_DBUS_CALL_FLAGS_NONE, 163 | PASSIM_SERVER_TIMEOUT, 164 | g_task_get_cancellable(task), 165 | passim_avahi_service_browser_start_cb, 166 | g_object_ref(task)); 167 | } 168 | 169 | static void 170 | passim_avahi_service_browser_prepare_cb(GObject *source, GAsyncResult *res, gpointer user_data) 171 | { 172 | g_autoptr(GTask) task = G_TASK(user_data); 173 | g_autoptr(GError) error = NULL; 174 | g_autoptr(GVariant) val = NULL; 175 | PassimAvahiServiceBrowserHelper *helper = g_task_get_task_data(task); 176 | 177 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 178 | if (val == NULL) { 179 | g_prefix_error(&error, "failed to create a new ServiceBrowser: "); 180 | g_task_return_error(task, g_steal_pointer(&error)); 181 | return; 182 | } 183 | g_variant_get(val, "(o)", &helper->object_path); 184 | g_debug("connecting to %s", helper->object_path); 185 | g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, 186 | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, 187 | NULL, 188 | "org.freedesktop.Avahi", 189 | helper->object_path, 190 | "org.freedesktop.Avahi.ServiceBrowser", 191 | g_task_get_cancellable(task), 192 | passim_avahi_service_browser_new_cb, 193 | g_object_ref(task)); 194 | } 195 | 196 | void 197 | passim_avahi_service_browser_async(GDBusProxy *proxy, 198 | const gchar *hash, 199 | AvahiProtocol protocol, 200 | GCancellable *cancellable, 201 | GAsyncReadyCallback callback, 202 | gpointer callback_data) 203 | { 204 | g_autofree gchar *subtype = passim_avahi_build_subtype_for_hash(hash); 205 | g_autoptr(GTask) task = NULL; 206 | g_autoptr(PassimAvahiServiceBrowserHelper) helper = 207 | g_new0(PassimAvahiServiceBrowserHelper, 1); 208 | 209 | helper->hash = g_strdup(hash); 210 | helper->items = g_ptr_array_new_with_free_func((GDestroyNotify)passim_avahi_service_free); 211 | 212 | task = g_task_new(proxy, cancellable, callback, callback_data); 213 | g_task_set_task_data(task, 214 | g_steal_pointer(&helper), 215 | (GDestroyNotify)passim_avahi_service_browser_helper_free); 216 | g_dbus_proxy_call(proxy, 217 | "ServiceBrowserPrepare", 218 | g_variant_new("(iissu)", 219 | AVAHI_IF_UNSPEC, 220 | protocol, 221 | subtype, 222 | PASSIM_SERVER_DOMAIN, 223 | 0), /* flags */ 224 | G_DBUS_CALL_FLAGS_NONE, 225 | PASSIM_SERVER_TIMEOUT, 226 | cancellable, 227 | passim_avahi_service_browser_prepare_cb, 228 | g_steal_pointer(&task)); 229 | } 230 | 231 | /* element-type: PassimAvahiService */ 232 | GPtrArray * 233 | passim_avahi_service_browser_finish(GAsyncResult *res, GError **error) 234 | { 235 | g_return_val_if_fail(res != NULL, NULL); 236 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 237 | return g_task_propagate_pointer(G_TASK(res), error); 238 | } 239 | -------------------------------------------------------------------------------- /src/passim-avahi-service-browser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "passim-avahi.h" 12 | 13 | void 14 | passim_avahi_service_browser_async(GDBusProxy *proxy, 15 | const gchar *hash, 16 | AvahiProtocol protocol, 17 | GCancellable *cancellable, 18 | GAsyncReadyCallback callback, 19 | gpointer callback_data); 20 | GPtrArray * 21 | passim_avahi_service_browser_finish(GAsyncResult *res, GError **error); 22 | -------------------------------------------------------------------------------- /src/passim-avahi-service-resolver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-avahi-service-resolver.h" 10 | #include "passim-avahi.h" 11 | 12 | typedef struct { 13 | gchar *object_path; 14 | gchar *signal_name; 15 | GVariant *parameters; 16 | } PassimAvahiSignal; 17 | 18 | static void 19 | passim_avahi_signal_free(PassimAvahiSignal *signal) 20 | { 21 | g_free(signal->object_path); 22 | g_free(signal->signal_name); 23 | g_variant_unref(signal->parameters); 24 | g_free(signal); 25 | } 26 | 27 | typedef struct { 28 | GDBusProxy *proxy; 29 | gchar *object_path; 30 | gchar *address; 31 | gulong signal_id; 32 | GDBusConnection *connection; /* no-ref -- not needed with new Avahi */ 33 | guint subscription_id; /* not needed with new Avahi */ 34 | GPtrArray *signals; /* element-type PassimAvahiSignal -- not needed with new Avahi */ 35 | } PassimAvahiServiceResolverHelper; 36 | 37 | static void 38 | passim_avahi_service_resolver_helper_free(PassimAvahiServiceResolverHelper *helper) 39 | { 40 | if (helper->signal_id > 0) 41 | g_signal_handler_disconnect(helper->proxy, helper->signal_id); 42 | if (helper->proxy != NULL) 43 | g_object_unref(helper->proxy); 44 | if (helper->subscription_id != 0) 45 | g_dbus_connection_signal_unsubscribe(helper->connection, helper->subscription_id); 46 | g_ptr_array_unref(helper->signals); 47 | g_free(helper->address); 48 | g_free(helper->object_path); 49 | g_free(helper); 50 | } 51 | 52 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(PassimAvahiServiceResolverHelper, 53 | passim_avahi_service_resolver_helper_free) 54 | 55 | static void 56 | passim_avahi_service_resolver_free_cb(GObject *source, GAsyncResult *res, gpointer user_data) 57 | { 58 | g_autoptr(GError) error = NULL; 59 | g_autoptr(GTask) task = G_TASK(user_data); 60 | g_autoptr(GVariant) val = NULL; 61 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 62 | 63 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 64 | if (val == NULL) { 65 | g_task_return_error(task, g_steal_pointer(&error)); 66 | return; 67 | } 68 | g_task_return_pointer(task, g_steal_pointer(&helper->address), g_free); 69 | } 70 | 71 | static void 72 | passim_avahi_service_resolver_free(GTask *task) 73 | { 74 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 75 | 76 | /* needed? */ 77 | if (helper->signal_id > 0) { 78 | g_signal_handler_disconnect(helper->proxy, helper->signal_id); 79 | helper->signal_id = 0; 80 | } 81 | g_dbus_proxy_call(helper->proxy, 82 | "Free", 83 | NULL, 84 | G_DBUS_CALL_FLAGS_NONE, 85 | PASSIM_SERVER_TIMEOUT, 86 | g_task_get_cancellable(task), 87 | passim_avahi_service_resolver_free_cb, 88 | task); 89 | } 90 | 91 | static void 92 | passim_avahi_service_resolver_signal(GTask *task, const gchar *signal_name, GVariant *parameters) 93 | { 94 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 95 | if (g_strcmp0(signal_name, "Failure") == 0) { 96 | const gchar *errmsg = NULL; 97 | g_variant_get(parameters, "(&s)", &errmsg); 98 | g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", errmsg); 99 | return; 100 | } 101 | if (g_strcmp0(signal_name, "Found") == 0) { 102 | const gchar *host = NULL; 103 | guint16 port = 0; 104 | g_autoptr(GSocketAddress) socket_addr = NULL; 105 | g_variant_get(parameters, 106 | "(iissssisqaayu)", 107 | NULL, 108 | NULL, 109 | NULL, 110 | NULL, 111 | NULL, 112 | NULL, 113 | NULL, 114 | &host, 115 | &port, 116 | NULL, 117 | NULL); 118 | socket_addr = g_inet_socket_address_new_from_string(host, port); 119 | if (g_socket_address_get_family(socket_addr) == G_SOCKET_FAMILY_IPV6) { 120 | helper->address = g_strdup_printf("[%s]:%i", host, port); 121 | } else { 122 | helper->address = g_strdup_printf("%s:%i", host, port); 123 | } 124 | passim_avahi_service_resolver_free(task); 125 | return; 126 | } 127 | g_warning("unhandled ServiceResolver signal: %s %s", 128 | signal_name, 129 | g_variant_get_type_string(parameters)); 130 | } 131 | 132 | static void 133 | passim_avahi_service_resolver_signal_cb(GDBusProxy *proxy, 134 | const gchar *sender_name, 135 | const gchar *signal_name, 136 | GVariant *parameters, 137 | gpointer user_data) 138 | { 139 | GTask *task = G_TASK(user_data); 140 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 141 | 142 | /* if we got here then we're either lucky or running with an Avahi that includes the 143 | * fix in https://github.com/lathiat/avahi/pull/468 */ 144 | if (helper->subscription_id != 0) { 145 | g_dbus_connection_signal_unsubscribe(helper->connection, helper->subscription_id); 146 | helper->subscription_id = 0; 147 | } 148 | passim_avahi_service_resolver_signal(task, signal_name, parameters); 149 | } 150 | 151 | static void 152 | passim_avahi_service_resolver_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) 153 | { 154 | GTask *task = G_TASK(user_data); /* unref when we get the signal */ 155 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 156 | g_autoptr(GError) error = NULL; 157 | g_autoptr(GVariant) val = NULL; 158 | 159 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 160 | if (val == NULL) { 161 | g_task_return_error(task, g_steal_pointer(&error)); 162 | g_object_unref(task); 163 | return; 164 | } 165 | g_debug("started %s", helper->object_path); 166 | for (guint i = 0; i < helper->signals->len; i++) { 167 | PassimAvahiSignal *signal = g_ptr_array_index(helper->signals, i); 168 | if (g_strcmp0(signal->object_path, helper->object_path) != 0) { 169 | g_debug("ignoring %s from %s", signal->signal_name, helper->object_path); 170 | continue; 171 | } 172 | g_info("working around Ahavi bug: %s sent before Start(), see " 173 | "https://github.com/lathiat/avahi/pull/468", 174 | signal->signal_name); 175 | passim_avahi_service_resolver_signal(task, signal->signal_name, signal->parameters); 176 | } 177 | } 178 | 179 | static void 180 | passim_avahi_service_resolver_new_cb(GObject *source, GAsyncResult *res, gpointer user_data) 181 | { 182 | g_autoptr(GTask) task = G_TASK(user_data); 183 | g_autoptr(GError) error = NULL; 184 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 185 | 186 | helper->proxy = g_dbus_proxy_new_for_bus_finish(res, &error); 187 | if (helper->proxy == NULL) { 188 | g_prefix_error(&error, "failed to use ServiceResolver %s: ", helper->object_path); 189 | g_task_return_error(task, g_steal_pointer(&error)); 190 | return; 191 | } 192 | helper->signal_id = g_signal_connect(helper->proxy, 193 | "g-signal", 194 | G_CALLBACK(passim_avahi_service_resolver_signal_cb), 195 | task); 196 | g_dbus_proxy_call(helper->proxy, 197 | "Start", 198 | NULL, 199 | G_DBUS_CALL_FLAGS_NONE, 200 | PASSIM_SERVER_TIMEOUT, 201 | g_task_get_cancellable(task), 202 | passim_avahi_service_resolver_start_cb, 203 | g_object_ref(task)); 204 | } 205 | 206 | static void 207 | passim_avahi_service_resolver_prepare_cb(GObject *source, GAsyncResult *res, gpointer user_data) 208 | { 209 | g_autoptr(GTask) task = G_TASK(user_data); 210 | g_autoptr(GError) error = NULL; 211 | g_autoptr(GVariant) val = NULL; 212 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 213 | 214 | val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); 215 | if (val == NULL) { 216 | g_prefix_error(&error, "failed to create a new ServiceResolver: "); 217 | g_task_return_error(task, g_steal_pointer(&error)); 218 | return; 219 | } 220 | g_variant_get(val, "(o)", &helper->object_path); 221 | g_debug("connecting to %s", helper->object_path); 222 | g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, 223 | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, 224 | NULL, 225 | "org.freedesktop.Avahi", 226 | helper->object_path, 227 | "org.freedesktop.Avahi.ServiceResolver", 228 | g_task_get_cancellable(task), 229 | passim_avahi_service_resolver_new_cb, 230 | g_object_ref(task)); 231 | } 232 | 233 | static void 234 | passim_avahi_service_resolver_signal_fallback_cb(GDBusConnection *connection, 235 | const gchar *sender_name, 236 | const gchar *object_path, 237 | const gchar *interface_name, 238 | const gchar *signal_name, 239 | GVariant *parameters, 240 | gpointer user_data) 241 | { 242 | GTask *task = G_TASK(user_data); 243 | PassimAvahiServiceResolverHelper *helper = g_task_get_task_data(task); 244 | PassimAvahiSignal *signal = g_new0(PassimAvahiSignal, 1); 245 | signal->object_path = g_strdup(object_path); 246 | signal->signal_name = g_strdup(signal_name); 247 | signal->parameters = g_variant_ref(parameters); 248 | g_ptr_array_add(helper->signals, signal); 249 | } 250 | 251 | void 252 | passim_avahi_service_resolver_async(GDBusProxy *proxy, 253 | PassimAvahiService *service, 254 | GCancellable *cancellable, 255 | GAsyncReadyCallback callback, 256 | gpointer callback_data) 257 | { 258 | g_autoptr(GTask) task = NULL; 259 | g_autoptr(PassimAvahiServiceResolverHelper) helper = 260 | g_new0(PassimAvahiServiceResolverHelper, 1); 261 | 262 | task = g_task_new(proxy, cancellable, callback, callback_data); 263 | 264 | /* work around a bug in Avahi, see https://github.com/lathiat/avahi/issues/446 */ 265 | helper->signals = g_ptr_array_new_with_free_func((GDestroyNotify)passim_avahi_signal_free); 266 | helper->connection = g_dbus_proxy_get_connection(proxy); 267 | helper->subscription_id = 268 | g_dbus_connection_signal_subscribe(helper->connection, 269 | g_dbus_proxy_get_name(proxy), 270 | "org.freedesktop.Avahi.ServiceResolver", 271 | NULL, 272 | NULL, 273 | NULL, /* argv */ 274 | G_DBUS_SIGNAL_FLAGS_NONE, 275 | passim_avahi_service_resolver_signal_fallback_cb, 276 | g_object_ref(task), 277 | (GDestroyNotify)g_object_unref); 278 | g_task_set_task_data(task, 279 | g_steal_pointer(&helper), 280 | (GDestroyNotify)passim_avahi_service_resolver_helper_free); 281 | g_dbus_proxy_call(proxy, 282 | "ServiceResolverPrepare", 283 | g_variant_new("(iisssiu)", 284 | service->interface, 285 | service->protocol, 286 | service->name, 287 | service->type, 288 | service->domain, 289 | service->protocol, 290 | 0), /* flags */ 291 | G_DBUS_CALL_FLAGS_NONE, 292 | PASSIM_SERVER_TIMEOUT, 293 | cancellable, 294 | passim_avahi_service_resolver_prepare_cb, 295 | g_steal_pointer(&task)); 296 | } 297 | 298 | gchar * 299 | passim_avahi_service_resolver_finish(GAsyncResult *res, GError **error) 300 | { 301 | g_return_val_if_fail(res != NULL, NULL); 302 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 303 | return g_task_propagate_pointer(G_TASK(res), error); 304 | } 305 | -------------------------------------------------------------------------------- /src/passim-avahi-service-resolver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "passim-avahi-service.h" 10 | 11 | void 12 | passim_avahi_service_resolver_async(GDBusProxy *proxy, 13 | PassimAvahiService *service, 14 | GCancellable *cancellable, 15 | GAsyncReadyCallback callback, 16 | gpointer callback_data); 17 | gchar * 18 | passim_avahi_service_resolver_finish(GAsyncResult *res, GError **error); 19 | -------------------------------------------------------------------------------- /src/passim-avahi-service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-avahi-service.h" 10 | 11 | void 12 | passim_avahi_service_print(PassimAvahiService *service) 13 | { 14 | g_debug("Service { iface:%i, proto:%i, name:%s, type:%s, domain:%s, flags:%u }", 15 | service->interface, 16 | service->protocol, 17 | service->name, 18 | service->type, 19 | service->domain, 20 | service->flags); 21 | } 22 | 23 | void 24 | passim_avahi_service_free(PassimAvahiService *service) 25 | { 26 | g_free(service->name); 27 | g_free(service->type); 28 | g_free(service->domain); 29 | g_free(service); 30 | } 31 | -------------------------------------------------------------------------------- /src/passim-avahi-service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | typedef struct { 12 | gint32 interface; 13 | gint32 protocol; 14 | gchar *name; 15 | gchar *type; 16 | gchar *domain; 17 | guint32 flags; 18 | } PassimAvahiService; 19 | 20 | void 21 | passim_avahi_service_free(PassimAvahiService *service); 22 | void 23 | passim_avahi_service_print(PassimAvahiService *service); 24 | 25 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(PassimAvahiService, passim_avahi_service_free) 26 | -------------------------------------------------------------------------------- /src/passim-avahi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-avahi-service-browser.h" 10 | #include "passim-avahi-service-resolver.h" 11 | #include "passim-avahi-service.h" 12 | #include "passim-avahi.h" 13 | 14 | struct _PassimAvahi { 15 | GObject parent_instance; 16 | gchar *name; 17 | GKeyFile *config; 18 | GDBusProxy *proxy; 19 | GDBusProxy *proxy_eg; 20 | }; 21 | 22 | G_DEFINE_TYPE(PassimAvahi, passim_avahi, G_TYPE_OBJECT) 23 | 24 | const gchar * 25 | passim_avahi_get_name(PassimAvahi *self) 26 | { 27 | return self->name; 28 | } 29 | 30 | static gchar * 31 | passim_avahi_truncate_hash(const gchar *hash) 32 | { 33 | return g_strndup(hash, 60); 34 | } 35 | 36 | gchar * 37 | passim_avahi_build_subtype_for_hash(const gchar *hash) 38 | { 39 | g_autofree gchar *truncated_hash = passim_avahi_truncate_hash(hash); 40 | return g_strdup_printf("_%s._sub.%s", truncated_hash, PASSIM_SERVER_TYPE); 41 | } 42 | 43 | static void 44 | passim_avahi_proxy_signal_cb(GDBusProxy *proxy, 45 | char *sender_name, 46 | char *signal_name, 47 | GVariant *parameters, 48 | gpointer user_data) 49 | { 50 | g_info("signal_name: %s %s", signal_name, g_variant_get_type_string(parameters)); 51 | } 52 | 53 | gboolean 54 | passim_avahi_connect(PassimAvahi *self, GError **error) 55 | { 56 | const gchar *object_path = NULL; 57 | g_autoptr(GVariant) object_pathv = NULL; 58 | 59 | g_return_val_if_fail(PASSIM_IS_AVAHI(self), FALSE); 60 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 61 | g_return_val_if_fail(self->proxy == NULL, FALSE); 62 | 63 | /* connect to daemon */ 64 | self->proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, 65 | G_DBUS_PROXY_FLAGS_NONE, 66 | NULL, 67 | "org.freedesktop.Avahi", 68 | "/", 69 | "org.freedesktop.Avahi.Server2", 70 | NULL, 71 | error); 72 | if (self->proxy == NULL) { 73 | g_prefix_error(error, "failed to contact Avahi: "); 74 | return FALSE; 75 | } 76 | g_signal_connect(self->proxy, "g-signal", G_CALLBACK(passim_avahi_proxy_signal_cb), self); 77 | 78 | /* create our entrygroup */ 79 | object_pathv = g_dbus_proxy_call_sync(self->proxy, 80 | "EntryGroupNew", 81 | NULL, 82 | G_DBUS_CALL_FLAGS_NONE, 83 | PASSIM_SERVER_TIMEOUT, 84 | NULL, 85 | error); 86 | if (object_pathv == NULL) { 87 | g_prefix_error(error, "failed to create a new entry group: "); 88 | return FALSE; 89 | } 90 | g_variant_get(object_pathv, "(&o)", &object_path); 91 | g_debug("connecting to %s", object_path); 92 | self->proxy_eg = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, 93 | G_DBUS_PROXY_FLAGS_NONE, 94 | NULL, 95 | "org.freedesktop.Avahi", 96 | object_path, 97 | "org.freedesktop.Avahi.EntryGroup", 98 | NULL, 99 | error); 100 | if (self->proxy_eg == NULL) { 101 | g_prefix_error(error, "failed to use EntryGroup %s: ", object_path); 102 | return FALSE; 103 | } 104 | g_signal_connect(self->proxy_eg, 105 | "g-signal", 106 | G_CALLBACK(passim_avahi_proxy_signal_cb), 107 | self); 108 | 109 | /* success */ 110 | return TRUE; 111 | } 112 | 113 | static gboolean 114 | passim_avahi_register_subtype(PassimAvahi *self, 115 | const gchar *hash, 116 | AvahiProtocol protocol, 117 | GError **error) 118 | { 119 | g_autofree gchar *subtype = passim_avahi_build_subtype_for_hash(hash); 120 | g_autoptr(GVariant) val = NULL; 121 | 122 | g_debug("adding subtype %s", subtype); 123 | val = g_dbus_proxy_call_sync(self->proxy_eg, 124 | "AddServiceSubtype", 125 | g_variant_new("(iiussss)", 126 | AVAHI_IF_UNSPEC, 127 | protocol, 128 | 0 /* flags */, 129 | self->name, 130 | PASSIM_SERVER_TYPE, 131 | PASSIM_SERVER_DOMAIN, 132 | subtype), 133 | G_DBUS_CALL_FLAGS_NONE, 134 | PASSIM_SERVER_TIMEOUT, 135 | NULL, 136 | error); 137 | if (val == NULL) { 138 | g_prefix_error(error, "failed to add service subtype: "); 139 | return FALSE; 140 | } 141 | return TRUE; 142 | } 143 | 144 | gboolean 145 | passim_avahi_unregister(PassimAvahi *self, GError **error) 146 | { 147 | g_autoptr(GVariant) val1 = NULL; 148 | 149 | g_return_val_if_fail(PASSIM_IS_AVAHI(self), FALSE); 150 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 151 | g_return_val_if_fail(self->proxy != NULL, FALSE); 152 | 153 | g_debug("resetting %s", self->name); 154 | val1 = g_dbus_proxy_call_sync(self->proxy_eg, 155 | "Reset", 156 | NULL, 157 | G_DBUS_CALL_FLAGS_NONE, 158 | PASSIM_SERVER_TIMEOUT, 159 | NULL, 160 | error); 161 | if (val1 == NULL) { 162 | g_prefix_error(error, "failed to reset entry group: "); 163 | return FALSE; 164 | } 165 | 166 | /* success */ 167 | return TRUE; 168 | } 169 | 170 | gboolean 171 | passim_avahi_register(PassimAvahi *self, gchar **keys, AvahiProtocol protocol, GError **error) 172 | { 173 | g_autoptr(GVariant) val2 = NULL; 174 | g_autoptr(GVariant) val4 = NULL; 175 | 176 | g_return_val_if_fail(PASSIM_IS_AVAHI(self), FALSE); 177 | g_return_val_if_fail(keys != NULL, FALSE); 178 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 179 | g_return_val_if_fail(self->proxy != NULL, FALSE); 180 | 181 | if (!passim_avahi_unregister(self, error)) 182 | return FALSE; 183 | val2 = g_dbus_proxy_call_sync(self->proxy_eg, 184 | "AddService", 185 | g_variant_new("(iiussssqaay)", 186 | AVAHI_IF_UNSPEC, 187 | protocol, 188 | 0 /* flags */, 189 | self->name, 190 | PASSIM_SERVER_TYPE, 191 | PASSIM_SERVER_DOMAIN, 192 | PASSIM_SERVER_HOST, 193 | passim_config_get_port(self->config), 194 | NULL), 195 | G_DBUS_CALL_FLAGS_NONE, 196 | PASSIM_SERVER_TIMEOUT, 197 | NULL, 198 | error); 199 | if (val2 == NULL) { 200 | g_prefix_error(error, "failed to add service: "); 201 | return FALSE; 202 | } 203 | for (guint i = 0; keys[i] != NULL; i++) { 204 | if (!passim_avahi_register_subtype(self, keys[i], protocol, error)) 205 | return FALSE; 206 | } 207 | val4 = g_dbus_proxy_call_sync(self->proxy_eg, 208 | "Commit", 209 | NULL, 210 | G_DBUS_CALL_FLAGS_NONE, 211 | PASSIM_SERVER_TIMEOUT, 212 | NULL, 213 | error); 214 | if (val4 == NULL) { 215 | g_prefix_error(error, "failed to commit entry group: "); 216 | return FALSE; 217 | } 218 | 219 | /* success */ 220 | return TRUE; 221 | } 222 | 223 | typedef struct { 224 | GDBusProxy *proxy; 225 | gchar *object_path; 226 | gchar *hash; 227 | gulong signal_id; 228 | GPtrArray *items; /* of PassimAvahiService */ 229 | GPtrArray *addresses; /* of utf-8 */ 230 | } PassimAvahiFindHelper; 231 | 232 | static void 233 | passim_avahi_service_resolve_next(GTask *task); 234 | 235 | static void 236 | passim_avahi_find_helper_free(PassimAvahiFindHelper *helper) 237 | { 238 | if (helper->proxy != NULL) 239 | g_object_unref(helper->proxy); 240 | if (helper->items != NULL) 241 | g_ptr_array_unref(helper->items); 242 | if (helper->addresses != NULL) 243 | g_ptr_array_unref(helper->addresses); 244 | g_free(helper->hash); 245 | g_free(helper->object_path); 246 | g_free(helper); 247 | } 248 | 249 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(PassimAvahiFindHelper, passim_avahi_find_helper_free) 250 | 251 | static void 252 | passim_avahi_service_resolve_cb(GObject *source, GAsyncResult *res, gpointer user_data) 253 | { 254 | g_autofree gchar *address = NULL; 255 | g_autoptr(GError) error = NULL; 256 | g_autoptr(GTask) task = G_TASK(user_data); 257 | PassimAvahiFindHelper *helper = g_task_get_task_data(task); 258 | 259 | address = passim_avahi_service_resolver_finish(res, &error); 260 | if (address == NULL) { 261 | g_task_return_error(task, g_steal_pointer(&error)); 262 | return; 263 | } 264 | if (g_ptr_array_find_with_equal_func(helper->addresses, address, g_str_equal, NULL)) { 265 | g_debug("already found %s, ignoring", address); 266 | } else { 267 | g_debug("new address %s, adding", address); 268 | g_ptr_array_add(helper->addresses, g_steal_pointer(&address)); 269 | } 270 | passim_avahi_service_resolve_next(g_steal_pointer(&task)); 271 | } 272 | 273 | static void 274 | passim_avahi_service_resolve_item(GTask *task, PassimAvahiService *item) 275 | { 276 | PassimAvahi *self = PASSIM_AVAHI(g_task_get_source_object(task)); 277 | 278 | g_debug( 279 | "ServiceResolverPrepare{ iface:%i, proto:%i, name:%s, type:%s, domain:%s, flags:%u }", 280 | item->interface, 281 | item->protocol, 282 | item->name, 283 | item->type, 284 | item->domain, 285 | item->flags); 286 | passim_avahi_service_resolver_async(self->proxy, 287 | item, 288 | g_task_get_cancellable(task), 289 | passim_avahi_service_resolve_cb, 290 | task); 291 | } 292 | 293 | static void 294 | passim_avahi_service_resolve_next(GTask *task) 295 | { 296 | PassimAvahiFindHelper *helper = g_task_get_task_data(task); 297 | PassimAvahiService *item; 298 | 299 | if (helper->items->len == 0) { 300 | if (helper->addresses->len > 0) { 301 | g_task_return_pointer(task, 302 | g_steal_pointer(&helper->addresses), 303 | (GDestroyNotify)g_ptr_array_unref); 304 | g_object_unref(task); 305 | return; 306 | } 307 | g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot find hash"); 308 | g_object_unref(task); 309 | return; 310 | } 311 | 312 | /* resolve the next one */ 313 | item = g_ptr_array_steal_index(helper->items, 0); 314 | passim_avahi_service_resolve_item(task, item); 315 | } 316 | 317 | static void 318 | passim_avahi_service_browser_cb(GObject *source, GAsyncResult *res, gpointer user_data) 319 | { 320 | g_autoptr(GTask) task = G_TASK(user_data); 321 | g_autoptr(GError) error = NULL; 322 | PassimAvahiFindHelper *helper = g_task_get_task_data(task); 323 | 324 | helper->items = passim_avahi_service_browser_finish(res, &error); 325 | if (helper->items == NULL) { 326 | g_task_return_error(task, g_steal_pointer(&error)); 327 | return; 328 | } 329 | for (guint i = 0; i < helper->items->len; i++) { 330 | PassimAvahiService *item = g_ptr_array_index(helper->items, i); 331 | passim_avahi_service_print(item); 332 | } 333 | passim_avahi_service_resolve_next(g_steal_pointer(&task)); 334 | } 335 | 336 | void 337 | passim_avahi_find_async(PassimAvahi *self, 338 | const gchar *hash, 339 | AvahiProtocol protocol, 340 | GCancellable *cancellable, 341 | GAsyncReadyCallback callback, 342 | gpointer callback_data) 343 | { 344 | g_autoptr(GTask) task = NULL; 345 | g_autofree gchar *truncated_hash = passim_avahi_truncate_hash(hash); 346 | g_autoptr(PassimAvahiFindHelper) helper = g_new0(PassimAvahiFindHelper, 1); 347 | 348 | g_return_if_fail(PASSIM_IS_AVAHI(self)); 349 | g_return_if_fail(hash != NULL); 350 | g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); 351 | g_return_if_fail(self->proxy != NULL); 352 | 353 | helper->hash = g_strdup(hash); 354 | helper->addresses = g_ptr_array_new_with_free_func(g_free); 355 | 356 | task = g_task_new(self, cancellable, callback, callback_data); 357 | g_task_set_task_data(task, 358 | g_steal_pointer(&helper), 359 | (GDestroyNotify)passim_avahi_find_helper_free); 360 | passim_avahi_service_browser_async(self->proxy, 361 | truncated_hash, 362 | protocol, 363 | cancellable, 364 | passim_avahi_service_browser_cb, 365 | g_steal_pointer(&task)); 366 | } 367 | 368 | /* element-type utf-8 */ 369 | GPtrArray * 370 | passim_avahi_find_finish(PassimAvahi *self, GAsyncResult *res, GError **error) 371 | { 372 | g_return_val_if_fail(PASSIM_IS_AVAHI(self), NULL); 373 | g_return_val_if_fail(g_task_is_valid(res, self), NULL); 374 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 375 | return g_task_propagate_pointer(G_TASK(res), error); 376 | } 377 | 378 | static void 379 | passim_avahi_init(PassimAvahi *self) 380 | { 381 | self->name = 382 | g_strdup_printf("%s-%04X", "Passim", (guint)g_random_int_range(0, G_MAXUINT16)); 383 | } 384 | 385 | static void 386 | passim_avahi_finalize(GObject *obj) 387 | { 388 | PassimAvahi *self = PASSIM_AVAHI(obj); 389 | g_free(self->name); 390 | g_key_file_unref(self->config); 391 | if (self->proxy != NULL) 392 | g_object_unref(self->proxy); 393 | if (self->proxy_eg != NULL) 394 | g_object_unref(self->proxy_eg); 395 | G_OBJECT_CLASS(passim_avahi_parent_class)->finalize(obj); 396 | } 397 | 398 | static void 399 | passim_avahi_class_init(PassimAvahiClass *klass) 400 | { 401 | GObjectClass *object_class = G_OBJECT_CLASS(klass); 402 | object_class->finalize = passim_avahi_finalize; 403 | } 404 | 405 | PassimAvahi * 406 | passim_avahi_new(GKeyFile *config) 407 | { 408 | PassimAvahi *self; 409 | self = g_object_new(PASSIM_TYPE_AVAHI, NULL); 410 | self->config = g_key_file_ref(config); 411 | return PASSIM_AVAHI(self); 412 | } 413 | -------------------------------------------------------------------------------- /src/passim-avahi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "passim-common.h" 10 | 11 | #define PASSIM_TYPE_AVAHI (passim_avahi_get_type()) 12 | G_DECLARE_FINAL_TYPE(PassimAvahi, passim_avahi, PASSIM, AVAHI, GObject) 13 | 14 | #define AVAHI_IF_UNSPEC -1 15 | 16 | typedef enum { 17 | AVAHI_PROTO_INET = 0, /* IPv4 */ 18 | AVAHI_PROTO_INET6 = 1, /* IPv6 */ 19 | AVAHI_PROTO_UNSPEC = -1, 20 | } AvahiProtocol; 21 | 22 | typedef enum { 23 | AVAHI_LOOKUP_USE_WIDE_AREA = 1, 24 | AVAHI_LOOKUP_USE_MULTICAST = 2, 25 | AVAHI_LOOKUP_NO_TXT = 4, 26 | AVAHI_LOOKUP_NO_ADDRESS = 8, 27 | } AvahiLookupFlags; 28 | 29 | typedef enum { 30 | AVAHI_LOOKUP_RESULT_CACHED = 1, 31 | AVAHI_LOOKUP_RESULT_WIDE_AREA = 2, 32 | AVAHI_LOOKUP_RESULT_MULTICAST = 4, 33 | AVAHI_LOOKUP_RESULT_LOCAL = 8, 34 | AVAHI_LOOKUP_RESULT_OUR_OWN = 16, 35 | AVAHI_LOOKUP_RESULT_STATIC = 32, 36 | } AvahiLookupResultFlags; 37 | 38 | #define PASSIM_SERVER_DOMAIN "" 39 | #define PASSIM_SERVER_HOST "" 40 | #define PASSIM_SERVER_TYPE "_cache._tcp" 41 | #define PASSIM_SERVER_TIMEOUT 150 /* ms */ 42 | 43 | PassimAvahi * 44 | passim_avahi_new(GKeyFile *config); 45 | gboolean 46 | passim_avahi_connect(PassimAvahi *self, GError **error); 47 | gboolean 48 | passim_avahi_unregister(PassimAvahi *self, GError **error); 49 | gboolean 50 | passim_avahi_register(PassimAvahi *self, gchar **keys, AvahiProtocol protocol, GError **error); 51 | const gchar * 52 | passim_avahi_get_name(PassimAvahi *self); 53 | gchar * 54 | passim_avahi_build_subtype_for_hash(const gchar *hash); 55 | 56 | void 57 | passim_avahi_find_async(PassimAvahi *self, 58 | const gchar *hash, 59 | AvahiProtocol protocol, 60 | GCancellable *cancellable, 61 | GAsyncReadyCallback callback, 62 | gpointer callback_data); 63 | GPtrArray * 64 | passim_avahi_find_finish(PassimAvahi *self, GAsyncResult *res, GError **error); 65 | -------------------------------------------------------------------------------- /src/passim-common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include 10 | 11 | #include "passim-common.h" 12 | 13 | #define PASSIM_CONFIG_GROUP "daemon" 14 | #define PASSIM_CONFIG_PORT "Port" 15 | #define PASSIM_CONFIG_IPV6 "IPv6" 16 | #define PASSIM_CONFIG_PATH "Path" 17 | #define PASSIM_CONFIG_MAX_ITEM_SIZE "MaxItemSize" 18 | #define PASSIM_CONFIG_CARBON_COST "CarbonCost" 19 | 20 | const gchar * 21 | passim_status_to_string(PassimStatus status) 22 | { 23 | if (status == PASSIM_STATUS_STARTING) 24 | return "starting"; 25 | if (status == PASSIM_STATUS_LOADING) 26 | return "loading"; 27 | if (status == PASSIM_STATUS_DISABLED_METERED) 28 | return "disabled-metered"; 29 | if (status == PASSIM_STATUS_RUNNING) 30 | return "running"; 31 | return NULL; 32 | } 33 | 34 | GKeyFile * 35 | passim_config_load(GError **error) 36 | { 37 | g_autoptr(GKeyFile) kf = g_key_file_new(); 38 | g_autofree gchar *fn = g_build_filename(PACKAGE_SYSCONFDIR, "passim.conf", NULL); 39 | 40 | if (g_file_test(fn, G_FILE_TEST_EXISTS)) { 41 | if (!g_key_file_load_from_file(kf, fn, G_KEY_FILE_NONE, error)) 42 | return NULL; 43 | } else { 44 | g_debug("not loading %s as it does not exist", fn); 45 | } 46 | 47 | if (!g_key_file_has_key(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PORT, NULL)) 48 | g_key_file_set_integer(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PORT, 27500); 49 | if (!g_key_file_has_key(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_MAX_ITEM_SIZE, NULL)) { 50 | g_key_file_set_uint64(kf, 51 | PASSIM_CONFIG_GROUP, 52 | PASSIM_CONFIG_MAX_ITEM_SIZE, 53 | 100 * 1024 * 1024); 54 | } 55 | if (!g_key_file_has_key(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PATH, NULL)) { 56 | g_autofree gchar *path = 57 | g_build_filename(PACKAGE_LOCALSTATEDIR, "lib", PACKAGE_NAME, "data", NULL); 58 | g_key_file_set_string(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PATH, path); 59 | } 60 | 61 | return g_steal_pointer(&kf); 62 | } 63 | 64 | guint16 65 | passim_config_get_port(GKeyFile *kf) 66 | { 67 | return g_key_file_get_integer(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PORT, NULL); 68 | } 69 | 70 | gboolean 71 | passim_config_get_ipv6(GKeyFile *kf) 72 | { 73 | return g_key_file_get_boolean(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_IPV6, NULL); 74 | } 75 | 76 | gsize 77 | passim_config_get_max_item_size(GKeyFile *kf) 78 | { 79 | return g_key_file_get_uint64(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_MAX_ITEM_SIZE, NULL); 80 | } 81 | 82 | gdouble 83 | passim_config_get_carbon_cost(GKeyFile *kf) 84 | { 85 | gdouble carbon_cost = 86 | g_key_file_get_double(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_CARBON_COST, NULL); 87 | if (carbon_cost < 0.00001) { 88 | /* using 89 | * https://www.carbonbrief.org/factcheck-what-is-the-carbon-footprint-of-streaming-video-on-netflix/ 90 | * we can see that 0.018 kg CO2e for 30 mins, where 3 GB/hr -- so this gives a 91 | * kg/GB of ~0.018 kg x (3h / 2) */ 92 | carbon_cost = 0.026367; 93 | } 94 | return carbon_cost; 95 | } 96 | 97 | gchar * 98 | passim_config_get_path(GKeyFile *kf) 99 | { 100 | return g_key_file_get_string(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_PATH, NULL); 101 | } 102 | 103 | gboolean 104 | passim_xattr_set_string(const gchar *filename, 105 | const gchar *name, 106 | const gchar *value, 107 | GError **error) 108 | { 109 | ssize_t rc = setxattr(filename, name, value, strlen(value), XATTR_CREATE); 110 | if (rc < 0) { 111 | g_set_error(error, 112 | G_IO_ERROR, 113 | g_io_error_from_errno(errno), 114 | "failed to set %s: %s", 115 | name, 116 | strerror(errno)); 117 | return FALSE; 118 | } 119 | return TRUE; 120 | } 121 | 122 | gchar * 123 | passim_xattr_get_string(const gchar *filename, const gchar *name, GError **error) 124 | { 125 | ssize_t rc; 126 | g_autofree gchar *buf = NULL; 127 | 128 | rc = getxattr(filename, name, NULL, 0); 129 | if (rc < 0) { 130 | if (errno == ENODATA) 131 | return g_strdup(""); 132 | g_set_error(error, 133 | G_IO_ERROR, 134 | g_io_error_from_errno(errno), 135 | "failed to get %s: %s", 136 | name, 137 | strerror(errno)); 138 | return NULL; 139 | } 140 | if (rc == 0) { 141 | g_set_error(error, 142 | G_IO_ERROR, 143 | G_IO_ERROR_INVALID_DATA, 144 | "invalid data for %s", 145 | name); 146 | return NULL; 147 | } 148 | 149 | /* copy out with appended NUL */ 150 | buf = g_new0(gchar, rc + 1); 151 | rc = getxattr(filename, name, buf, rc + 1); 152 | if (rc < 0) { 153 | g_set_error(error, 154 | G_IO_ERROR, 155 | g_io_error_from_errno(errno), 156 | "failed to get %s: %s", 157 | name, 158 | strerror(errno)); 159 | return NULL; 160 | } 161 | return g_steal_pointer(&buf); 162 | } 163 | 164 | gboolean 165 | passim_xattr_set_uint32(const gchar *filename, const gchar *name, guint32 value, GError **error) 166 | { 167 | ssize_t rc = setxattr(filename, name, &value, sizeof(value), XATTR_CREATE); 168 | if (rc < 0) { 169 | g_set_error(error, 170 | G_IO_ERROR, 171 | g_io_error_from_errno(errno), 172 | "failed to set %s: %s", 173 | name, 174 | strerror(errno)); 175 | return FALSE; 176 | } 177 | return TRUE; 178 | } 179 | 180 | guint32 181 | passim_xattr_get_uint32(const gchar *filename, 182 | const gchar *name, 183 | guint32 value_fallback, 184 | GError **error) 185 | { 186 | guint32 value = 0; 187 | ssize_t rc = getxattr(filename, name, &value, sizeof(value)); 188 | if (rc < 0) { 189 | if (errno == ENODATA) { 190 | g_debug("using fallback %s=%u for %s", 191 | name, 192 | (guint)value_fallback, 193 | filename); 194 | return value_fallback; 195 | } 196 | g_set_error(error, 197 | G_IO_ERROR, 198 | g_io_error_from_errno(errno), 199 | "failed to get %s: %s", 200 | name, 201 | strerror(errno)); 202 | return G_MAXUINT32; 203 | } 204 | if (value == G_MAXUINT32) { 205 | g_set_error(error, 206 | G_IO_ERROR, 207 | G_IO_ERROR_INVALID_DATA, 208 | "invalid data for %s", 209 | name); 210 | return G_MAXUINT32; 211 | } 212 | return value; 213 | } 214 | 215 | gboolean 216 | passim_mkdir(const gchar *dirname, GError **error) 217 | { 218 | g_return_val_if_fail(dirname != NULL, FALSE); 219 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 220 | 221 | if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) 222 | g_debug("creating path %s", dirname); 223 | if (g_mkdir_with_parents(dirname, 0700) == -1) { 224 | g_set_error(error, 225 | G_IO_ERROR, 226 | g_io_error_from_errno(errno), 227 | "failed to create '%s': %s", 228 | dirname, 229 | g_strerror(errno)); 230 | return FALSE; 231 | } 232 | return TRUE; 233 | } 234 | 235 | gboolean 236 | passim_mkdir_parent(const gchar *filename, GError **error) 237 | { 238 | g_autofree gchar *parent = NULL; 239 | 240 | g_return_val_if_fail(filename != NULL, FALSE); 241 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 242 | 243 | parent = g_path_get_dirname(filename); 244 | return passim_mkdir(parent, error); 245 | } 246 | 247 | GBytes * 248 | passim_load_input_stream(GInputStream *stream, gsize count, GError **error) 249 | { 250 | guint8 tmp[0x8000] = {0x0}; 251 | g_autoptr(GByteArray) buf = g_byte_array_new(); 252 | g_autoptr(GError) error_local = NULL; 253 | 254 | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); 255 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); 256 | 257 | /* this is invalid */ 258 | if (count == 0) { 259 | g_set_error_literal(error, 260 | G_IO_ERROR, 261 | G_IO_ERROR_NOT_SUPPORTED, 262 | "A maximum read size must be specified"); 263 | return NULL; 264 | } 265 | 266 | /* read from stream in 32kB chunks */ 267 | while (TRUE) { 268 | gssize sz; 269 | sz = g_input_stream_read(stream, tmp, sizeof(tmp), NULL, &error_local); 270 | if (sz == 0) 271 | break; 272 | if (sz < 0) { 273 | g_set_error_literal(error, 274 | G_IO_ERROR, 275 | G_IO_ERROR_INVALID_DATA, 276 | error_local->message); 277 | return NULL; 278 | } 279 | g_byte_array_append(buf, tmp, sz); 280 | if (buf->len > count) { 281 | g_set_error(error, 282 | G_IO_ERROR, 283 | G_IO_ERROR_NO_SPACE, 284 | "cannot read from fd: 0x%x > 0x%x", 285 | buf->len, 286 | (guint)count); 287 | return NULL; 288 | } 289 | } 290 | return g_bytes_new(buf->data, buf->len); 291 | } 292 | 293 | gchar * 294 | passim_get_boot_time(void) 295 | { 296 | g_autofree gchar *buf = NULL; 297 | g_auto(GStrv) lines = NULL; 298 | if (!g_file_get_contents("/proc/stat", &buf, NULL, NULL)) 299 | return NULL; 300 | lines = g_strsplit(buf, "\n", -1); 301 | for (guint i = 0; lines[i] != NULL; i++) { 302 | if (g_str_has_prefix(lines[i], "btime ")) 303 | return g_strdup(lines[i] + 6); 304 | } 305 | return NULL; 306 | } 307 | 308 | gboolean 309 | passim_file_set_contents(const gchar *filename, GBytes *bytes, GError **error) 310 | { 311 | gsize size = 0; 312 | const gchar *data = g_bytes_get_data(bytes, &size); 313 | g_debug("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size); 314 | return g_file_set_contents_full(filename, 315 | data, 316 | size, 317 | G_FILE_SET_CONTENTS_CONSISTENT, 318 | 0600, 319 | error); 320 | } 321 | 322 | GBytes * 323 | passim_file_get_contents(const gchar *filename, GError **error) 324 | { 325 | gchar *data = NULL; 326 | gsize len = 0; 327 | if (!g_file_get_contents(filename, &data, &len, error)) 328 | return NULL; 329 | g_debug("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len); 330 | return g_bytes_new_take(data, len); 331 | } 332 | -------------------------------------------------------------------------------- /src/passim-common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | const gchar * 12 | passim_status_to_string(PassimStatus status); 13 | GKeyFile * 14 | passim_config_load(GError **error); 15 | guint16 16 | passim_config_get_port(GKeyFile *kf); 17 | gboolean 18 | passim_config_get_ipv6(GKeyFile *kf); 19 | gsize 20 | passim_config_get_max_item_size(GKeyFile *kf); 21 | gdouble 22 | passim_config_get_carbon_cost(GKeyFile *kf); 23 | gchar * 24 | passim_config_get_path(GKeyFile *kf); 25 | gboolean 26 | passim_xattr_set_uint32(const gchar *filename, const gchar *name, guint32 value, GError **error); 27 | guint32 28 | passim_xattr_get_uint32(const gchar *filename, 29 | const gchar *name, 30 | guint32 value_fallback, 31 | GError **error); 32 | gboolean 33 | passim_xattr_set_string(const gchar *filename, 34 | const gchar *name, 35 | const gchar *value, 36 | GError **error); 37 | gchar * 38 | passim_xattr_get_string(const gchar *filename, const gchar *name, GError **error); 39 | gboolean 40 | passim_mkdir(const gchar *dirname, GError **error); 41 | gboolean 42 | passim_mkdir_parent(const gchar *filename, GError **error); 43 | gchar * 44 | passim_get_boot_time(void); 45 | GBytes * 46 | passim_load_input_stream(GInputStream *stream, gsize count, GError **error); 47 | gboolean 48 | passim_file_set_contents(const gchar *filename, GBytes *bytes, GError **error); 49 | GBytes * 50 | passim_file_get_contents(const gchar *filename, GError **error); 51 | -------------------------------------------------------------------------------- /src/passim-gnutls.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include "passim-gnutls.h" 10 | 11 | gnutls_x509_crt_t 12 | passim_gnutls_load_crt_from_blob(GBytes *blob, gnutls_x509_crt_fmt_t format, GError **error) 13 | { 14 | gnutls_datum_t d = {0}; 15 | int rc; 16 | g_auto(gnutls_x509_crt_t) crt = NULL; 17 | 18 | /* create certificate */ 19 | rc = gnutls_x509_crt_init(&crt); 20 | if (rc < 0) { 21 | g_set_error(error, 22 | G_IO_ERROR, 23 | G_IO_ERROR_INVALID_DATA, 24 | "crt_init: %s [%i]", 25 | gnutls_strerror(rc), 26 | rc); 27 | return NULL; 28 | } 29 | 30 | /* import the certificate */ 31 | d.size = g_bytes_get_size(blob); 32 | d.data = (unsigned char *)g_bytes_get_data(blob, NULL); 33 | rc = gnutls_x509_crt_import(crt, &d, format); 34 | if (rc < 0) { 35 | g_set_error(error, 36 | G_IO_ERROR, 37 | G_IO_ERROR_INVALID_DATA, 38 | "crt_import: %s [%i]", 39 | gnutls_strerror(rc), 40 | rc); 41 | return NULL; 42 | } 43 | return g_steal_pointer(&crt); 44 | } 45 | 46 | gnutls_privkey_t 47 | passim_gnutls_load_privkey_from_blob(GBytes *blob, GError **error) 48 | { 49 | int rc; 50 | gnutls_datum_t d = {0}; 51 | g_auto(gnutls_privkey_t) key = NULL; 52 | 53 | /* load the private key */ 54 | rc = gnutls_privkey_init(&key); 55 | if (rc < 0) { 56 | g_set_error(error, 57 | G_IO_ERROR, 58 | G_IO_ERROR_INVALID_DATA, 59 | "privkey_init: %s [%i]", 60 | gnutls_strerror(rc), 61 | rc); 62 | return NULL; 63 | } 64 | d.size = g_bytes_get_size(blob); 65 | d.data = (unsigned char *)g_bytes_get_data(blob, NULL); 66 | rc = gnutls_privkey_import_x509_raw(key, &d, GNUTLS_X509_FMT_PEM, NULL, 0); 67 | if (rc < 0) { 68 | g_set_error(error, 69 | G_IO_ERROR, 70 | G_IO_ERROR_INVALID_DATA, 71 | "privkey_import_x509_raw: %s [%i]", 72 | gnutls_strerror(rc), 73 | rc); 74 | return NULL; 75 | } 76 | return g_steal_pointer(&key); 77 | } 78 | 79 | gnutls_pubkey_t 80 | passim_gnutls_load_pubkey_from_privkey(gnutls_privkey_t privkey, GError **error) 81 | { 82 | g_auto(gnutls_pubkey_t) pubkey = NULL; 83 | int rc; 84 | 85 | /* get the public key part of the private key */ 86 | rc = gnutls_pubkey_init(&pubkey); 87 | if (rc < 0) { 88 | g_set_error(error, 89 | G_IO_ERROR, 90 | G_IO_ERROR_INVALID_DATA, 91 | "pubkey_init: %s [%i]", 92 | gnutls_strerror(rc), 93 | rc); 94 | return NULL; 95 | } 96 | rc = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0); 97 | if (rc < 0) { 98 | g_set_error(error, 99 | G_IO_ERROR, 100 | G_IO_ERROR_INVALID_DATA, 101 | "pubkey_import_privkey: %s [%i]", 102 | gnutls_strerror(rc), 103 | rc); 104 | return NULL; 105 | } 106 | 107 | /* success */ 108 | return g_steal_pointer(&pubkey); 109 | } 110 | 111 | gchar * 112 | passim_gnutls_datum_to_dn_str(const gnutls_datum_t *raw) 113 | { 114 | g_auto(gnutls_x509_dn_t) dn = NULL; 115 | g_autoptr(gnutls_datum_t) str = NULL; 116 | int rc; 117 | rc = gnutls_x509_dn_init(&dn); 118 | if (rc < 0) 119 | return NULL; 120 | rc = gnutls_x509_dn_import(dn, raw); 121 | if (rc < 0) 122 | return NULL; 123 | str = (gnutls_datum_t *)gnutls_malloc(sizeof(gnutls_datum_t)); 124 | str->data = NULL; 125 | rc = gnutls_x509_dn_get_str2(dn, str, 0); 126 | if (rc < 0) 127 | return NULL; 128 | return g_strndup((const gchar *)str->data, str->size); 129 | } 130 | 131 | /* generates a private key just like `certtool --generate-privkey` */ 132 | GBytes * 133 | passim_gnutls_create_private_key(GError **error) 134 | { 135 | gnutls_datum_t d = {0}; 136 | int bits; 137 | int key_type = GNUTLS_PK_RSA; 138 | int rc; 139 | g_auto(gnutls_x509_privkey_t) key = NULL; 140 | g_auto(gnutls_x509_spki_t) spki = NULL; 141 | g_autoptr(gnutls_data_t) d_payload = NULL; 142 | 143 | /* initialize key and SPKI */ 144 | rc = gnutls_x509_privkey_init(&key); 145 | if (rc < 0) { 146 | g_set_error(error, 147 | G_IO_ERROR, 148 | G_IO_ERROR_INVALID_DATA, 149 | "privkey_init: %s [%i]", 150 | gnutls_strerror(rc), 151 | rc); 152 | return NULL; 153 | } 154 | rc = gnutls_x509_spki_init(&spki); 155 | if (rc < 0) { 156 | g_set_error(error, 157 | G_IO_ERROR, 158 | G_IO_ERROR_INVALID_DATA, 159 | "spki_init: %s [%i]", 160 | gnutls_strerror(rc), 161 | rc); 162 | return NULL; 163 | } 164 | 165 | /* generate key */ 166 | bits = gnutls_sec_param_to_pk_bits(key_type, GNUTLS_SEC_PARAM_HIGH); 167 | g_debug("generating a %d bit %s private key...", 168 | bits, 169 | gnutls_pk_algorithm_get_name(key_type)); 170 | rc = gnutls_x509_privkey_generate2(key, key_type, bits, 0, NULL, 0); 171 | if (rc < 0) { 172 | g_set_error(error, 173 | G_IO_ERROR, 174 | G_IO_ERROR_INVALID_DATA, 175 | "privkey_generate2: %s [%i]", 176 | gnutls_strerror(rc), 177 | rc); 178 | return NULL; 179 | } 180 | rc = gnutls_x509_privkey_verify_params(key); 181 | if (rc < 0) { 182 | g_set_error(error, 183 | G_IO_ERROR, 184 | G_IO_ERROR_INVALID_DATA, 185 | "privkey_verify_params: %s [%i]", 186 | gnutls_strerror(rc), 187 | rc); 188 | return NULL; 189 | } 190 | 191 | /* save to file */ 192 | rc = gnutls_x509_privkey_export2(key, GNUTLS_X509_FMT_PEM, &d); 193 | if (rc < 0) { 194 | g_set_error(error, 195 | G_IO_ERROR, 196 | G_IO_ERROR_INVALID_DATA, 197 | "privkey_export2: %s [%i]", 198 | gnutls_strerror(rc), 199 | rc); 200 | return NULL; 201 | } 202 | d_payload = d.data; 203 | return g_bytes_new(d_payload, d.size); 204 | } 205 | 206 | /* generates a self signed certificate just like: 207 | * `certtool --generate-self-signed --load-privkey priv.pem` */ 208 | GBytes * 209 | passim_gnutls_create_certificate(gnutls_privkey_t privkey, GError **error) 210 | { 211 | int rc; 212 | gnutls_datum_t d = {0}; 213 | guchar sha1buf[20]; 214 | gsize sha1bufsz = sizeof(sha1buf); 215 | g_auto(gnutls_pubkey_t) pubkey = NULL; 216 | g_auto(gnutls_x509_crt_t) crt = NULL; 217 | g_autoptr(gnutls_data_t) d_payload = NULL; 218 | 219 | /* load the public key from the private key */ 220 | pubkey = passim_gnutls_load_pubkey_from_privkey(privkey, error); 221 | if (pubkey == NULL) 222 | return NULL; 223 | 224 | /* create certificate */ 225 | rc = gnutls_x509_crt_init(&crt); 226 | if (rc < 0) { 227 | g_set_error(error, 228 | G_IO_ERROR, 229 | G_IO_ERROR_INVALID_DATA, 230 | "crt_init: %s [%i]", 231 | gnutls_strerror(rc), 232 | rc); 233 | return NULL; 234 | } 235 | 236 | /* set public key */ 237 | rc = gnutls_x509_crt_set_pubkey(crt, pubkey); 238 | if (rc < 0) { 239 | g_set_error(error, 240 | G_IO_ERROR, 241 | G_IO_ERROR_INVALID_DATA, 242 | "crt_set_pubkey: %s [%i]", 243 | gnutls_strerror(rc), 244 | rc); 245 | return NULL; 246 | } 247 | 248 | /* set positive random serial number */ 249 | rc = gnutls_rnd(GNUTLS_RND_NONCE, sha1buf, sizeof(sha1buf)); 250 | if (rc < 0) { 251 | g_set_error(error, 252 | G_IO_ERROR, 253 | G_IO_ERROR_INVALID_DATA, 254 | "gnutls_rnd: %s [%i]", 255 | gnutls_strerror(rc), 256 | rc); 257 | return NULL; 258 | } 259 | sha1buf[0] &= 0x7f; 260 | rc = gnutls_x509_crt_set_serial(crt, sha1buf, sizeof(sha1buf)); 261 | if (rc < 0) { 262 | g_set_error(error, 263 | G_IO_ERROR, 264 | G_IO_ERROR_INVALID_DATA, 265 | "crt_set_serial: %s [%i]", 266 | gnutls_strerror(rc), 267 | rc); 268 | return NULL; 269 | } 270 | 271 | /* set activation */ 272 | rc = gnutls_x509_crt_set_activation_time(crt, time(NULL)); 273 | if (rc < 0) { 274 | g_set_error(error, 275 | G_IO_ERROR, 276 | G_IO_ERROR_INVALID_DATA, 277 | "set_activation_time: %s [%i]", 278 | gnutls_strerror(rc), 279 | rc); 280 | return NULL; 281 | } 282 | 283 | /* set expiration */ 284 | rc = gnutls_x509_crt_set_expiration_time(crt, (time_t)-1); 285 | if (rc < 0) { 286 | g_set_error(error, 287 | G_IO_ERROR, 288 | G_IO_ERROR_INVALID_DATA, 289 | "set_expiration_time: %s [%i]", 290 | gnutls_strerror(rc), 291 | rc); 292 | return NULL; 293 | } 294 | 295 | /* set basic constraints */ 296 | rc = gnutls_x509_crt_set_basic_constraints(crt, 0, -1); 297 | if (rc < 0) { 298 | g_set_error(error, 299 | G_IO_ERROR, 300 | G_IO_ERROR_INVALID_DATA, 301 | "set_basic_constraints: %s [%i]", 302 | gnutls_strerror(rc), 303 | rc); 304 | return NULL; 305 | } 306 | 307 | /* set usage */ 308 | rc = gnutls_x509_crt_set_key_usage(crt, GNUTLS_KEY_DIGITAL_SIGNATURE); 309 | if (rc < 0) { 310 | g_set_error(error, 311 | G_IO_ERROR, 312 | G_IO_ERROR_INVALID_DATA, 313 | "set_key_usage: %s [%i]", 314 | gnutls_strerror(rc), 315 | rc); 316 | return NULL; 317 | } 318 | 319 | /* make suitable for TLS */ 320 | rc = gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0); 321 | if (rc < 0) { 322 | g_set_error(error, 323 | G_IO_ERROR, 324 | G_IO_ERROR_INVALID_DATA, 325 | "set_key_purpose_oid: %s [%i]", 326 | gnutls_strerror(rc), 327 | rc); 328 | return NULL; 329 | } 330 | 331 | /* set subject key ID */ 332 | rc = gnutls_x509_crt_get_key_id(crt, GNUTLS_KEYID_USE_SHA1, sha1buf, &sha1bufsz); 333 | if (rc < 0) { 334 | g_set_error(error, 335 | G_IO_ERROR, 336 | G_IO_ERROR_INVALID_DATA, 337 | "get_key_id: %s [%i]", 338 | gnutls_strerror(rc), 339 | rc); 340 | return NULL; 341 | } 342 | rc = gnutls_x509_crt_set_subject_key_id(crt, sha1buf, sha1bufsz); 343 | if (rc < 0) { 344 | g_set_error(error, 345 | G_IO_ERROR, 346 | G_IO_ERROR_INVALID_DATA, 347 | "set_subject_key_id: %s [%i]", 348 | gnutls_strerror(rc), 349 | rc); 350 | return NULL; 351 | } 352 | 353 | /* set version */ 354 | rc = gnutls_x509_crt_set_version(crt, 3); 355 | if (rc < 0) { 356 | g_set_error(error, 357 | G_IO_ERROR, 358 | G_IO_ERROR_INVALID_DATA, 359 | "error setting certificate version: %s [%i]", 360 | gnutls_strerror(rc), 361 | rc); 362 | return NULL; 363 | } 364 | 365 | /* self-sign certificate */ 366 | rc = gnutls_x509_crt_privkey_sign(crt, crt, privkey, GNUTLS_DIG_SHA256, 0); 367 | if (rc < 0) { 368 | g_set_error(error, 369 | G_IO_ERROR, 370 | G_IO_ERROR_INVALID_DATA, 371 | "crt_privkey_sign: %s [%i]", 372 | gnutls_strerror(rc), 373 | rc); 374 | return NULL; 375 | } 376 | 377 | /* export to file */ 378 | rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_PEM, &d); 379 | if (rc < 0) { 380 | g_set_error(error, 381 | G_IO_ERROR, 382 | G_IO_ERROR_INVALID_DATA, 383 | "crt_export2: %s [%i]", 384 | gnutls_strerror(rc), 385 | rc); 386 | return NULL; 387 | } 388 | d_payload = d.data; 389 | return g_bytes_new(d_payload, d.size); 390 | } 391 | -------------------------------------------------------------------------------- /src/passim-gnutls.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef guchar gnutls_data_t; 15 | 16 | static void 17 | _gnutls_datum_deinit(gnutls_datum_t *d) 18 | { 19 | gnutls_free(d->data); 20 | gnutls_free(d); 21 | } 22 | 23 | #pragma clang diagnostic push 24 | #pragma clang diagnostic ignored "-Wunused-function" 25 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pkcs7_t, gnutls_pkcs7_deinit, NULL) 26 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_privkey_t, gnutls_privkey_deinit, NULL) 27 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL) 28 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL) 29 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_dn_t, gnutls_x509_dn_deinit, NULL) 30 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_privkey_t, gnutls_x509_privkey_deinit, NULL) 31 | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_spki_t, gnutls_x509_spki_deinit, NULL) 32 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free) 33 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_pkcs7_signature_info_st, gnutls_pkcs7_signature_info_deinit) 34 | G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, _gnutls_datum_deinit) 35 | #pragma clang diagnostic pop 36 | 37 | gchar * 38 | passim_gnutls_datum_to_dn_str(const gnutls_datum_t *raw); 39 | gnutls_x509_crt_t 40 | passim_gnutls_load_crt_from_blob(GBytes *blob, gnutls_x509_crt_fmt_t format, GError **error); 41 | gnutls_privkey_t 42 | passim_gnutls_load_privkey_from_blob(GBytes *blob, GError **error); 43 | gnutls_pubkey_t 44 | passim_gnutls_load_pubkey_from_privkey(gnutls_privkey_t privkey, GError **error); 45 | 46 | GBytes * 47 | passim_gnutls_create_private_key(GError **error); 48 | GBytes * 49 | passim_gnutls_create_certificate(gnutls_privkey_t privkey, GError **error); 50 | -------------------------------------------------------------------------------- /src/passim-self-test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Richard Hughes 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "passim-common.h" 13 | 14 | #if 0 15 | static GMainLoop *_test_loop = NULL; 16 | static guint _test_loop_timeout_id = 0; 17 | 18 | static gboolean 19 | passim_test_hang_check_cb(gpointer user_data) 20 | { 21 | g_main_loop_quit(_test_loop); 22 | _test_loop_timeout_id = 0; 23 | return G_SOURCE_REMOVE; 24 | } 25 | 26 | static void 27 | passim_test_loop_run_with_timeout(guint timeout_ms) 28 | { 29 | g_assert_cmpint(_test_loop_timeout_id, ==, 0); 30 | g_assert_null(_test_loop); 31 | _test_loop = g_main_loop_new(NULL, FALSE); 32 | _test_loop_timeout_id = g_timeout_add(timeout_ms, passim_test_hang_check_cb, NULL); 33 | g_main_loop_run(_test_loop); 34 | } 35 | 36 | static void 37 | passim_test_loop_quit(void) 38 | { 39 | if (_test_loop_timeout_id > 0) { 40 | g_source_remove(_test_loop_timeout_id); 41 | _test_loop_timeout_id = 0; 42 | } 43 | if (_test_loop != NULL) { 44 | g_main_loop_quit(_test_loop); 45 | g_main_loop_unref(_test_loop); 46 | _test_loop = NULL; 47 | } 48 | } 49 | #endif 50 | 51 | static void 52 | passim_common_func(void) 53 | { 54 | gboolean ret; 55 | guint32 value_u32; 56 | g_autofree gchar *boot_time = NULL; 57 | g_autofree gchar *value_str1 = NULL; 58 | g_autofree gchar *value_str2 = NULL; 59 | g_autofree gchar *xargs_fn = NULL; 60 | g_autofree gchar *xargs_path = NULL; 61 | g_autoptr(GError) error = NULL; 62 | 63 | /* ensure we got *something* */ 64 | boot_time = passim_get_boot_time(); 65 | g_assert_cmpstr(boot_time, !=, NULL); 66 | 67 | /* create dir for next step */ 68 | xargs_fn = g_test_build_filename(G_TEST_BUILT, "tests", "test.conf", NULL); 69 | xargs_path = g_path_get_dirname(xargs_fn); 70 | ret = passim_mkdir(xargs_path, &error); 71 | g_assert_no_error(error); 72 | g_assert_true(ret); 73 | ret = passim_mkdir(xargs_path, &error); 74 | g_assert_no_error(error); 75 | g_assert_true(ret); 76 | 77 | /* check xargs */ 78 | (void)g_unlink(xargs_fn); 79 | ret = g_file_set_contents(xargs_fn, "[daemon]", -1, &error); 80 | g_assert_no_error(error); 81 | g_assert_true(ret); 82 | ret = passim_xattr_set_uint32(xargs_fn, "user.test_u32", 123, &error); 83 | if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { 84 | g_test_skip("no xattr support"); 85 | return; 86 | } 87 | g_assert_no_error(error); 88 | g_assert_true(ret); 89 | ret = passim_xattr_set_string(xargs_fn, "user.test_str", "hey", &error); 90 | g_assert_no_error(error); 91 | g_assert_true(ret); 92 | value_u32 = passim_xattr_get_uint32(xargs_fn, "user.test_u32", 456, &error); 93 | g_assert_no_error(error); 94 | g_assert_cmpint(value_u32, ==, 123); 95 | value_u32 = passim_xattr_get_uint32(xargs_fn, "user.test_MISSING", 456, &error); 96 | g_assert_no_error(error); 97 | g_assert_cmpint(value_u32, ==, 456); 98 | value_str1 = passim_xattr_get_string(xargs_fn, "user.test_str", &error); 99 | g_assert_no_error(error); 100 | g_assert_cmpstr(value_str1, ==, "hey"); 101 | value_str2 = passim_xattr_get_string(xargs_fn, "user.test_MISSING", &error); 102 | g_assert_no_error(error); 103 | g_assert_cmpstr(value_str2, ==, ""); 104 | } 105 | 106 | int 107 | main(int argc, char **argv) 108 | { 109 | (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); 110 | (void)g_setenv("G_TEST_BUILDDIR", BUILDDIR, FALSE); 111 | g_test_init(&argc, &argv, NULL); 112 | 113 | /* only critical and error are fatal */ 114 | g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); 115 | (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); 116 | 117 | g_test_add_func("/passim/common", passim_common_func); 118 | return g_test_run(); 119 | } 120 | -------------------------------------------------------------------------------- /src/passim.1: -------------------------------------------------------------------------------- 1 | .TH passim 1 "0.1.0" "A local caching server" 2 | .SH NAME 3 | passim \- client control of the local caching server 4 | .SH SYNOPSIS 5 | .B passim 6 | [CMD] 7 | .SH DESCRIPTION 8 | This tool allows an administrator to display and share files using passim. 9 | .SH OPTIONS 10 | The passim command takes various options depending on the action. 11 | Run 12 | .B passim --help 13 | for the full list. 14 | .SH EXIT STATUS 15 | Commands that successfully execute will return "0", with generic failure as "1". 16 | .SH BUGS 17 | See GitHub Issues: 18 | Changes made: 19 | --------------------------------------------------------------------------------