91 Commits
Author SHA1 Message Date
Dmitry Baryshkov 128cd6e0a3 Merge pull request #48 from lumag/xz
tqftpserv: add support for xz decompression
2026-10-07 00:10:01 +02:00
Dmitry Baryshkov 49f6a862b4 ci: exercise xz autodetection
Mirror the zstd handling for xz: add an "xz" matrix dimension that
installs the liblzma development package for the "enabled" variant and
omits it for the "disabled" one, and verify with check-xz.sh that
xz-decompress.c is compiled iff liblzma was available. liblzma ships in
the Arch base image, so the xz-disabled variant is excluded there.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-10-06 09:07:03 +02:00
Dmitry Baryshkov 80a65872be tqftpserv: add support for xz decompression
Some distributions (e.g. Fedora) use xz compression for the firmware
files stored in /lib/firmware. Add an xz decompression path using
liblzma, mirroring the existing zstd support, and try the .xz variant
when opening a possibly-compressed file.

Closes: #46
Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-10-06 09:07:03 +02:00
Dmitry Baryshkov c0da4aa516 Merge pull request #49 from lumag/ci
Add GitHub Actions build CI with zstd autodetection
2026-10-06 09:06:30 +02:00
Dmitry Baryshkov 998ea7dabc ci: cross-compile for Android with the NDK
Add an Android job to the CI that cross-compiles tqftpserv with the
Android NDK for the arm64-v8a, armeabi-v7a and x86_64 ABIs. libqrtr is
cross-built from source and linked in, matching the Android.bp build.
The NDK download and the per-ABI libqrtr build are cached.

Define the ANDROID macro when building for an Android host so the vendor
firmware paths in translate.c are selected.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-09-15 23:02:45 +03:00
Dmitry Baryshkov c4aee5ebc4 tqftpserv: fix seek option format on 32-bit
The seek option value is an off_t, but it was formatted with %zd, which
consumes an ssize_t. On 32-bit platforms with _FILE_OFFSET_BITS=64 off_t
is 64-bit while ssize_t is 32-bit, so the wrong value is emitted (and the
NDK build flags it as a -Wformat error). Cast to size_t to match the
conversion specifier, as is done for the other numeric options.

Fixes: 7557ca4754 ("tftp: Ensure that the oack message stays within bounds")
Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-09-15 23:02:45 +03:00
Dmitry Baryshkov 1d25793a68 ci: add GitHub Actions build matrix
Add a GitHub Actions workflow modeled on the cdba project's CI. It builds
tqftpserv across a matrix of distributions (Alpine, Arch, Debian, Fedora,
Ubuntu) and compilers (gcc, clang).

The matrix additionally toggles zstd support: the "enabled" variant
installs the libzstd development package, the "disabled" variant does
not, so that both autodetected configurations are exercised. A small
check verifies that the autodetection matched the requested variant.

qrtr is installed from the distribution packages where available; on Arch
(where it only exists in the AUR) it is built from source. zstd is part
of the Arch base image and cannot be removed, so the zstd-disabled
variant is excluded there.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-09-15 23:02:45 +03:00
Dmitry Baryshkov c2559a2609 Merge pull request #53 from f-izzo/populate-readwrite
tqftpserv-populate-readwrite: add unit to copy files from persist part
2026-09-02 03:26:54 +03:00
Federico Amedeo Izzo 33e310c141 tqftpserv-populate-readwrite: add unit to copy files from persist part
Some devices (motorola-dubai) need to access readwrite files found in the
persist partition in `rfs/msm/mpss` to have working modem.

Create a new systemd unit that calls a shell script to copy the files
into the readwrite folder (currently /var/lib/tqftpserv).
The units runs only if and after /dev/disk/by-partlabel/persist is available.

Signed-off-by: Federico Amedeo Izzo <federico@izzo.pro>
2026-08-30 10:07:10 +02:00
Dmitry Baryshkov 3d2774d60e meson: fall back to finding libqrtr without pkg-config
Support old libqrtr that does not ship a qrtr.pc pkg-config file by
locating the library and its header directly when the pkg-config lookup
fails.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-07-06 02:25:36 +03:00
Dmitry Baryshkov dec7b7e543 meson: autodetect zstd support
Make libzstd an optional, autodetected dependency instead of a hard
requirement: build in the zstd decompression path when libzstd is
available and fall back to the stub in zstd-decompress.h otherwise. This
lets tqftpserv be built on systems without libzstd and makes it possible
to exercise both configurations in CI.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-07-06 02:13:33 +03:00
Dmitry Baryshkov 836161d68d zstd-decompress: include stdio.h in the stub
The fallback zstd_decompress_file() stub, used when built without zstd
support, calls fprintf() but relied on the includer having pulled in
stdio.h already. Include it directly so the header is self-contained.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-07-06 02:13:33 +03:00
Dmitry Baryshkov 1085b1f948 meson: define systemd_system_unit_dir in the else branch
systemd_system_unit_dir was only assigned inside the if/elif handling the
systemd-unit-prefix option and a found systemd pkg-config file. When
neither applies (e.g. on musl-based distros without a systemd .pc file),
the variable was left undefined and referenced later, aborting configure.
Add an else branch that sets it to an empty string.

Fixes: d4c7f06435 ("build: allow building systemd unit files without systemd dependency")
Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-07-06 02:13:33 +03:00
Dmitry Baryshkov b6bb92d40c Merge pull request #44 from orbisai0security/fix-v001-path-traversal-wrq
fix: add path validation in tqftpserv.c
2026-05-19 01:51:27 +03:00
Dmitry Baryshkov eda00d2917 Merge pull request #43 from zjc-enigma/topic/readonly-vendor-firmware
translate: support /readonly/vendor/firmware/ paths
2026-05-19 01:51:12 +03:00
OrbisAI Security 566445f44f tqftpserv: use memcpy instead of strcpy in tftp_send_error
Replace strcpy() with memcpy() when copying the error message into
the TFTP error packet buffer. The buffer length is already computed
as (4 + strlen(msg) + 1), so memcpy with (len - 4) copies exactly
the right amount. This avoids reliance on strcpy's null-terminator
scanning behavior for a fixed-length copy.

Signed-off-by: OrbisAI Security <mediratta01.pally@gmail.com>
2026-05-18 21:01:24 +05:30
OrbisAI Security fc66ee26a4 translate: reject path traversal in readwrite handler
The translate_readwrite() function opens files relative to the
persistent readwrite directory based on client-supplied paths.
A malicious TFTP client could use '..' sequences to escape the
intended directory and read/write arbitrary files on the host.

Add a strstr() check to reject any path containing '..' before
opening the file.

Signed-off-by: OrbisAI Security <mediratta01.pally@gmail.com>
2026-05-18 21:01:10 +05:30
Dmitry Baryshkov 7259073ad9 translate: verify asprintf return value
asprintf() can fail, keeping the path_with_zstd_extension unchanged,
which can lead to further errors / crashes. Return early if asprintf
fails.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2026-05-18 09:25:12 +02:00
Astro Enigma e3533ef115 translate: support /readonly/vendor/firmware/ paths
Some Qualcomm modem firmware (Xiaomi raphael on sm8150, db845c, ...)
requests its protection-domain firmware blobs via TFTP-over-QRTR at
Android-style vendor partition paths, which tqftpserv currently
rejects with "invalid path ... rejecting", leaving the wifi (or other)
protection domain unloaded.

Add two more readonly prefixes that delegate to translate_readonly()
(the existing remoteproc-firmware-dir search):

  /readonly/vendor/firmware/             - Xiaomi raphael (sm8150), ...
  /readonly/vendor/firmware_mnt/image/   - db845c (per issue #7)

For this to actually serve a blob, the file needs to exist in one of
the firmware directories that translate_readonly() iterates (i.e.
alongside a remoteproc's firmware= attribute). On postmarketOS
xiaomi-raphael, the firmware-xiaomi-raphael-wlan package currently
ships wlanmdsp.mbn at /lib/firmware/wlanmdsp.mbn; once that is moved
(or symlinked) into /lib/firmware/qcom/sm8150/xiaomi/raphael/
(alongside modem.mbn), ath10k_snoc completes its probe, phy0 appears,
and NetworkManager auto-creates wlan0.

Refs: https://github.com/linux-msm/tqftpserv/issues/7

Signed-off-by: Astro Enigma <astronazer@gmail.com>
2026-05-18 12:10:36 +08:00
Dmitry Baryshkov 238de1c6a2 Merge pull request #39 from bhaskarv79/topic/rw_persist
translate: add persistent path for read-write and fix read-only search path
2026-05-13 12:47:19 +03:00
Bhaskar Valaboju 3828238cff translate: map /readonly/firmware/modem_pr to FIRMWARE_BASE/modem_pr
On qcm6490, the inbuilt modem requests firmware images via TFTP
RRQ using the path prefix /readonly/firmware/modem_pr/so/ instead of the
previously expected /readonly/firmware/image/. The server fails to match
this path and does not invoke translate_readonly, causing the modem boot
to fail.

Add READONLY_FW_BASE and READONLY_MODEM_PATH macros to explicitly match
/readonly/firmware/modem_pr and reuse translate_readonly() by passing
the modem_pr relative path (e.g., modem_pr/so/file.mbn).
translate_readonly() then searches the remoteproc firmware directory
and constructs the full path as:

  READONLY_FW_BASE/<fw_dir>/modem_pr/so/file.mbn

With this fix, the modem boots successfully and the link comes up.

Signed-off-by: Bhaskar Valaboju <bhaskar.valaboju@oss.qualcomm.com>
2026-05-12 18:27:16 +05:30
Bhaskar Valaboju ac0e4ac2df translate: rename TQFTPSERV_TMP to TQFTPSERV_RW_DIR
The macro name TQFTPSERV_TMP is misleading since /var/lib/tqftpserv
is persistent storage, not a temporary directory. Rename it to
TQFTPSERV_RW_DIR to reflect its role as the base directory for
/readwrite/ path requests (WRQ operations).

Also update the warn() messages and kdoc comment in
translate_readwrite() to drop the stale "temporary" wording.

Signed-off-by: Bhaskar Valaboju <bhaskar.valaboju@oss.qualcomm.com>
2026-05-12 18:27:16 +05:30
Bhaskar Valaboju 614a2de2ed translate: use /var/lib/tqftpserv for persistent readwrite storage
Modem subsystems write calibration data and other persistent files via
TFTP to the readwrite path. These files must survive system reboots to
avoid re-calibration on every boot cycle.

Change TQFTPSERV_TMP from /tmp/tqftpserv to /var/lib/tqftpserv on
non-Android builds. The /tmp directory is typically a tmpfs that is
cleared on reboot, which would cause loss of modem calibration data.
/var/lib/tqftpserv provides persistent storage that survives reboots
and is the appropriate location for application state data.

Update tqftpserv.service.in and include StateDirectory to let systemd
create /var/lib/tqftpserv if it is not present.

Signed-off-by: Bhaskar Valaboju <bhaskar.valaboju@oss.qualcomm.com>
2026-05-11 14:10:11 +05:30
Dmitry Baryshkov a8a29e947d Merge pull request #36 from me-cafebabe/master
Fix build for Android
2026-04-22 17:34:20 +03:00
Dmitry Baryshkov cbc47b5855 Merge pull request #37 from bhaskarv79/topic/fix_wsize_resp
tftp: ACK short DATA packets in WRQ window mode
2026-04-22 17:33:44 +03:00
Bhaskar Valaboju 9ef11c0902 tftp: ACK short DATA packets in WRQ window mode
When receiving DATA packets during a WRQ transfer, the payload size
may be smaller than the negotiated blksize. This indicates the final
DATA packet, even if fewer than wsize packets have been received.

ACK such packets immediately instead of waiting for a full window,
so the transfer can complete correctly.

Signed-off-by: Bhaskar Valaboju <bhaskar.valaboju@oss.qualcomm.com>
2026-04-22 19:35:12 +05:30
Yumi Yukimura a5107244ce Fix build for Android
Signed-off-by: Yumi Yukimura <me.cafebabe@gmail.com>
2026-03-14 13:35:19 +08:00
Bjorn Andersson 0e78f03409 tftp: Polish up remaining logging a bit
The current logs are a mishmash of prints to stdout and stderr. This
both presents a bad user experience and doesn't fold well with
integration into syslog or similar systems.

Similar to log_debug(), introduce a log_err() and log_info(). The prior
can be used to indicate errors in the server or events that the
implementer/owner of the system might benefit from paying attention to.
The latter can be used to provide useful insights into the what the
server is doing (e.g. what files does it serve).

The "[TQFTP]" prefix is removed from the strings, as they don't add any
value when routed through any form of logging framework.

Further polishing of the log entries, both in placement and content, is
expected but the baseline is less of a mishmash now.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson e2a2ecd226 tftp: Replace commented out debug prints with log call
There's a few commented out development/debug prints lingering in the
code that can be useful to enable in order to get some basic insights
into what's going on in the server.

While more work is needed to determine what level of debug prints are
useful, and where they should be placed, introduce a '-d' argument to
tqftpserv and translate these to runtime-conditional prints for now.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 6e6d765eda tftp: Inform client that read transmisison is terminated
While servicing a read, the only valid messages to receive are OP_ACK
and OP_ERROR. The code correctly handles this in terms of cleaning up
resources and closing the socket, but it doesn't inform the client that
the transfer is terminated.

Resolve this by sending out an error message.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 7557ca4754 tftp: Ensure that the oack message stays within bounds
The creation of the oath message is mix of strcpy() and sprintf(). While
it won't overflow the fixed size buffer today, it's better to introduce
bounds check for the future, and to avoid the "dangerous" functions.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 006afcbefe tftp: add path traversal protection
The server currently accepts any filename from clients without
validation, allowing directory traversal attacks using paths like
'../../../etc/passwd'. This could expose sensitive files outside the
intended serving directory.

Add a sanitize step to validate that the filename does not contain '..'
portions.

Log rejected paths for security auditing.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson d2b8ad5f07 tftp: use lseek() for file size determination and handle errors
Replace fstat() with lseek(SEEK_END) for determining file size when
the tsize option is requested. This approach is more portable and
doesn't require struct stat.

Additionally, handle the case where lseek() fails (e.g., on unseekable
files like pipes or special devices). When this occurs, send a TFTP
ERROR with code 0 (Not defined) and a descriptive message to inform
the client that the file size cannot be determined.

After obtaining the size, reset the file position to the beginning
with lseek(SEEK_SET) to ensure subsequent reads start from the correct
offset.

Also fix resource leak: close the socket when file open fails, ensuring
proper cleanup in all error paths.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson dd4eebcf5e tftp: Signal that the file system write failed
The current behavior when failing to write out a block of data to the
storage media is to print an error and return, presumably hoping that
once the client retries the "lost" packet there will suddenly be space
in the file system.

Instead communicate the error to the client, as expected.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 477f031648 tftp: clean up error handling in RRQ and WRQ handlers
Most of the error handling in RRQ and WRQ are "handled" by /* XXX */
comments. Implement some actual error handling, to not leak the
resources.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson f84cefc24e tftp: Avoid double free in tftp_send_data() on failure
While we don't actively tear down the client context on failure, when it
happens (as the remote processor or the client socket goes away), the
block buffer object will be freed.

tftp_send_data() freeing the buffer on failure will therefor result in a
double free, so leave it around.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson af9bb54a43 tftp: send error response when file read fails during transfer
When pread() fails during an active file transfer, send a TFTP ERROR
packet to inform the client rather than silently aborting. This can
occur due to I/O errors, filesystem issues, or the file being truncated
during transfer.

Per RFC 1350, we use error code 0 (Not defined) with a descriptive
message, as there's no specific error code for read failures during
transfer. The error message now includes strerror(errno) to provide
detailed diagnostic information in the server logs.

This ensures the client receives proper notification of the failure
rather than timing out waiting for data that will never arrive.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson b3f80df192 tftp: send error response when WRQ file open fails
Per RFC 1350, when the server cannot create or open a file for writing,
it must send an ERROR packet with error code 2 (Access violation) to
inform the client of the failure.

Previously, when translate_open() failed during a write request, we
would log the error locally but fail to notify the client. This left
the client waiting indefinitely for an acknowledgment that would never
arrive.

Now we properly send an error response using the standard TFTP error
mechanism, allowing the client to handle the failure appropriately
rather than timing out. This could occur due to permission issues,
disk full conditions, or other filesystem errors.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson f47b088a89 tftp: reject non-octet transfer modes
Per RFC 1350, TFTP supports multiple transfer modes (netascii, octet,
mail), but this server only implements octet mode. When a client
requests a different mode, we must send an ERROR packet with error
code 4 (Illegal TFTP operation) rather than silently dropping the
request.

Previously, we would log the rejection but fail to notify the client,
leaving it waiting indefinitely for a response that would never come.

Now we properly inform the client that only octet mode is supported,
allowing it to either retry with the correct mode or report a
meaningful error to the user.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 0a041debc7 tftp: validate NUL-termination of filename and mode in WRQ and RRQ
Both handle_rrq() and handle_wrq() functions uses strlen() on the
filename and mode fields from network packets without first verifying
they were NUL-terminated and within the packet bounds. Malformed packets
could cause buffer overruns when these strings were processed.

Replace strlen() with strnlen() and verify both filename and mode
are properly NUL-terminated within the received packet buffer.
Reject malformed packets early with appropriate error logging.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson f6f9b15284 tftp: validate option values to prevent resource exhaustion
The parse_options() function invokes strlen() without considering that
the incoming options might not be NUL-terminated, uses atoi() without
error checking and does not validate option values against RFC-specified
limits. This allows malicious clients to trick the server to read beyond
the buffer, request invalid values that could cause resource exhaustion
or integer overflow attacks.

Validate that each option is within bounds, then add RFC-compliant
constants for option limits (MIN/MAX_BLKSIZE per RFC 2348, MIN/MAX_WSIZE
per RFC 7440, and reasonable limits for custom options). Replace atoi()
with strtoll() for proper error detection. Validate all option values
and reject requests with out-of-range values, logging the invalid values
for diagnostics.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 465ced8be7 tftp: Relocate the tftp_send_error() function
The tftp_send_error() function is going to be called in many more
places, rather than keeping it in the middle of the code and introduce a
forward declaration, move the function up.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson c7cf88b05e tftp: use symbolic constants for error codes
Replace magic numbers with RFC-defined symbolic constants for TFTP
error codes throughout the codebase. This improves code readability
and maintainability by making the error semantics explicit.

Defined constants per RFC 1350 and RFC 2347:
- TFTP_ERROR_ENOENT (1): File not found
- TFTP_ERROR_EACCESS (2): Access violation
- TFTP_ERROR_EBADOP (4): Illegal TFTP operation
- TFTP_ERROR_EOPTNEG (8): Option negotiation failed

The use of symbolic names makes it immediately clear what error
condition is being reported, and ensures consistency across all
error reporting paths in the server.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
Bjorn Andersson 1995188db0 tftp: Fix block buffer signedness warning
The buffer type differs in signedness (buffer is unsigned, local
pointer is signed), resulting in build warnings even with default
settings.

Correct the local type to fix the warnings.

Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
2026-01-23 20:06:10 -06:00
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
23 changed files with 1598 additions and 254 deletions

No files matched your search

+147
View File
@@ -0,0 +1,147 @@
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Loosely based on the cdba project CI:
# https://github.com/linux-msm/cdba
#
name: "Builds"
on:
pull_request:
push:
schedule:
# Run at 1:01 PM, every Tuesday
- cron: '1 13 * * 2'
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
# zstd and xz support are autodetected: the "enabled" variant installs
# the corresponding development package, the "disabled" one does not.
zstd: [enabled, disabled]
xz: [enabled, disabled]
container:
- alpine:latest
- archlinux:latest
- debian:testing
- debian:stable
- fedora:latest
- ubuntu:latest
- ubuntu:noble
exclude:
# zstd and xz ship in the Arch base image and cannot be removed, so
# the "disabled" builds cannot be exercised there.
- container: archlinux:latest
zstd: disabled
- container: archlinux:latest
xz: disabled
container:
image: ${{ matrix.container }}
env:
CC: ${{ matrix.compiler }}
ZSTD: ${{ matrix.zstd }}
XZ: ${{ matrix.xz }}
steps:
- name: Show OS
run: cat /etc/os-release
- name: Git checkout
uses: actions/checkout@v5
- name: Install additional packages
run: |
INSTALL=${{ matrix.container }}
INSTALL="${INSTALL%%:*}"
INSTALL="${INSTALL%%/*}"
./ci/$INSTALL.sh
- name: Compiler version
run: $CC --version
- name: Meson setup
run: meson setup --errorlogs --werror build .
- name: Compile
run: ninja -C build
- name: Verify zstd autodetection
run: ./ci/check-zstd.sh
- name: Verify xz autodetection
run: ./ci/check-xz.sh
- name: Install
run: ninja -C build install
android:
name: Android
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
abi: [arm64-v8a, armeabi-v7a, x86_64]
env:
NDK_VERSION: r27d
QRTR_REF: v1.2
API: "24"
ABI: ${{ matrix.abi }}
QRTR_SRC: ${{ github.workspace }}/qrtr
QRTR_PREFIX: ${{ github.workspace }}/qrtr-install/${{ matrix.abi }}
steps:
- name: Git checkout
uses: actions/checkout@v5
- name: Install build tools
run: |
sudo apt update
sudo apt install -y --no-install-recommends meson ninja-build pkg-config
- name: Cache Android NDK
id: ndk-cache
uses: actions/cache@v4
with:
path: android-ndk-${{ env.NDK_VERSION }}
key: ndk-${{ env.NDK_VERSION }}-linux
- name: Download Android NDK
if: steps.ndk-cache.outputs.cache-hit != 'true'
run: |
curl -sSLO https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip
unzip -q android-ndk-${NDK_VERSION}-linux.zip
rm android-ndk-${NDK_VERSION}-linux.zip
- name: Cache libqrtr
id: qrtr-cache
uses: actions/cache@v4
with:
path: qrtr-install/${{ matrix.abi }}
key: qrtr-android-${{ matrix.abi }}-${{ env.QRTR_REF }}-ndk-${{ env.NDK_VERSION }}
- name: Checkout qrtr
if: steps.qrtr-cache.outputs.cache-hit != 'true'
uses: actions/checkout@v5
with:
repository: linux-msm/qrtr
ref: ${{ env.QRTR_REF }}
path: qrtr
- name: Build for Android
run: |
export ANDROID_NDK="$GITHUB_WORKSPACE/android-ndk-${NDK_VERSION}"
./ci/android.sh
-25
View File
@@ -1,25 +0,0 @@
TQFTPSERV := tqftpserv
CFLAGS := -Wall -g -O2
LDFLAGS := -lqrtr
prefix ?= /usr/local
bindir := $(prefix)/bin
servicedir := $(prefix)/lib/systemd/system
SRCS := tqftpserv.c translate.c
OBJS := $(SRCS:.c=.o)
$(TQFTPSERV): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
tqftpserv.service: tqftpserv.service.in
@sed 's+TQFTPSERV_PATH+$(bindir)+g' $< > $@
install: $(TQFTPSERV) tqftpserv.service
@install -D -m 755 $(TQFTPSERV) $(DESTDIR)$(bindir)/$(TQFTPSERV)
@install -D -m 644 tqftpserv.service $(DESTDIR)$(servicedir)/tqftpserv.service
clean:
rm -f $(TQFTPSERV) $(OBJS) tqftpserv.service
+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
```
Executable
+39
View File
@@ -0,0 +1,39 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Install build dependencies on Alpine Linux.
set -ex
# build-base pulls in binutils (the linker/assembler) and the musl headers,
# which clang needs as well; add clang on top when it is the compiler.
PKGS_CC=""
case $CC in
clang*)
PKGS_CC="clang"
;;
esac
PKGS_ZSTD=""
if [ "$ZSTD" = "enabled" ]; then
PKGS_ZSTD="zstd-dev"
fi
PKGS_XZ=""
if [ "$XZ" = "enabled" ]; then
PKGS_XZ="xz-dev"
fi
apk add \
build-base \
pkgconf \
meson \
ninja \
qrtr-dev \
$PKGS_ZSTD \
$PKGS_XZ \
$PKGS_CC
echo "Install finished: $0"
Executable
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Cross-build tqftpserv (and its libqrtr dependency) for Android using the NDK.
#
# Expects the following environment variables:
# ABI - Android ABI (arm64-v8a, armeabi-v7a, x86_64)
# API - Android API level to target
# ANDROID_NDK - path to an unpacked Android NDK
# QRTR_SRC - path to a checked-out qrtr source tree
# QRTR_PREFIX - prefix into which libqrtr is (or was) installed
set -ex
TOOLCHAIN="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64"
case "$ABI" in
arm64-v8a)
CPU_FAMILY=aarch64
CPU=aarch64
TRIPLE=aarch64-linux-android
ELF_MACHINE=AArch64
;;
armeabi-v7a)
CPU_FAMILY=arm
CPU=armv7a
TRIPLE=armv7a-linux-androideabi
ELF_MACHINE=ARM
;;
x86_64)
CPU_FAMILY=x86_64
CPU=x86_64
TRIPLE=x86_64-linux-android
ELF_MACHINE=X86-64
;;
*)
echo "Unknown ABI '$ABI'" >&2
exit 1
;;
esac
# The per-ABI clang wrapper already selects the correct target and sysroot, so
# the cross file does not need to spell those out.
cat > android-cross.txt <<EOF
[binaries]
c = '$TOOLCHAIN/bin/${TRIPLE}${API}-clang'
ar = '$TOOLCHAIN/bin/llvm-ar'
strip = '$TOOLCHAIN/bin/llvm-strip'
pkg-config = 'pkg-config'
[host_machine]
system = 'android'
cpu_family = '$CPU_FAMILY'
cpu = '$CPU'
endian = 'little'
[properties]
pkg_config_libdir = '$QRTR_PREFIX/lib/pkgconfig'
EOF
cat android-cross.txt
# Build and install libqrtr, unless it was already restored from the cache.
if [ ! -f "$QRTR_PREFIX/lib/pkgconfig/qrtr.pc" ]; then
meson setup --cross-file android-cross.txt \
--prefix="$QRTR_PREFIX" --libdir=lib \
"$QRTR_SRC/build-$ABI" "$QRTR_SRC"
ninja -C "$QRTR_SRC/build-$ABI" install
fi
meson setup --errorlogs --werror --cross-file android-cross.txt build .
ninja -C build
# Confirm the resulting binary really is for the requested ABI.
"$TOOLCHAIN/bin/llvm-readelf" -h build/tqftpserv | grep -q "Machine:.*$ELF_MACHINE"
echo "Android build for $ABI ($ELF_MACHINE) succeeded"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Install build dependencies on Arch Linux.
#
# qrtr is only packaged in the AUR, which is not available in the CI
# container, so build and install it from source.
set -ex
PKGS_CC="gcc"
case $CC in
clang*)
PKGS_CC="clang"
;;
esac
# zstd and xz (liblzma) ship as part of the Arch base image and cannot be
# removed, so there is no "disabled" variant to install here.
pacman -Syu --noconfirm \
pkgconf \
meson \
ninja \
git \
zstd \
xz \
$PKGS_CC
QRTR_SRC=$(mktemp -d)
git clone --depth 1 https://github.com/linux-msm/qrtr "$QRTR_SRC"
meson setup --prefix=/usr "$QRTR_SRC/build" "$QRTR_SRC"
ninja -C "$QRTR_SRC/build"
ninja -C "$QRTR_SRC/build" install
rm -rf "$QRTR_SRC"
echo "Install finished: $0"
Executable
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Verify that xz support was autodetected as expected: xz-decompress.c is
# only compiled when liblzma is available.
set -eu
OBJ=build/tqftpserv.p/xz-decompress.c.o
case "$XZ" in
enabled)
if [ ! -f "$OBJ" ]; then
echo "ERROR: expected xz support, but xz-decompress.c was not built"
exit 1
fi
;;
disabled)
if [ -f "$OBJ" ]; then
echo "ERROR: expected no xz support, but xz-decompress.c was built"
exit 1
fi
;;
*)
echo "ERROR: unexpected XZ value '$XZ'"
exit 1
;;
esac
echo "xz support check ($XZ) passed"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Verify that zstd support was autodetected as expected: zstd-decompress.c is
# only compiled when libzstd is available.
set -eu
OBJ=build/tqftpserv.p/zstd-decompress.c.o
case "$ZSTD" in
enabled)
if [ ! -f "$OBJ" ]; then
echo "ERROR: expected zstd support, but zstd-decompress.c was not built"
exit 1
fi
;;
disabled)
if [ -f "$OBJ" ]; then
echo "ERROR: expected no zstd support, but zstd-decompress.c was built"
exit 1
fi
;;
*)
echo "ERROR: unexpected ZSTD value '$ZSTD'"
exit 1
;;
esac
echo "zstd support check ($ZSTD) passed"
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Install build dependencies on Debian and Ubuntu.
set -ex
apt update
# Some distros might pull tzdata which asks questions
export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true
PKGS_CC="build-essential"
case $CC in
clang*)
PKGS_CC="clang"
;;
esac
PKGS_ZSTD=""
if [ "$ZSTD" = "enabled" ]; then
PKGS_ZSTD="libzstd-dev"
fi
PKGS_XZ=""
if [ "$XZ" = "enabled" ]; then
PKGS_XZ="liblzma-dev"
fi
apt install -y --no-install-recommends \
pkg-config \
meson \
ninja-build \
libqrtr-dev \
$PKGS_ZSTD \
$PKGS_XZ \
$PKGS_CC
echo "Install finished: $0"
Executable
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#
# Install build dependencies on Fedora.
set -ex
PKGS_CC="gcc"
case $CC in
clang*)
PKGS_CC="clang"
;;
esac
PKGS_ZSTD=""
if [ "$ZSTD" = "enabled" ]; then
PKGS_ZSTD="libzstd-devel"
fi
PKGS_XZ=""
if [ "$XZ" = "enabled" ]; then
PKGS_XZ="xz-devel"
fi
dnf -y install \
pkgconf-pkg-config \
meson \
ninja-build \
qrtr-devel \
$PKGS_ZSTD \
$PKGS_XZ \
$PKGS_CC
echo "Install finished: $0"
+1
View File
@@ -0,0 +1 @@
debian.sh
+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__
+69
View File
@@ -0,0 +1,69 @@
# SPDX-License-Identifier: BSD-3-Clause
project('tqftpserv',
'c',
version: '1.1.1',
default_options: [
'warning_level=1',
'buildtype=release',
])
prefix = get_option('prefix')
if host_machine.system() == 'android'
add_project_arguments('-DANDROID', language : 'c')
endif
tqftpserv_srcs = ['translate.c',
'tqftpserv.c']
tqftpserv_deps = []
xz_dep = dependency('liblzma', required : false)
if xz_dep.found()
add_project_arguments('-DHAVE_XZ', language : 'c')
tqftpserv_srcs += 'xz-decompress.c'
tqftpserv_deps += xz_dep
endif
zstd_dep = dependency('libzstd', required : false)
if zstd_dep.found()
add_project_arguments('-DHAVE_ZSTD', language : 'c')
tqftpserv_srcs += 'zstd-decompress.c'
tqftpserv_deps += zstd_dep
endif
# 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])
else
systemd_system_unit_dir = ''
endif
qrtr_dep = dependency('qrtr', required : false)
if not qrtr_dep.found()
# Support old libqrtr that does not ship a qrtr.pc pkg-config file.
cc = meson.get_compiler('c')
qrtr_dep = cc.find_library('qrtr', has_headers : 'libqrtr.h')
endif
tqftpserv_deps += qrtr_dep
executable('tqftpserv',
tqftpserv_srcs,
dependencies : tqftpserv_deps,
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'
)
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/sh
# Populate tqftp readwrite data from persist partition
set -eu
PERSIST_DEV="/dev/disk/by-partlabel/persist"
PERSIST_MOUNT="/mnt/persist"
TQFTPSERV_TMP="/var/lib/tqftpserv"
if [ -f $TQFTPSERV_TMP/readwrite_ready ]; then
exit 0
fi
mkdir -p $PERSIST_MOUNT
mount $PERSIST_DEV -o ro,noatime $PERSIST_MOUNT
mkdir -p $TQFTPSERV_TMP
cp -r $PERSIST_MOUNT/rfs/msm/mpss/* $TQFTPSERV_TMP/
touch $TQFTPSERV_TMP/readwrite_ready
umount $PERSIST_MOUNT
rmdir $PERSIST_MOUNT
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description= Populate tqftp readwrite data from persist partition
Requires=dev-disk-by\x2dpartlabel-persist.device
After=dev-disk-by\x2dpartlabel-persist.device
[Service]
Type=oneshot
ExecStart=@prefix@/bin/populate-readwrite.sh
[Install]
WantedBy=multi-user.target
+604 -154
View File
File diff suppressed because it is too large. Load diff
+2 -4
View File
@@ -1,12 +1,10 @@
[Unit]
Description=QRTR TFTP service
Requires=qrtr-ns.service
After=qrtr-ns.service
[Service]
ExecStart=TQFTPSERV_PATH/tqftpserv
ExecStart=@prefix@/bin/tqftpserv
Restart=always
StateDirectory=tqftpserv
[Install]
WantedBy=multi-user.target
+141 -44
View File
@@ -1,32 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2019, 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.
*/
#include <sys/stat.h>
#include <sys/types.h>
@@ -37,15 +11,45 @@
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "translate.h"
#include "xz-decompress.h"
#include "zstd-decompress.h"
#define READONLY_PATH "/readonly/firmware/image/"
#define READWRITE_PATH "/readwrite/"
#define UPDATES_DIR "updates/"
#define READONLY_FW_BASE "/readonly/firmware/"
#define READONLY_MODEM_PATH READONLY_FW_BASE "modem_pr"
#define READONLY_VENDOR_PATH "/readonly/vendor/firmware/"
#define READONLY_VENDOR_MNT_PATH "/readonly/vendor/firmware_mnt/image/"
#ifndef ANDROID
#define FIRMWARE_BASE "/lib/firmware/"
#define TQFTPSERV_RW_DIR "/var/lib/tqftpserv"
#else
#define FIRMWARE_BASE "/vendor/firmware/"
#define TQFTPSERV_RW_DIR "/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
@@ -64,8 +68,11 @@
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;
@@ -73,6 +80,8 @@ static int translate_readonly(const char *file)
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");
@@ -82,7 +91,8 @@ static int translate_readonly(const char *file)
class_dir = fdopendir(class_fd);
if (!class_dir) {
warn("failed to opendir");
goto close_class;
close(class_fd);
return -1;
}
while ((de = readdir(class_dir)) != NULL) {
@@ -103,17 +113,47 @@ static int translate_readonly(const char *file)
if (n < 0) {
continue;
}
firmware_value[n] = '\0';
if (strlen(FIRMWARE_BASE) + strlen(firmware_value) + 1 +
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, dirname(firmware_value));
strcat(path, UPDATES_DIR);
strcat(path, firmware_path);
strcat(path, "/");
strcat(path, file);
fd = open(path, O_RDONLY);
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;
@@ -121,16 +161,14 @@ static int translate_readonly(const char *file)
warn("failed to open %s", path);
}
free(firmware_value_copy);
closedir(class_dir);
close_class:
close(class_fd);
return fd;
}
/**
* translate_readwrite() - open "file" from a temporary directory
* translate_readwrite() - open "file" from the persistent readwrite directory
* @file: relative path of the requested file, with /readwrite/ stripped
* @flags: flags to be passed to open(2)
*
@@ -142,15 +180,22 @@ static int translate_readwrite(const char *file, int flags)
int ret;
int fd;
ret = mkdir("/tmp/tqftpserv", 0700);
if (ret < 0 && errno != EEXIST) {
warn("failed to create /tmp/tqftpserv");
/* Reject directory traversal attempts */
if (strstr(file, "..")) {
warn("path traversal attempt rejected: %s", file);
errno = EACCES;
return -1;
}
base = open("/tmp/tqftpserv", O_RDONLY | O_DIRECTORY);
ret = mkdir(TQFTPSERV_RW_DIR, 0700);
if (ret < 0 && errno != EEXIST) {
warn("failed to create tqftpserv readwrite directory");
return -1;
}
base = open(TQFTPSERV_RW_DIR, O_RDONLY | O_DIRECTORY);
if (base < 0) {
warn("failed top open /tmp/tqftpserv");
warn("failed to open tqftpserv readwrite directory");
return -1;
}
@@ -165,19 +210,71 @@ static int translate_readwrite(const char *file, int flags)
/**
* translate_open() - open file after translating path
*
* Strips /readonly/firmware/image and search among remoteproc firmware.
* Replaces /readwrite with a temporary directory.
* Strips /readonly/firmware/image/ and searches among remoteproc firmware.
* Strips /readonly/firmware/modem_pr and searches among remoteproc firmware
* using the modem_pr relative path (e.g., maps to READONLY_FW_BASE/<fw_dir>/modem_pr/...).
* Strips /readonly/vendor/firmware/ and /readonly/vendor/firmware_mnt/image/
* and searches among remoteproc firmware.
* Replaces /readwrite/ with a persistent 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, READONLY_MODEM_PATH, strlen(READONLY_MODEM_PATH)))
return translate_readonly(path + strlen(READONLY_FW_BASE));
else if (!strncmp(path, READWRITE_PATH, strlen(READWRITE_PATH)))
return translate_readwrite(path + strlen(READWRITE_PATH), flags);
else if (!strncmp(path, READONLY_VENDOR_MNT_PATH, strlen(READONLY_VENDOR_MNT_PATH)))
return translate_readonly(path + strlen(READONLY_VENDOR_MNT_PATH));
else if (!strncmp(path, READONLY_VENDOR_PATH, strlen(READONLY_VENDOR_PATH)))
return translate_readonly(path + strlen(READONLY_VENDOR_PATH));
fprintf(stderr, "invalid path %s, rejecting\n", path);
errno = ENOENT;
return -1;
}
/* linux-firmware uses .xz as file extension */
#define XZ_EXTENSION ".xz"
/* 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_extension = NULL;
int fd = -1;
int ret;
if (access(path, F_OK) == 0)
return open(path, O_RDONLY);
ret = asprintf(&path_with_extension, "%s%s", path, XZ_EXTENSION);
if (ret < 0)
return ret;
if (access(path_with_extension, F_OK) == 0)
fd = xz_decompress_file(path_with_extension);
free(path_with_extension);
if (fd != -1)
return fd;
ret = asprintf(&path_with_extension, "%s%s", path, ZSTD_EXTENSION);
if (ret < 0)
return ret;
if (access(path_with_extension, F_OK) == 0)
fd = zstd_decompress_file(path_with_extension);
free(path_with_extension);
return fd;
}
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*
* Based on the code copyright by:
* 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <lzma.h>
#include "xz-decompress.h"
/**
* xz_decompress_file() - decompress an xz-compressed file
* @filename: path to a file to decompress
*
* The xz format does not store the uncompressed size in an easily
* queryable header, so decode the stream incrementally and write the
* output to a memfd as it is produced.
*
* Return: opened fd on success, -1 on error
*/
int xz_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);
lzma_stream strm = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&strm, UINT64_MAX, 0);
if (ret != LZMA_OK) {
fprintf(stderr, "lzma_stream_decoder failed for %s (error %d)\n", filename, ret);
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");
lzma_end(&strm);
munmap(compressed_buffer, file_size);
return -1;
}
uint8_t output_buffer[BUFSIZ];
strm.next_in = compressed_buffer;
strm.avail_in = file_size;
do {
strm.next_out = output_buffer;
strm.avail_out = sizeof(output_buffer);
ret = lzma_code(&strm, LZMA_FINISH);
if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
fprintf(stderr, "lzma_code failed for %s (error %d)\n", filename, ret);
close(output_file_fd);
lzma_end(&strm);
munmap(compressed_buffer, file_size);
return -1;
}
const size_t produced = sizeof(output_buffer) - strm.avail_out;
if (write(output_file_fd, output_buffer, produced) != (ssize_t)produced) {
perror("write failed");
close(output_file_fd);
lzma_end(&strm);
munmap(compressed_buffer, file_size);
return -1;
}
} while (ret != LZMA_STREAM_END);
lzma_end(&strm);
munmap(compressed_buffer, file_size);
return output_file_fd;
}
Loaded 20 of 23 files, more files were not shown because too many files have changed in this diff. Show more