Reland "[libc] Implement basename and dirname in libgen.h #204554" (#205352)

Added the POSIX standard functions basename and dirname under a new libgen.h header. The implementations modify the input path in-place using cpp::string_view to determine boundaries safely.

Added find_last_not_of to cpp::string_view to support trailing slash removal.

Implemented:

libc/include/libgen.yaml, libgen.h.def: Public API definitions.
libc/src/libgen/basename.cpp, dirname.cpp: Generic implementations.
libc/test/src/libgen/: Unit and hermetic tests.
Registered the new entrypoints for all active Linux targets (x86_64, aarch64, arm, riscv) and added docgen configuration.

The tests are skipped when using ASan because death tests do not currently work with them.

Assisted-by: Automated tooling, human reviewed.
This commit is contained in:
Jeff Bailey 2026-06-23 15:59:59 +01:00 committed by GitHub
parent 34b6e1c3b0
commit dc658f1f11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 460 additions and 0 deletions

View file

@ -109,6 +109,10 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.inttypes.strtoimax
libc.src.inttypes.strtoumax
# libgen.h entrypoints
libc.src.libgen.basename
libc.src.libgen.dirname
# stdbit.h entrypoints
libc.src.stdbit.stdc_bit_ceil_uc
libc.src.stdbit.stdc_bit_ceil_ui

View file

@ -73,6 +73,10 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.inttypes.strtoimax
libc.src.inttypes.strtoumax
# libgen.h entrypoints
libc.src.libgen.basename
libc.src.libgen.dirname
# stdbit.h entrypoints
libc.src.stdbit.stdc_bit_ceil_uc
libc.src.stdbit.stdc_bit_ceil_ui

View file

@ -109,6 +109,10 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.inttypes.strtoimax
libc.src.inttypes.strtoumax
# libgen.h entrypoints
libc.src.libgen.basename
libc.src.libgen.dirname
# stdbit.h entrypoints
libc.src.stdbit.stdc_bit_ceil_uc
libc.src.stdbit.stdc_bit_ceil_ui

View file

@ -125,6 +125,10 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.inttypes.wcstoimax
libc.src.inttypes.wcstoumax
# libgen.h entrypoints
libc.src.libgen.basename
libc.src.libgen.dirname
# stdbit.h entrypoints
libc.src.stdbit.stdc_bit_ceil_uc
libc.src.stdbit.stdc_bit_ceil_ui

View file

@ -54,6 +54,7 @@ if (SPHINX_FOUND)
float
glob
inttypes
libgen
locale
nl_types
net/if

View file

@ -20,6 +20,7 @@ Implementation Status
float
glob
inttypes
libgen
locale
math/index.rst
net/if

View file

@ -130,6 +130,14 @@ add_header_macro(
.llvm-libc-macros.float_macros
)
add_header_macro(
libgen
../libc/include/libgen.yaml
libgen.h
DEPENDS
.llvm_libc_common_h
)
add_header_macro(
limits
../libc/include/limits.yaml

20
libc/include/libgen.yaml Normal file
View file

@ -0,0 +1,20 @@
header: libgen.h
standards:
- posix
macros: []
types: []
enums: []
objects: []
functions:
- name: basename
standards:
- posix
return_type: char *
arguments:
- type: char *
- name: dirname
standards:
- posix
return_type: char *
arguments:
- type: char *

View file

@ -7,6 +7,7 @@ add_subdirectory(dlfcn)
add_subdirectory(errno)
add_subdirectory(fenv)
add_subdirectory(inttypes)
add_subdirectory(libgen)
add_subdirectory(link)
add_subdirectory(math)
add_subdirectory(netinet)

View file

@ -205,6 +205,15 @@ public:
return npos;
}
LIBC_INLINE constexpr size_t find_last_not_of(const char c,
size_t end = npos) const {
end = end >= size() ? size() : end + 1;
for (; end > 0; --end)
if ((*this)[end - 1] != c)
return end - 1;
return npos;
}
// Finds the first character not equal to c in this view, starting at
// position From.
LIBC_INLINE constexpr size_t find_first_not_of(const char c,

View file

@ -0,0 +1,23 @@
add_entrypoint_object(
basename
SRCS
basename.cpp
HDRS
basename.h
DEPENDS
libc.src.__support.CPP.string_view
libc.src.__support.common
libc.src.__support.macros.config
)
add_entrypoint_object(
dirname
SRCS
dirname.cpp
HDRS
dirname.h
DEPENDS
libc.src.__support.CPP.string_view
libc.src.__support.common
libc.src.__support.macros.config
)

View file

@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Implementation of basename.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/basename.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(char *, basename, (char *path)) {
if (path == nullptr || path[0] == '\0')
return const_cast<char *>(".");
cpp::string_view sv(path);
size_t last_non_slash = sv.find_last_not_of('/');
if (last_non_slash == cpp::string_view::npos)
return const_cast<char *>("/");
size_t last_slash = sv.substr(0, last_non_slash).find_last_of('/');
size_t start = (last_slash == cpp::string_view::npos) ? 0 : last_slash + 1;
size_t end = last_non_slash + 1;
if (end < sv.size())
path[end] = '\0';
return path + start;
}
} // namespace LIBC_NAMESPACE_DECL

View file

@ -0,0 +1,30 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Header for basename.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_LIBGEN_BASENAME_H
#define LLVM_LIBC_SRC_LIBGEN_BASENAME_H
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
/// Return the last component of a pathname.
///
/// \param path Pointer to the null-terminated pathname string.
/// \return Pointer to the last component of path, or "." if path is null or
/// empty, or "/" if path is all slashes.
char *basename(char *path);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_LIBGEN_BASENAME_H

View file

@ -0,0 +1,48 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Implementation of dirname.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/dirname.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(char *, dirname, (char *path)) {
if (path == nullptr || path[0] == '\0')
return const_cast<char *>(".");
cpp::string_view sv(path);
size_t last_non_slash = sv.find_last_not_of('/');
if (last_non_slash == cpp::string_view::npos)
return const_cast<char *>("/");
size_t last_slash = sv.substr(0, last_non_slash).find_last_of('/');
if (last_slash == cpp::string_view::npos)
return const_cast<char *>(".");
cpp::string_view dir_sv = sv.substr(0, last_slash);
size_t dir_last_non_slash = dir_sv.find_last_not_of('/');
if (dir_last_non_slash == cpp::string_view::npos) {
path[1] = '\0';
return path;
}
path[dir_last_non_slash + 1] = '\0';
return path;
}
} // namespace LIBC_NAMESPACE_DECL

30
libc/src/libgen/dirname.h Normal file
View file

@ -0,0 +1,30 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Header for dirname.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_LIBGEN_DIRNAME_H
#define LLVM_LIBC_SRC_LIBGEN_DIRNAME_H
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
/// Return the directory component of a pathname.
///
/// \param path Pointer to the null-terminated pathname string.
/// \return Pointer to the directory component of path, or "." if path is null
/// or empty, or "/" if path is all slashes.
char *dirname(char *path);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_LIBGEN_DIRNAME_H

View file

@ -63,6 +63,7 @@ add_subdirectory(complex)
add_subdirectory(ctype)
add_subdirectory(errno)
add_subdirectory(fenv)
add_subdirectory(libgen)
add_subdirectory(link)
add_subdirectory(math)
add_subdirectory(netinet)

View file

@ -0,0 +1,45 @@
add_custom_target(libc-libgen-tests)
add_libc_test(
basename_test
SUITE
libc-libgen-tests
SRCS
basename_test.cpp
DEPENDS
libc.src.libgen.basename
)
add_libc_test(
dirname_test
SUITE
libc-libgen-tests
SRCS
dirname_test.cpp
DEPENDS
libc.src.libgen.dirname
)
if (NOT LLVM_USE_SANITIZER)
add_libc_test(
basename_death_test
UNIT_TEST_ONLY
SUITE
libc-libgen-tests
SRCS
basename_death_test.cpp
DEPENDS
libc.src.libgen.basename
)
add_libc_test(
dirname_death_test
UNIT_TEST_ONLY
SUITE
libc-libgen-tests
SRCS
dirname_death_test.cpp
DEPENDS
libc.src.libgen.dirname
)
endif()

View file

@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Death tests for basename.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/basename.h"
#include "test/UnitTest/Test.h"
#ifdef ENABLE_SUBPROCESS_TESTS
TEST(LlvmLibcBasenameTest, ModifyReturnValue) {
char *r = LIBC_NAMESPACE::basename(nullptr);
ASSERT_DEATH([r]() { r[0] = 'a'; }, WITH_SIGNAL(-1));
}
#endif

View file

@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Unittests for basename.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/basename.h"
#include "test/UnitTest/Test.h"
TEST(LlvmLibcBasenameTest, NullPointer) {
ASSERT_STREQ(LIBC_NAMESPACE::basename(nullptr), ".");
}
TEST(LlvmLibcBasenameTest, EmptyString) {
char path[] = "";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), ".");
}
TEST(LlvmLibcBasenameTest, RegularPath) {
char path[] = "/usr/lib";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "lib");
}
TEST(LlvmLibcBasenameTest, TrailingSlash) {
char path[] = "/usr/";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "usr");
ASSERT_STREQ(path, "/usr");
}
TEST(LlvmLibcBasenameTest, SingleSlash) {
char path[] = "/";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "/");
}
TEST(LlvmLibcBasenameTest, MultipleSlashes) {
char path[] = "///";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "/");
}
TEST(LlvmLibcBasenameTest, SimpleName) {
char path[] = "a";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "a");
}
TEST(LlvmLibcBasenameTest, SimpleNameTrailingSlash) {
char path[] = "a/";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "a");
ASSERT_STREQ(path, "a");
}
TEST(LlvmLibcBasenameTest, ComplexPath) {
char path[] = "///a///";
ASSERT_STREQ(LIBC_NAMESPACE::basename(path), "a");
ASSERT_STREQ(path, "///a");
}

View file

@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Death tests for dirname.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/dirname.h"
#include "test/UnitTest/Test.h"
#ifdef ENABLE_SUBPROCESS_TESTS
TEST(LlvmLibcDirnameTest, ModifyReturnValue) {
char *r = LIBC_NAMESPACE::dirname(nullptr);
ASSERT_DEATH([r]() { r[0] = 'a'; }, WITH_SIGNAL(-1));
}
#endif

View file

@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Unittests for dirname.
///
//===----------------------------------------------------------------------===//
#include "src/libgen/dirname.h"
#include "test/UnitTest/Test.h"
TEST(LlvmLibcDirnameTest, NullPointer) {
ASSERT_STREQ(LIBC_NAMESPACE::dirname(nullptr), ".");
}
TEST(LlvmLibcDirnameTest, EmptyString) {
char path[] = "";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), ".");
}
TEST(LlvmLibcDirnameTest, RegularPath) {
char path[] = "/usr/lib";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/usr");
ASSERT_STREQ(path, "/usr");
}
TEST(LlvmLibcDirnameTest, TrailingSlash) {
char path[] = "/usr/";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/");
ASSERT_STREQ(path, "/");
}
TEST(LlvmLibcDirnameTest, SingleSlash) {
char path[] = "/";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/");
}
TEST(LlvmLibcDirnameTest, MultipleSlashes) {
char path[] = "///";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/");
}
TEST(LlvmLibcDirnameTest, SimpleName) {
char path[] = "a";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), ".");
}
TEST(LlvmLibcDirnameTest, SimpleNameTrailingSlash) {
char path[] = "a/";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), ".");
}
TEST(LlvmLibcDirnameTest, ComplexPath) {
char path[] = "///a///b///";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "///a");
ASSERT_STREQ(path, "///a");
}
TEST(LlvmLibcDirnameTest, SlashA) {
char path[] = "/a";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/");
ASSERT_STREQ(path, "/");
}
TEST(LlvmLibcDirnameTest, MultipleSlashesA) {
char path[] = "///a";
ASSERT_STREQ(LIBC_NAMESPACE::dirname(path), "/");
ASSERT_STREQ(path, "/");
}

View file

@ -0,0 +1,5 @@
functions:
basename:
in-latest-posix: ''
dirname:
in-latest-posix: ''