We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Backport] /usr/lib/PXELINUX/linux.c32 does not exist, can't create a symlink to it
### Original feature issue

- Issue: #3574
- PR: #3576 

### Target release

- [x] release33
- [ ] release32
- [ ] release30

### Reason

Stabilization for Debian of Cobbler 3.3.x.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cobbler/actions/mkloaders.py`
Content:
```
1 """Cobbler action to create bootable Grub2 images.
2 
3 This action calls grub2-mkimage for all bootloader formats configured in
4 Cobbler's settings. See man(1) grub2-mkimage for available formats.
5 """
6 import logging
7 import pathlib
8 import re
9 import subprocess
10 import sys
11 import typing
12 
13 from cobbler import utils
14 
15 
16 # NOTE: does not warrant being a class, but all Cobbler actions use a class's ".run()" as the entrypoint
17 class MkLoaders:
18     """
19     Action to create bootloader images.
20     """
21 
22     def __init__(self, api):
23         """
24         MkLoaders constructor.
25 
26         :param api: CobblerAPI instance for accessing settings
27         """
28         self.logger = logging.getLogger()
29         self.bootloaders_dir = pathlib.Path(api.settings().bootloaders_dir)
30         # GRUB 2
31         self.grub2_mod_dir = pathlib.Path(api.settings().grub2_mod_dir)
32         self.boot_loaders_formats: typing.Dict = api.settings().bootloaders_formats
33         self.modules: typing.List = api.settings().bootloaders_modules
34         # Syslinux
35         self.syslinux_folder = pathlib.Path(api.settings().syslinux_dir)
36         self.syslinux_memdisk_folder = pathlib.Path(api.settings().syslinux_memdisk_folder)
37         self.syslinux_pxelinux_folder = pathlib.Path(api.settings().syslinux_pxelinux_folder)
38         # Shim
39         self.shim_glob = pathlib.Path(api.settings().bootloaders_shim_folder)
40         self.shim_regex = re.compile(api.settings().bootloaders_shim_file)
41         # iPXE
42         self.ipxe_folder = pathlib.Path(api.settings().bootloaders_ipxe_folder)
43 
44     def run(self):
45         """
46         Run GrubImages action. If the files or executables for the bootloader is not available we bail out and skip the
47         creation after it is logged that this is not available.
48         """
49         self.create_directories()
50 
51         self.make_shim()
52         self.make_ipxe()
53         self.make_syslinux()
54         self.make_grub()
55 
56     def make_shim(self):
57         """
58         Create symlink of the shim bootloader in case it is available on the system.
59         """
60         # Check well-known locations
61         # Absolute paths are not supported BUT we can get around that: https://stackoverflow.com/a/51108375/4730773
62         parts = self.shim_glob.parts
63         start_at = 1 if self.shim_glob.is_absolute() else 0
64         bootloader_path_parts = pathlib.Path(*parts[start_at:])
65         results = sorted(pathlib.Path(self.shim_glob.root).glob(str(bootloader_path_parts)))
66         # If no match, then report and bail out.
67         if len(results) <= 0:
68             self.logger.info('Unable to find the folder which should be scanned for "shim.efi"! Bailing out of linking '
69                              'the shim!')
70             return
71         # Now scan the folders with the regex
72         target_shim = None
73         for possible_folder in results:
74             for child in possible_folder.iterdir():
75                 if self.shim_regex.search(str(child)):
76                     target_shim = child.resolve()
77                     break
78         # If no match is found report and return
79         if target_shim is None:
80             self.logger.info('Unable to find "shim.efi" file. Please adjust "bootloaders_shim_file" regex. Bailing out '
81                              'of linking the shim!')
82             return
83         # Symlink the absolute target of the match
84         symlink(
85             target_shim,
86             self.bootloaders_dir.joinpath(pathlib.Path("grub/shim.efi")),
87             skip_existing=True
88         )
89 
90     def make_ipxe(self):
91         """
92         Create symlink of the iPXE bootloader in case it is available on the system.
93         """
94         if not self.ipxe_folder.exists():
95             self.logger.info('ipxe directory did not exist. Please adjust the "bootloaders_ipxe_folder". Bailing out '
96                              'of iPXE setup!')
97             return
98         symlink(
99             self.ipxe_folder.joinpath("undionly.kpxe"),
100             self.bootloaders_dir.joinpath(pathlib.Path("undionly.pxe")),
101             skip_existing=True
102         )
103 
104     def make_syslinux(self):
105         """
106         Create symlink of the important syslinux bootloader files in case they are available on the system.
107         """
108         if not utils.command_existing("syslinux"):
109             self.logger.info("syslinux command not available. Bailing out of syslinux setup!")
110             return
111         syslinux_version = get_syslinux_version()
112         # Make modules
113         symlink(
114             self.syslinux_folder.joinpath("menu.c32"),
115             self.bootloaders_dir.joinpath("menu.c32"),
116             skip_existing=True
117         )
118         # According to https://wiki.syslinux.org/wiki/index.php?title=Library_modules,
119         # 'menu.c32' depends on 'libutil.c32'.
120         libutil_c32_path = self.syslinux_folder.joinpath("libutil.c32")
121         if syslinux_version > 4 and libutil_c32_path.exists():
122             symlink(
123                 libutil_c32_path,
124                 self.bootloaders_dir.joinpath("libutil.c32"),
125                 skip_existing=True,
126             )
127         if syslinux_version < 5:
128             # This file is only required for Syslinux 5 and newer.
129             # Source: https://wiki.syslinux.org/wiki/index.php?title=Library_modules
130             self.logger.info('syslinux version 4 detected! Skip making symlink of "ldlinux.c32" file!')
131         else:
132             symlink(
133                 self.syslinux_folder.joinpath("ldlinux.c32"),
134                 self.bootloaders_dir.joinpath("ldlinux.c32"),
135                 skip_existing=True
136             )
137         # Make memdisk
138         symlink(
139             self.syslinux_memdisk_folder.joinpath("memdisk"),
140             self.bootloaders_dir.joinpath("memdisk"),
141             skip_existing=True
142         )
143         # Make pxelinux.0
144         symlink(
145             self.syslinux_pxelinux_folder.joinpath("pxelinux.0"),
146             self.bootloaders_dir.joinpath("pxelinux.0"),
147             skip_existing=True
148         )
149         # Make linux.c32 for syslinux + wimboot
150         libcom32_c32_path = self.syslinux_folder.joinpath("libcom32.c32")
151         if syslinux_version > 4 and libcom32_c32_path.exists():
152             symlink(
153                 self.syslinux_pxelinux_folder.joinpath("linux.c32"),
154                 self.bootloaders_dir.joinpath("linux.c32"),
155                 skip_existing=True,
156             )
157             # Make libcom32.c32
158             # 'linux.c32' depends on 'libcom32.c32'
159             symlink(
160                 self.syslinux_pxelinux_folder.joinpath("libcom32.c32"),
161                 self.bootloaders_dir.joinpath("libcom32.c32"),
162                 skip_existing=True,
163             )
164 
165     def make_grub(self):
166         """
167         Create symlink of the GRUB 2 bootloader in case it is available on the system. Additionally build the loaders
168         for other architectures if the modules to do so are available.
169         """
170         if not utils.command_existing("grub2-mkimage"):
171             self.logger.info("grub2-mkimage command not available. Bailing out of GRUB2 generation!")
172             return
173 
174         for image_format, options in self.boot_loaders_formats.items():
175             bl_mod_dir = options.get("mod_dir", image_format)
176             mod_dir = self.grub2_mod_dir.joinpath(bl_mod_dir)
177             if not mod_dir.exists():
178                 self.logger.info(
179                     'GRUB2 modules directory for arch "%s" did no exist. Skipping GRUB2 creation',
180                     image_format
181                 )
182                 continue
183             try:
184                 mkimage(
185                     image_format,
186                     self.bootloaders_dir.joinpath("grub", options["binary_name"]),
187                     self.modules + options.get("extra_modules", []),
188                 )
189             except subprocess.CalledProcessError:
190                 self.logger.info('grub2-mkimage failed for arch "%s"! Maybe you did forget to install the grub modules '
191                                  'for the architecture?', image_format)
192                 utils.log_exc()
193                 # don't create module symlinks if grub2-mkimage is unsuccessful
194                 continue
195             self.logger.info('Successfully built bootloader for arch "%s"!', image_format)
196 
197             # Create a symlink for GRUB 2 modules
198             # assumes a single GRUB can be used to boot all kinds of distros
199             # if this assumption turns out incorrect, individual "grub" subdirectories are needed
200             symlink(
201                 mod_dir,
202                 self.bootloaders_dir.joinpath("grub", bl_mod_dir),
203                 skip_existing=True
204             )
205 
206     def create_directories(self):
207         """
208         Create the required directories so that this succeeds. If existing, do nothing. This should create the tree for
209         all supported bootloaders, regardless of the capabilities to symlink/install/build them.
210         """
211         if not self.bootloaders_dir.exists():
212             raise FileNotFoundError("Main bootloader directory not found! Please create it yourself!")
213 
214         grub_dir = self.bootloaders_dir.joinpath("grub")
215         if not grub_dir.exists():
216             grub_dir.mkdir(mode=0o644)
217 
218 
219 # NOTE: move this to cobbler.utils?
220 # cobbler.utils.linkfile does a lot of things, it might be worth it to have a
221 # function just for symbolic links
222 def symlink(target: pathlib.Path, link: pathlib.Path, skip_existing: bool = False):
223     """Create a symlink LINK pointing to TARGET.
224 
225     :param target: File/directory that the link will point to. The file/directory must exist.
226     :param link: Filename for the link.
227     :param skip_existing: Controls if existing links are skipped, defaults to False.
228     :raises FileNotFoundError: ``target`` is not an existing file.
229     :raises FileExistsError: ``skip_existing`` is False and ``link`` already exists.
230     """
231 
232     if not target.exists():
233         raise FileNotFoundError(
234             f"{target} does not exist, can't create a symlink to it."
235         )
236     try:
237         link.symlink_to(target)
238     except FileExistsError:
239         if not skip_existing:
240             raise
241 
242 
243 def mkimage(image_format: str, image_filename: pathlib.Path, modules: typing.List):
244     """Create a bootable image of GRUB using grub2-mkimage.
245 
246     :param image_format: Format of the image that is being created. See man(1)
247         grub2-mkimage for a list of supported formats.
248     :param image_filename: Location of the image that is being created.
249     :param modules: List of GRUB modules to include into the image
250     :raises subprocess.CalledProcessError: Error raised by ``subprocess.run``.
251     """
252 
253     if not image_filename.parent.exists():
254         image_filename.parent.mkdir(parents=True)
255 
256     cmd = ["grub2-mkimage"]
257     cmd.extend(("--format", image_format))
258     cmd.extend(("--output", str(image_filename)))
259     cmd.append("--prefix=")
260     cmd.extend(modules)
261 
262     # The Exception raised by subprocess already contains everything useful, it's simpler to use that than roll our
263     # own custom exception together with cobbler.utils.subprocess_* functions
264     subprocess.run(cmd, check=True)
265 
266 
267 def get_syslinux_version() -> int:
268     """
269     This calls syslinux and asks for the version number.
270 
271     :return: The major syslinux release number.
272     :raises subprocess.CalledProcessError: Error raised by ``subprocess.run`` in case syslinux does not return zero.
273     """
274     # Example output: "syslinux 4.04  Copyright 1994-2011 H. Peter Anvin et al"
275     cmd = ["syslinux", "-v"]
276     completed_process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
277                                        encoding=sys.getdefaultencoding())
278     output = completed_process.stdout.split()
279     return int(float(output[1]))
280
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cobbler/actions/mkloaders.py b/cobbler/actions/mkloaders.py
--- a/cobbler/actions/mkloaders.py
+++ b/cobbler/actions/mkloaders.py
@@ -150,14 +150,14 @@
         libcom32_c32_path = self.syslinux_folder.joinpath("libcom32.c32")
         if syslinux_version > 4 and libcom32_c32_path.exists():
             symlink(
-                self.syslinux_pxelinux_folder.joinpath("linux.c32"),
+                self.syslinux_folder.joinpath("linux.c32"),
                 self.bootloaders_dir.joinpath("linux.c32"),
                 skip_existing=True,
             )
             # Make libcom32.c32
             # 'linux.c32' depends on 'libcom32.c32'
             symlink(
-                self.syslinux_pxelinux_folder.joinpath("libcom32.c32"),
+                self.syslinux_folder.joinpath("libcom32.c32"),
                 self.bootloaders_dir.joinpath("libcom32.c32"),
                 skip_existing=True,
             )

{"golden_diff": "diff --git a/cobbler/actions/mkloaders.py b/cobbler/actions/mkloaders.py\n--- a/cobbler/actions/mkloaders.py\n+++ b/cobbler/actions/mkloaders.py\n@@ -150,14 +150,14 @@\n         libcom32_c32_path = self.syslinux_folder.joinpath(\"libcom32.c32\")\n         if syslinux_version > 4 and libcom32_c32_path.exists():\n             symlink(\n-                self.syslinux_pxelinux_folder.joinpath(\"linux.c32\"),\n+                self.syslinux_folder.joinpath(\"linux.c32\"),\n                 self.bootloaders_dir.joinpath(\"linux.c32\"),\n                 skip_existing=True,\n             )\n             # Make libcom32.c32\n             # 'linux.c32' depends on 'libcom32.c32'\n             symlink(\n-                self.syslinux_pxelinux_folder.joinpath(\"libcom32.c32\"),\n+                self.syslinux_folder.joinpath(\"libcom32.c32\"),\n                 self.bootloaders_dir.joinpath(\"libcom32.c32\"),\n                 skip_existing=True,\n             )\n", "issue": "[Backport] /usr/lib/PXELINUX/linux.c32 does not exist, can't create a symlink to it\n### Original feature issue\r\n\r\n- Issue: #3574\r\n- PR: #3576 \r\n\r\n### Target release\r\n\r\n- [x] release33\r\n- [ ] release32\r\n- [ ] release30\r\n\r\n### Reason\r\n\r\nStabilization for Debian of Cobbler 3.3.x.\r\n\n", "before_files": [{"content": "\"\"\"Cobbler action to create bootable Grub2 images.\n\nThis action calls grub2-mkimage for all bootloader formats configured in\nCobbler's settings. See man(1) grub2-mkimage for available formats.\n\"\"\"\nimport logging\nimport pathlib\nimport re\nimport subprocess\nimport sys\nimport typing\n\nfrom cobbler import utils\n\n\n# NOTE: does not warrant being a class, but all Cobbler actions use a class's \".run()\" as the entrypoint\nclass MkLoaders:\n    \"\"\"\n    Action to create bootloader images.\n    \"\"\"\n\n    def __init__(self, api):\n        \"\"\"\n        MkLoaders constructor.\n\n        :param api: CobblerAPI instance for accessing settings\n        \"\"\"\n        self.logger = logging.getLogger()\n        self.bootloaders_dir = pathlib.Path(api.settings().bootloaders_dir)\n        # GRUB 2\n        self.grub2_mod_dir = pathlib.Path(api.settings().grub2_mod_dir)\n        self.boot_loaders_formats: typing.Dict = api.settings().bootloaders_formats\n        self.modules: typing.List = api.settings().bootloaders_modules\n        # Syslinux\n        self.syslinux_folder = pathlib.Path(api.settings().syslinux_dir)\n        self.syslinux_memdisk_folder = pathlib.Path(api.settings().syslinux_memdisk_folder)\n        self.syslinux_pxelinux_folder = pathlib.Path(api.settings().syslinux_pxelinux_folder)\n        # Shim\n        self.shim_glob = pathlib.Path(api.settings().bootloaders_shim_folder)\n        self.shim_regex = re.compile(api.settings().bootloaders_shim_file)\n        # iPXE\n        self.ipxe_folder = pathlib.Path(api.settings().bootloaders_ipxe_folder)\n\n    def run(self):\n        \"\"\"\n        Run GrubImages action. If the files or executables for the bootloader is not available we bail out and skip the\n        creation after it is logged that this is not available.\n        \"\"\"\n        self.create_directories()\n\n        self.make_shim()\n        self.make_ipxe()\n        self.make_syslinux()\n        self.make_grub()\n\n    def make_shim(self):\n        \"\"\"\n        Create symlink of the shim bootloader in case it is available on the system.\n        \"\"\"\n        # Check well-known locations\n        # Absolute paths are not supported BUT we can get around that: https://stackoverflow.com/a/51108375/4730773\n        parts = self.shim_glob.parts\n        start_at = 1 if self.shim_glob.is_absolute() else 0\n        bootloader_path_parts = pathlib.Path(*parts[start_at:])\n        results = sorted(pathlib.Path(self.shim_glob.root).glob(str(bootloader_path_parts)))\n        # If no match, then report and bail out.\n        if len(results) <= 0:\n            self.logger.info('Unable to find the folder which should be scanned for \"shim.efi\"! Bailing out of linking '\n                             'the shim!')\n            return\n        # Now scan the folders with the regex\n        target_shim = None\n        for possible_folder in results:\n            for child in possible_folder.iterdir():\n                if self.shim_regex.search(str(child)):\n                    target_shim = child.resolve()\n                    break\n        # If no match is found report and return\n        if target_shim is None:\n            self.logger.info('Unable to find \"shim.efi\" file. Please adjust \"bootloaders_shim_file\" regex. Bailing out '\n                             'of linking the shim!')\n            return\n        # Symlink the absolute target of the match\n        symlink(\n            target_shim,\n            self.bootloaders_dir.joinpath(pathlib.Path(\"grub/shim.efi\")),\n            skip_existing=True\n        )\n\n    def make_ipxe(self):\n        \"\"\"\n        Create symlink of the iPXE bootloader in case it is available on the system.\n        \"\"\"\n        if not self.ipxe_folder.exists():\n            self.logger.info('ipxe directory did not exist. Please adjust the \"bootloaders_ipxe_folder\". Bailing out '\n                             'of iPXE setup!')\n            return\n        symlink(\n            self.ipxe_folder.joinpath(\"undionly.kpxe\"),\n            self.bootloaders_dir.joinpath(pathlib.Path(\"undionly.pxe\")),\n            skip_existing=True\n        )\n\n    def make_syslinux(self):\n        \"\"\"\n        Create symlink of the important syslinux bootloader files in case they are available on the system.\n        \"\"\"\n        if not utils.command_existing(\"syslinux\"):\n            self.logger.info(\"syslinux command not available. Bailing out of syslinux setup!\")\n            return\n        syslinux_version = get_syslinux_version()\n        # Make modules\n        symlink(\n            self.syslinux_folder.joinpath(\"menu.c32\"),\n            self.bootloaders_dir.joinpath(\"menu.c32\"),\n            skip_existing=True\n        )\n        # According to https://wiki.syslinux.org/wiki/index.php?title=Library_modules,\n        # 'menu.c32' depends on 'libutil.c32'.\n        libutil_c32_path = self.syslinux_folder.joinpath(\"libutil.c32\")\n        if syslinux_version > 4 and libutil_c32_path.exists():\n            symlink(\n                libutil_c32_path,\n                self.bootloaders_dir.joinpath(\"libutil.c32\"),\n                skip_existing=True,\n            )\n        if syslinux_version < 5:\n            # This file is only required for Syslinux 5 and newer.\n            # Source: https://wiki.syslinux.org/wiki/index.php?title=Library_modules\n            self.logger.info('syslinux version 4 detected! Skip making symlink of \"ldlinux.c32\" file!')\n        else:\n            symlink(\n                self.syslinux_folder.joinpath(\"ldlinux.c32\"),\n                self.bootloaders_dir.joinpath(\"ldlinux.c32\"),\n                skip_existing=True\n            )\n        # Make memdisk\n        symlink(\n            self.syslinux_memdisk_folder.joinpath(\"memdisk\"),\n            self.bootloaders_dir.joinpath(\"memdisk\"),\n            skip_existing=True\n        )\n        # Make pxelinux.0\n        symlink(\n            self.syslinux_pxelinux_folder.joinpath(\"pxelinux.0\"),\n            self.bootloaders_dir.joinpath(\"pxelinux.0\"),\n            skip_existing=True\n        )\n        # Make linux.c32 for syslinux + wimboot\n        libcom32_c32_path = self.syslinux_folder.joinpath(\"libcom32.c32\")\n        if syslinux_version > 4 and libcom32_c32_path.exists():\n            symlink(\n                self.syslinux_pxelinux_folder.joinpath(\"linux.c32\"),\n                self.bootloaders_dir.joinpath(\"linux.c32\"),\n                skip_existing=True,\n            )\n            # Make libcom32.c32\n            # 'linux.c32' depends on 'libcom32.c32'\n            symlink(\n                self.syslinux_pxelinux_folder.joinpath(\"libcom32.c32\"),\n                self.bootloaders_dir.joinpath(\"libcom32.c32\"),\n                skip_existing=True,\n            )\n\n    def make_grub(self):\n        \"\"\"\n        Create symlink of the GRUB 2 bootloader in case it is available on the system. Additionally build the loaders\n        for other architectures if the modules to do so are available.\n        \"\"\"\n        if not utils.command_existing(\"grub2-mkimage\"):\n            self.logger.info(\"grub2-mkimage command not available. Bailing out of GRUB2 generation!\")\n            return\n\n        for image_format, options in self.boot_loaders_formats.items():\n            bl_mod_dir = options.get(\"mod_dir\", image_format)\n            mod_dir = self.grub2_mod_dir.joinpath(bl_mod_dir)\n            if not mod_dir.exists():\n                self.logger.info(\n                    'GRUB2 modules directory for arch \"%s\" did no exist. Skipping GRUB2 creation',\n                    image_format\n                )\n                continue\n            try:\n                mkimage(\n                    image_format,\n                    self.bootloaders_dir.joinpath(\"grub\", options[\"binary_name\"]),\n                    self.modules + options.get(\"extra_modules\", []),\n                )\n            except subprocess.CalledProcessError:\n                self.logger.info('grub2-mkimage failed for arch \"%s\"! Maybe you did forget to install the grub modules '\n                                 'for the architecture?', image_format)\n                utils.log_exc()\n                # don't create module symlinks if grub2-mkimage is unsuccessful\n                continue\n            self.logger.info('Successfully built bootloader for arch \"%s\"!', image_format)\n\n            # Create a symlink for GRUB 2 modules\n            # assumes a single GRUB can be used to boot all kinds of distros\n            # if this assumption turns out incorrect, individual \"grub\" subdirectories are needed\n            symlink(\n                mod_dir,\n                self.bootloaders_dir.joinpath(\"grub\", bl_mod_dir),\n                skip_existing=True\n            )\n\n    def create_directories(self):\n        \"\"\"\n        Create the required directories so that this succeeds. If existing, do nothing. This should create the tree for\n        all supported bootloaders, regardless of the capabilities to symlink/install/build them.\n        \"\"\"\n        if not self.bootloaders_dir.exists():\n            raise FileNotFoundError(\"Main bootloader directory not found! Please create it yourself!\")\n\n        grub_dir = self.bootloaders_dir.joinpath(\"grub\")\n        if not grub_dir.exists():\n            grub_dir.mkdir(mode=0o644)\n\n\n# NOTE: move this to cobbler.utils?\n# cobbler.utils.linkfile does a lot of things, it might be worth it to have a\n# function just for symbolic links\ndef symlink(target: pathlib.Path, link: pathlib.Path, skip_existing: bool = False):\n    \"\"\"Create a symlink LINK pointing to TARGET.\n\n    :param target: File/directory that the link will point to. The file/directory must exist.\n    :param link: Filename for the link.\n    :param skip_existing: Controls if existing links are skipped, defaults to False.\n    :raises FileNotFoundError: ``target`` is not an existing file.\n    :raises FileExistsError: ``skip_existing`` is False and ``link`` already exists.\n    \"\"\"\n\n    if not target.exists():\n        raise FileNotFoundError(\n            f\"{target} does not exist, can't create a symlink to it.\"\n        )\n    try:\n        link.symlink_to(target)\n    except FileExistsError:\n        if not skip_existing:\n            raise\n\n\ndef mkimage(image_format: str, image_filename: pathlib.Path, modules: typing.List):\n    \"\"\"Create a bootable image of GRUB using grub2-mkimage.\n\n    :param image_format: Format of the image that is being created. See man(1)\n        grub2-mkimage for a list of supported formats.\n    :param image_filename: Location of the image that is being created.\n    :param modules: List of GRUB modules to include into the image\n    :raises subprocess.CalledProcessError: Error raised by ``subprocess.run``.\n    \"\"\"\n\n    if not image_filename.parent.exists():\n        image_filename.parent.mkdir(parents=True)\n\n    cmd = [\"grub2-mkimage\"]\n    cmd.extend((\"--format\", image_format))\n    cmd.extend((\"--output\", str(image_filename)))\n    cmd.append(\"--prefix=\")\n    cmd.extend(modules)\n\n    # The Exception raised by subprocess already contains everything useful, it's simpler to use that than roll our\n    # own custom exception together with cobbler.utils.subprocess_* functions\n    subprocess.run(cmd, check=True)\n\n\ndef get_syslinux_version() -> int:\n    \"\"\"\n    This calls syslinux and asks for the version number.\n\n    :return: The major syslinux release number.\n    :raises subprocess.CalledProcessError: Error raised by ``subprocess.run`` in case syslinux does not return zero.\n    \"\"\"\n    # Example output: \"syslinux 4.04  Copyright 1994-2011 H. Peter Anvin et al\"\n    cmd = [\"syslinux\", \"-v\"]\n    completed_process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n                                       encoding=sys.getdefaultencoding())\n    output = completed_process.stdout.split()\n    return int(float(output[1]))\n", "path": "cobbler/actions/mkloaders.py"}], "after_files": [{"content": "\"\"\"Cobbler action to create bootable Grub2 images.\n\nThis action calls grub2-mkimage for all bootloader formats configured in\nCobbler's settings. See man(1) grub2-mkimage for available formats.\n\"\"\"\nimport logging\nimport pathlib\nimport re\nimport subprocess\nimport sys\nimport typing\n\nfrom cobbler import utils\n\n\n# NOTE: does not warrant being a class, but all Cobbler actions use a class's \".run()\" as the entrypoint\nclass MkLoaders:\n    \"\"\"\n    Action to create bootloader images.\n    \"\"\"\n\n    def __init__(self, api):\n        \"\"\"\n        MkLoaders constructor.\n\n        :param api: CobblerAPI instance for accessing settings\n        \"\"\"\n        self.logger = logging.getLogger()\n        self.bootloaders_dir = pathlib.Path(api.settings().bootloaders_dir)\n        # GRUB 2\n        self.grub2_mod_dir = pathlib.Path(api.settings().grub2_mod_dir)\n        self.boot_loaders_formats: typing.Dict = api.settings().bootloaders_formats\n        self.modules: typing.List = api.settings().bootloaders_modules\n        # Syslinux\n        self.syslinux_folder = pathlib.Path(api.settings().syslinux_dir)\n        self.syslinux_memdisk_folder = pathlib.Path(api.settings().syslinux_memdisk_folder)\n        self.syslinux_pxelinux_folder = pathlib.Path(api.settings().syslinux_pxelinux_folder)\n        # Shim\n        self.shim_glob = pathlib.Path(api.settings().bootloaders_shim_folder)\n        self.shim_regex = re.compile(api.settings().bootloaders_shim_file)\n        # iPXE\n        self.ipxe_folder = pathlib.Path(api.settings().bootloaders_ipxe_folder)\n\n    def run(self):\n        \"\"\"\n        Run GrubImages action. If the files or executables for the bootloader is not available we bail out and skip the\n        creation after it is logged that this is not available.\n        \"\"\"\n        self.create_directories()\n\n        self.make_shim()\n        self.make_ipxe()\n        self.make_syslinux()\n        self.make_grub()\n\n    def make_shim(self):\n        \"\"\"\n        Create symlink of the shim bootloader in case it is available on the system.\n        \"\"\"\n        # Check well-known locations\n        # Absolute paths are not supported BUT we can get around that: https://stackoverflow.com/a/51108375/4730773\n        parts = self.shim_glob.parts\n        start_at = 1 if self.shim_glob.is_absolute() else 0\n        bootloader_path_parts = pathlib.Path(*parts[start_at:])\n        results = sorted(pathlib.Path(self.shim_glob.root).glob(str(bootloader_path_parts)))\n        # If no match, then report and bail out.\n        if len(results) <= 0:\n            self.logger.info('Unable to find the folder which should be scanned for \"shim.efi\"! Bailing out of linking '\n                             'the shim!')\n            return\n        # Now scan the folders with the regex\n        target_shim = None\n        for possible_folder in results:\n            for child in possible_folder.iterdir():\n                if self.shim_regex.search(str(child)):\n                    target_shim = child.resolve()\n                    break\n        # If no match is found report and return\n        if target_shim is None:\n            self.logger.info('Unable to find \"shim.efi\" file. Please adjust \"bootloaders_shim_file\" regex. Bailing out '\n                             'of linking the shim!')\n            return\n        # Symlink the absolute target of the match\n        symlink(\n            target_shim,\n            self.bootloaders_dir.joinpath(pathlib.Path(\"grub/shim.efi\")),\n            skip_existing=True\n        )\n\n    def make_ipxe(self):\n        \"\"\"\n        Create symlink of the iPXE bootloader in case it is available on the system.\n        \"\"\"\n        if not self.ipxe_folder.exists():\n            self.logger.info('ipxe directory did not exist. Please adjust the \"bootloaders_ipxe_folder\". Bailing out '\n                             'of iPXE setup!')\n            return\n        symlink(\n            self.ipxe_folder.joinpath(\"undionly.kpxe\"),\n            self.bootloaders_dir.joinpath(pathlib.Path(\"undionly.pxe\")),\n            skip_existing=True\n        )\n\n    def make_syslinux(self):\n        \"\"\"\n        Create symlink of the important syslinux bootloader files in case they are available on the system.\n        \"\"\"\n        if not utils.command_existing(\"syslinux\"):\n            self.logger.info(\"syslinux command not available. Bailing out of syslinux setup!\")\n            return\n        syslinux_version = get_syslinux_version()\n        # Make modules\n        symlink(\n            self.syslinux_folder.joinpath(\"menu.c32\"),\n            self.bootloaders_dir.joinpath(\"menu.c32\"),\n            skip_existing=True\n        )\n        # According to https://wiki.syslinux.org/wiki/index.php?title=Library_modules,\n        # 'menu.c32' depends on 'libutil.c32'.\n        libutil_c32_path = self.syslinux_folder.joinpath(\"libutil.c32\")\n        if syslinux_version > 4 and libutil_c32_path.exists():\n            symlink(\n                libutil_c32_path,\n                self.bootloaders_dir.joinpath(\"libutil.c32\"),\n                skip_existing=True,\n            )\n        if syslinux_version < 5:\n            # This file is only required for Syslinux 5 and newer.\n            # Source: https://wiki.syslinux.org/wiki/index.php?title=Library_modules\n            self.logger.info('syslinux version 4 detected! Skip making symlink of \"ldlinux.c32\" file!')\n        else:\n            symlink(\n                self.syslinux_folder.joinpath(\"ldlinux.c32\"),\n                self.bootloaders_dir.joinpath(\"ldlinux.c32\"),\n                skip_existing=True\n            )\n        # Make memdisk\n        symlink(\n            self.syslinux_memdisk_folder.joinpath(\"memdisk\"),\n            self.bootloaders_dir.joinpath(\"memdisk\"),\n            skip_existing=True\n        )\n        # Make pxelinux.0\n        symlink(\n            self.syslinux_pxelinux_folder.joinpath(\"pxelinux.0\"),\n            self.bootloaders_dir.joinpath(\"pxelinux.0\"),\n            skip_existing=True\n        )\n        # Make linux.c32 for syslinux + wimboot\n        libcom32_c32_path = self.syslinux_folder.joinpath(\"libcom32.c32\")\n        if syslinux_version > 4 and libcom32_c32_path.exists():\n            symlink(\n                self.syslinux_folder.joinpath(\"linux.c32\"),\n                self.bootloaders_dir.joinpath(\"linux.c32\"),\n                skip_existing=True,\n            )\n            # Make libcom32.c32\n            # 'linux.c32' depends on 'libcom32.c32'\n            symlink(\n                self.syslinux_folder.joinpath(\"libcom32.c32\"),\n                self.bootloaders_dir.joinpath(\"libcom32.c32\"),\n                skip_existing=True,\n            )\n\n    def make_grub(self):\n        \"\"\"\n        Create symlink of the GRUB 2 bootloader in case it is available on the system. Additionally build the loaders\n        for other architectures if the modules to do so are available.\n        \"\"\"\n        if not utils.command_existing(\"grub2-mkimage\"):\n            self.logger.info(\"grub2-mkimage command not available. Bailing out of GRUB2 generation!\")\n            return\n\n        for image_format, options in self.boot_loaders_formats.items():\n            bl_mod_dir = options.get(\"mod_dir\", image_format)\n            mod_dir = self.grub2_mod_dir.joinpath(bl_mod_dir)\n            if not mod_dir.exists():\n                self.logger.info(\n                    'GRUB2 modules directory for arch \"%s\" did no exist. Skipping GRUB2 creation',\n                    image_format\n                )\n                continue\n            try:\n                mkimage(\n                    image_format,\n                    self.bootloaders_dir.joinpath(\"grub\", options[\"binary_name\"]),\n                    self.modules + options.get(\"extra_modules\", []),\n                )\n            except subprocess.CalledProcessError:\n                self.logger.info('grub2-mkimage failed for arch \"%s\"! Maybe you did forget to install the grub modules '\n                                 'for the architecture?', image_format)\n                utils.log_exc()\n                # don't create module symlinks if grub2-mkimage is unsuccessful\n                continue\n            self.logger.info('Successfully built bootloader for arch \"%s\"!', image_format)\n\n            # Create a symlink for GRUB 2 modules\n            # assumes a single GRUB can be used to boot all kinds of distros\n            # if this assumption turns out incorrect, individual \"grub\" subdirectories are needed\n            symlink(\n                mod_dir,\n                self.bootloaders_dir.joinpath(\"grub\", bl_mod_dir),\n                skip_existing=True\n            )\n\n    def create_directories(self):\n        \"\"\"\n        Create the required directories so that this succeeds. If existing, do nothing. This should create the tree for\n        all supported bootloaders, regardless of the capabilities to symlink/install/build them.\n        \"\"\"\n        if not self.bootloaders_dir.exists():\n            raise FileNotFoundError(\"Main bootloader directory not found! Please create it yourself!\")\n\n        grub_dir = self.bootloaders_dir.joinpath(\"grub\")\n        if not grub_dir.exists():\n            grub_dir.mkdir(mode=0o644)\n\n\n# NOTE: move this to cobbler.utils?\n# cobbler.utils.linkfile does a lot of things, it might be worth it to have a\n# function just for symbolic links\ndef symlink(target: pathlib.Path, link: pathlib.Path, skip_existing: bool = False):\n    \"\"\"Create a symlink LINK pointing to TARGET.\n\n    :param target: File/directory that the link will point to. The file/directory must exist.\n    :param link: Filename for the link.\n    :param skip_existing: Controls if existing links are skipped, defaults to False.\n    :raises FileNotFoundError: ``target`` is not an existing file.\n    :raises FileExistsError: ``skip_existing`` is False and ``link`` already exists.\n    \"\"\"\n\n    if not target.exists():\n        raise FileNotFoundError(\n            f\"{target} does not exist, can't create a symlink to it.\"\n        )\n    try:\n        link.symlink_to(target)\n    except FileExistsError:\n        if not skip_existing:\n            raise\n\n\ndef mkimage(image_format: str, image_filename: pathlib.Path, modules: typing.List):\n    \"\"\"Create a bootable image of GRUB using grub2-mkimage.\n\n    :param image_format: Format of the image that is being created. See man(1)\n        grub2-mkimage for a list of supported formats.\n    :param image_filename: Location of the image that is being created.\n    :param modules: List of GRUB modules to include into the image\n    :raises subprocess.CalledProcessError: Error raised by ``subprocess.run``.\n    \"\"\"\n\n    if not image_filename.parent.exists():\n        image_filename.parent.mkdir(parents=True)\n\n    cmd = [\"grub2-mkimage\"]\n    cmd.extend((\"--format\", image_format))\n    cmd.extend((\"--output\", str(image_filename)))\n    cmd.append(\"--prefix=\")\n    cmd.extend(modules)\n\n    # The Exception raised by subprocess already contains everything useful, it's simpler to use that than roll our\n    # own custom exception together with cobbler.utils.subprocess_* functions\n    subprocess.run(cmd, check=True)\n\n\ndef get_syslinux_version() -> int:\n    \"\"\"\n    This calls syslinux and asks for the version number.\n\n    :return: The major syslinux release number.\n    :raises subprocess.CalledProcessError: Error raised by ``subprocess.run`` in case syslinux does not return zero.\n    \"\"\"\n    # Example output: \"syslinux 4.04  Copyright 1994-2011 H. Peter Anvin et al\"\n    cmd = [\"syslinux\", \"-v\"]\n    completed_process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n                                       encoding=sys.getdefaultencoding())\n    output = completed_process.stdout.split()\n    return int(float(output[1]))\n", "path": "cobbler/actions/mkloaders.py"}]}