Skip to content

Bug: 'unload' command - variable 'st' used where it has no value (get_sg_node can't handled symlinked tape devices) #1

Description

@philpem

I noticed this in my Bacula logs:

30-Oct 11:23 syrys-sd JobId 202: End of medium on Volume "BAK101L6" Bytes=2,903,978,804,224 Blocks=1,384,725 at 30-Oct-2025 11:23.
30-Oct 11:23 syrys-sd JobId 202: 3307 Issuing autochanger "unload Volume BAK101L6, Slot 2, Drive 0" command.
30-Oct 11:25 syrys-sd JobId 202: 3995 Bad autochanger "unload Volume BAK101L6, Slot 2, Drive 0": ERR=Child exited with code 1
Results=Traceback (most recent call last):
  File "/etc/bacula/scripts/mtx-changer-python/mtx-changer-python.py", line 1095, in <module>
    result = unload()
             ^^^^^^^^
  File "/etc/bacula/scripts/mtx-changer-python/mtx-changer-python.py", line 898, in unload
    if checkdrive() == 1:
       ^^^^^^^^^^^^
  File "/etc/bacula/scripts/mtx-changer-python/mtx-changer-python.py", line 717, in checkdrive
    sg = get_sg_node()
         ^^^^^^^^^^^^^
  File "/etc/bacula/scripts/mtx-changer-python/mtx-changer-python.py", line 603, in get_sg_node
    sg_search = re.search('.*' + st + ' .*(/dev/sg\\d+)', result.stdout)
                                 ^^
UnboundLocalError: cannot access local variable 'st' where it is not associated with a value

30-Oct 11:25 syrys-dir JobId 202: Recycled volume "BAK103L6"
30-Oct 11:25 syrys-sd JobId 202: 3304 Issuing autochanger "load Volume BAK103L6, Slot 4, Drive 0" command.

From the mtx-changer-python log, it seems like this is related to finding the SG node for the tape drive:

2025-10-30 03:39:04 | ----------[ Starting MTX-Changer-Python v1.32 ]----------
2025-10-30 03:39:04 | Config File: /etc/bacula/scripts/mtx-changer-python/mtx-changer-python.conf
2025-10-30 03:39:04
2025-10-30 11:23:47 | Slot: 2
2025-10-30 11:23:47 | ----------------------

2025-10-30 11:23:49 | ----------[ Starting MTX-Changer-Python v1.32 ]----------
2025-10-30 11:23:49 | Config File: /etc/bacula/scripts/mtx-changer-python/mtx-changer-python.conf
2025-10-30 11:23:49 | Config Section: [DEFAULT]
2025-10-30 11:23:49 | Changer Device: /dev/changer
2025-10-30 11:23:49 | Drive Device: /dev/tape_nst
2025-10-30 11:23:49 | Command: unload
2025-10-30 11:23:49 | Drive Index: 0
2025-10-30 11:23:49 | Slot: 2
2025-10-30 11:23:49 | ----------------------
2025-10-30 11:23:51 - Checking if drive device /dev/tape_nst (drive index: 0) is loaded
2025-10-30 11:23:53 - Drive device /dev/tape_nst (drive index: 0) is loaded with volume (BAK101L6) from slot 2
2025-10-30 11:23:53 - Unloading volume (BAK101L6) from drive device /dev/tape_nst (drive index: 0) to slot 2
2025-10-30 11:25:19 - Successfully unloaded volume (BAK101L6) from drive device /dev/tape_nst (drive index: 0) to slot 2
2025-10-30 11:25:19 - The chk_drive variable is True, calling checkdrive() function
2025-10-30 11:25:19 - Found the following cleaning tapes: [('16', 'CLN001L4')]
2025-10-30 11:25:19 - Determining the tape drive's scsi generic device node required by sg_logs

It doesn't seem to stop the backup from running, but it does seem to interrupt the process of loading a cleaning tape if one is needed.

The cause of the failed assignment seems to be that the "if" statement from 588..598 has no "else" case for detecting a failure to identify the drive's SG device:

if '/dev/st' in drive_device or '/dev/nst' in drive_device:
# OK, we caught the simple /dev/st# or /dev/nst# case
# ---------------------------------------------------
st = drive_device
elif '/by-id' in drive_device or '/by-path' in drive_device:
# OK, we caught the /dev/tape/by-id or /dev/tape/by-path case
# -----------------------------------------------------------
# The ls command outputs a line feed that needs to be stripped
# ------------------------------------------------------------
st = '/dev/' + re.sub(r'.* -> .*/n*(st\d+).*$', '\\1', result.stdout.rstrip('\n'), re.S)
cmd = lsscsi_bin + ' -g'

However the root cause is that I've symlinked the drive and use the symlinks in my Bacula configuration:

/dev/changer -> /dev/sg2
/dev/tape_nst -> /dev/nst0
/dev/tape_sg -> /dev/sg1

The if statement ideally needs an else case to detect an unknown type of device node and fail appropriately, but adding support for symlinked device nodes only requires a call to os.path.realpath:

        # -----------------------------------------------------------                                                                                                                                                          
        # TODO: waa - 20240302 - These lines before the if statement                                                                                                                                                           
        # are not necessary. Probably are here for logging mainly                                                                                                                                                              
        # -----------------------------------------------------------                                                                                                                                                          
        real_drive_device = os.path.realpath(drive_device)
        log('Resolved drive device to ' + real_drive_device, 20)
        cmd = ls_bin + ' -l ' + real_drive_device
        log('ls command: ' + cmd, 30)
        result = get_shell_result(cmd)
               log_cmd_results(result)
        chk_cmd_result(result, cmd)
        if '/dev/st' in real_drive_device or '/dev/nst' in drive_device:                                                                                                                                                                                

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions