https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127555

            Bug ID: 127555
           Summary: filesystem::remove_all fails on targets with neither
                    unlinkat nor   dirent::d_type
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: gokbeykeskin at gmail dot com
  Target Milestone: ---

Created attachment 65660
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65660&action=edit
Suggested patch

When libstdc++ is configured with both _GLIBCXX_HAVE_UNLINKAT and
_GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE undefined, remove_all() fails on the
first non-empty sub-directory, returning (uintmax_t)-1.

Reproduced on x86_64-pc-linux-gnu (trunk, 7ca5288cc68) by forcing the two
configure cache variables off:

  ../gcc/configure --disable-bootstrap --enable-languages=c,c++ \
      glibcxx_cv_unlinkat=no glibcxx_cv_dirent_d_type=no
  make && make check-target-libstdc++-v3 \
     
RUNTESTFLAGS="conformance.exp=27_io/filesystem/operations/create_directories.cc
\
                    conformance.exp=27_io/filesystem/operations/remove_all.cc"

Both FAIL; create_directories.cc:82 "VERIFY( count == 6 )" gets count ==
(uintmax_t)-1 and ec == ENOTEMPTY.  I originally hit this on a target whose
rmdir() reports EEXIST rather than ENOTEMPTY for a non-empty directory
(POSIX permits either), where ec == EEXIST instead.

Analysis:

Without d_type every entry has file_type::none (fs_dir.cc:71-78), so
__erase() skips the directory branch and calls _Dir::unlink() at
fs_dir.cc:510.  Without unlinkat, _Dir::do_unlink() ignores its
is_directory argument and always calls fs::remove() (fs_dir.cc:146-166),
i.e. ::remove(), which internally retries as rmdir() for a directory.  So
unlinking a non-empty directory reports the rmdir errno for "not empty"
(ENOTEMPTY or EEXIST), not the unlink-on-a-directory errno.  The retry
logic at fs_dir.cc:512-531 only accepts EPERM and EISDIR, so the loop
exits with ec set and remove_all() returns -1 (fs_ops.cc:1565-1570).

An empty directory of unknown type is still removed correctly (::remove
falls back to rmdir and succeeds), so the failure needs a tree at least
two levels deep.

I attached the suggested patch.

Reply via email to