From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7A493C77B7C for ; Wed, 25 Jun 2025 23:20:22 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id 65BA38D0019; Wed, 25 Jun 2025 19:19:43 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id 60B9C6B00E8; Wed, 25 Jun 2025 19:19:43 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 4ACA88D0019; Wed, 25 Jun 2025 19:19:43 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0014.hostedemail.com [216.40.44.14]) by kanga.kvack.org (Postfix) with ESMTP id 355B16B00E7 for ; Wed, 25 Jun 2025 19:19:43 -0400 (EDT) Received: from smtpin06.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay04.hostedemail.com (Postfix) with ESMTP id 004201A07B0 for ; Wed, 25 Jun 2025 23:19:42 +0000 (UTC) X-FDA: 83595492246.06.CE84975 Received: from mail-yb1-f175.google.com (mail-yb1-f175.google.com [209.85.219.175]) by imf13.hostedemail.com (Postfix) with ESMTP id 2EEA42000F for ; Wed, 25 Jun 2025 23:19:41 +0000 (UTC) Authentication-Results: imf13.hostedemail.com; dkim=pass header.d=soleen-com.20230601.gappssmtp.com header.s=20230601 header.b=lpKfhmkm; spf=pass (imf13.hostedemail.com: domain of pasha.tatashin@soleen.com designates 209.85.219.175 as permitted sender) smtp.mailfrom=pasha.tatashin@soleen.com; dmarc=pass (policy=none) header.from=soleen.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1750893581; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=60o0+pILn7oYpaQU4GzfpCaIJI/wVLOztDCQbTE/8Fw=; b=V1EFjVxTBa5EtbxybW7rGkCWdheZMys4PpNcjQVSkGq0OYdRv9mQIz5K34iTOnPeRHliGF luSk1+5Rv1abREZmI6TB4RchhdviDp6QO4bu5+HgeLeF0YU4MwCeBU+OdtYugshnBLkhqf Tw+NgwXCE6dezSa2hpAVWz6dp78uBLI= ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1750893581; a=rsa-sha256; cv=none; b=odcRo3McqpHVHwwbACEbtH4J7xxoqPCFij6SVcq46O9O8EGxp/+xY9b6XvWYKtF2z3lpoQ eVKEQP+k0xtnd07ABli//E/CKwtAovlIIXV2zVnt4wgJDpIdkg/s8ltUTv5gQdsKG86HU3 Um+vaupyzpw+xp70IHPEySTQqkdg5bU= ARC-Authentication-Results: i=1; imf13.hostedemail.com; dkim=pass header.d=soleen-com.20230601.gappssmtp.com header.s=20230601 header.b=lpKfhmkm; spf=pass (imf13.hostedemail.com: domain of pasha.tatashin@soleen.com designates 209.85.219.175 as permitted sender) smtp.mailfrom=pasha.tatashin@soleen.com; dmarc=pass (policy=none) header.from=soleen.com Received: by mail-yb1-f175.google.com with SMTP id 3f1490d57ef6-e7569ccf04cso320104276.0 for ; Wed, 25 Jun 2025 16:19:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=soleen-com.20230601.gappssmtp.com; s=20230601; t=1750893580; x=1751498380; darn=kvack.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=60o0+pILn7oYpaQU4GzfpCaIJI/wVLOztDCQbTE/8Fw=; b=lpKfhmkm9NmqEjPD/nBX0yUnRiF+G6xcqdbrACVp8lHuDuhFM2ZqIZvQL0BzbgNfTu Sv0gzNLV41FGpVxmqgXnUvY3nhi3nb64z0YgUHLw9wP9NkvDhOQJPAK3PwlS2zew4XEi OcawHKJtBx9zZflkRSV9OoAPI/CsoilvejOo5LCQPxbR2Al3DAnJyoUfutT0LnZrGqnD 4rAHX764VX37nGf40vHywgU7svcGTFVFxwMBk03jifLJSM4PZzrkygJiuLErtuBykPWP tqhjK8UZhe1kaj7tAKKZj5YFcn1pV/QO4C8G+z967ZR1oiSBLtpudL4d4RSdc9P4h4kd O9FQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750893580; x=1751498380; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=60o0+pILn7oYpaQU4GzfpCaIJI/wVLOztDCQbTE/8Fw=; b=ZN7Dwhu3Y0Z6zDvDmsvda5LhT9rHC0XM8Vp281LKDE/Wf+PHjKKNwjvKObttRP100A I7uCLg1ZUMVZ1C9rfd27Mq2djDWy8SDc3DVPSbOuSNi//rdGyxCRcz9C9Lq8eN3UWOd5 EVyZUHuKBdWRfX+PrD5D4ALjN+LJSzxNJwCQUL2eGpWWSpUZPQWhdWWHhajtStTA5Uy5 K6QP/CEp//TlmBCj3AS/luA2+fWNhyfx4wj/0NK+DG5SF8q3QOjPy+4S+9b486rnaSf7 J255t4C4ya8ZoRB7NzIsn3RPtBr05B1HqU0xIXkOj8jLAMwI0LbzrQ8KGKknX5mAFBis DEOQ== X-Forwarded-Encrypted: i=1; AJvYcCXbiRVuwGOrjrj4mCOwAWvtR2c/fQvGeoxhSmBYM1oS/M/NIJk8yEHBdOjbUjs2S60qIhXs6z3Jxw==@kvack.org X-Gm-Message-State: AOJu0YxOPL5RwfzpSBlSeTRK2+x/PBP1kXRUe4uGBZjpAOjZvFkg0QwM GlQVTwv6C9rA8SGIWnPzQA2wCFRIpfBblVUIzEgigvfBesmlrhVJslEeRnToIlMC2NA= X-Gm-Gg: ASbGncvhgugZapV7eZq2Y1E2mmxBVxEsldY9/2QhWx0ddmR0eDCjt9bqriNHndSGYLH wPXlKeRqCw+S1QiJsML4NzXFVzPS4kmgd2lq+03WIogepQ8c85wsP7+0alRcRedbcsuCKQUeZxx I9w6aquyB8Ji01aQ/WHLZL08P0kOzs0dHqgj2T+f6YPDH7V8J9U2krWjO6tTbWTMwbaJlPqU2jH oQV97mM5vPpObbXJrA+mYZYsmOavMS2IjMPfhB0oF6hy7HYr7/OhM/9zaByEB+OPc38zHhfPqGF JoSYPw1LfQ6bR4TArK297DSD3O/O+H3FgtPZffGMjHxmCcUmL2lmjnm0fcpAOJEdFhEWKL9QqjS aQjmejfKbixLR8ZZOlSLutfOLwbIUZzYl/qToM6Yr4l/d4OrMONdB X-Google-Smtp-Source: AGHT+IGMtC4mqGRTCqwsD7zQuJKELdqefWUDOU17sadjrHFNIDwPtfJBpjKQ/cUFNH+/Umt7SFD3zg== X-Received: by 2002:a05:6902:90d:b0:e84:43bd:e9b6 with SMTP id 3f1490d57ef6-e86016f89damr6516568276.13.1750893580131; Wed, 25 Jun 2025 16:19:40 -0700 (PDT) Received: from soleen.c.googlers.com.com (64.167.245.35.bc.googleusercontent.com. [35.245.167.64]) by smtp.gmail.com with ESMTPSA id 3f1490d57ef6-e842ac5c538sm3942684276.33.2025.06.25.16.19.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Jun 2025 16:19:39 -0700 (PDT) From: Pasha Tatashin To: pratyush@kernel.org, jasonmiu@google.com, graf@amazon.com, changyuanl@google.com, pasha.tatashin@soleen.com, rppt@kernel.org, dmatlack@google.com, rientjes@google.com, corbet@lwn.net, rdunlap@infradead.org, ilpo.jarvinen@linux.intel.com, kanie@linux.alibaba.com, ojeda@kernel.org, aliceryhl@google.com, masahiroy@kernel.org, akpm@linux-foundation.org, tj@kernel.org, yoann.congal@smile.fr, mmaurer@google.com, roman.gushchin@linux.dev, chenridong@huawei.com, axboe@kernel.dk, mark.rutland@arm.com, jannh@google.com, vincent.guittot@linaro.org, hannes@cmpxchg.org, dan.j.williams@intel.com, david@redhat.com, joel.granados@kernel.org, rostedt@goodmis.org, anna.schumaker@oracle.com, song@kernel.org, zhangguopeng@kylinos.cn, linux@weissschuh.net, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-mm@kvack.org, gregkh@linuxfoundation.org, tglx@linutronix.de, mingo@redhat.com, bp@alien8.de, dave.hansen@linux.intel.com, x86@kernel.org, hpa@zytor.com, rafael@kernel.org, dakr@kernel.org, bartosz.golaszewski@linaro.org, cw00.choi@samsung.com, myungjoo.ham@samsung.com, yesanishhere@gmail.com, Jonathan.Cameron@huawei.com, quic_zijuhu@quicinc.com, aleksander.lobakin@intel.com, ira.weiny@intel.com, andriy.shevchenko@linux.intel.com, leon@kernel.org, lukas@wunner.de, bhelgaas@google.com, wagi@kernel.org, djeffery@redhat.com, stuart.w.hayes@gmail.com, ptyadav@amazon.de, lennart@poettering.net, brauner@kernel.org, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org Subject: [RFC v1 32/32] libluo: add tests Date: Wed, 25 Jun 2025 23:18:19 +0000 Message-ID: <20250625231838.1897085-33-pasha.tatashin@soleen.com> X-Mailer: git-send-email 2.50.0.727.gbf7dc18ff4-goog In-Reply-To: <20250625231838.1897085-1-pasha.tatashin@soleen.com> References: <20250625231838.1897085-1-pasha.tatashin@soleen.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: 2EEA42000F X-Rspam-User: X-Rspamd-Server: rspam06 X-Stat-Signature: s6a3hmkz78ak94taj3yeit8iqm5kfxc7 X-HE-Tag: 1750893581-375619 X-HE-Meta: U2FsdGVkX18XDdFOFDf+AfWzCX+TWX7Xo67eQV5KyO1/mLJgM8erM2phNOC64LPoNAQsIzjMHAoxfY0Axi7ln5QoI7B3CDS+PiHokQsT1ioy0yiIuNyDJNDbhyBHh6wYJ+iOqJYnBQJFw83z38GbyH7tW7tqrlJkRe3vX9iuBpHML3xXvjPjaRjpr+MyQ5RnHjdm62V1ivbbPuj4x3jDR3GkN3S6lqXK1O2/hk/aBlTY4wMMQnpqwLgW4bPQd6hMQCVGWr8kpVM+MSXScRuVtYaJgzvGlmx3RQ4C13gBJKIj3JjKpaDOZqci8PWQQEHufKg2nUv8v3hAh8RuD9b0XoDjhpTK4+NEvnfwft0FfvQFCng43tJHQru9Bqeo987/Pbe1QgR9YbDxO3ue6MjL1RICv8WG3JctcUvcqE/+FAgewx/5U+iMDsIMAvCyJ1bNpcHjbPhy3Imemmt4/aemSx6qq4ESy69S9q/RftTQRqOnU5OYozCjtxJCC9mLkWu6wSKndCaE7rr5WlZk0CkeVgwBWbsbch8+YMqOszC9NFQAcerVa8iB/Hv3KVBICU2AKUvCBIfFKUIV8Hac4DxTPK+KHG0TQVUgC9lNTlJFLsIRfSWDdV19y9EMqEF5godfuSe9yphyBL5JgLorvmbzpVVSOgLI7hiA4LwzCK8jdRhVD/XGKNZFkzAtitxhsomYp62qCzxcGUWyhLX738ixAeV7/8BMTUKdqwDCvlil9m2IT1dlMfoEa3uwyRZKSfDNsJsQvHsC+pF3hSvIc/8J7fQN4PG8A6+3gs3M1SQjTpCYtFnSCMTo1zw6JUi/7RHBmSdmCYL448/4+tG0LR1/3F1woCvjqUWQBmeGOGZB4/14ZLKraZCdTEiedO1cVQlAh3YrXICsiBlnw8v7y7ZqN7G1IQ2W3uqi5L/RVDfTW5+18vDBo/GEMnhur5/hwKK8dJBU5XtZ7DALdQMP5Kz F8O+WEQ0 xbJFBod4PXNCxmRWEGHoU4Puv32AnCXUYONjEVPdHLIRdzBAWBRH8sWgtCuDG/iF9h5roSI21U4J4Bk6zi7e9Oayds9dAPHWT/QIISWcLpEjEbZYRZBOQ25ILds7xgiE1fCJ8pBI5hDSWmbW5DOYWWh82w/A6t4waBMjYkK6lGZcsQYVAUNCSJW/37WHzx3k2jLPA+Iixl4QXdnYzzRPewdLKJUxKtZSRYXjy9LsJg90fZ07sPrBJ9KizQTJftt+hlTKPOHEXwi55qlrsycreDpQzB527plRYoFfrD5dNfycorlzmULZz4alvF/hFEW9XxJ3NH/yd6TWIjEJUHjKBG1vf/uTzGndksEbyS5KVCcZkuQR4hZE0uz3u2SC2KxUNrHpMaRVFJSpBSbbLLFrbw3Q7a4e/2dzjlA3/U/YM9V9iWVjpBfo0i4HkMAfnTRoXUJErdFIB2fqMx3WgKsV4kS+TjffLLPAsKwSc X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: From: Pratyush Yadav Add a test suite for libluo itself, and for the kernel LUO interface. The below tests are added: 1. init - Tests the initialization and cleanup functions of libluo. 2. state - Tests the luo_get_state() API, which in turn tests the LIVEUPDATE_IOCTL_GET_STATE ioctl 3. preserve - Creates a memfd, preserves it, puts LUO in prepared state, cancels liveupdate, and makes sure memfd is functional. 4. prepared - Puts a memfd in LUO enters prepared state. Then it makes sure the memfd stays functional but remains in restricted mode. It makes sure the memfd can't grow or shrink, but can be read from or written to. 5. transitions - Tests transitions from normal to prepared to cancel state work. 6. error - Tests error handling of the library on invalid inputs. 7. kexec - Tests the main functionality of LUO -- preserving a FD over kexec. It creates a memfd with random data, saves the data to a file on disk, and then preserves the FD and goes into prepared state. Now the test runner must perform a kexec. Once rebooted, running the test again resumes the test. It fetches the memfd back, nd compares its content with the saved data on disk. A specific test can be selected or excluded uring the -t or -e arguments. Sample run: $ ./test LibLUO Test Suite ================= Testing initialization and cleanup... PASSED Testing get_state... PASSED (current state: normal) Testing state transitions... PASSED Testing fd_preserve with freeze and cancel... PASSED Testing operations on prepared memfd... PASSED Testing error handling... PASSED Testing fd preserve for kexec... READY FOR KEXEC (token: 3) Run kexec now and then run this test again to complete. All requested tests completed. Signed-off-by: Pratyush Yadav Signed-off-by: Pasha Tatashin --- tools/lib/luo/Makefile | 4 + tools/lib/luo/tests/.gitignore | 1 + tools/lib/luo/tests/Makefile | 18 + tools/lib/luo/tests/test.c | 848 +++++++++++++++++++++++++++++++++ 4 files changed, 871 insertions(+) create mode 100644 tools/lib/luo/tests/.gitignore create mode 100644 tools/lib/luo/tests/Makefile create mode 100644 tools/lib/luo/tests/test.c diff --git a/tools/lib/luo/Makefile b/tools/lib/luo/Makefile index e8f6bd3b9e85..ef4c489efcc5 100644 --- a/tools/lib/luo/Makefile +++ b/tools/lib/luo/Makefile @@ -29,9 +29,13 @@ $(SHARED_LIB): $(OBJS) cli: $(STATIC_LIB) $(MAKE) -C cli +tests: $(STATIC_LIB) + $(MAKE) -C tests + clean: rm -f $(OBJS) $(STATIC_LIB) $(SHARED_LIB) $(MAKE) -C cli clean + $(MAKE) -C tests clean install: all install -d $(DESTDIR)/usr/local/lib diff --git a/tools/lib/luo/tests/.gitignore b/tools/lib/luo/tests/.gitignore new file mode 100644 index 000000000000..ee4c92682341 --- /dev/null +++ b/tools/lib/luo/tests/.gitignore @@ -0,0 +1 @@ +/test diff --git a/tools/lib/luo/tests/Makefile b/tools/lib/luo/tests/Makefile new file mode 100644 index 000000000000..7f4689722ff6 --- /dev/null +++ b/tools/lib/luo/tests/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +TESTS = test +INCLUDE_DIR = ../include +HEADERS = $(wildcard $(INCLUDE_DIR)/*.h) + +CC = gcc +CFLAGS = -Wall -Wextra -O2 -g -I$(INCLUDE_DIR) +LDFLAGS = -L.. -l:libluo.a + +.PHONY: all clean + +all: $(TESTS) + +test: test.c ../libluo.a $(HEADERS) + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +clean: + rm -f $(TESTS) diff --git a/tools/lib/luo/tests/test.c b/tools/lib/luo/tests/test.c new file mode 100644 index 000000000000..7963ae8ebadf --- /dev/null +++ b/tools/lib/luo/tests/test.c @@ -0,0 +1,848 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +#define _GNU_SOURCE +/** + * @file test.c + * @brief Test program for the LibLUO library + * + * This program tests the basic functionality of the LibLUO library. + * + * Copyright (C) 2025 Amazon.com Inc. or its affiliates. + * Author: Pratyush Yadav + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Path to store token for kexec test */ +#define TOKEN_FILE "libluo_test_token" +#define TEST_DATA_FILE "libluo_test_data" +#define MEMFD_NAME "libluo_test_memfd" + +/* Size of the random data buffer (1 MiB) */ +#define RANDOM_BUFFER_SIZE (1 << 20) +static char random_buffer[RANDOM_BUFFER_SIZE]; + +/* Test IDs */ +#define TEST_INIT_CLEANUP (1 << 0) +#define TEST_GET_STATE (1 << 1) +#define TEST_FD_PRESERVE (1 << 2) +#define TEST_ERROR_HANDLING (1 << 3) +#define TEST_FD_KEXEC (1 << 4) +#define TEST_FD_PREPARED (1 << 5) +#define TEST_STATE_TRANSITIONS (1 << 6) +#define TEST_ALL (TEST_INIT_CLEANUP | TEST_GET_STATE | \ + TEST_FD_PRESERVE | TEST_ERROR_HANDLING | \ + TEST_FD_KEXEC | TEST_FD_PREPARED | \ + TEST_STATE_TRANSITIONS) + +/* + * luo_fd_preserve() needs a unique token. Generate a monotonically increasing + * token. + */ +static uint64_t next_token() +{ + static uint64_t token = 0; + + return token++; +} + +/* Read exactly specified size from fd. Any less results in error. */ +static int read_size(int fd, char *buffer, size_t size) +{ + size_t remain = size; + ssize_t bytes_read; + + while (remain) { + bytes_read = read(fd, buffer, remain); + if (bytes_read == 0) + return -ENODATA; + if (bytes_read < 0) + return -errno; + + remain -= bytes_read; + } + + return 0; +} + +/* Write exactly specified size from fd. Any less results in error. */ +static int write_size(int fd, const char *buffer, size_t size) +{ + size_t remain = size; + ssize_t written; + + while (remain) { + written = write(fd, buffer, remain); + if (written == 0) + return -EIO; + if (written < 0) + return -errno; + + remain -= written; + } + + return 0; +} + +static int generate_random_data(char *buffer, size_t size) +{ + int fd, ret; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -errno; + + ret = read_size(fd, buffer, size); + close(fd); + return ret; +} + +static int save_test_data(const char *buffer, size_t size) +{ + int fd, ret; + + fd = open(TEST_DATA_FILE, O_RDWR); + if (fd < 0) + return -errno; + + ret = write_size(fd, buffer, size); + close(fd); + return ret; +} + +static int load_test_data(char *buffer, size_t size) +{ + int fd, ret; + + fd = open(TEST_DATA_FILE, O_RDONLY); + if (fd < 0) + return -errno; + + ret = read_size(fd, buffer, size); + close(fd); + return ret; +} + +/* Create and initialize a memfd with random data. */ +static int create_test_fd(const char *memfd_name, char *buffer, size_t size) +{ + int fd; + int ret; + + fd = memfd_create(memfd_name, 0); + if (fd < 0) + return -errno; + + ret = generate_random_data(buffer, size); + if (ret < 0) { + close(fd); + return ret; + } + + if (write_size(fd, buffer, size) < 0) { + close(fd); + return -errno; + } + + /* Reset file position to beginning */ + if (lseek(fd, 0, SEEK_SET) < 0) { + close(fd); + return -errno; + } + + return fd; +} + +/* + * Make sure fd contains expected data up to size. Returns 0 on success, 1 on + * data mismatch, -errno on error. + */ +static int verify_fd_content(int fd, const char *expected_data, size_t size) +{ + char buffer[size]; + int ret; + + /* Reset file position to beginning */ + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + + ret = read_size(fd, buffer, size); + if (ret < 0) + return ret; + + if (memcmp(buffer, expected_data, size) != 0) + return 1; + + return 0; +} + +/* Save token to file for kexec test. */ +static int save_token(uint64_t token) +{ + FILE *file = fopen(TOKEN_FILE, "w"); + + if (!file) + return -errno; + + if (fprintf(file, "%lu", token) < 0) { + fclose(file); + return -errno; + } + + fclose(file); + return 0; +} + +/* Load token from file for kexec test. */ +static int load_token(uint64_t *token) +{ + FILE *file = fopen(TOKEN_FILE, "r"); + + if (!file) + return -errno; + + if (fscanf(file, "%lu", token) != 1) { + fclose(file); + return -EINVAL; + } + + fclose(file); + return 0; +} + +/* Test initialization and cleanup */ +static void test_init_cleanup(void) +{ + int ret; + + printf("Testing initialization and cleanup... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + luo_cleanup(); + printf("PASSED\n"); +} + +/* Test getting LUO state */ +static void test_get_state(void) +{ + int ret; + enum liveupdate_state state; + + printf("Testing get_state... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + ret = luo_get_state(&state); + if (ret < 0) { + printf("FAILED (get_state: %s)\n", strerror(-ret)); + luo_cleanup(); + return; + } + + printf("PASSED (current state: %s)\n", luo_state_to_string(state)); + luo_cleanup(); +} + +/* Test preserving and unpreserving a file descriptor with prepare and cancel */ +static void test_fd_preserve_unpreserve(void) +{ + uint64_t token = next_token(); + int ret, fd = -1; + + printf("Testing fd_preserve with freeze and cancel... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + fd = create_test_fd(MEMFD_NAME, random_buffer, sizeof(random_buffer)); + if (fd < 0) { + ret = fd; + printf("FAILED (create_test_fd: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + ret = luo_fd_preserve(fd, token); + if (ret < 0) { + printf("FAILED (preserve: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + ret = luo_prepare(); + if (ret < 0) { + printf("FAILED (prepare: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + ret = luo_cancel(); + if (ret < 0) { + printf("FAILED (cancel: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + ret = luo_fd_unpreserve(token); + if (ret < 0) { + printf("FAILED (unpreserve: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + ret = verify_fd_content(fd, random_buffer, sizeof(random_buffer)); + if (ret < 0) { + printf("FAILED (verify_fd_content: %s)\n", + ret == 1 ? "data mismatch" : strerror(-ret)); + goto out_close_fd; + } + + printf("PASSED\n"); + goto out_close_fd; + +out_unpreserve: + luo_fd_unpreserve(token); +out_close_fd: + close(fd); +out_cleanup: + luo_cleanup(); +} + +/* Test error handling with invalid inputs. */ +static void test_error_handling(void) +{ + int ret; + + printf("Testing error handling... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + /* Test with invalid file descriptor */ + ret = luo_fd_preserve(-1, next_token()); + if (ret != -EINVAL) { + printf("FAILED (expected EINVAL for invalid fd, got %d)\n", ret); + luo_cleanup(); + return; + } + + /* Test with NULL state pointer */ + ret = luo_get_state(NULL); + if (ret != -EINVAL) { + printf("FAILED (expected EINVAL for NULL state, got %d)\n", ret); + luo_cleanup(); + return; + } + + luo_cleanup(); + printf("PASSED\n"); +} + +/* Test preserving a file descriptor for kexec reboot */ +static void test_fd_preserve_for_kexec(void) +{ + enum liveupdate_state state; + int fd = -1, ret; + uint64_t token; + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + /* Check if we're in post-kexec state */ + ret = luo_get_state(&state); + if (ret < 0) { + printf("FAILED (get_state: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + if (state == LIVEUPDATE_STATE_UPDATED) { + /* Post-kexec: restore the file descriptor */ + printf("Testing memfd restore after kexec... "); + + ret = load_token(&token); + if (ret < 0) { + printf("FAILED (load_token: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + ret = load_test_data(random_buffer, RANDOM_BUFFER_SIZE); + if (ret < 0) { + printf("FAILED (load_test_data: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + ret = luo_fd_restore(token, &fd); + if (ret < 0) { + printf("FAILED (restore: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + /* Verify the file descriptor content with stored data. */ + ret = verify_fd_content(fd, random_buffer, RANDOM_BUFFER_SIZE); + if (ret) { + printf("FAILED (verify_fd_content: %s)\n", + ret == 1 ? "data mismatch" : strerror(-ret)); + goto out_close_fd; + } + + ret = luo_finish(); + if (ret < 0) { + printf("FAILED (finish: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + printf("PASSED\n"); + goto out_close_fd; + } else { + /* Pre-kexec: preserve the file descriptor */ + printf("Testing fd preserve for kexec... "); + + fd = create_test_fd(MEMFD_NAME, random_buffer, RANDOM_BUFFER_SIZE); + if (fd < 0) { + ret = fd; + printf("FAILED (create_test_fd: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + /* Save random data to file for post-kexec verification */ + ret = save_test_data(random_buffer, RANDOM_BUFFER_SIZE); + if (ret < 0) { + printf("FAILED (save_test_data: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + token = next_token(); + ret = luo_fd_preserve(fd, token); + if (ret < 0) { + printf("FAILED (preserve: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + /* Save token to file for post-kexec restoration */ + ret = save_token(token); + if (ret < 0) { + printf("FAILED (save_token: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + ret = luo_prepare(); + if (ret < 0) { + printf("FAILED (prepare: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + printf("READY FOR KEXEC (token: %lu)\n", token); + printf("Run kexec now and then run this test again to complete.\n"); + + /* Note: At this point, the system should perform kexec reboot. + * The test will continue in the new kernel with the + * LIVEUPDATE_STATE_UPDATED state. + * + * Since the FD is now preserved, we can close it. + */ + goto out_close_fd; + } + +out_unpreserve: + luo_fd_unpreserve(token); +out_close_fd: + close(fd); +out_cleanup: + luo_cleanup(); +} + +/* + * Test that prepared memfd can't grow or shrink, but reads and writes still + * work. + */ +static void test_fd_prepared_operations(void) +{ + char write_buffer[128] = {'A'}; + size_t initial_size, file_size; + int ret, fd = -1; + uint64_t token; + + printf("Testing operations on prepared memfd... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init: %s)\n", strerror(-ret)); + return; + } + + /* Create and initialize test file descriptor */ + fd = create_test_fd(MEMFD_NAME, random_buffer, sizeof(random_buffer)); + if (fd < 0) { + ret = fd; + printf("FAILED (create_test_fd: %s)\n", strerror(-ret)); + goto out_cleanup; + } + + /* Get initial file size */ + ret = lseek(fd, 0, SEEK_END); + if (ret < 0) { + printf("FAILED (lseek to end: %s)\n", strerror(errno)); + goto out_close_fd; + } + initial_size = (size_t)ret; + + token = next_token(); + ret = luo_fd_preserve(fd, token); + if (ret < 0) { + printf("FAILED (preserve: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + ret = luo_prepare(); + if (ret < 0) { + printf("FAILED (prepare: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + /* Test 1: Write to the prepared file descriptor (within existing size) */ + if (lseek(fd, 0, SEEK_SET) < 0) { + printf("FAILED (lseek before write: %s)\n", strerror(errno)); + goto out_cancel; + } + + /* Write buffer is smaller than total file size. */ + ret = write_size(fd, write_buffer, sizeof(write_buffer)); + if (ret < 0) { + printf("FAILED (write to prepared fd: %s)\n", strerror(errno)); + goto out_cancel; + } + + ret = verify_fd_content(fd, write_buffer, sizeof(write_buffer)); + if (ret) { + printf("FAILED (verify_fd_content after write: %s)\n", + ret == 1 ? "data mismatch" : strerror(-ret)); + goto out_cancel; + } + + /* Test 2: Try to grow the file using write(). */ + + /* First, seek to one byte behind initial size. */ + ret = lseek(fd, initial_size - 1, SEEK_SET); + if (ret < 0) { + printf("FAILED: (lseek after write verification: %s)\n", + strerror(errno)); + } + + /* + * Then, write some data that should increase the file size. This should + * fail. + */ + ret = write_size(fd, write_buffer, sizeof(write_buffer)); + if (ret == 0) { + printf("FAILED: (write beyond initial size succeeded)\n"); + goto out_cancel; + } + + ret = lseek(fd, 0, SEEK_END); + if (ret < 0) { + printf("FAILED (lseek after larger write: %s)\n", strerror(errno)); + goto out_cancel; + } + file_size = (size_t)ret; + + if (file_size != initial_size) { + printf("FAILED (file grew beyond initial size: %zu != %zu)\n", + (size_t)file_size, initial_size); + goto out_cancel; + } + + /* Test 3: Try to shrink the file using truncate */ + ret = ftruncate(fd, initial_size / 2); + if (ret == 0) { + printf("FAILED (file was truncated)\n"); + goto out_cancel; + } + + ret = lseek(fd, 0, SEEK_END); + if (ret < 0) { + printf("FAILED (lseek after shrink attempt: %s)\n", strerror(errno)); + goto out_cancel; + } + file_size = (size_t)ret; + + if (file_size != initial_size) { + printf("FAILED (file shrunk from initial size: %zu != %zu)\n", + (size_t)file_size, initial_size); + goto out_cancel; + } + + ret = luo_cancel(); + if (ret < 0) { + printf("FAILED (cancel: %s)\n", strerror(-ret)); + goto out_unpreserve; + } + + ret = luo_fd_unpreserve(token); + if (ret < 0) { + printf("FAILED (unpreserve: %s)\n", strerror(-ret)); + goto out_close_fd; + } + + printf("PASSED\n"); + goto out_close_fd; + +out_cancel: + luo_cancel(); +out_unpreserve: + luo_fd_unpreserve(token); +out_close_fd: + close(fd); +out_cleanup: + luo_cleanup(); +} + +static int test_prepare_cancel_sequence(const char *sequence_name) +{ + int ret; + enum liveupdate_state state; + + /* Initial state should be NORMAL */ + ret = luo_get_state(&state); + if (ret < 0) { + printf("FAILED (%s get initial state failed: %s)\n", + sequence_name, strerror(-ret)); + return ret; + } + + if (state != LIVEUPDATE_STATE_NORMAL) { + printf("FAILED (%s unexpected initial state: %s)\n", + sequence_name, luo_state_to_string(state)); + return -EINVAL; + } + + /* Test NORMAL -> PREPARED transition */ + ret = luo_prepare(); + if (ret < 0) { + printf("FAILED (%s prepare failed: %s)\n", + sequence_name, strerror(-ret)); + return ret; + } + + ret = luo_get_state(&state); + if (ret < 0) { + printf("FAILED (%s get state after prepare failed: %s)\n", + sequence_name, strerror(-ret)); + goto out_cancel; + } + + if (state != LIVEUPDATE_STATE_PREPARED) { + printf("FAILED (%s expected PREPARED state, got %s)\n", + sequence_name, luo_state_to_string(state)); + ret = -EINVAL; + goto out_cancel; + } + + /* Test PREPARED -> NORMAL transition via cancel */ + ret = luo_cancel(); + if (ret < 0) { + printf("FAILED (%s cancel failed: %s)\n", + sequence_name, strerror(-ret)); + return ret; + } + + ret = luo_get_state(&state); + if (ret < 0) { + printf("FAILED (%s get state after cancel failed: %s)\n", + sequence_name, strerror(-ret)); + return ret; + } + + if (state != LIVEUPDATE_STATE_NORMAL) { + printf("FAILED (%s expected NORMAL state after cancel, got %s)\n", + sequence_name, luo_state_to_string(state)); + return -EINVAL; + } + + return 0; + +out_cancel: + luo_cancel(); + return ret; +} + +/* Test all state transitions */ +static void test_state_transitions(void) +{ + int ret; + + printf("Testing state transitions... "); + + ret = luo_init(); + if (ret < 0) { + printf("FAILED (init failed: %s)\n", strerror(-ret)); + return; + } + + /* Test first prepare -> cancel sequence */ + ret = test_prepare_cancel_sequence("first"); + if (ret < 0) + goto out; + + /* + * Test second prepare -> freeze -> cancel sequence in case the + * previous cancellation left some side effects. + */ + ret = test_prepare_cancel_sequence("second"); + if (ret < 0) + goto out; + + printf("PASSED\n"); + +out: + luo_cleanup(); +} + +/* Test name to flag mapping */ +struct test { + const char *name; + void (*fn)(void); + unsigned int flag; +}; + +/* Array of test names and their corresponding flags */ +static struct test tests[] = { + {"init", test_init_cleanup, TEST_INIT_CLEANUP}, + {"state", test_get_state, TEST_GET_STATE}, + {"transitions", test_state_transitions, TEST_STATE_TRANSITIONS}, + {"preserve", test_fd_preserve_unpreserve, TEST_FD_PRESERVE}, + {"prepared", test_fd_prepared_operations, TEST_FD_PREPARED}, + {"error", test_error_handling, TEST_ERROR_HANDLING}, + {"kexec", test_fd_preserve_for_kexec, TEST_FD_KEXEC}, + {NULL, NULL, 0} +}; + +static int parse_test_names(char *arg, unsigned int *flags) +{ + char *name; + struct test *test; + + *flags = 0; + name = strtok(arg, ","); + + while (name != NULL) { + test = tests; + while (test->name) { + if (strcmp(name, test->name) == 0) { + *flags |= test->flag; + break; + } + test++; + } + + /* Check if we found a match */ + if (!test->name) { + printf("Unknown test: %s\n", name); + return 1; + } + + name = strtok(NULL, ","); + } + + return 0; +} + +static void usage(const char *program_name) +{ + printf("Usage: %s [options]\n", program_name); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" -t, --test=TEST_ID Run specific test(s)\n"); + printf(" -e, --exclude=TEST_ID Exclude specific test(s)\n"); + printf("\n"); + printf("Test IDs:\n"); + printf(" init - Test initialization and cleanup\n"); + printf(" state - Test getting LUO state\n"); + printf(" preserve - Test memfd preserve/unpreserve with freeze/cancel\n"); + printf(" prepared - Test memfd functions can read/write but not grow after prepare\n"); + printf(" transitions - Test all state transitions (NORMAL->PREPARED->FROZEN->NORMAL)\n"); + printf(" error - Test error handling\n"); + printf(" kexec - Test memfd preserve for kexec\n"); + printf("\n"); + printf("Multiple tests can be specified with comma separation.\n"); + printf("Example: %s --test=init,state --exclude=kexec\n", program_name); + printf("By default, all tests are run.\n"); +} + +int main(int argc, char *argv[]) +{ + unsigned int tests_to_run = TEST_ALL; + unsigned int tests_to_exclude = 0; + struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"test", required_argument, 0, 't'}, + {"exclude", required_argument, 0, 'e'}, + {0, 0, 0, 0} + }; + struct test *test; + int opt; + + printf("LibLUO Test Suite\n"); + printf("=================\n\n"); + + if (!luo_is_available()) { + printf("LUO is not available on this system. Skipping tests.\n"); + return 0; + } + + while ((opt = getopt_long(argc, argv, "ht:e:", long_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(argv[0]); + return 0; + case 't': + if (parse_test_names(optarg, &tests_to_run)) + return 1; + break; + case 'e': + if (parse_test_names(optarg, &tests_to_exclude)) + return 1; + break; + default: + printf("Try '%s --help' for more information.\n", argv[0]); + return 1; + } + } + + /* Apply exclusions to the tests to run */ + tests_to_run &= ~tests_to_exclude; + if (!tests_to_run) { + printf("ERROR: all tests excluded\n"); + return 1; + } + + /* Run selected tests */ + test = tests; + while (test->name) { + if (tests_to_run & test->flag) + test->fn(); + test++; + } + + printf("\nAll requested tests completed.\n"); + return 0; +} -- 2.50.0.727.gbf7dc18ff4-goog