54 Commits
Author SHA1 Message Date
Dmitry Baryshkov 443c82aada Merge pull request #31 from lumag/tqftp-1.1.1
meson.build: prepare 1.1.1 release
2026-01-07 19:22:17 +02:00
Dmitry Baryshkov 3e568ebf78 meson.build: prepare 1.1.1 release
Bugfix release:
- fix zstd integration
- fix qrtr-ns service dependencies
- fix systemd unit installation
- fix buffer / ACK handling in corner cases

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-01-07 19:14:20 +02:00
Dmitry Baryshkov 0ed681362b Merge pull request #30 from fossdd/systemd
build: allow building systemd unit files without systemd dependency
2025-12-24 06:28:44 +02:00
Achill Gilgenast d4c7f06435 build: allow building systemd unit files without systemd dependency
If systemd-unit-prefix is passed, there is no need for the systemd build
dependency.

Allows building in postmarketOS, where the build environment can't
install systemd.

Signed-off-by: Achill Gilgenast <achill@achill.org>
2025-11-18 17:15:00 +01:00
Dmitry Baryshkov 408ca1ed5e Merge pull request #26 from quic-bhaskarv/topic/fix_wsize
tqftpserv: ACK during write based on wsize
2025-08-01 14:48:48 +03:00
Bhaskar Valaboju 33a3b4891e tqftpserv: Cache wsize buffers in client instance
The client sends data packets of blksize size and waits for an ACK
after sending wsize packets. If packets are not received in sequence,
the server should send NACK, prompting the client to resend the packets
starting from beginning of the window.

For example, with wsize = 10 and blksize = 5000, the client sends
blocks [201–210] and waits. If all are received, the server replies
with ACK; otherwise, it sends NACK, and the client restarts from block 201.

Use rw_buf to cache received blocks and track the next expected block
using blk_expected. Reset the expected block to the start of the window
if out-of-sequence packets are detected.

Once all buffers are received, commit the cached buffers to the file.

Signed-off-by: Bhaskar Valaboju <quic_bhaskarv@quicinc.com>
2025-07-18 18:34:46 +05:30
Bhaskar Valaboju ad282fcab3 tqftpserv: Use blksize buffer in send/recvfrom for TFTP packets
Use the blksize-sized buffer from the client instance for send
and recvfrom operations during TFTP packet handling.

This eliminates repeated memory allocation in handler_reader
and supports receiving full blksize payloads in handler_writer.

Signed-off-by: Bhaskar Valaboju <quic_bhaskarv@quicinc.com>
2025-07-18 18:26:43 +05:30
Bhaskar Valaboju 7d5fcd440a tqftpserv: Allocate memory in client instance to hold blksize buffer
Allocate memory in the client instance to support transmission and
reception of TFTP data packets sized according to the negotiated
blksize option. This buffer ensures proper handling of variable-length
payloads during transfer operations.

Signed-off-by: Bhaskar Valaboju <quic_bhaskarv@quicinc.com>
2025-07-18 18:09:57 +05:30
Bhaskar Valaboju 44748aa794 tqftpserv: Set default wsize to 1 if not specified in WRQ/RRQ
Ensure the default wsize value is set to 1 when it is not provided
in a Write Request (WRQ) or Read Request (RRQ).

Signed-off-by: Bhaskar Valaboju <quic_bhaskarv@quicinc.com>
2025-07-18 18:02:19 +05:30
Bhaskar Valaboju 4b3054aed3 tqftpserv: Allocate wsize buffer in TFTP client instance
Allocate a buffer within the client instance that can hold
the wsize value when reading from or writing to the TFTP client.

Signed-off-by: Bhaskar Valaboju <quic_bhaskarv@quicinc.com>
2025-07-18 17:59:41 +05:30
Dmitry Baryshkov 9fc5fdc53b Merge pull request #28 from z3ntu/zstd-errors
translate: Check existance of .zstd firmware before accessing
2025-07-09 13:26:37 +03:00
Luca Weiss 1a516e7a78 translate: Check existance of .zstd firmware before accessing
Since we're trying multiple different paths, fail quietly if the
.zstd-compressed version of the firmware does not exist.

We're trying multiple different paths anyways and have error prints for
this in other places.

Tested cases with 901_0_0.mbn as an example:
* 901_0_0.mbn exists -> no error
* 901_0_0.mbn.zstd exists -> no error
* neither exists -> error print
    [TQFTP] unable to open /readonly/firmware/image/modem_pr/so/901_0_0.mbn (2), reject

Signed-off-by: Luca Weiss <luca@lucaweiss.eu>
2025-07-07 09:27:42 +02:00
Luca Weiss 72aad03e24 zstd-decompress: Print file path in "stat failed" message
Make the errors a bit more useful than just "stat failed" to know which
file it's about.

[TQFTP] RRQ: /readonly/firmware/image/modem_pr/so/901_0_0.mbn (mode=octet rsize=4096 seek=0)
stat /lib/firmware/postmarketos/qcom/qcm6490/fairphone5/modem_pr/so/901_0_0.mbn.zst failed (No such file or directory)
stat /lib/firmware/updates/qcom/qcm6490/fairphone5/modem_pr/so/901_0_0.mbn.zst failed (No such file or directory)

Signed-off-by: Luca Weiss <luca@lucaweiss.eu>
2025-07-07 09:27:42 +02:00
Dmitry Baryshkov b0f786c1a7 Merge pull request #27 from z3ntu/readme
Add README.md
2025-07-06 13:24:51 +03:00
Luca Weiss e2bfdbb52f Add README.md
Try and document what tqftpserv is actually doing, so that people who
are new to the stack can get some understanding of it, without first
needing to figure out everything.

I've spent sime time working on tqftpserv so I hope the description is
correct, but no guarantees.

Signed-off-by: Luca Weiss <luca@lucaweiss.eu>
2025-07-06 12:06:06 +02:00
Dmitry Baryshkov 533779cb8a Merge pull request #24 from nik012003/master
tqftpserv.service.in: remove dependency on qrtr-ns.service
2024-11-21 18:13:30 +02:00
Nicola Guerrera cab8b18f9a tqftpserv.service.in: remove dependency on qrtr-ns.service
QRTR is built in to the kernel now, the other packages (such as rmtfs) have since removed qrtr-ns as a service dependency

Signed-off-by: Nicola Guerrera <guerrera.nicola@gmail.com>
2024-11-09 00:22:37 +01:00
Dmitry Baryshkov 859c6e3969 Merge pull request #23 from a-wai/firmware-updates
translate: lookup firmware files under /lib/firmware/updates
2024-10-19 19:37:05 +03:00
Dmitry Baryshkov bbfff09e16 Merge pull request #22 from evelikov/zstd-followups
Follow-up zstd fixes
2024-10-16 17:33:58 +03:00
Arnaud Ferraris 4744c0ce07 translate: lookup firmware files under /lib/firmware/updates
`/lib/firmware/updates` is a legitimate location for firmware files,
`tqftpserv` should look there as well.
2024-09-25 10:52:52 +02:00
Emil Velikov f9da0667fb Fix Android build without zstd
Bit of a blind fix, since I don't have an Android dev setup nearby.

Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2024-09-21 16:10:16 +01:00
Emil Velikov 0f7fd27524 Use context-less zstd decompression API
Currently we pre-allocate around 128K of zstd state ahead of time,
keeping if for the whole daemon lifetime... Even if we don't need to
decompress any files. As mentioned by Dmitry:

    Granted that tqftpserv handles only few files during the whole
    lifetime and most of devices don't have swap, I think it's fine to
    use non-context versions of the functions. Let's free the memory for
    other software.

Swap ZSTD_decompressDCtx() for ZSTD_decompress() which will allocate and
free the state when needed.

Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2024-09-21 16:09:21 +01:00
Emil Velikov 95d0c67322 Explicitly open zstd file as read-only
Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2024-09-19 12:53:27 +01:00
Emil Velikov 085a87c279 Clear memory leaks during zstd decompression
Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2024-09-19 12:48:16 +01:00
Konrad Dybcio 48e143d273 meson.build: Set v1.1 version
Signed-off-by: Konrad Dybcio <konrad.dybcio@linaro.org>
2024-07-23 14:25:36 +02:00
Stefan Hansson 0197df8e61 Add support for zstd-compressed readonly files
We need this to load compressed files from linux-firmware.

Closes https://github.com/linux-msm/tqftpserv/issues/19

Signed-off-by: Stefan Hansson <newbyte@postmarketos.org>
Tested-by: Nikita Travkin <nikita@trvn.ru>
2024-07-23 14:20:00 +02:00
Alexey Minnekhanov 47e8957271 meson.build: Make systemd dep optional
Some distros still don't use SystemD (yet), make it optional.

It is not required to build the binary, just to install systemd
unit file.

Signed-off-by: Alexey Minnekhanov <alexey.min@gmail.com>
2024-04-30 23:51:30 +02:00
Konrad Dybcio 5f9f263ae2 treewide: meson 2024-04-23 19:38:37 +02:00
Konrad Dybcio 3a10a7dd6f treewide: use SPDX license IDs 2024-04-08 23:53:19 +02:00
Luca Weiss a09948e5e3 tqftpserv: don't print "End of Transfer" as an error
On newer modems, the firmware seems to use a stat-like operation
relatively often which end the transfer with error 9 "End of Transfer".

In practise we're getting a RRQ with the tsize option set, we'll query
the file size from the filesystem, respond with the updated tsize in
OACK, then the modem will send us OP_ERROR with code 9 "End of
Transfer".

Since that's expected in this operation, let's not make it look like an
error to not confuse users/developers trying to debug the modem.
2024-04-08 23:17:36 +02:00
Luca Weiss 588a8d3c8f tqftpserv: add rsize & offset options to RRQ print
Since new modems send multiple file requests with different rsize and
offset on a single file, it's useful to print those also.
2024-04-08 23:17:36 +02:00
Luca Weiss e167336fca tqftpserv: handle rsize & offset options
The rsize (number of bytes the client wants to receive) and offset (the
offset in bytes from the start of file) are used on newer modems (such
as SM7225 or QCM6490), so let's support them properly.

Some examples to illustrate the usage:
* rsize=160 seek=52: we should send 160 bytes starting at byte 52 in the
  requested file. With blksize=7680 this is a single 160(+4) sized
  packet.
* rsize=313028 seek=81920: we should send in total 313028 bytes,
  starting at byte 81920. With blksize=7680 and wsize=10 this spans
  multiple windows of 10 packets each, that example would be packed into
  41 packets with an ACK between every 10 windows - and a final ACK at
  the end.
* rsize=53760 blksize=7680: We send 7 packets of 7680 bytes each, then
  we send 1 packet with 0 bytes of data (only 4 bytes header) and then
  get an ACK on packet 8.
2024-04-08 23:17:36 +02:00
Luca Weiss 69c7cfcf73 tqftpserv: add seek option parsing
Actual handling will be added in follow-up commits as rsize is also not
currently handled, and both are quite interconnected.
2024-04-08 23:17:36 +02:00
Luca Weiss 2dd48a2899 tqftpserv: allow sending data packet with 0-byte data payload
If the client requests a file that's completely empty, it makes sense to
send a response to that request with - well - 0 bytes of data and just
the 4-byte header.

But also if the client requests for example a file of rsize=53760 and
blksize=7680, then will send 7 full packets of data in the window, but
afterwards we still need to send an empty packet (just the 4 bytes of
header) to make sure the client understands that we've sent all the
requested data. Otherwise it's going to time out and re-request the
blocks and we're stuck in a loop.

So consider pread return value of 0 to not be an error and send a
response packet back.
2024-04-08 23:17:36 +02:00
Luca Weiss 98d162c9ff translate: Fix firmware path for 'fallback' case
The dirname() function can and will modify the parameter, so if we call
dirname() twice on the same firmware_value parameter we will get
separate results messing up our names.

Copy the firmware_value value before passing it to dirname(). Also let's
just do this operation once to save copy-pasting some code.

Fixes: a4c755d ("Use firmware path from sysfs")
2024-04-08 22:31:59 +02:00
Konrad Dybcio 37669ab1e2 Merge pull request #12 from minlexx/respect-firmware-sysfs-path
Use firmware path from sysfs
2024-03-27 21:27:08 +01:00
Alexey Minnekhanov a4c755db17 Use firmware path from sysfs
Some linux distributions adjust path fro kernel to load
firmware from using /sys/module/firmware_class/parameters/path.
Kernel supports it, teach tqftpserv to respect it too.

Signed-off-by: Alexey Minnekhanov <alexeymin@postmarketos.org>
2024-03-24 00:00:31 +03:00
Bjorn Andersson 289214a5d1 Merge pull request #11 from z3ntu/misc-improvements
Some improvements for error handling
2024-01-22 09:57:48 -06:00
Luca Weiss 00fe2775de tqftpserv: add docs for options
The explanation is spread throughout various RFCs or the code - or at
least for rsize based on strace'ing the communication.
2024-01-19 11:52:32 +01:00
Luca Weiss fb20008c86 translate: don't try to close already closed class_fd
After calling fdopendir "the file descriptor is under the control of the
system" and calling closedir(class_dir) already closes class_fd, so
let's not try to close it again.

Just close class_fd when fdopendir fails and remove the close call from
the regular exit path.
2024-01-18 15:33:46 +01:00
Luca Weiss a540a1c307 tqftpserv: also print value for unknown options 2023-02-10 13:17:28 +01:00
Luca Weiss 3fa49eafe0 tqftpserv: handle OP_ERROR code
When we get an error from the remote we should print it, for example:

  [TQFTP] received error:  8 - Expected OACK for UNLINK
2023-02-10 12:13:00 +01:00
Luca Weiss aaba76c6ba tqftpserv: also print error code on read error
Along with the error message we also get a uint16 error code that we can
print.
2023-02-10 12:10:46 +01:00
John Stultz de42697a24 translate: Null terminate firmware_value string
Null terminate firmware_value string to prevent
strlen() read past the end of buffer.

Change-Id: I57cd01c4d0c58c6057872bc4b36129f09bc034a3
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: John Stultz <john.stultz@linaro.org>
2023-01-17 22:57:14 -06:00
Amit Pundir 538efb3096 translate: Use /data/vendor for temporary files
On AOSP, /tmp is not available. Use /data/vendor instead
for temporary files.

Change-Id: I1d6ebd669573b3c3134e6da943ea9355ed83a3fe
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: John Stultz <john.stultz@linaro.org>
2023-01-17 22:57:14 -06:00
Sireesh Kodali 48b9e1cbb1 tqftpserv: add support for TFTP options on WRQs 2023-01-17 22:56:21 -06:00
Jami Kettunen 3488007880 Makefile: allow $(CFLAGS), $(LDFLAGS) override
The caller might have specified CFLAGS or LDFLAGS. Let's respect those.
2023-01-17 22:54:25 -06:00
Amit Pundir 783425b550 ANDROID: Add Android.bp makefile
Add Android.bp makefile to build tqftpserv for AOSP.

Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2020-02-07 10:43:39 -08:00
Khem Raj 2324458f18 include limits.h for PATH_MAX
Signed-off-by: Khem Raj <raj.khem@gmail.com>
2019-12-02 13:45:16 -08:00
Bjorn Andersson fe53d2a810 tqftpserv: Implement path translation
The tftp clients in the modem requests files from either /readwrite or
/readonly/firmware/image, but as the wlanmdsp.mbn file requested is
signed with a vendor specific key these paths needs to be translated.

/readwrite is translated to /tmp/tqftpserv for now and the
/readonly/firmware/image is translated to a search for the requested
file alongside any of the remoteproc firmware files. The result is that
the wlanmdsp.mbn can be stored in linux-firmware alongside it's
device/vendor specific modem firmware.

Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2019-10-26 11:14:03 -07:00
Bjorn Andersson 75a7a89398 tqftpserv.service: Add systemd service
Add tqftpserv.service and install this.

Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2019-07-25 11:36:16 -07:00
Bjorn Andersson 6348c52f20 tqftpserv: Remove the spamming of stdout
Some mechanism for toggling the verbosity of the prints should be
introduced, but for now just comment out all the debug prints.

Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2019-06-10 16:40:11 -07:00
Bjorn Andersson 7167c88ead tqftpserv: Don't close socket at EOF
When during a read we close the socket as we send the last block the
remote will be notified about the closure and consider the transfer
incomplete. Leave the socket open until we receive a EOF error from the
remote side.

Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2019-06-10 16:36:13 -07:00
Bjorn Andersson 2ae2758cba Add LICENSE file
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
2019-03-15 11:34:42 -07:00
13 changed files with 749 additions and 151 deletions

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
cc_binary {
name: "tqftpserv",
vendor: true,
srcs: [
"tqftpserv.c",
"translate.c",
],
shared_libs: ["libqrtr"],
}
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018, Linaro Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-17
View File
@@ -1,17 +0,0 @@
TQFTPSERV := tqftpserv
CFLAGS := -Wall -g -O2
LDFLAGS := -lqrtr
SRCS := tqftpserv.c
OBJS := $(SRCS:.c=.o)
$(TQFTPSERV): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
install: $(TQFTPSERV)
install -D -m 755 $< $(DESTDIR)$(prefix)/bin/$<
clean:
rm -f $(TQFTPSERV) $(OBJS)
+53
View File
@@ -0,0 +1,53 @@
# tqftpserv
The tqftpserv software is an implementation of a TFTP (Trivial File Transfer Protocol) server which runs on top of the AF_QIPCRTR (a.k.a QRTR) socket type.
The main purpose of tqftpserv is to serve files from the Linux file system to other processors on the Qualcomm SoCs as requested.
The protocol implemented here is (loosely) based on RFC 1350 including some extensions to the protocol.
In basic terms, the protocol supports RRQ (Read Request) and WRQ (Write Request) messages which read and write files respectively. A request can have some extra options, like `blksize` ior `wsize`. The meaning of those is documented in the `parse_options` function.
For reference, the proprietary implementation of this that is used on practically all Qualcomm-based Android devices is called "tftp_server".
## File paths
There's two different virtual paths prefixes that are supported:
* `/readonly/firmware/image/` for read-only files such as firmware files
* `/readwrite/` for read-write (temporary) files
Requests to those paths are "translated" (see `translate.c`) to paths in the Linux filesystem. In a regular setup readonly requests go to `/lib/firmware/` and readwrite requests go to `/tmp/tqftpserv/`.
Translating those readonly request paths tries to take into account custom Linux firmware paths, custom "firmware-name" paths for remoteprocs, .zstd compressed firmware and more. In case of doubt, please consult the source code.
For example on a QCM6490 Fairphone 5 smartphone the path for `/readonly/firmware/image/modem_pr/so/901_0_0.mbn` will be translated to `/lib/firmware/qcom/qcm6490/fairphone5/modem_pr/so/901_0_0.mbn`, based on the devicetree property `firmware-name = "qcom/qcm6490/fairphone5/modem.mbn";` in the modem/mpss DT node.
The actual paths that are used and requested are dependent on the TFTP clients, which is usually the firmware that is running on the Hexagon-based modem processor on the SoC.
## Example requests
The following are some example requests which are grabbed from the tqftpserv log during modem bootup.
* Write content to a file called "server_check.txt":
```
[TQFTP] WRQ: /readwrite/server_check.txt (octet)
```
* Read a file called "ota_firewall/ruleset" which does not exist:
```
[TQFTP] RRQ: /readwrite/ota_firewall/ruleset (mode=octet rsize=0 seek=0)
tqftpserv: failed to open ota_firewall/ruleset: No such file or directory
[TQFTP] unable to open /readwrite/ota_firewall/ruleset (2), reject
```
* Stat a file with the path "modem_pr/so/901_0_0.mbn", and then read it:
```
[TQFTP] RRQ: /readonly/firmware/image/modem_pr/so/901_0_0.mbn (mode=octet rsize=0 seek=0)
[TQFTP] Remote returned END OF TRANSFER: 9 - End of Transfer
[TQFTP] RRQ: /readonly/firmware/image/modem_pr/so/901_0_0.mbn (mode=octet rsize=52 seek=0)
```
In binary such a request can look like the following:
```
\0\1/readonly/firmware/image/modem_pr/so/901_0_0.mbn\0octet\0blksize\0007680\0timeoutms\0001000\0tsize\0000\0wsize\00010\0
```
+1 -27
View File
@@ -1,32 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2016, Linaro Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LIST_H__
#define __LIST_H__
+45
View File
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: BSD-3-Clause
project('tqftpserv',
'c',
version: '1.1.1',
default_options: [
'warning_level=1',
'buildtype=release',
])
prefix = get_option('prefix')
zstd_dep = dependency('libzstd')
add_project_arguments('-DHAVE_ZSTD', language : 'c')
# Not required to build the executable,
# only to automatically retrieve the install dir for unit files
systemd = dependency('systemd', required : false)
if get_option('systemd-unit-prefix') != ''
systemd_system_unit_dir = get_option('systemd-unit-prefix')
elif systemd.found()
systemd_system_unit_dir = systemd.get_variable(
pkgconfig : 'systemdsystemunitdir',
pkgconfig_define: ['prefix', prefix])
endif
qrtr_dep = dependency('qrtr')
tqftpserv_srcs = ['translate.c',
'tqftpserv.c',
'zstd-decompress.c']
executable('tqftpserv',
tqftpserv_srcs,
dependencies : [qrtr_dep, zstd_dep],
install : true)
if systemd_system_unit_dir != ''
systemd_unit_conf = configuration_data()
systemd_unit_conf.set('prefix', prefix)
configure_file(
input : 'tqftpserv.service.in',
output : 'tqftpserv.service',
configuration : systemd_unit_conf,
install_dir : systemd_system_unit_dir)
endif
+4
View File
@@ -0,0 +1,4 @@
option('systemd-unit-prefix',
type: 'string',
description: 'Directory for systemd system unit files'
)
+230 -107
View File
File diff suppressed because it is too large. Load diff
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=QRTR TFTP service
[Service]
ExecStart=@prefix@/bin/tqftpserv
Restart=always
[Install]
WantedBy=multi-user.target
+243
View File
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2019, Linaro Ltd.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "translate.h"
#include "zstd-decompress.h"
#define READONLY_PATH "/readonly/firmware/image/"
#define READWRITE_PATH "/readwrite/"
#ifndef ANDROID
#define FIRMWARE_BASE "/lib/firmware/"
#define TQFTPSERV_TMP "/tmp/tqftpserv"
#define UPDATES_DIR "updates/"
#else
#define FIRMWARE_BASE "/vendor/firmware/"
#define TQFTPSERV_TMP "/data/vendor/tmp/tqftpserv"
#endif
static int open_maybe_compressed(const char *path);
static void read_fw_path_from_sysfs(char *outbuffer, size_t bufsize)
{
size_t pathsize;
FILE *f = fopen("/sys/module/firmware_class/parameters/path", "rt");
if (!f)
return;
pathsize = fread(outbuffer, sizeof(char), bufsize, f);
fclose(f);
if (pathsize == 0)
return;
/* truncate newline */
outbuffer[pathsize - 1] = '\0';
}
/**
* translate_readonly() - open "file" residing with remoteproc firmware
* @file: file requested, stripped of "/readonly/image/" prefix
*
* It is assumed that the readonly files requested by the client resides under
* /lib/firmware in the same place as its associated remoteproc firmware. This
* function scans through all entries under /sys/class/remoteproc and read the
* dirname of each "firmware" file in an attempt to find, and open(2), the
* requested file.
*
* As these files are readonly, it's not possible to pass flags to open(2).
*
* Return: opened fd on success, -1 otherwise
*/
static int translate_readonly(const char *file)
{
char firmware_value[PATH_MAX];
char *firmware_value_copy = NULL;
char *firmware_path;
char firmware_attr[32];
char path[PATH_MAX];
char fw_sysfs_path[PATH_MAX];
struct dirent *de;
int firmware_fd;
DIR *class_dir;
int class_fd;
ssize_t n;
int fd = -1;
read_fw_path_from_sysfs(fw_sysfs_path, sizeof(fw_sysfs_path));
class_fd = open("/sys/class/remoteproc", O_RDONLY | O_DIRECTORY);
if (class_fd < 0) {
warn("failed to open remoteproc class");
return -1;
}
class_dir = fdopendir(class_fd);
if (!class_dir) {
warn("failed to opendir");
close(class_fd);
return -1;
}
while ((de = readdir(class_dir)) != NULL) {
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
continue;
if (strlen(de->d_name) + sizeof("/firmware") > sizeof(firmware_attr))
continue;
strcpy(firmware_attr, de->d_name);
strcat(firmware_attr, "/firmware");
firmware_fd = openat(class_fd, firmware_attr, O_RDONLY);
if (firmware_fd < 0)
continue;
n = read(firmware_fd, firmware_value, sizeof(firmware_value));
close(firmware_fd);
if (n < 0) {
continue;
}
firmware_value[n] = '\0';
firmware_value_copy = strdup(firmware_value);
firmware_path = dirname(firmware_value_copy);
/* first try path from sysfs */
if ((strlen(fw_sysfs_path) > 0) &&
(strlen(fw_sysfs_path) + 1 + strlen(firmware_value) + 1 + strlen(file) + 1 < sizeof(path))) {
strcpy(path, fw_sysfs_path);
strcat(path, "/");
strcat(path, firmware_path);
strcat(path, "/");
strcat(path, file);
fd = open_maybe_compressed(path);
if (fd >= 0)
break;
if (errno != ENOENT)
warn("failed to open %s", path);
}
/* now try with base path */
if (strlen(FIRMWARE_BASE) + strlen(UPDATES_DIR) + strlen(firmware_value) + 1 +
strlen(file) + 1 > sizeof(path))
continue;
strcpy(path, FIRMWARE_BASE);
strcat(path, UPDATES_DIR);
strcat(path, firmware_path);
strcat(path, "/");
strcat(path, file);
fd = open_maybe_compressed(path);
if (fd < 0) {
strcpy(path, FIRMWARE_BASE);
strcat(path, firmware_path);
strcat(path, "/");
strcat(path, file);
fd = open_maybe_compressed(path);
}
if (fd >= 0)
break;
if (errno != ENOENT)
warn("failed to open %s", path);
}
free(firmware_value_copy);
closedir(class_dir);
return fd;
}
/**
* translate_readwrite() - open "file" from a temporary directory
* @file: relative path of the requested file, with /readwrite/ stripped
* @flags: flags to be passed to open(2)
*
* Return: opened fd on success, -1 otherwise
*/
static int translate_readwrite(const char *file, int flags)
{
int base;
int ret;
int fd;
ret = mkdir(TQFTPSERV_TMP, 0700);
if (ret < 0 && errno != EEXIST) {
warn("failed to create temporary tqftpserv directory");
return -1;
}
base = open(TQFTPSERV_TMP, O_RDONLY | O_DIRECTORY);
if (base < 0) {
warn("failed top open temporary tqftpserv directory");
return -1;
}
fd = openat(base, file, flags, 0600);
close(base);
if (fd < 0)
warn("failed to open %s", file);
return fd;
}
/**
* translate_open() - open file after translating path
*
* Strips /readonly/firmware/image and search among remoteproc firmware.
* Replaces /readwrite with a temporary directory.
*/
int translate_open(const char *path, int flags)
{
if (!strncmp(path, READONLY_PATH, strlen(READONLY_PATH)))
return translate_readonly(path + strlen(READONLY_PATH));
else if (!strncmp(path, READWRITE_PATH, strlen(READWRITE_PATH)))
return translate_readwrite(path + strlen(READWRITE_PATH), flags);
fprintf(stderr, "invalid path %s, rejecting\n", path);
errno = ENOENT;
return -1;
}
/* linux-firmware uses .zst as file extension */
#define ZSTD_EXTENSION ".zst"
/**
* open_maybe_compressed() - open a file and maybe decompress it if necessary
* @filename: path to a file that may be compressed (should not include compression format extension)
*
* Return: opened fd on success, -1 on error
*/
static int open_maybe_compressed(const char *path)
{
char *path_with_zstd_extension = NULL;
int fd = -1;
if (access(path, F_OK) == 0)
return open(path, O_RDONLY);
asprintf(&path_with_zstd_extension, "%s%s", path, ZSTD_EXTENSION);
if (access(path_with_zstd_extension, F_OK) == 0)
fd = zstd_decompress_file(path_with_zstd_extension);
free(path_with_zstd_extension);
return fd;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef __TRANSLATE_H__
#define __TRANSLATE_H__
int translate_open(const char *path, int flags);
#endif
+97
View File
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2024, Stefan Hansson
* Copyright (c) 2024, Emil Velikov
*/
/* For memfd_create */
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zstd.h>
#include "zstd-decompress.h"
/**
* zstd_decompress_file() - decompress a zstd-compressed file
* @filename: path to a file to decompress
*
* Return: opened fd on success, -1 on error
*/
int zstd_decompress_file(const char *filename)
{
/* Figure out the size of the file. */
struct stat file_stat;
if (stat(filename, &file_stat) == -1) {
fprintf(stderr, "stat %s failed (%s)\n", filename, strerror(errno));
return -1;
}
const size_t file_size = file_stat.st_size;
const int input_file_fd = open(filename, O_RDONLY);
if (input_file_fd == -1) {
perror("open failed");
return -1;
}
void* const compressed_buffer = mmap(NULL, file_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, input_file_fd, 0);
if (compressed_buffer == MAP_FAILED) {
perror("mmap failed");
close(input_file_fd);
return -1;
}
close(input_file_fd);
const unsigned long long decompressed_size = ZSTD_getFrameContentSize(compressed_buffer, file_size);
if (decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
fprintf(stderr, "Content size could not be determined for %s\n", filename);
munmap(compressed_buffer, file_size);
return -1;
}
if (decompressed_size == ZSTD_CONTENTSIZE_ERROR) {
fprintf(stderr, "Error getting content size for %s\n", filename);
munmap(compressed_buffer, file_size);
return -1;
}
void* const decompressed_buffer = malloc((size_t)decompressed_size);
if (decompressed_buffer == NULL) {
perror("malloc failed");
munmap(compressed_buffer, file_size);
return -1;
}
const size_t return_size = ZSTD_decompress(decompressed_buffer, decompressed_size, compressed_buffer, file_size);
if (ZSTD_isError(return_size)) {
fprintf(stderr, "ZSTD_decompress failed: %s\n", ZSTD_getErrorName(return_size));
free(decompressed_buffer);
munmap(compressed_buffer, file_size);
return -1;
}
const int output_file_fd = memfd_create(filename, 0);
if (output_file_fd == -1) {
perror("memfd_create failed");
free(decompressed_buffer);
munmap(compressed_buffer, file_size);
return -1;
}
if (write(output_file_fd, decompressed_buffer, decompressed_size) != decompressed_size) {
perror("write failed");
close(output_file_fd);
free(decompressed_buffer);
munmap(compressed_buffer, file_size);
return -1;
}
return output_file_fd;
}
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2024, Stefan Hansson
*/
#ifndef __ZSTD_DECOMPRESS_H__
#define __ZSTD_DECOMPRESS_H__
#include <stdbool.h>
#ifdef HAVE_ZSTD
int zstd_decompress_file(const char *filename);
#else
static int zstd_decompress_file(const char *filename)
{
fprintf(stderr, "Built without ZSTD support\n");
return -1;
}
#endif
#endif