def add_symlink(self, symlink_path, rr_symlink_name=None, rr_path=None,
                    joliet_path=None, udf_symlink_path=None, udf_target=None):
        # type: (str, Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> None
        '''
        Add a symlink from rr_symlink_name to the rr_path.  The ISO must have
        either Rock Ridge or UDF support (or both).

        Parameters:
         symlink_path - The ISO9660 name of the symlink itself on the ISO.
         rr_symlink_name - The Rock Ridge name of the symlink itself on the ISO.
         rr_path - The Rock Ridge name of the entry on the ISO that the symlink
                   points to.
         joliet_path - The Joliet name of the symlink (if this ISO has Joliet).
         udf_symlink_path - The UDF path of the symlink itself on the ISO.
         udf_target - The UDF name of the entry on the ISO that the symlink
                      points to.
        Returns:
         Nothing.
        '''
        if not self._initialized:
            raise pycdlibexception.PyCdlibInvalidInput('This object is not yet initialized; call either open() or new() to create an ISO')

        # There are actually quite a few combinations and rules to think about
        # here.  Rules:
        #
        # 1.  All symlinks must have an ISO9660 name.
        # 2.  If the ISO is Rock Ridge, it must have a Rock Ridge name for the
        #     ISO9660 Directory Record (rr_symlink_name).
        # 3.  Conversely, rr_symlink_name must not be provided for a
        #     non-Rock Ridge ISO.
        # 4.  rr_path is the optional target for the symlink; if it is provided,
        #     then the ISO must be a Rock Ridge one.
        # 5.  udf_symlink_path is the optional UDF name for the symlink; if it
        #     is provided, then this must be a UDF ISO and udf_target must also
        #     be provided.
        # 6.  Conversely, if this is a non-UDF ISO, udf_symlink_path must not
        #     be provided.
        # 7.  udf_target is the optional UDF target for the symlink; if it is
        #     provided, then this must be a UDF ISO and udf_symlink_path must
        #     also be provided.
        # 8.  Conversely, if this is a non-UDF ISO, udf_target must not be
        #     provided.
        # 9.  joliet_path is the optional path on the Joliet filesystem; if it
        #     is provided, the ISO must be a Joliet one.
        # 10. Conversely, if this is a non-Joliet ISO, joliet_path must not be
        #     provided.
        # 11. At least one of rr_path and the pair of
        #     udf_symlink_path, udf_target must be provided.

        if self.rock_ridge:
            # Rule 2
            if rr_symlink_name is None:
                raise pycdlibexception.PyCdlibInvalidInput('A Rock Ridge name must be passed for a Rock Ridge ISO')
        else:
            # Rule 3
            if rr_symlink_name is not None:
                raise pycdlibexception.PyCdlibInvalidInput('A Rock Ridge name can only be passed for a Rock Ridge ISO')

        if rr_path is not None:
            # Rule 4
            if not self.rock_ridge:
                raise pycdlibexception.PyCdlibInvalidInput('Can only add a symlink to a Rock Ridge or UDF ISO')

        if udf_symlink_path is not None:
            # Rule 5/6/7/8
            if not self._has_udf:
                raise pycdlibexception.PyCdlibInvalidInput('Can only add a UDF symlink to a UDF ISO')
            if udf_target is None:
                raise pycdlibexception.PyCdlibInvalidInput('A udf_target must be supplied along with a udf_symlink_path')

        if joliet_path is not None:
            # Rule 9/10
            if self.joliet_vd is None:
                raise pycdlibexception.PyCdlibInvalidInput('A Joliet path can only be specified for a Joliet ISO')

        if rr_path is None and udf_symlink_path is None:
            # Rule 11
            raise pycdlibexception.PyCdlibInvalidInput('At least one of a Rock Ridge or a UDF target must be specified')

        symlink_path_bytes = utils.normpath(symlink_path)
        (name, parent) = self._iso_name_and_parent_from_path(symlink_path_bytes)

        log_block_size = self.pvd.logical_block_size()

        # The ISO9660 directory record; this will be added in all cases.
        rec = dr.DirectoryRecord()

        num_bytes_to_add = 0
        if rr_path is not None and rr_symlink_name is not None:
            # We specifically do *not* normalize rr_path here, since that
            # potentially changes the meaning of what the user wanted.

            rr_symlink_name_bytes = rr_symlink_name.encode('utf-8')
            rec.new_symlink(self.pvd, name, parent, rr_path.encode('utf-8'),
                            self.pvd.sequence_number(), self.rock_ridge,
                            rr_symlink_name_bytes, self.xa)
            num_bytes_to_add += self._add_child_to_dr(rec, log_block_size)

            num_bytes_to_add += self._update_rr_ce_entry(rec)

        if udf_symlink_path is not None and udf_target is not None:
            # If we aren't making a Rock Ridge symlink at the same time, we need
            # to add a new zero-byte file to the ISO.
            if rr_path is None:
                rrname = name.decode('utf-8')
                if not self.rock_ridge:
                    rrname = ''
                tmp_joliet_path = joliet_path
                if tmp_joliet_path is None:
                    tmp_joliet_path = ''
                num_bytes_to_add += self._add_fp(None, 0, False,
                                                 symlink_path, rrname,
                                                 tmp_joliet_path, '', None, False)

            udf_symlink_path_bytes = utils.normpath(udf_symlink_path)

            # We specifically do *not* normalize udf_target here, since that
            # potentially changes the meaning of what the user wanted.

            (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_symlink_path_bytes)
            file_ident = udfmod.UDFFileIdentifierDescriptor()
            file_ident.new(False, False, udf_name, udf_parent)
            num_new_extents = udf_parent.add_file_ident_desc(file_ident, log_block_size)
            num_bytes_to_add += num_new_extents * log_block_size

            # Generate the bytearry representing the symlink
            symlink_bytearray = udfmod.symlink_to_bytes(udf_target)

            # The UDF File Entry
            file_entry = udfmod.UDFFileEntry()
            file_entry.new(len(symlink_bytearray), 'symlink', udf_parent,
                           log_block_size)
            file_ident.file_entry = file_entry
            file_entry.file_ident = file_ident
            num_bytes_to_add += log_block_size
            num_bytes_to_add += file_entry.info_len

            # The inode for the symlink array.
            ino = inode.Inode()
            ino.new(len(symlink_bytearray), BytesIO(symlink_bytearray), False, 0)
            ino.linked_records.append(file_entry)
            ino.num_udf += 1
            file_entry.inode = ino
            self.inodes.append(ino)

            self.udf_logical_volume_integrity.logical_volume_impl_use.num_files += 1

            # Note that we explicitly do *not* link this record to the ISO9660
            # record; that's because there is no way to correlate them during
            # parse time.  Instead, we treat them as individual entries, which
            # has the knock-on effect of requiring two operations to remove;
            # rm_file() to remove the ISO9660 record, and rm_hard_link() to
            # remove the UDF record.

        if joliet_path is not None:
            if self.joliet_vd is None:
                raise pycdlibexception.PyCdlibInternalError('Tried to add a Joliet path to a non-Joliet ISO')
            joliet_path_bytes = self._normalize_joliet_path(joliet_path)
            (joliet_name, joliet_parent) = self._joliet_name_and_parent_from_path(joliet_path_bytes)

            # Add in a "fake" symlink entry for Joliet.
            joliet_rec = dr.DirectoryRecord()
            joliet_rec.new_file(self.joliet_vd, 0, joliet_name, joliet_parent,
                                self.joliet_vd.sequence_number(), '', b'',
                                self.xa, -1)
            num_bytes_to_add += self._add_child_to_dr(joliet_rec,
                                                      self.joliet_vd.logical_block_size())

        self._finish_add(0, num_bytes_to_add)