pax_global_header00006660000000000000000000000064151275131110014507gustar00rootroot0000000000000052 comment=443c82aadae2862dc7c12af48ac0b900f4bb0fe7 tqftpserv-1.1.1/000077500000000000000000000000001512751311100135455ustar00rootroot00000000000000tqftpserv-1.1.1/Android.bp000066400000000000000000000002261512751311100154500ustar00rootroot00000000000000cc_binary { name: "tqftpserv", vendor: true, srcs: [ "tqftpserv.c", "translate.c", ], shared_libs: ["libqrtr"], } tqftpserv-1.1.1/LICENSE000066400000000000000000000030401512751311100145470ustar00rootroot00000000000000/* * 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. */ tqftpserv-1.1.1/README.md000066400000000000000000000056411512751311100150320ustar00rootroot00000000000000# 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 ``` tqftpserv-1.1.1/list.h000066400000000000000000000036201512751311100146720ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016, Linaro Ltd. */ #ifndef __LIST_H__ #define __LIST_H__ #include #include #ifndef container_of #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member)*__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #endif struct list_head { struct list_head *prev; struct list_head *next; }; #define LIST_INIT(list) { &(list), &(list) } static inline void list_init(struct list_head *list) { list->prev = list->next = list; } static inline bool list_empty(struct list_head *list) { return list->next == list; } static inline void list_add(struct list_head *list, struct list_head *item) { struct list_head *prev = list->prev; item->next = list; item->prev = prev; prev->next = list->prev = item; } static inline void list_del(struct list_head *item) { item->prev->next = item->next; item->next->prev = item->prev; } #define list_for_each(item, list) \ for (item = (list)->next; item != list; item = item->next) #define list_for_each_safe(item, next, list) \ for (item = (list)->next, next = item->next; item != list; item = next, next = item->next) #define list_entry(item, type, member) \ container_of(item, type, member) #define list_entry_first(list, type, member) \ container_of((list)->next, type, member) #define list_entry_next(item, member) \ container_of((item)->member.next, typeof(*(item)), member) #define list_for_each_entry(item, list, member) \ for (item = list_entry_first(list, typeof(*(item)), member); \ &item->member != list; \ item = list_entry_next(item, member)) #define list_for_each_entry_safe(item, next, list, member) \ for (item = list_entry_first(list, typeof(*(item)), member), \ next = list_entry_next(item, member); \ &item->member != list; \ item = next, \ next = list_entry_next(item, member)) \ #endif tqftpserv-1.1.1/meson.build000066400000000000000000000025241512751311100157120ustar00rootroot00000000000000# 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 tqftpserv-1.1.1/meson_options.txt000066400000000000000000000001531512751311100172010ustar00rootroot00000000000000option('systemd-unit-prefix', type: 'string', description: 'Directory for systemd system unit files' ) tqftpserv-1.1.1/tqftpserv.c000066400000000000000000000370671512751311100157640ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2018, Linaro Ltd. */ #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "translate.h" #define MAX(x, y) ((x) > (y) ? (x) : (y)) enum { OP_RRQ = 1, OP_WRQ, OP_DATA, OP_ACK, OP_ERROR, OP_OACK, }; enum { ERROR_END_OF_TRANSFER = 9, }; struct tftp_client { struct list_head node; struct sockaddr_qrtr sq; int sock; int fd; size_t block; size_t blksize; size_t rsize; size_t wsize; unsigned int timeoutms; off_t seek; uint8_t *blk_buf; uint8_t *rw_buf; size_t rw_buf_size; size_t blk_offset; uint16_t blk_expected; }; static struct list_head readers = LIST_INIT(readers); static struct list_head writers = LIST_INIT(writers); static ssize_t tftp_send_data(struct tftp_client *client, unsigned int block, size_t offset, size_t response_size) { ssize_t len; size_t send_len; char *buf = client->blk_buf; char *p = buf; *p++ = 0; *p++ = OP_DATA; *p++ = (block >> 8) & 0xff; *p++ = block & 0xff; len = pread(client->fd, p, client->blksize, offset); if (len < 0) { printf("[TQFTP] failed to read data\n"); free(buf); return len; } p += len; /* If rsize was set, we should limit the data in the response to n bytes */ if (response_size != 0) { /* Header (4 bytes) + data size */ send_len = 4 + response_size; if (send_len > p - buf) { printf("[TQFTP] requested data of %ld bytes but only read %ld bytes from file, rejecting\n", response_size, len); free(buf); return -EINVAL; } } else { send_len = p - buf; } // printf("[TQFTP] Sending %zd bytes of DATA\n", send_len); return send(client->sock, buf, send_len, 0); } static int tftp_send_ack(int sock, int block) { struct { uint16_t opcode; uint16_t block; } ack = { htons(OP_ACK), htons(block) }; return send(sock, &ack, sizeof(ack), 0); } static int tftp_send_oack(int sock, size_t *blocksize, size_t *tsize, size_t *wsize, unsigned int *timeoutms, size_t *rsize, off_t *seek) { char buf[512]; char *p = buf; int n; *p++ = 0; *p++ = OP_OACK; if (blocksize) { strcpy(p, "blksize"); p += 8; n = sprintf(p, "%zd", *blocksize); p += n; *p++ = '\0'; } if (timeoutms) { strcpy(p, "timeoutms"); p += 10; n = sprintf(p, "%d", *timeoutms); p += n; *p++ = '\0'; } if (tsize && *tsize != -1) { strcpy(p, "tsize"); p += 6; n = sprintf(p, "%zd", *tsize); p += n; *p++ = '\0'; } if (wsize) { strcpy(p, "wsize"); p += 6; n = sprintf(p, "%zd", *wsize); p += n; *p++ = '\0'; } if (rsize) { strcpy(p, "rsize"); p += 6; n = sprintf(p, "%zd", *rsize); p += n; *p++ = '\0'; } if (seek) { strcpy(p, "seek"); p += 5; n = sprintf(p, "%zd", *seek); p += n; *p++ = '\0'; } return send(sock, buf, p - buf, 0); } static int tftp_send_error(int sock, int code, const char *msg) { size_t len; char *buf; int rc; len = 4 + strlen(msg) + 1; buf = calloc(1, len); if (!buf) return -1; *(uint16_t*)buf = htons(OP_ERROR); *(uint16_t*)(buf + 2) = htons(code); strcpy(buf + 4, msg); rc = send(sock, buf, len, 0); free(buf); return rc; } static void parse_options(const char *buf, size_t len, size_t *blksize, ssize_t *tsize, size_t *wsize, unsigned int *timeoutms, size_t *rsize, off_t *seek) { const char *opt, *value; const char *p = buf; while (p < buf + len) { /* XXX: ensure we're not running off the end */ opt = p; p += strlen(p) + 1; /* XXX: ensure we're not running off the end */ value = p; p += strlen(p) + 1; /* * blksize: block size - how many bytes to send at once * timeoutms: timeout in milliseconds * tsize: total size - request to get file size in bytes * rsize: read size - how many bytes to send, not full file * wsize: window size - how many blocks to send without ACK * seek: offset from beginning of file in bytes to start reading */ if (!strcmp(opt, "blksize")) { *blksize = atoi(value); } else if (!strcmp(opt, "timeoutms")) { *timeoutms = atoi(value); } else if (!strcmp(opt, "tsize")) { *tsize = atoi(value); } else if (!strcmp(opt, "rsize")) { *rsize = atoi(value); } else if (!strcmp(opt, "wsize")) { *wsize = atoi(value); } else if (!strcmp(opt, "seek")) { *seek = atoi(value); } else { printf("[TQFTP] Ignoring unknown option '%s' with value '%s'\n", opt, value); } } } static void handle_rrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) { struct tftp_client *client; const char *filename; const char *mode; struct stat sb; const char *p; ssize_t tsize = -1; size_t blksize = 512; unsigned int timeoutms = 1000; size_t rsize = 0; size_t wsize = 1; off_t seek = 0; bool do_oack = false; int sock; int ret; int fd; p = buf + 2; filename = p; p += strlen(p) + 1; mode = p; p += strlen(p) + 1; if (strcasecmp(mode, "octet")) { /* XXX: error */ printf("[TQFTP] not octet, reject\n"); return; } if (p < buf + len) { do_oack = true; parse_options(p, len - (p - buf), &blksize, &tsize, &wsize, &timeoutms, &rsize, &seek); } printf("[TQFTP] RRQ: %s (mode=%s rsize=%ld seek=%ld)\n", filename, mode, rsize, seek); sock = qrtr_open(0); if (sock < 0) { /* XXX: error */ printf("[TQFTP] unable to create new qrtr socket, reject\n"); return; } ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq)); if (ret < 0) { /* XXX: error */ printf("[TQFTP] unable to connect new qrtr socket to remote\n"); return; } fd = translate_open(filename, O_RDONLY); if (fd < 0) { printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno); tftp_send_error(sock, 1, "file not found"); return; } if (tsize != -1) { fstat(fd, &sb); tsize = sb.st_size; } client = calloc(1, sizeof(*client)); client->sq = *sq; client->sock = sock; client->fd = fd; client->blksize = blksize; client->rsize = rsize; client->wsize = wsize; client->timeoutms = timeoutms; client->seek = seek; client->rw_buf_size = blksize * wsize; client->blk_buf = calloc(1, blksize + 4); if (!client->blk_buf) { printf("[TQFTP] Memory allocation failure\n"); return; } client->rw_buf = calloc(1, client->rw_buf_size); if (!client->rw_buf) { printf("[TQFTP] Memory allocation failure\n"); return; } // printf("[TQFTP] new reader added\n"); list_add(&readers, &client->node); if (do_oack) { tftp_send_oack(client->sock, &blksize, tsize ? (size_t*)&tsize : NULL, wsize ? &wsize : NULL, &client->timeoutms, rsize ? &rsize : NULL, seek ? &seek : NULL); } else { tftp_send_data(client, 1, 0, 0); } } static void handle_wrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) { struct tftp_client *client; const char *filename; const char *mode; const char *p; ssize_t tsize = -1; size_t blksize = 512; unsigned int timeoutms = 1000; size_t rsize = 0; size_t wsize = 1; off_t seek = 0; bool do_oack = false; int sock; int ret; int fd; filename = buf + 2; mode = buf + 2 + strlen(filename) + 1; p = mode + strlen(mode) + 1; if (strcasecmp(mode, "octet")) { /* XXX: error */ printf("[TQFTP] not octet, reject\n"); return; } printf("[TQFTP] WRQ: %s (%s)\n", filename, mode); if (p < buf + len) { do_oack = true; parse_options(p, len - (p - buf), &blksize, &tsize, &wsize, &timeoutms, &rsize, &seek); } fd = translate_open(filename, O_WRONLY | O_CREAT); if (fd < 0) { /* XXX: error */ printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno); return; } sock = qrtr_open(0); if (sock < 0) { /* XXX: error */ printf("[TQFTP] unable to create new qrtr socket, reject\n"); return; } ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq)); if (ret < 0) { /* XXX: error */ printf("[TQFTP] unable to connect new qrtr socket to remote\n"); return; } client = calloc(1, sizeof(*client)); client->sq = *sq; client->sock = sock; client->fd = fd; client->blksize = blksize; client->rsize = rsize; client->wsize = wsize; client->timeoutms = timeoutms; client->seek = seek; client->rw_buf_size = blksize * wsize; client->blk_expected = 1; client->blk_buf = calloc(1, blksize + 4); if (!client->blk_buf) { printf("[TQFTP] Memory allocation failure\n"); return; } client->rw_buf = calloc(1, client->rw_buf_size); if (!client->rw_buf) { printf("[TQFTP] Memory allocation failure\n"); return; } // printf("[TQFTP] new writer added\n"); list_add(&writers, &client->node); if (do_oack) { tftp_send_oack(client->sock, &blksize, tsize ? (size_t*)&tsize : NULL, wsize ? &wsize : NULL, &client->timeoutms, rsize ? &rsize : NULL, seek ? &seek : NULL); } else { tftp_send_data(client, 1, 0, 0); } } static int handle_reader(struct tftp_client *client) { struct sockaddr_qrtr sq; uint16_t block; uint16_t last; char buf[128]; socklen_t sl; ssize_t len; ssize_t n = 0; int opcode; int ret; sl = sizeof(sq); len = recvfrom(client->sock, buf, sizeof(buf), 0, (void *)&sq, &sl); if (len < 0) { ret = -errno; if (ret != -ENETRESET) fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); return -1; } /* Drop unsolicited messages */ if (sq.sq_node != client->sq.sq_node || sq.sq_port != client->sq.sq_port) { printf("[TQFTP] Discarding spoofed message\n"); return -1; } opcode = buf[0] << 8 | buf[1]; if (opcode == OP_ERROR) { buf[len] = '\0'; int err = buf[2] << 8 | buf[3]; /* "End of Transfer" is not an error, used with stat(2)-like calls */ if (err == ERROR_END_OF_TRANSFER) printf("[TQFTP] Remote returned END OF TRANSFER: %d - %s\n", err, buf + 4); else printf("[TQFTP] Remote returned an error: %d - %s\n", err, buf + 4); return -1; } else if (opcode != OP_ACK) { printf("[TQFTP] Expected ACK, got %d\n", opcode); return -1; } last = buf[2] << 8 | buf[3]; // printf("[TQFTP] Got ack for %d\n", last); /* We've sent enough data for rsize already */ if (last * client->blksize > client->rsize) return 0; for (block = last; block < last + client->wsize; block++) { size_t offset = client->seek + block * client->blksize; size_t response_size = 0; /* Check if need to limit response size based for requested rsize */ if ((block + 1) * client->blksize > client->rsize) response_size = client->rsize % client->blksize; n = tftp_send_data(client, block + 1, offset, response_size); if (n < 0) { printf("[TQFTP] Sent block %d failed: %zd\n", block + 1, n); break; } // printf("[TQFTP] Sent block %d of %zd\n", block + 1, n); if (n == 0) break; /* We've sent enough data for rsize already */ if ((block + 1) * client->blksize > client->rsize) break; } return 1; } static int handle_writer(struct tftp_client *client) { struct sockaddr_qrtr sq; uint16_t block; size_t payload; uint8_t *buf = client->blk_buf; socklen_t sl; ssize_t len; int opcode; int ret; sl = sizeof(sq); len = recvfrom(client->sock, buf, client->blksize + 4, 0, (void *)&sq, &sl); if (len < 0) { ret = -errno; if (ret != -ENETRESET) fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); return -1; } /* Drop unsolicited messages */ if (sq.sq_node != client->sq.sq_node || sq.sq_port != client->sq.sq_port) return -1; opcode = buf[0] << 8 | buf[1]; block = buf[2] << 8 | buf[3]; if (opcode != OP_DATA) { printf("[TQFTP] Expected DATA opcode, got %d\n", opcode); tftp_send_error(client->sock, 4, "Expected DATA opcode"); return -1; } payload = len - 4; buf += 4; /* Check if we recieved expected block */ if (block != client->blk_expected) { uint16_t blk_expected = client->blk_expected; printf("[TQFTP] Block number out of sequence: %d (expected %d)\n", block, blk_expected); tftp_send_error(client->sock, 4, "Block number out of sequence"); /* Set blk_expected to beginning of current window */ if ((blk_expected % client->wsize) == 0) blk_expected -= client->wsize + 1; else blk_expected -= (blk_expected % client->wsize) - 1; client->blk_expected = blk_expected; client->blk_offset = 0; return -1; } client->blk_expected++; /* Copy the data to the destination buffer */ memcpy(client->rw_buf + client->blk_offset, buf, payload); client->blk_offset += payload; /* Write to file if all the wsize blocks are recieved */ if (block % client->wsize == 0) { ret = write(client->fd, client->rw_buf, client->blk_offset); if (ret < 0) { /* XXX: report error */ printf("[TQFTP] failed to write data\n"); return -1; } client->blk_offset = 0; tftp_send_ack(client->sock, block); } return payload == client->blksize ? 1 : 0; } static void client_close_and_free(struct tftp_client *client) { list_del(&client->node); close(client->sock); close(client->fd); free (client->blk_buf); free (client->rw_buf); free(client); } int main(int argc, char **argv) { struct tftp_client *client; struct tftp_client *next; struct sockaddr_qrtr sq; struct qrtr_packet pkt; socklen_t sl; ssize_t len; char buf[4096]; fd_set rfds; int nfds; int opcode; int ret; int fd; fd = qrtr_open(0); if (fd < 0) { fprintf(stderr, "failed to open qrtr socket\n"); exit(1); } ret = qrtr_publish(fd, 4096, 1, 0); if (ret < 0) { fprintf(stderr, "failed to publish service registry service\n"); exit(1); } for (;;) { FD_ZERO(&rfds); FD_SET(fd, &rfds); nfds = fd; list_for_each_entry(client, &writers, node) { FD_SET(client->sock, &rfds); nfds = MAX(nfds, client->sock); } list_for_each_entry(client, &readers, node) { FD_SET(client->sock, &rfds); nfds = MAX(nfds, client->sock); } ret = select(nfds + 1, &rfds, NULL, NULL, NULL); if (ret < 0) { if (errno == EINTR) { continue; } else { fprintf(stderr, "select failed\n"); break; } } list_for_each_entry_safe(client, next, &writers, node) { if (FD_ISSET(client->sock, &rfds)) { ret = handle_writer(client); if (ret <= 0) client_close_and_free(client); } } list_for_each_entry_safe(client, next, &readers, node) { if (FD_ISSET(client->sock, &rfds)) { ret = handle_reader(client); if (ret <= 0) client_close_and_free(client); } } if (FD_ISSET(fd, &rfds)) { sl = sizeof(sq); len = recvfrom(fd, buf, sizeof(buf), 0, (void *)&sq, &sl); if (len < 0) { ret = -errno; if (ret != -ENETRESET) fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); return ret; } /* Ignore control messages */ if (sq.sq_port == QRTR_PORT_CTRL) { ret = qrtr_decode(&pkt, buf, len, &sq); if (ret < 0) { fprintf(stderr, "[TQFTP] unable to decode qrtr packet\n"); return ret; } switch (pkt.type) { case QRTR_TYPE_BYE: // fprintf(stderr, "[TQFTP] got bye\n"); list_for_each_entry_safe(client, next, &writers, node) { if (client->sq.sq_node == sq.sq_node) client_close_and_free(client); } break; case QRTR_TYPE_DEL_CLIENT: // fprintf(stderr, "[TQFTP] got del_client\n"); list_for_each_entry_safe(client, next, &writers, node) { if (!memcmp(&client->sq, &sq, sizeof(sq))) client_close_and_free(client); } break; } } else { if (len < 2) continue; opcode = buf[0] << 8 | buf[1]; switch (opcode) { case OP_RRQ: handle_rrq(buf, len, &sq); break; case OP_WRQ: // printf("[TQFTP] write\n"); handle_wrq(buf, len, &sq); break; case OP_ERROR: buf[len] = '\0'; printf("[TQFTP] received error: %d - %s\n", buf[2] << 8 | buf[3], buf + 4); break; default: printf("[TQFTP] unhandled op %d\n", opcode); break; } } } } close(fd); return 0; } tqftpserv-1.1.1/tqftpserv.service.in000066400000000000000000000002071512751311100175710ustar00rootroot00000000000000[Unit] Description=QRTR TFTP service [Service] ExecStart=@prefix@/bin/tqftpserv Restart=always [Install] WantedBy=multi-user.target tqftpserv-1.1.1/translate.c000066400000000000000000000136761512751311100157230ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2019, Linaro Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include #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; } tqftpserv-1.1.1/translate.h000066400000000000000000000001521512751311100157110ustar00rootroot00000000000000#ifndef __TRANSLATE_H__ #define __TRANSLATE_H__ int translate_open(const char *path, int flags); #endif tqftpserv-1.1.1/zstd-decompress.c000066400000000000000000000050771512751311100170500ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2024, Stefan Hansson * Copyright (c) 2024, Emil Velikov */ /* For memfd_create */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #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; } tqftpserv-1.1.1/zstd-decompress.h000066400000000000000000000005701512751311100170460ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2024, Stefan Hansson */ #ifndef __ZSTD_DECOMPRESS_H__ #define __ZSTD_DECOMPRESS_H__ #include #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