From patchwork Thu Jul 9 20:52:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Trevor Woerner X-Patchwork-Id: 92141 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 aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C460AC43458 for ; Thu, 9 Jul 2026 20:52:51 +0000 (UTC) Received: from mail-qk1-f180.google.com (mail-qk1-f180.google.com [209.85.222.180]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.957.1783630367701658742 for ; Thu, 09 Jul 2026 13:52:47 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=MrWGAUGe; spf=pass (domain: gmail.com, ip: 209.85.222.180, mailfrom: twoerner@gmail.com) Received: by mail-qk1-f180.google.com with SMTP id af79cd13be357-92e533aacf2so11063185a.2 for ; Thu, 09 Jul 2026 13:52:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783630366; x=1784235166; darn=lists.yoctoproject.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:content-type; bh=yav5KicuPccoti0w8SDQdAF5YeD5u7fAYl+xoxmtsfU=; b=MrWGAUGeYY2IRWFf0CmjPydpfaJtCsxjC5sc+aQCzkJpvolYkuMYY6JVtVOFH0pARw zJTe1IH6v59KRZp/u/0yW2Ryxi7VH4YIW4yO9qAL7S6ag7tXDW+eUJgx7wDNKl45f0Ge 4A0sZn34I4+336vn6/cCARANRddgnRqkOPvQ/RiHaTz4qbjTM+PC8xSrK+ay8+jFSEPM RJgAxIReu0ZqBmOFdseDxH1svRbWVM7hj0h3qF72Ofdkc8wV4ZFkjUrrd12s3VBNC7Rs K2TmAuVrxQrtihGfs6/TK6DQhPN6xb1xd04MOGNMFyCidI9/IWHkCBG5IPR9eChk2LQC t4aQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783630366; x=1784235166; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=yav5KicuPccoti0w8SDQdAF5YeD5u7fAYl+xoxmtsfU=; b=rvPsZxiwjrRtu7iwllmLR30n2jBS+7ZMqsoiLKezi+q5BW3TxU5CXP7Dv7CiUdMtX6 jNd/6d3rxUvTllmfI4wDDeHcJo9MdlZldMTybtKwvxBlEc32Rjrx7lpVmuAY4TQRHSso QFnabRIEJhY0vOL2u/qGpTrH7DOOebMzL0KrBCzzu5UwgXlRT6Tm5aZ0qtve1kDA/3rV s5KTfsQ0v+tMWHDtJsYeOXUgWBYlAoxZVJIbysS1wJI9YfYpXEo5B4s0QU+KqR8297pD HmGn7I17pBrTaFrB6pZIjYdr2/GF99PG+uq/huNAvjme4qn+7FwabP4Ekpq9i1ECXzZx yW6Q== X-Gm-Message-State: AOJu0Yy4Y/FhCcLeeqjpLNr8sR/lEnKtS228tGgiBK9ULI9hPD/w6XQ2 WgUHnjbU0ttHoQ6YEJE/n9gyfwHBzjp04yWeV2+HmXQVo2NE3rm/rR3uJJYobw== X-Gm-Gg: AfdE7cmIQlX5ClTApB8iR9+9sUXTVJE27/QiH05IIdxmuVq11shk96LZYZ9SuoPfq8/ 6U25Qx/Ejl4hgLtzSvnEcRqClUKexYORmDV6bKjh25TaU4VC806upnGAofvtqNnWC2ozeKpOTU9 0z/AEzBvCL4a8HQgUGs1rJhAMu49O+U8+CIlfWFKLulZWzY9xd8YIy+Y4R3Tk/2H5weRXuvfE/R XHbFVkiMvHFhw1VZRn+yxp6fROerFE4aCFBEENB1Zc7EK+RtXXP4uWu4xiQCWK1gd0g++kUXLM5 ZucA5ROGpZoXeuxrzEDCX2pjH5HCm/GCMPkuZNwkpa3n5ORCiOJs8nwH+6HU2NThbxuATZcC3gm I5dIs0I922eENqQwuPrUmFnNfXJuXzc6V7g6CSmj8BfdxYK88aAOrT3KD8axFV02hui9gNmef2p MQNCBwcPedxA/OUn2Xv4qPti//QHe3EV6vJsnAGC0w4zEfOKvbpVbNZFI= X-Received: by 2002:a05:620a:400b:b0:92b:67e6:8ace with SMTP id af79cd13be357-92ecf8d4120mr947757585a.72.1783630366442; Thu, 09 Jul 2026 13:52:46 -0700 (PDT) Received: from localhost.localdomain (pppoe-209-91-167-254.vianet.ca. [209.91.167.254]) by smtp.gmail.com with ESMTPSA id af79cd13be357-92ee5d37540sm32893085a.38.2026.07.09.13.52.45 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Jul 2026 13:52:45 -0700 (PDT) From: Trevor Woerner To: yocto-patches@lists.yoctoproject.org Subject: [wic][PATCH 1/4] oe/path: fix bare `false` NameError in __realpath's isdir guard Date: Thu, 9 Jul 2026 16:52:24 -0400 Message-ID: <20260709205227.3470712-2-twoerner@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260709205227.3470712-1-twoerner@gmail.com> References: <20260709205227.3470712-1-twoerner@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 09 Jul 2026 20:52:51 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4460 __realpath() finishes by deciding whether the resolved path is a directory, tolerating any error from the stat: try: is_dir = os.path.isdir(file) except: is_dir = false but `false` is not a Python name -- the intended value is the builtin False. os.path.isdir() swallows almost everything and simply returns False for a path it cannot stat, so the except branch is rarely taken; that is why the typo has stayed hidden. But when isdir() does raise -- for instance an OSError deep in os.stat() on a broken mount -- the handler that was meant to absorb the error instead raises NameError: name 'false' is not defined, masking the original failure with a misleading one. Use the builtin False so the fallback behaves as intended: a path that cannot be stat'd is simply reported as not-a-directory. AI-Generated: codex/claude-opus 4.8 (xhigh) Signed-off-by: Trevor Woerner --- src/wic/oe/path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wic/oe/path.py b/src/wic/oe/path.py index 3e95ea0ec923..862edc532ade 100644 --- a/src/wic/oe/path.py +++ b/src/wic/oe/path.py @@ -233,7 +233,7 @@ def __realpath(file, root, loop_cnt, assume_dir): try: is_dir = os.path.isdir(file) except: - is_dir = false + is_dir = False return (file, is_dir) From patchwork Thu Jul 9 20:52:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Trevor Woerner X-Patchwork-Id: 92142 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 aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id D2C19C44501 for ; Thu, 9 Jul 2026 20:52:51 +0000 (UTC) Received: from mail-qv1-f54.google.com (mail-qv1-f54.google.com [209.85.219.54]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.959.1783630369094074989 for ; Thu, 09 Jul 2026 13:52:49 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=J8nbtKPX; spf=pass (domain: gmail.com, ip: 209.85.219.54, mailfrom: twoerner@gmail.com) Received: by mail-qv1-f54.google.com with SMTP id 6a1803df08f44-8eff5ce3b95so2677986d6.3 for ; Thu, 09 Jul 2026 13:52:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783630368; x=1784235168; darn=lists.yoctoproject.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:content-type; bh=1MZXIBj4uSLo0hGPYjb+s/MPK/IOTMyIR81VKB391bg=; b=J8nbtKPXj5YIvKCOLZnxu3A9hLGlijVCb8A54yJLu6+16ex8s/nLBiG3ZejM1K5nAb VVURQBJP/LW84eShHCJhZGj31WJAUTQlBvvQakoVgtIK99KPLEAgjrrD35K534xcIFpA AaCJb0UKyii2jpeoSbyDZxmI7c8JOPjAclI84YLd6cQQrZPcYbzgMaWQewTT0sj0/G8g MnznF79aF7ojOv2bD4JW3ZWGlQsBogccmsMEouIp//yjNg418Ur+P1rs2HM63/LnzSZ/ 1MmQo/rBv2BLq0Uqmgz89vwvCMGWA8I3QAbXRmzl9zHBVTkSld4ad7nMe1AUT3dhC9Gn Jk5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783630368; x=1784235168; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=1MZXIBj4uSLo0hGPYjb+s/MPK/IOTMyIR81VKB391bg=; b=L0kWsGenYHL/3PKbT9kbLM+xeGu23RL3FUd3mpPnDDdxVwV8mDuAwhI8nmM9WrO/0g c7CCVaPaHOfFuTeSnZGm2pS1tE2x9flzPS0yKci+8At4zjhlIV6rYvxTT2iuLoPo9JI+ 2x/7GjaLTGIZEOUifkzoloFulBEOghxsufn0oPDxqd4viYdsfLdqAk1zsZL1Ih8cZsgm 45TNXTHCS3MzngvP7TXoYE9jKie0d+fTQwjv9n9v9pgEDLVlHUJWZLuXBDZAapOo7wuy oC0yNG35d2QyBIwqV9hjOmv9dsCc9qnygaNBAkCl435rYVVMwy4xQfsWlivdZ+ixdjLr HMLg== X-Gm-Message-State: AOJu0Yxc9UaFv6Hpu9eV7ajkmm6etK/TQaVbtfXAk6AbtO/rXd9TLvaF 1CrVcJZaNGAwXpvgVpbGno+L/qaWwnrgVl8LvlY3rv0fUahguyipTKXO8xXmxw== X-Gm-Gg: AfdE7ckOM6UFCYEA1mPTAjWguIi+P3LbYN322J4AZIdkT+TUSng2D7+JuSvoUIcgUhd NWHMFx8MArOZLW1c5DTSHCuP9ef0tMxG04HH03QATCLtP0H5zUJ6wIc3A+cYrujFKMyQnblSqWb 3Wf/JrNQeDMTExid9E7iPIZCDEOm39oph4RQ1vfKbCCMCh3tT1+jaoXgVTw0rDZJaWXlG7nMTZ6 ndJG0zZP62yXWdqvcBeSws34aepaGha/LlLpm2PX9htUib/Er6pjOEzb6UwFSZ+INxMKmEETaCB WF19hJHJovx+uWCyM08VxEGuGep/MJBkQ/Yw4S0DHsnPD1HCqu6cgQ+fJWFdOmpTowkt7Bp+xPJ uI0qprxl50XmYWCuUP3IKgXuviEJDU27cVz/iq+fDmuFDO66xwVsgoutBGNhfemTmwJxbYE9QFV CadvHaZXEh2NRkZOp9Ia+cUyx5ZgHKxGfLgMw8s07FL+rS15znLqRsOG8= X-Received: by 2002:a05:620a:444e:b0:92e:c118:18aa with SMTP id af79cd13be357-92ecf940e8bmr908524885a.93.1783630367911; Thu, 09 Jul 2026 13:52:47 -0700 (PDT) Received: from localhost.localdomain (pppoe-209-91-167-254.vianet.ca. [209.91.167.254]) by smtp.gmail.com with ESMTPSA id af79cd13be357-92ee5d37540sm32893085a.38.2026.07.09.13.52.46 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Jul 2026 13:52:47 -0700 (PDT) From: Trevor Woerner To: yocto-patches@lists.yoctoproject.org Subject: [wic][PATCH 2/4] oe/path: don't glob-expand the destination in symlink(force=True) Date: Thu, 9 Jul 2026 16:52:25 -0400 Message-ID: <20260709205227.3470712-3-twoerner@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260709205227.3470712-1-twoerner@gmail.com> References: <20260709205227.3470712-1-twoerner@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 09 Jul 2026 20:52:51 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4461 symlink(source, destination, force=True) cleared an existing destination by calling remove(destination). remove() is a rm -rf helper that runs its argument through glob.glob() before unlinking, so it treats the destination as a glob pattern rather than a literal path. For ordinary names this is merely wasteful, but a destination that contains glob metacharacters is actively dangerous. A name with a '[' may fail to match itself and silently leave the old link in place, and a pattern that happens to match other entries could unlink files the caller never named. remove() even warns about exactly this in its own docstring. Remove the literal destination directly instead: unlink it, fall back to shutil.rmtree() when it turns out to be a directory (EISDIR), and treat a missing destination (ENOENT) as success. This mirrors what remove() does per matched name, minus the glob expansion. AI-Generated: codex/claude-opus 4.8 (xhigh) Signed-off-by: Trevor Woerner --- src/wic/oe/path.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/wic/oe/path.py b/src/wic/oe/path.py index 862edc532ade..13c52b363694 100644 --- a/src/wic/oe/path.py +++ b/src/wic/oe/path.py @@ -168,7 +168,18 @@ def symlink(source, destination, force=False): """Create a symbolic link""" try: if force: - remove(destination) + # Remove the exact destination path. Do not route this through + # remove(), which treats its argument as a glob pattern: a + # destination containing glob metacharacters (for example a + # '[' in the name) could fail to match, or match and delete + # unrelated files. + try: + os.unlink(destination) + except OSError as exc: + if exc.errno == errno.EISDIR: + shutil.rmtree(destination) + elif exc.errno != errno.ENOENT: + raise os.symlink(source, destination) except OSError as e: if e.errno != errno.EEXIST or os.readlink(destination) != source: From patchwork Thu Jul 9 20:52:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Trevor Woerner X-Patchwork-Id: 92143 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 aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id ED3F8C44506 for ; Thu, 9 Jul 2026 20:53:01 +0000 (UTC) Received: from mail-qk1-f171.google.com (mail-qk1-f171.google.com [209.85.222.171]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.961.1783630372195023633 for ; Thu, 09 Jul 2026 13:52:52 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=QcPEev9o; spf=pass (domain: gmail.com, ip: 209.85.222.171, mailfrom: twoerner@gmail.com) Received: by mail-qk1-f171.google.com with SMTP id af79cd13be357-92e85499ffbso16157585a.0 for ; Thu, 09 Jul 2026 13:52:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783630371; x=1784235171; darn=lists.yoctoproject.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:content-type; bh=IyTe0muG2yLwepIh9MwU4EFl2Y0p6e17Fb/7mFLehhg=; b=QcPEev9owTveffqL7eaWxCa2ReHpSFtFrNGfLLD8bj9upcDPhhGwoDvRdT248ZdmKn MgbZ9Uw5FiOVD5CzXq+2NMBjpVAeATa3kcgN0/Jd4VvCMT6GO+xLrO+S0i0iM8gwZ93s fvOjg+SSvxp9Di99H502vrurpxCefL9jNo1f3QSYfvrl3YTY4KkVhQNe6z8lIJroMVNE qzks5lRmDT9v9xY/jga0rBlP4f9Wp99YVYnMPG/prbh37/IsVQeVoRP8K79+DPQnw652 rQbKGtHy8eV+bYBOwv76g8z+e6IuuBLKiiT++Kqk9gkqYdEkJCZerRvf9xtB7iLVGOt8 KX8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783630371; x=1784235171; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=IyTe0muG2yLwepIh9MwU4EFl2Y0p6e17Fb/7mFLehhg=; b=aKAAuJbUSEF701yd2iKjDuexID5XkODIiovPyFeE/LzkDs629c9PbtnT2YG9hK89W+ X2wT8L1Gjz/F77vpHxCfgMR+fGZImsFiCVvWwNqjBD1NjQHmntGmy3Vg5fKAVzfQenhf 3sHjYTutdFVD2n335hahVvoM4QM7jl0dZBpsI51pxuSc8x97lNOrpOo/766rw9IptMqP dRkdBM54fbuI9GXMX4Mlvmcvflrmvp9KjJ6M4pd9mpQuK6azovK3nbW6KMuOdjBNuoNC wLomU6UMu3iIZMRtXa8/d615C1HMB+cbEXoJzli1REQxTabJ/t4QcDer4QDGcS4dfvMo WsPA== X-Gm-Message-State: AOJu0YxpxJVYWxgwjPWSHEkc0dmo0krQDJJkNK+iYRS7oeN7IXlbCepn 6LRDY4gNC1mC7UF/OwpeEcf3zG1AUuoxl6eVjXu+QbGD4GrbmUG6NZmLDDUTig== X-Gm-Gg: AfdE7cnrsplR0eOoYsxcKQV54Hrgr3EB4TXD9YJRejCe0U3hHSQ1TCS5MzMJiOkNESS l22QXzGZCCwDRahBK74BkejamDe7zL4Oq1ioGrFuXRerRsokpywgmq4AWJ6TCuiIRM4fu1bvZTr KmhCvL2NieCH1+xjtuwD9F0UzEMuEq8KsK24m2sJdX3ZfT5T4l1Mqi8NdLMDWP3+mfcXLZPgWVE fBEih3wM4lvUaE+A663uoci10vOLiYcKml02C/cJ41zriP26wCAEVHNUzYkYnNiQHLOt3DuSJ7N sVMTjGDruYZBCer7qFmpmSser4ObfIghc5wr89iR7Lp2fczkUFTFBUPIMmeswguZa7N9WXZyhTs JtKIY8V7wSvMIBGhwAPYnBVDhfMHCJ2o9Bax+OuehUoP+ip7hHlrIMSqBb26mpbEWVnoHO+HL3v gW4brgrtTUsiDLndo6EAY91dcVVfcwT/fjEm0IVaj/UKMx1f1uHX5Itn3dktwtT6XlLg== X-Received: by 2002:a05:620a:2703:b0:92b:d4e3:1f08 with SMTP id af79cd13be357-92ecf6c6ea0mr1012778685a.55.1783630371118; Thu, 09 Jul 2026 13:52:51 -0700 (PDT) Received: from localhost.localdomain (pppoe-209-91-167-254.vianet.ca. [209.91.167.254]) by smtp.gmail.com with ESMTPSA id af79cd13be357-92ee5d37540sm32893085a.38.2026.07.09.13.52.48 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Jul 2026 13:52:48 -0700 (PDT) From: Trevor Woerner To: yocto-patches@lists.yoctoproject.org Subject: [wic][PATCH 3/4] oe/path: canonicalize('') should return '' rather than the cwd Date: Thu, 9 Jul 2026 16:52:26 -0400 Message-ID: <20260709205227.3470712-4-twoerner@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260709205227.3470712-1-twoerner@gmail.com> References: <20260709205227.3470712-1-twoerner@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 09 Jul 2026 20:53:01 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4462 canonicalize() splits its input on the separator and, for every token that does not contain an unexpanded bitbake variable, appends os.path.realpath(path). It never guarded against an empty token, and os.path.realpath('') returns the current working directory. So canonicalize('') and canonicalize(None) produced the cwd instead of an empty result, and a stray separator such as 'a,,b' injected a spurious cwd entry between the real paths. Skip empty tokens alongside the '$' tokens the loop already skips, so empty input canonicalizes to '' and redundant separators no longer introduce phantom cwd entries. AI-Generated: codex/claude-opus 4.8 (xhigh) Signed-off-by: Trevor Woerner --- src/wic/oe/path.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wic/oe/path.py b/src/wic/oe/path.py index 13c52b363694..95bb9648fab6 100644 --- a/src/wic/oe/path.py +++ b/src/wic/oe/path.py @@ -355,7 +355,11 @@ def canonicalize(paths, sep=','): # prefixes in sting compares later on, where the slashes then are important. canonical_paths = [] for path in (paths or '').split(sep): - if '$' not in path: + # Skip empty tokens as well as unexpanded bitbake variables: an + # empty path would otherwise reach os.path.realpath(''), which + # returns the current working directory, so canonicalize('') or + # canonicalize(None) would wrongly produce the cwd instead of ''. + if path and '$' not in path: trailing_slash = path.endswith('/') and '/' or '' canonical_paths.append(os.path.realpath(path) + trailing_slash) From patchwork Thu Jul 9 20:52:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Trevor Woerner X-Patchwork-Id: 92144 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 aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id E0834C43458 for ; Thu, 9 Jul 2026 20:53:01 +0000 (UTC) Received: from mail-qk1-f176.google.com (mail-qk1-f176.google.com [209.85.222.176]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.989.1783630374255971111 for ; Thu, 09 Jul 2026 13:52:54 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=ek9991ch; spf=pass (domain: gmail.com, ip: 209.85.222.176, mailfrom: twoerner@gmail.com) Received: by mail-qk1-f176.google.com with SMTP id af79cd13be357-92e7c6ec9dbso10392085a.0 for ; Thu, 09 Jul 2026 13:52:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783630373; x=1784235173; darn=lists.yoctoproject.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:content-type; bh=KWdT6n65kSRkzEI1EWmER4yPJP150V+zupUZrjgcdro=; b=ek9991chGuD0ZfFNEzuSqiQbFcAHiGR20YAx+wGP39v4ZLD99SaZFecHSRzIuGuQeY RVPLTR/DSBxhzndhVcvQEj8v5aFGvRHl5xdq3UKI3fZie04bJo5JfukYj5pBD06ca0lK IK0RtGdubqEssRyxQHLhVfiPtg5xZ88FtBJhTvMpH+PgePllDtyXwVNDTovgfR9ampPI HG1co6OZsNywkvPZX6PlJ4Tlc61B105bjbOWDcnLRt+6PjNFoNrGZj3ksiheNMVhdwor 2XTxVxBoccWqbdwoXh1U86dx7AYeAI9KApF8oO2gj78+zFlilT5JvjuYBSai30kMvIIa Tjpw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783630373; x=1784235173; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=KWdT6n65kSRkzEI1EWmER4yPJP150V+zupUZrjgcdro=; b=l7sbVARxyR1zxRVZylXp3WEncBv3LjuI9YwVUUGfSVVNuli3BLYjlkb/xaVz7er2wC RZayFjypHORrJIbgH417qpEIiRuL3dMlK6N7FFZTO83JJmpog3qQw5RFNK3dMKjvnV9l 9CWCYz7zg6Gzu0uYJR4Fe04C4WmrPh4MEaFfRU8wEFSymXpMlsOm+bGyukxmssi2H9Kv WO9r4xllY/QW1UGBzegvosVVVbZ0tcqxA1mLMNd1+Jo7ZWj7xUEi+DW35sZbMquQQTXq 7EkyBQYtp9HGrim5+jGE0qfSLLOZDR+YkgnCWFBhSHKwEFiAahWni8pTl68MqlWqJ9as DDKw== X-Gm-Message-State: AOJu0Ywvi+YDzJSBWH6yeSaLkS3Quc82F2eMoO1/EwFLXfOchbtbQCIV 0aAh5iX8pK28uNyNrXXoP/CFETv1aXeHFSEJLAiUfHG/DFR3wyjhgX4NH6S4xw== X-Gm-Gg: AfdE7cniLnc2nAfo4K68YzSfPID42PYpw1M5PlLyoi4WJjVuX7w8ojdJdUBc1nCUBS3 0cSwri5T5NMh5QtpidWtQxaHAlIq+7HL8biK1/FJJWgTasvNgZ0FUDHgAGLVmaBo7oQMCTxJTXR A5UBxVW7THy1D9ghWrQyMV4yjGpRWNFde6LqqbJi3+M0rtANVK1TQ40fvgOQZr+EXoCawNgq6Cf +ieEYvCKWt1F3wQiKZWe7Gi3a+i5AWhKTuFKNyRnRol7l0OMt/D0liMtApEtT1nKmJLYMXjcMcE QFVYBaa2mXxhr3rbyOhyVf3uEdcjJYArOlEJQnMXLSd9n2GQinPHW+fSbqYwDRDmJKWDSNcNYRa vcHUrwcEhpGxVagAefej/e7njYLYF9w0zadFkknl0jQYHLTg8iEifV2b86c9FFRGFHcmBRvzAaz g+2kn8o1HcQg5wZYGMnXfCVjhxuaeZefKC6MVcUnJmGxJh0XVrQ8rRBbg= X-Received: by 2002:a05:620a:268d:b0:914:7e9a:2716 with SMTP id af79cd13be357-92ecf5e36b3mr960609285a.38.1783630372809; Thu, 09 Jul 2026 13:52:52 -0700 (PDT) Received: from localhost.localdomain (pppoe-209-91-167-254.vianet.ca. [209.91.167.254]) by smtp.gmail.com with ESMTPSA id af79cd13be357-92ee5d37540sm32893085a.38.2026.07.09.13.52.51 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Jul 2026 13:52:51 -0700 (PDT) From: Trevor Woerner To: yocto-patches@lists.yoctoproject.org Subject: [wic][PATCH 4/4] tests/unit/test_oe_path: cover oe/path's own path logic Date: Thu, 9 Jul 2026 16:52:27 -0400 Message-ID: <20260709205227.3470712-5-twoerner@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260709205227.3470712-1-twoerner@gmail.com> References: <20260709205227.3470712-1-twoerner@gmail.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 09 Jul 2026 20:53:01 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4463 Add unit coverage for the wic-specific behaviour in oe/path.py: - join() keeps an absolute right-hand component relative and normalises redundant separators, '.', and '..'. - is_path_parent() treats containment by path component, so a shared string prefix such as /usrlocal is not below /usr, and every supplied path must be below the parent. - symlink() creates links, is idempotent for a matching link, raises on a conflicting one, and with force=True replaces the literal destination -- including names with glob metacharacters and an existing directory -- without deleting glob siblings. - make_relative_symlink() leaves non-links and already-relative links alone and rewrites an absolute link to a working relative one. - canonicalize() expands real paths, drops '$' and empty tokens, keeps trailing slashes, and returns '' for '' and None. - which_wild() honours an explicit search path, expands wildcards, and returns the first match per name (last with reverse=True). - realpath() resolves links below root, rejects paths outside root, and falls back cleanly when os.path.isdir() raises. AI-Generated: codex/claude-opus 4.8 (xhigh) Signed-off-by: Trevor Woerner --- tests/unit/test_oe_path.py | 298 +++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 tests/unit/test_oe_path.py diff --git a/tests/unit/test_oe_path.py b/tests/unit/test_oe_path.py new file mode 100644 index 000000000000..a978fcf789d2 --- /dev/null +++ b/tests/unit/test_oe_path.py @@ -0,0 +1,298 @@ +""" +Unit tests for wic/oe/path.py, covering wic's own path logic: join, +is_path_parent, symlink, make_relative_symlink, canonicalize, which_wild, +and realpath. Thin standard-library wrappers (relative -> os.path.relpath, +find -> os.walk) are left to the standard library. +""" +import os +import sys +from pathlib import Path + +import pytest + +_SRC = Path(__file__).resolve().parent.parent.parent / "src" +if str(_SRC) not in sys.path: + sys.path.insert(0, str(_SRC)) + +from wic.oe.path import ( + join, + is_path_parent, + symlink, + make_relative_symlink, + canonicalize, + which_wild, + realpath, +) + + +class TestJoin: + """join() is os.path.normpath("/".join(paths)); unlike os.path.join it + does not treat an absolute right-hand component specially.""" + + def test_two_paths(self): + assert join("a", "b") == "a/b" + + def test_three_paths(self): + assert join("a", "b", "c") == "a/b/c" + + def test_absolute_rhs_is_not_special(self): + # os.path.join("a", "/b") == "/b"; join() keeps it relative. + assert join("a", "/b") == "a/b" + + def test_redundant_separators_normalised(self): + assert join("a//", "b") == "a/b" + + def test_pardir_normalised(self): + assert join("a", "..", "b") == "b" + + def test_curdir_normalised(self): + assert join("a", ".", "b") == "a/b" + + def test_single_component(self): + assert join("a") == "a" + + def test_empty_leading_component_is_absolute(self): + # "/".join(["", "b"]) is "/b", which normpath leaves absolute. + assert join("", "b") == "/b" + + +class TestIsPathParent: + def test_direct_child(self): + assert is_path_parent("/usr", "/usr/bin") is True + + def test_deep_child(self): + assert is_path_parent("/usr", "/usr/share/doc/readme") is True + + def test_unrelated_path(self): + assert is_path_parent("/usr", "/tmp") is False + + def test_child_is_not_parent_of_its_parent(self): + assert is_path_parent("/usr/bin", "/usr") is False + + def test_prefix_string_is_not_a_path_parent(self): + # "/usrlocal" shares a string prefix with "/usr" but is not below it; + # the trailing-separator handling must reject it. + assert is_path_parent("/usr", "/usrlocal") is False + + def test_no_paths_returns_false(self): + assert is_path_parent("/usr") is False + + def test_all_paths_must_be_below(self): + assert is_path_parent("/usr", "/usr/bin", "/usr/lib") is True + + def test_one_path_outside_fails(self): + assert is_path_parent("/usr", "/usr/bin", "/tmp") is False + + +class TestSymlink: + def test_creates_symlink(self, tmp_path): + src = tmp_path / "target.txt" + src.write_text("content") + dst = tmp_path / "link" + symlink(str(src), str(dst)) + assert os.path.islink(str(dst)) + assert os.readlink(str(dst)) == str(src) + + def test_matching_existing_symlink_is_idempotent(self, tmp_path): + src = tmp_path / "target.txt" + src.write_text("content") + dst = tmp_path / "link" + symlink(str(src), str(dst)) + symlink(str(src), str(dst)) + assert os.readlink(str(dst)) == str(src) + + def test_conflicting_existing_symlink_raises(self, tmp_path): + src1 = tmp_path / "t1.txt" + src2 = tmp_path / "t2.txt" + src1.write_text("a") + src2.write_text("b") + dst = tmp_path / "link" + symlink(str(src1), str(dst)) + with pytest.raises(OSError): + symlink(str(src2), str(dst)) + + def test_force_overwrites_existing_symlink(self, tmp_path): + src1 = tmp_path / "t1" + src2 = tmp_path / "t2" + src1.write_text("a") + src2.write_text("b") + dst = tmp_path / "link" + symlink(str(src1), str(dst)) + symlink(str(src2), str(dst), force=True) + assert os.readlink(str(dst)) == str(src2) + + def test_force_replaces_destination_with_glob_metacharacters(self, tmp_path): + # A destination whose name contains glob metacharacters must still be + # replaced. Routing the removal through glob (the old behaviour) fails + # to match "link[1]" against itself, leaving the stale entry in place. + src = tmp_path / "target" + src.write_text("content") + dst = tmp_path / "link[1]" + dst.write_text("stale regular file") + symlink(str(src), str(dst), force=True) + assert os.path.islink(str(dst)) + assert os.readlink(str(dst)) == str(src) + + def test_force_does_not_delete_glob_siblings(self, tmp_path): + # The destination "keep?.txt" does not exist, but a sibling + # "keepX.txt" matches it as a glob pattern. force=True must remove + # only the literal destination, never a pattern sibling. + sibling = tmp_path / "keepX.txt" + sibling.write_text("do not delete me") + src = tmp_path / "target" + src.write_text("content") + dst = tmp_path / "keep?.txt" + symlink(str(src), str(dst), force=True) + assert sibling.exists() + assert os.readlink(str(dst)) == str(src) + + def test_force_replaces_existing_directory(self, tmp_path): + # An existing directory at the destination is torn down (EISDIR -> + # rmtree) before the link is created. + src = tmp_path / "target" + src.write_text("content") + dst = tmp_path / "dir" + dst.mkdir() + (dst / "child").write_text("x") + symlink(str(src), str(dst), force=True) + assert os.path.islink(str(dst)) + assert os.readlink(str(dst)) == str(src) + + +class TestMakeRelativeSymlink: + def test_non_symlink_is_ignored(self, tmp_path): + regular = tmp_path / "file.txt" + regular.write_text("content") + make_relative_symlink(str(regular)) + assert regular.is_file() + + def test_already_relative_symlink_is_unchanged(self, tmp_path): + target = tmp_path / "target.txt" + target.write_text("content") + link = tmp_path / "link" + os.symlink("target.txt", str(link)) + make_relative_symlink(str(link)) + assert os.readlink(str(link)) == "target.txt" + + def test_absolute_symlink_becomes_relative_and_resolves(self, tmp_path): + target = tmp_path / "target.txt" + target.write_text("content") + link = tmp_path / "link" + os.symlink(str(target), str(link)) + assert os.path.isabs(os.readlink(str(link))) + make_relative_symlink(str(link)) + result = os.readlink(str(link)) + assert not os.path.isabs(result) + assert os.path.exists(str(link)) + assert Path(str(link)).read_text() == "content" + + +class TestCanonicalize: + def test_real_path_returned(self, tmp_path): + result = canonicalize(str(tmp_path)) + assert result == os.path.realpath(str(tmp_path)) + + def test_unexpanded_variable_is_skipped(self): + assert canonicalize("$SOME_VAR") == "" + + def test_variable_token_dropped_leaving_real_path(self, tmp_path): + result = canonicalize("$VAR," + str(tmp_path)) + # The "$VAR" token is dropped entirely: no empty placeholder, no + # leading separator, just the canonical real path. + assert result == os.path.realpath(str(tmp_path)) + + def test_empty_string_returns_empty_string(self): + # os.path.realpath("") is the cwd; canonicalize("") must not leak it. + assert canonicalize("") == "" + + def test_none_returns_empty_string(self): + assert canonicalize(None) == "" + + def test_empty_token_between_paths_is_dropped(self, tmp_path): + p1 = tmp_path / "a" + p2 = tmp_path / "b" + p1.mkdir() + p2.mkdir() + # The stray separator in "a,,b" must not inject a cwd entry. + result = canonicalize("%s,,%s" % (p1, p2)) + assert result == "%s,%s" % ( + os.path.realpath(str(p1)), os.path.realpath(str(p2))) + + def test_trailing_slash_preserved(self, tmp_path): + result = canonicalize(str(tmp_path) + "/") + assert result == os.path.realpath(str(tmp_path)) + "/" + + +class TestWhichWild: + def test_finds_existing_executable(self): + results = which_wild("python3") + assert results + assert all(os.path.isabs(p) for p in results) + assert all(os.path.basename(p) == "python3" for p in results) + + def test_missing_tool_returns_empty(self): + assert which_wild("totally_nonexistent_tool_xyz") == [] + + def test_explicit_search_path(self, tmp_path): + tool = tmp_path / "mytool" + tool.write_text("#!/bin/sh\n") + tool.chmod(0o755) + results = which_wild("mytool", path=str(tmp_path)) + assert results == [str(tool)] + + def test_wildcard_pattern(self, tmp_path): + (tmp_path / "foo-a").write_text("") + (tmp_path / "foo-b").write_text("") + (tmp_path / "bar").write_text("") + results = which_wild("foo-*", path=str(tmp_path)) + assert sorted(os.path.basename(p) for p in results) == ["foo-a", "foo-b"] + + def test_first_match_per_name_wins(self, tmp_path): + # A name found in an earlier PATH element shadows the same name later. + first = tmp_path / "first" + second = tmp_path / "second" + first.mkdir() + second.mkdir() + (first / "tool").write_text("") + (second / "tool").write_text("") + search = "%s:%s" % (first, second) + assert which_wild("tool", path=search) == [str(first / "tool")] + # reverse=True walks PATH the other way, so the other copy wins. + assert which_wild("tool", path=search, reverse=True) == [str(second / "tool")] + + +class TestRealpath: + def test_resolves_symlink_below_root(self, tmp_path): + target = tmp_path / "real" + target.mkdir() + link = tmp_path / "lnk" + os.symlink("real", str(link)) + result = realpath(str(link), str(tmp_path)) + assert result == str(target) + + def test_plain_path_is_returned(self, tmp_path): + sub = tmp_path / "d" + sub.mkdir() + assert realpath(str(sub), str(tmp_path)) == str(sub) + + def test_path_outside_root_raises(self, tmp_path): + root = tmp_path / "root" + outside = tmp_path / "outside" + root.mkdir() + outside.mkdir() + with pytest.raises(OSError): + realpath(str(outside), str(root)) + + def test_isdir_failure_falls_back_without_nameerror(self, tmp_path, monkeypatch): + # The final stanza of __realpath tolerates any failure from + # os.path.isdir by treating the path as not-a-directory. Force isdir + # to raise and confirm the fallback returns the resolved path rather + # than raising NameError from an undefined fallback value. + sub = tmp_path / "d" + sub.mkdir() + + def boom(_path): + raise OSError("synthetic stat failure") + + monkeypatch.setattr(os.path, "isdir", boom) + assert realpath(str(sub), str(tmp_path), use_physdir=False) == str(sub)