diff mbox series

[7/8] fetch/{clearcase,gomod,hg,perforce,repo,s3}: Convert to use lists of command arguments

Message ID 20260603104840.815399-7-richard.purdie@linuxfoundation.org
State New
Headers show
Series [1/8] fetch2: Allow runfetchcmd to handle lists of command arguments | expand

Commit Message

Richard Purdie June 3, 2026, 10:48 a.m. UTC
To follow best practises and avoid shell=True subprocess usage, convert
the fetcher commands to use lists instead of strings. This improves variable
quoting and models modern coding standards.

Some of these fetchers could not be tested so the changes are made blind.

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
---
 lib/bb/fetch2/clearcase.py | 39 ++++++++++++++++---------------
 lib/bb/fetch2/gomod.py     |  4 ++--
 lib/bb/fetch2/hg.py        | 47 ++++++++++++++++----------------------
 lib/bb/fetch2/perforce.py  | 30 ++++++++++++++----------
 lib/bb/fetch2/repo.py      | 14 ++++++------
 lib/bb/fetch2/s3.py        |  7 +++---
 6 files changed, 71 insertions(+), 70 deletions(-)
diff mbox series

Patch

diff --git a/lib/bb/fetch2/clearcase.py b/lib/bb/fetch2/clearcase.py
index 17500daf952..cb3f8b650c7 100644
--- a/lib/bb/fetch2/clearcase.py
+++ b/lib/bb/fetch2/clearcase.py
@@ -49,6 +49,7 @@  User credentials:
 #
 
 import os
+import shlex
 import shutil
 import bb
 from   bb.fetch2 import FetchMethod
@@ -94,7 +95,7 @@  class ClearCase(FetchMethod):
         else:
             ud.module = ""
 
-        ud.basecmd = d.getVar("FETCHCMD_ccrc") or "/usr/bin/env cleartool || rcleartool"
+        ud.fallbackcmd = shlex.split(d.getVar("FETCHCMD_ccrc") or "")
 
         if d.getVar("SRCREV") == "INVALID":
           raise FetchError("Set a valid SRCREV for the clearcase fetcher in your recipe, e.g. SRCREV = \"/main/LATEST\" or any other label of your choice.")
@@ -122,7 +123,6 @@  class ClearCase(FetchMethod):
         self.debug("type            = %s" % ud.type)
         self.debug("vob             = %s" % ud.vob)
         self.debug("module          = %s" % ud.module)
-        self.debug("basecmd         = %s" % ud.basecmd)
         self.debug("label           = %s" % ud.label)
         self.debug("ccasedir        = %s" % ud.ccasedir)
         self.debug("viewdir         = %s" % ud.viewdir)
@@ -135,34 +135,35 @@  class ClearCase(FetchMethod):
         Build up a commandline based on ud
         command is: mkview, setcs, rmview
         """
-        options = []
+        cmd = [shutil.which("cleartool"), command]
+        if cmd[0] is None:
+            cmd = [shutil.which("rcleartool"), command]
+        if cmd[0] is None:
+            cmd = ud.fallbackcmd + [command]
 
-        if "rcleartool" in ud.basecmd:
-            options.append("-server %s" % ud.server)
-
-        basecmd = "%s %s" % (ud.basecmd, command)
+        if cmd[0].endswith("rcleartool"):
+            cmd += ['-server', ud.server]
 
         if command == 'mkview':
-            if not "rcleartool" in ud.basecmd:
+            if not cmd[0].endswith("rcleartool"):
                 # Cleartool needs a -snapshot view
-                options.append("-snapshot")
-            options.append("-tag %s" % ud.viewname)
-            options.append(ud.viewdir)
+                cmd.append("-snapshot")
+            cmd += ["-tag", ud.viewname]
+            cmd.append(ud.viewdir)
 
         elif command == 'rmview':
-            options.append("-force")
-            options.append("%s" % ud.viewdir)
+            cmd.append("-force")
+            cmd.append(ud.viewdir)
 
         elif command == 'setcs':
-            options.append("-overwrite")
-            options.append(ud.configspecfile)
+            cmd.append("-overwrite")
+            cmd.append(ud.configspecfile)
 
         else:
             raise FetchError("Invalid ccase command %s" % command)
 
-        ccasecmd = "%s %s" % (basecmd, " ".join(options))
-        self.debug("ccasecmd = %s" % ccasecmd)
-        return ccasecmd
+        self.debug("ccasecmd = %s" % cmd)
+        return cmd
 
     def _write_configspec(self, ud, d):
         """
@@ -235,7 +236,7 @@  class ClearCase(FetchMethod):
 
         # Clean clearcase meta-data before tar
 
-        runfetchcmd('tar -czf "%s" .' % (ud.localpath), d, cleanup = [ud.localpath], workdir = ud.viewdir)
+        runfetchcmd(['tar', '-czf', ud.localpath, '.'], d, cleanup = [ud.localpath], workdir = ud.viewdir)
 
         # Clean up so we can create a new view next time
         self.clean(ud, d);
diff --git a/lib/bb/fetch2/gomod.py b/lib/bb/fetch2/gomod.py
index 53c1d8d115b..8a64f644dfd 100644
--- a/lib/bb/fetch2/gomod.py
+++ b/lib/bb/fetch2/gomod.py
@@ -242,9 +242,9 @@  class GoModGit(Git):
         srcrev = ud.parm['srcrev']
         version = ud.parm['version']
         escaped_version = escape(version)
-        cmd = f"git ls-tree -r --name-only '{srcrev}'"
+        cmd = ['git', 'ls-tree', '-r', '--name-only', srcrev]
         if 'subpath' in ud.parm:
-            cmd += f" '{ud.parm['subpath']}'"
+            cmd.append(ud.parm['subpath'])
         files = runfetchcmd(cmd, d, workdir=repodir).split()
         name = escaped_version + '.mod'
         bb.note(f"Unpacking {name} to {unpackdir}/")
diff --git a/lib/bb/fetch2/hg.py b/lib/bb/fetch2/hg.py
index cbff8c490c9..5c65a1985bb 100644
--- a/lib/bb/fetch2/hg.py
+++ b/lib/bb/fetch2/hg.py
@@ -15,6 +15,7 @@  BitBake 'Fetch' implementation for mercurial DRCS (hg).
 import os
 import bb
 import errno
+import shlex
 from bb.fetch2 import FetchMethod
 from bb.fetch2 import FetchError
 from bb.fetch2 import MissingParameterError
@@ -63,7 +64,7 @@  class Hg(FetchMethod):
         ud.pkgdir = os.path.join(hgdir, hgsrcname)
         ud.moddir = os.path.join(ud.pkgdir, ud.module)
         ud.localfile = ud.moddir
-        ud.basecmd = d.getVar("FETCHCMD_hg") or "/usr/bin/env hg"
+        ud.basecmd = shlex.split(d.getVar("FETCHCMD_hg") or "") or ["hg"]
 
         ud.setup_revisions(d)
 
@@ -113,7 +114,7 @@  class Hg(FetchMethod):
                 hgroot = ud.user + "@" + host + ud.path
 
         if command == "info":
-            return "%s identify -i %s://%s/%s" % (ud.basecmd, proto, hgroot, ud.module)
+            return ud.basecmd + ['identify', '-i', '%s://%s/%s' % (proto, hgroot, ud.module)]
 
         options = [];
 
@@ -122,26 +123,21 @@  class Hg(FetchMethod):
         # the tag actually exists in the specified revision + 1, so it won't
         # be available when used in any successive commands.
         if ud.revision and command != "fetch":
-            options.append("-r %s" % ud.revision)
+            options += ["-r", ud.revision]
+
+        authconfig = []
+        if ud.user and ud.pswd:
+            authconfig = ['--config', 'auth.default.prefix=*', '--config', 'auth.default.username=' + ud.user, '--config', 'auth.default.password=' + ud.pswd, '--config', 'auth.default.schemes=' + proto]
 
         if command == "fetch":
-            if ud.user and ud.pswd:
-                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" clone %s %s://%s/%s %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options), proto, hgroot, ud.module, ud.module)
-            else:
-                cmd = "%s clone %s %s://%s/%s %s" % (ud.basecmd, " ".join(options), proto, hgroot, ud.module, ud.module)
+            cmd = ud.basecmd + authconfig + ['clone'] + options + ['%s://%s/%s' % (proto, hgroot, ud.module), ud.module]
         elif command == "pull":
             # do not pass options list; limiting pull to rev causes the local
             # repo not to contain it and immediately following "update" command
             # will crash
-            if ud.user and ud.pswd:
-                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull" % (ud.basecmd, ud.user, ud.pswd, proto)
-            else:
-                cmd = "%s pull" % (ud.basecmd)
+            cmd = ud.basecmd + authconfig + ['pull']
         elif command == "update" or command == "up":
-            if ud.user and ud.pswd:
-                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" update -C %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options))
-            else:
-                cmd = "%s update -C %s" % (ud.basecmd, " ".join(options))
+            cmd = ud.basecmd + authconfig + ['update', '-C'] + options
         else:
             raise FetchError("Invalid hg command %s" % command, ud.url)
 
@@ -155,7 +151,7 @@  class Hg(FetchMethod):
         # If the checkout doesn't exist and the mirror tarball does, extract it
         if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror):
             bb.utils.mkdirhier(ud.pkgdir)
-            runfetchcmd("tar -xzf %s" % (ud.fullmirror), d, workdir=ud.pkgdir)
+            runfetchcmd(['tar', '-xzf', ud.fullmirror], d, workdir=ud.pkgdir)
 
         if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK):
             # Found the source, check whether need pull
@@ -228,8 +224,8 @@  class Hg(FetchMethod):
                 os.unlink(ud.fullmirror)
 
             logger.info("Creating tarball of hg repository")
-            runfetchcmd("tar -czf %s %s" % (ud.fullmirror, ud.module), d, workdir=ud.pkgdir)
-            runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=ud.pkgdir)
+            runfetchcmd(['tar', '-czf', ud.fullmirror, ud.module], d, workdir=ud.pkgdir)
+            runfetchcmd(['touch', '%s.done' % ud.fullmirror], d, workdir=ud.pkgdir)
 
     def localpath(self, ud, d):
         return ud.pkgdir
@@ -249,16 +245,13 @@  class Hg(FetchMethod):
             proto = ud.parm.get('protocol', 'http')
             if not os.access(os.path.join(codir, '.hg'), os.R_OK):
                 logger.debug2("Unpack: creating new hg repository in '" + codir + "'")
-                runfetchcmd("%s init %s" % (ud.basecmd, codir), d)
+                runfetchcmd(ud.basecmd + ['init', codir], d)
             logger.debug2("Unpack: updating source in '" + codir + "'")
+            authconfig = []
             if ud.user and ud.pswd:
-                runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull %s" % (ud.basecmd, ud.user, ud.pswd, proto, ud.moddir), d, workdir=codir)
-            else:
-                runfetchcmd("%s pull %s" % (ud.basecmd, ud.moddir), d, workdir=codir)
-            if ud.user and ud.pswd:
-                runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" up -C %s" % (ud.basecmd, ud.user, ud.pswd, proto, revflag), d, workdir=codir)
-            else:
-                runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d, workdir=codir)
+                authconfig = ['--config', 'auth.default.prefix=*', '--config', 'auth.default.username=' + ud.user, '--config', 'auth.default.password=' + ud.pswd, '--config', 'auth.default.schemes=' + proto]
+            runfetchcmd(ud.basecmd + authconfig + ['pull', ud.moddir], d, workdir=codir)
+            runfetchcmd(ud.basecmd + authconfig + ['up', '-C', revflag], d, workdir=codir)
         else:
             logger.debug2("Unpack: extracting source to '" + codir + "'")
-            runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d, workdir=ud.moddir)
+            runfetchcmd(ud.basecmd + ['archive', '-t', 'files', revflag, codir], d, workdir=ud.moddir)
diff --git a/lib/bb/fetch2/perforce.py b/lib/bb/fetch2/perforce.py
index 3b6fa4b1ec9..52ea061c04b 100644
--- a/lib/bb/fetch2/perforce.py
+++ b/lib/bb/fetch2/perforce.py
@@ -26,6 +26,7 @@  Supported SRC_URI options are:
 
 import os
 import bb
+import shlex
 from   bb.fetch2 import FetchMethod
 from   bb.fetch2 import FetchError
 from   bb.fetch2 import logger
@@ -73,7 +74,7 @@  class Perforce(FetchMethod):
         provided by the env, use it.  If P4PORT is specified by the recipe, use
         its values, which may override the settings in P4CONFIG.
         """
-        ud.basecmd = d.getVar("FETCHCMD_p4") or "/usr/bin/env p4"
+        ud.basecmd = shlex.split(d.getVar("FETCHCMD_p4") or "") or ["p4"]
 
         ud.dldir = d.getVar("P4DIR") or (d.getVar("DL_DIR") + "/p4")
 
@@ -95,10 +96,15 @@  class Perforce(FetchMethod):
         else:
             logger.debug('Trying to use P4CONFIG to automatically set P4PORT...')
             ud.usingp4config = True
-            p4cmd = '%s info | grep "Server address"' % ud.basecmd
+            p4cmd = ud.basecmd + ['info']
             bb.fetch2.check_network_access(d, p4cmd, ud.url)
-            ud.host = runfetchcmd(p4cmd, d, True)
-            ud.host = ud.host.split(': ')[1].strip()
+            output = runfetchcmd(p4cmd, d, True)
+            ud.host = None
+            for line in output.splitlines():
+                if "Server address" in line:
+                    ud.host = line.split(': ')[1].strip()
+                    break
+
             logger.debug('Determined P4PORT to be: %s' % ud.host)
             if not ud.host:
                 raise FetchError('Could not determine P4PORT from P4CONFIG')
@@ -142,16 +148,16 @@  class Perforce(FetchMethod):
         "files".  depot_filename is the full path to the file in the depot
         including the trailing '#rev' value.
         """
-        p4opt = ""
+        p4opt = []
 
         if ud.user:
-            p4opt += ' -u "%s"' % (ud.user)
+            p4opt += ['-u', ud.user]
 
         if ud.pswd:
-            p4opt += ' -P "%s"' % (ud.pswd)
+            p4opt += ['-P', ud.pswd]
 
         if ud.host and not ud.usingp4config:
-            p4opt += ' -p %s' % (ud.host)
+            p4opt += ['-p', ud.host]
 
         if hasattr(ud, 'revision') and ud.revision:
             pathnrev = '%s@%s' % (ud.path, ud.revision)
@@ -176,14 +182,14 @@  class Perforce(FetchMethod):
             filename = filename[:filename.find('#')] # Remove trailing '#rev'
 
         if command == 'changes':
-            p4cmd = '%s%s changes -m 1 //%s' % (ud.basecmd, p4opt, pathnrev)
+            p4cmd = ud.basecmd + p4opt + ['changes', '-m', '1', '//%s' % pathnrev]
         elif command == 'print':
             if depot_filename is not None:
-                p4cmd = '%s%s print -o "p4/%s" "%s"' % (ud.basecmd, p4opt, filename, depot_filename)
+                p4cmd = ud.basecmd + p4opt + ['print', '-o', 'p4/%s' % filename, depot_filename]
             else:
                 raise FetchError('No depot file name provided to p4 %s' % command, ud.url)
         elif command == 'files':
-            p4cmd = '%s%s files //%s' % (ud.basecmd, p4opt, pathnrev)
+            p4cmd = ud.basecmd + p4opt + ['files', '//%s' % pathnrev]
         else:
             raise FetchError('Invalid p4 command %s' % command, ud.url)
 
@@ -231,7 +237,7 @@  class Perforce(FetchMethod):
             bb.fetch2.check_network_access(d, p4fetchcmd, ud.url)
             runfetchcmd(p4fetchcmd, d, workdir=ud.pkgdir, log=progresshandler)
 
-        runfetchcmd('tar -czf %s p4' % (ud.localpath), d, cleanup=[ud.localpath], workdir=ud.pkgdir)
+        runfetchcmd(['tar', '-czf', ud.localpath, 'p4'], d, cleanup=[ud.localpath], workdir=ud.pkgdir)
 
     def clean(self, ud, d):
         """ Cleanup p4 specific files and dirs"""
diff --git a/lib/bb/fetch2/repo.py b/lib/bb/fetch2/repo.py
index fa4cb8149b7..1f9ede04430 100644
--- a/lib/bb/fetch2/repo.py
+++ b/lib/bb/fetch2/repo.py
@@ -33,7 +33,7 @@  class Repo(FetchMethod):
         "master".
         """
 
-        ud.basecmd = d.getVar("FETCHCMD_repo") or "/usr/bin/env repo"
+        ud.basecmd = shlex.split(d.getVar("FETCHCMD_repo") or "") or ["repo"]
 
         ud.proto = ud.parm.get('protocol', 'git')
         ud.branch = ud.parm.get('branch', 'master')
@@ -63,19 +63,19 @@  class Repo(FetchMethod):
         bb.utils.mkdirhier(repodir)
         if not os.path.exists(os.path.join(repodir, ".repo")):
             bb.fetch2.check_network_access(d, "%s init -m %s -b %s -u %s://%s%s%s" % (ud.basecmd, ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), ud.url)
-            runfetchcmd("%s init -m %s -b %s -u %s://%s%s%s" % (ud.basecmd, ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), d, workdir=repodir)
+            runfetchcmd(ud.basecmd + ['init', '-m', ud.manifest, '-b', ud.branch, '-u', '%s://%s%s%s' % (ud.proto, username, ud.host, ud.path)], d, workdir=repodir)
 
-        bb.fetch2.check_network_access(d, "%s sync %s" % (ud.basecmd, ud.url), ud.url)
-        runfetchcmd("%s sync" % ud.basecmd, d, workdir=repodir)
+        bb.fetch2.check_network_access(d, ud.basecmd + ['sync'], ud.url)
+        runfetchcmd(ud.basecmd + ['sync'], d, workdir=repodir)
 
         scmdata = ud.parm.get("scmdata", "")
         if scmdata == "keep":
-            tar_flags = ""
+            tar_flags = []
         else:
-            tar_flags = "--exclude='.repo' --exclude='.git'"
+            tar_flags = ["--exclude='.repo'", "--exclude='.git'"]
 
         # Create a cache
-        runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.join(".", "*") ), d, workdir=codir)
+        runfetchcmd(['tar'] + tar_flags + ['-czf', ud.localpath, os.path.join(".", "*")], d, workdir=codir)
 
     def supports_srcrev(self):
         return False
diff --git a/lib/bb/fetch2/s3.py b/lib/bb/fetch2/s3.py
index 22c05381392..05935101471 100644
--- a/lib/bb/fetch2/s3.py
+++ b/lib/bb/fetch2/s3.py
@@ -19,6 +19,7 @@  import os
 import bb
 import urllib.request, urllib.parse, urllib.error
 import re
+import shlex
 from bb.fetch2 import FetchMethod
 from bb.fetch2 import FetchError
 from bb.fetch2 import runfetchcmd
@@ -79,7 +80,7 @@  class S3(FetchMethod):
 
         ud.localfile = ud.basename
 
-        ud.basecmd = d.getVar("FETCHCMD_s3") or "/usr/bin/env aws s3"
+        ud.basecmd = shlex.split(d.getVar("FETCHCMD_s3") or "") or ["aws", "s3"]
 
     def download(self, ud, d):
         """
@@ -87,7 +88,7 @@  class S3(FetchMethod):
         Assumes localpath was called first
         """
 
-        cmd = '%s cp s3://%s%s %s' % (ud.basecmd, ud.host, ud.path, ud.localpath)
+        cmd = ud.basecmd + ['cp', 's3://%s%s' % (ud.basecmd, ud.host, ud.path), ud.localpath]
         bb.fetch2.check_network_access(d, cmd, ud.url)
 
         progresshandler = S3ProgressHandler(d)
@@ -111,7 +112,7 @@  class S3(FetchMethod):
         Check the status of a URL
         """
 
-        cmd = '%s ls s3://%s%s' % (ud.basecmd, ud.host, ud.path)
+        cmd = ud.basecmd + ['ls', 's3://%s%s' % (ud.basecmd, ud.host, ud.path)]
         bb.fetch2.check_network_access(d, cmd, ud.url)
         output = runfetchcmd(cmd, d)