def add_directory(self, iso_path=None, rr_name=None, joliet_path=None,
                      file_mode=None, udf_path=None):
        # type: (Optional[str], Optional[str], Optional[str], int, Optional[str]) -> None
        '''
        Add a directory to the ISO.  At least one of an iso_path, joliet_path,
        or udf_path must be provided.  Providing joliet_path on a non-Joliet
        ISO, or udf_path on a non-UDF ISO, is an error.  If the ISO contains
        Rock Ridge, then a Rock Ridge name must be provided.

        Parameters:
         iso_path - The ISO9660 absolute path to use for the directory.
         rr_name - The Rock Ridge name to use for the directory.
         joliet_path - The Joliet absolute path to use for the directory.
         file_mode - The POSIX file mode to use for the directory.  This only
                     applies for Rock Ridge ISOs.
         udf_path - The UDF absolute path to use for the directory.
        Returns:
         Nothing.
        '''
        if not self._initialized:
            raise pycdlibexception.PyCdlibInvalidInput('This object is not yet initialized; call either open() or new() to create an ISO')

        if iso_path is None and joliet_path is None and udf_path is None:
            raise pycdlibexception.PyCdlibInvalidInput('Either iso_path or joliet_path must be passed')

        if file_mode is not None and not self.rock_ridge:
            raise pycdlibexception.PyCdlibInvalidInput('A file mode can only be specified for Rock Ridge ISOs')

        # For backwards-compatibility reasons, if the mode was not specified we
        # just assume 555.  We should probably eventually make file_mode
        # required for Rock Ridge and remove this assumption.
        if file_mode is None:
            file_mode = 0o040555

        num_bytes_to_add = 0
        if iso_path is not None:
            iso_path_bytes = utils.normpath(iso_path)

            new_rr_name = self._check_rr_name(rr_name)

            depth = len(utils.split_path(iso_path_bytes))

            if not self.rock_ridge and self.enhanced_vd is None:
                _check_path_depth(iso_path_bytes)
            (name, parent) = self._iso_name_and_parent_from_path(iso_path_bytes)

            _check_iso9660_directory(name, self.interchange_level)

            relocated = False
            fake_dir_rec = None
            orig_parent = None
            iso9660_name = name
            if self.rock_ridge and (depth % 8) == 0 and self.enhanced_vd is None:
                # If the depth was a multiple of 8, then we are going to have to
                # make a relocated entry for this record.

                num_bytes_to_add += self._find_or_create_rr_moved()

                # With a depth of 8, we have to add the directory both to the
                # original parent with a CL link, and to the new parent with an
                # RE link.  Here we make the 'fake' record, as a child of the
                # original place; the real one will be done below.
                fake_dir_rec = dr.DirectoryRecord()
                fake_dir_rec.new_dir(self.pvd, name, parent,
                                     self.pvd.sequence_number(),
                                     self.rock_ridge, new_rr_name,
                                     self.pvd.logical_block_size(), True, False,
                                     self.xa, file_mode)
                num_bytes_to_add += self._add_child_to_dr(fake_dir_rec,
                                                          self.pvd.logical_block_size())

                # The fake dir record doesn't get an entry in the path table record.

                relocated = True
                orig_parent = parent
                parent = self._rr_moved_record

                # Since we are moving the entry underneath the RR_MOVED
                # directory, there is now the chance of a name collision (this
                # can't happen without relocation since _add_child_to_dr() below
                # won't allow duplicate names).  Check for that here and
                # generate a new name.
                index = 0
                while True:
                    for child in self._rr_moved_record.children:
                        if child.file_ident == iso9660_name:
                            # Python 3.4 doesn't support substitution with a byte
                            # array, so we do it as a string and encode to bytes.
                            iso9660_name = name + ('%03d' % (index)).encode()
                            index += 1
                            break
                    else:
                        break

            rec = dr.DirectoryRecord()
            rec.new_dir(self.pvd, iso9660_name, parent,
                        self.pvd.sequence_number(), self.rock_ridge, new_rr_name,
                        self.pvd.logical_block_size(), False, relocated,
                        self.xa, file_mode)
            num_bytes_to_add += self._add_child_to_dr(rec, self.pvd.logical_block_size())
            if rec.rock_ridge is not None:
                if relocated:
                    fake_dir_rec.rock_ridge.cl_to_moved_dr = rec  # type: ignore
                    rec.rock_ridge.moved_to_cl_dr = fake_dir_rec  # type: ignore
                num_bytes_to_add += self._update_rr_ce_entry(rec)

            self._create_dot(self.pvd, rec, self.rock_ridge, self.xa, file_mode)

            parent_file_mode = -1
            if parent.rock_ridge is not None:
                parent_file_mode = parent.rock_ridge.get_file_mode()
            else:
                if parent.is_root:
                    parent_file_mode = file_mode

            dotdot = self._create_dotdot(self.pvd, rec, self.rock_ridge,
                                         relocated, self.xa, parent_file_mode)
            if dotdot.rock_ridge is not None and relocated:
                dotdot.rock_ridge.parent_link = orig_parent

            # We always need to add an entry to the path table record
            ptr = path_table_record.PathTableRecord()
            ptr.new_dir(iso9660_name)

            num_bytes_to_add += self._add_to_ptr_size(ptr) + self.pvd.logical_block_size()

            rec.set_ptr(ptr)

        if joliet_path is not None:
            num_bytes_to_add += self._add_joliet_dir(self._normalize_joliet_path(joliet_path))

        if udf_path is not None:
            if self.udf_root is None:
                raise pycdlibexception.PyCdlibInvalidInput('Can only specify a udf_path for a UDF ISO')

            log_block_size = self.pvd.logical_block_size()

            udf_path_bytes = utils.normpath(udf_path)
            (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_path_bytes)

            file_ident = udfmod.UDFFileIdentifierDescriptor()
            file_ident.new(True, 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

            file_entry = udfmod.UDFFileEntry()
            file_entry.new(0, 'dir', udf_parent, log_block_size)
            file_ident.file_entry = file_entry
            file_entry.file_ident = file_ident
            num_bytes_to_add += log_block_size

            udf_dotdot = udfmod.UDFFileIdentifierDescriptor()
            udf_dotdot.new(True, True, b'', udf_parent)
            num_new_extents = file_ident.file_entry.add_file_ident_desc(udf_dotdot, log_block_size)
            num_bytes_to_add += num_new_extents * log_block_size

            self.udf_logical_volume_integrity.logical_volume_impl_use.num_dirs += 1

        self._finish_add(0, num_bytes_to_add)
Add a directory to the ISO.  At least one of an iso_path, joliet_path,
        or udf_path must be provided.  Providing joliet_path on a non-Joliet
        ISO, or udf_path on a non-UDF ISO, is an error.  If the ISO contains
        Rock Ridge, then a Rock Ridge name must be provided.

        Parameters:
         iso_path - The ISO9660 absolute path to use for the directory.
         rr_name - The Rock Ridge name to use for the directory.
         joliet_path - The Joliet absolute path to use for the directory.
         file_mode - The POSIX file mode to use for the directory.  This only
                     applies for Rock Ridge ISOs.
         udf_path - The UDF absolute path to use for the directory.
        Returns:
         Nothing.