https://bugs.kde.org/show_bug.cgi?id=524702

            Bug ID: 524702
           Summary: Regression:  Ark commit 189f1f4e causes Drag-and-drop
                    extraction with the libarchive plugin to silently omit
                    empty directories
    Classification: Applications
           Product: ark
      Version First 26.03.80
       Reported In:
          Platform: Fedora RPMs
                OS: Linux
            Status: REPORTED
          Severity: major
          Priority: NOR
         Component: plugins
          Assignee: [email protected]
          Reporter: [email protected]
                CC: [email protected]
  Target Milestone: ---

Created attachment 195436
  --> https://bugs.kde.org/attachment.cgi?id=195436&action=edit
picture of archive contents vs. extraction result

Ark silently omits empty directories when a directory tree is dragged from the
Ark GUI and dropped into a folder in Dolphin. This is a regression: `25.12.3`
works, while `26.04.2`, `26.04.3`, and `26.08.0` reproduce the problem.

Normal full-archive extraction is not affected. The same archive extracts
correctly using Ark's regular Extract/Extract All operation, `ark --batch`,
`bsdtar`, or `tar`.

# Steps To Reproduce

1. Create a small archive containing both an empty and a non-empty directory:

   ```bash
   rm -rf /tmp/ark-dnd-source /tmp/ark-dnd-target
   mkdir -p /tmp/ark-dnd-source/root/empty
   mkdir -p /tmp/ark-dnd-source/root/nonempty
   printf 'test\n' > /tmp/ark-dnd-source/root/nonempty/file.txt
   tar -C /tmp/ark-dnd-source -czf /tmp/ark-dnd-empty-dir.tar.gz root
   mkdir -p /tmp/ark-dnd-target
   ```

2. Open `/tmp/ark-dnd-empty-dir.tar.gz` in Ark.
3. Drag the `root` directory from Ark's archive view and drop it into
`/tmp/ark-dnd-target` in Dolphin.
4. Inspect the extracted tree:

   ```bash
   find /tmp/ark-dnd-target -print | sort
   ```

# Observed Result

`root/nonempty/file.txt` is extracted, and its parent directories are
implicitly recreated, but `root/empty` is missing.

# Expected Result

The complete selected tree is extracted, including `root/empty`.

# Regression Range

- Last tested working version: Ark/ark-libs `25.12.3`
- First tested broken version: Ark/ark-libs `26.04.2`
- Also broken: `26.04.3` and `26.08.0`
- The regression is introduced by commit
`189f1f4e411086d996d4e581c425a380d160b899` (included beginning with the
`26.03.80` tag):
 
<https://invent.kde.org/utilities/ark/-/commit/189f1f4e411086d996d4e581c425a380d160b899>

# Technical Analysis

Commit `189f1f4e411086d996d4e581c425a380d160b899` correctly normalizes
libarchive directory entry names by removing trailing `/` characters before
checking for existing destination files. However, partial extraction still
builds `fullPaths` and `remainingFiles` with
`ReadOnlyArchiveInterface::entryFullPaths()` using its default
`WithTrailingSlash` format.

During drag-and-drop extraction Ark supplies an explicit selected-entry list. A
selected directory is therefore stored as `root/empty/`, while the
corresponding libarchive entry is normalized to `root/empty`. The membership
test fails and Ark skips the directory entry. Parent directories for regular
files are recreated as a side effect, which hides the problem for every
directory except genuinely empty ones.

Full extraction passes an empty selected-entry list and uses the `extractAll`
path, so it bypasses the mismatched membership test and works correctly.

# Proposed Fix

Normalize the two explicit selected-entry lists with the same representation
used for libarchive entries:

```cpp
const QStringList fullPaths = entryFullPaths(files, NoTrailingSlash);
QStringList remainingFiles = entryFullPaths(files, NoTrailingSlash);
```

The attached patch also adds a regression test using the existing
`empty_folders.tar.gz` fixture and drag-and-drop extraction options:

```
>From 4cadf90043fbbe2c5bfc48355e2405b18f337811 Mon Sep 17 00:00:00 2001
From: GloriousEggroll <[email protected]>
Date: Mon, 24 Aug 2026 06:04:05 -0600
Subject: [PATCH] libarchive: Preserve empty directories during partial
 extraction

Commit 189f1f4e411086d996d4e581c425a380d160b899 normalized libarchive directory
entry names by removing trailing slashes. The explicit selected-entry lists
retained their trailing slashes, so directory entries no longer matched during
partial and drag-and-drop extraction. Non-empty parent directories were
recreated while extracting files, but empty directories were silently omitted.

Normalize both selected-entry lists to the same no-trailing-slash
representation and add coverage for drag-and-drop extraction of empty
directories.

Fixes: 189f1f4e411086d996d4e581c425a380d160b899
---
 autotests/kerfuffle/extracttest.cpp     | 13 +++++++++++++
 plugins/libarchive/libarchiveplugin.cpp |  4 ++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/autotests/kerfuffle/extracttest.cpp
b/autotests/kerfuffle/extracttest.cpp
index 8f9774fd..10721326 100644
--- a/autotests/kerfuffle/extracttest.cpp
+++ b/autotests/kerfuffle/extracttest.cpp
@@ -181,6 +181,19 @@ void ExtractTest::testExtraction_data()
     archivePath = QFINDTESTDATA("data/empty_folders.tar.gz");
     setupRow("tar with empty folders", archivePath, QList<Archive::Entry *>(),
optionsPreservePaths, 5);

+    archivePath = QFINDTESTDATA("data/empty_folders.tar.gz");
+    setupRow("extract selected empty folders from a tar.gz, drag-and-drop",
+             archivePath,
+             QList<Archive::Entry *>{
+                 new Archive::Entry(this, QStringLiteral("A/"), QString()),
+                 new Archive::Entry(this, QStringLiteral("A/B/"), QString()),
+                 new Archive::Entry(this, QStringLiteral("A/B/test.txt"),
QString()),
+                 new Archive::Entry(this, QStringLiteral("A/B/C/"),
QString()),
+                 new Archive::Entry(this, QStringLiteral("A/B1/"), QString()),
+             },
+             dragAndDropOptions,
+             5);
+
     archivePath = QFINDTESTDATA("data/simplearchive.tar.bz2");
     setupRow("extract selected entries from a bzip2-compressed tarball without
path",
              archivePath,
diff --git a/plugins/libarchive/libarchiveplugin.cpp
b/plugins/libarchive/libarchiveplugin.cpp
index d625ea51..26e59bd7 100644
--- a/plugins/libarchive/libarchiveplugin.cpp
+++ b/plugins/libarchive/libarchiveplugin.cpp
@@ -293,8 +293,8 @@ bool LibarchivePlugin::extractFiles(const
QList<Archive::Entry *> &files, const
     QString fileBeingRenamed;
     // To avoid traversing the entire archive when extracting a limited set of
     // entries, we maintain a list of remaining entries and stop when it's
empty.
-    const QStringList fullPaths = entryFullPaths(files);
-    QStringList remainingFiles = entryFullPaths(files);
+    const QStringList fullPaths = entryFullPaths(files, NoTrailingSlash);
+    QStringList remainingFiles = entryFullPaths(files, NoTrailingSlash);

     // Iterate through all entries in archive.
     while (!QThread::currentThread()->isInterruptionRequested() &&
(archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK)) {
-- 
2.55.0
```

# System Information

- OS: Nobara Linux 44 KDE Plasma Edition
- Working Ark version currently installed: `25.12.3-1.fc44`
- Broken Ark versions tested: `26.04.2-1.fc43`, `26.04.3-1.fc44`,
`26.08.0-1.fc44`
- libarchive: `3.8.7-1.fc44`
- KDE Plasma: `6.7.4-1.fc44`
- KDE Frameworks KArchive: `6.29.0-1.fc44`
- Qt: `6.11.1-1.fc44`

Extra:

I changed the severity to major as it effects basic user experience and can
have severe trickle-down effects.

For example users manually installing custom proton versions for Steam will
unknowingly end up with broken proton extractions and subsequently broken game
launches. 

Specifically, the default_pfx folder in proton builds can break because it
contains several empty directories that games/applications expect to exist in
the prefix. Since proton copies default_pfx instead of using wineboot, those
folders never get re-created:

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to