forked from OSchip/llvm-project
[libc] Add a simple implementation of the posix_spawn function.
The implementation currently ignores all spawn attributes. Support for them will be added in future changes. A simple allocator for integration tests has been added so that the integration test for posix_spawn can use the posix_spawn_file_actions_add* functions. Reviewed By: michaelrj Differential Revision: https://reviews.llvm.org/D135752
This commit is contained in:
parent
dde9db5f93
commit
02a543db66
|
@ -432,7 +432,8 @@ function(add_integration_test test_name)
|
|||
libc.src.__support.threads.thread
|
||||
libc.src.stdlib.atexit
|
||||
libc.src.stdlib.exit
|
||||
libc.src.unistd.environ)
|
||||
libc.src.unistd.environ
|
||||
libc.utils.IntegrationTest.test)
|
||||
list(REMOVE_DUPLICATES fq_deps_list)
|
||||
|
||||
# We don't want memory functions to be dependencies on integration tests.
|
||||
|
|
|
@ -291,5 +291,5 @@ def SysUtsNameAPI : PublicAPI<"sys/utsname.h"> {
|
|||
}
|
||||
|
||||
def SpawnAPI : PublicAPI<"spawn.h"> {
|
||||
let Types = ["mode_t", "posix_spawn_file_actions_t"];
|
||||
let Types = ["mode_t", "pid_t", "posix_spawnattr_t", "posix_spawn_file_actions_t"];
|
||||
}
|
||||
|
|
|
@ -391,6 +391,7 @@ if(LLVM_LIBC_FULL_BUILD)
|
|||
libc.src.signal.signal
|
||||
|
||||
# spawn.h entrypoints
|
||||
libc.src.spawn.posix_spawn
|
||||
libc.src.spawn.posix_spawn_file_actions_addclose
|
||||
libc.src.spawn.posix_spawn_file_actions_adddup2
|
||||
libc.src.spawn.posix_spawn_file_actions_addopen
|
||||
|
|
|
@ -220,6 +220,8 @@ add_gen_header(
|
|||
DEPENDS
|
||||
.llvm_libc_common_h
|
||||
.llvm-libc-types.mode_t
|
||||
.llvm-libc-types.pid_t
|
||||
.llvm-libc-types.posix_spawnattr_t
|
||||
.llvm-libc-types.posix_spawn_file_actions_t
|
||||
)
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ add_header(off_t HDR off_t.h)
|
|||
add_header(once_flag HDR once_flag.h DEPENDS .__futex_word)
|
||||
add_header(pid_t HDR pid_t.h)
|
||||
add_header(posix_spawn_file_actions_t HDR posix_spawn_file_actions_t.h)
|
||||
add_header(posix_spawnattr_t HDR posix_spawnattr_t.h)
|
||||
add_header(pthread_attr_t HDR pthread_attr_t.h DEPENDS .size_t)
|
||||
add_header(pthread_key_t HDR pthread_key_t.h)
|
||||
add_header(pthread_mutex_t HDR pthread_mutex_t.h DEPENDS .__futex_word .__mutex_type)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
//===-- Definition of type posix_spawn_file_actions_t ---------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
|
||||
#define __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
|
||||
|
||||
typedef struct {
|
||||
// This data structure will be populated as required.
|
||||
} posix_spawnattr_t;
|
||||
|
||||
#endif // __LLVM_LIBC_TYPES_POSIX_SPAWNATTR_T_H
|
|
@ -52,8 +52,12 @@ def AtForkCallbackT : NamedType<"__atfork_callback_t">;
|
|||
|
||||
def PosixSpawnFileActionsT : NamedType<"posix_spawn_file_actions_t">;
|
||||
def PosixSpawnFileActionsTPtr : PtrType<PosixSpawnFileActionsT>;
|
||||
def ConstPosixSpawnFileActionsTPtr : ConstType<PosixSpawnFileActionsTPtr>;
|
||||
def PosixSpawnFileActionsTRestrictedPtr : RestrictedPtrType<PosixSpawnFileActionsT>;
|
||||
|
||||
def PosixSpawnAttrT : NamedType<"posix_spawnattr_t">;
|
||||
def RestrictedPosixSpawnAttrTPtrType : RestrictedPtrType<PosixSpawnAttrT>;
|
||||
|
||||
def POSIX : StandardSpec<"POSIX"> {
|
||||
PtrType CharPtr = PtrType<CharType>;
|
||||
RestrictedPtrType RestrictedCharPtr = RestrictedPtrType<CharType>;
|
||||
|
@ -1052,7 +1056,7 @@ def POSIX : StandardSpec<"POSIX"> {
|
|||
HeaderSpec Spawn = HeaderSpec<
|
||||
"spawn.h",
|
||||
[], // Macros
|
||||
[PosixSpawnFileActionsT, ModeTType],
|
||||
[ModeTType, PosixSpawnAttrT, PidT, PosixSpawnFileActionsT],
|
||||
[], // Enumerations
|
||||
[
|
||||
FunctionSpec<
|
||||
|
@ -1081,6 +1085,13 @@ def POSIX : StandardSpec<"POSIX"> {
|
|||
RetValSpec<IntType>,
|
||||
[ArgSpec<PosixSpawnFileActionsTPtr>]
|
||||
>,
|
||||
FunctionSpec<
|
||||
"posix_spawn",
|
||||
RetValSpec<IntType>,
|
||||
[ArgSpec<RestrictedPidTPtr>, ArgSpec<ConstCharRestrictedPtr>,
|
||||
ArgSpec<PosixSpawnFileActionsTPtr>, ArgSpec<RestrictedPosixSpawnAttrTPtrType>,
|
||||
ArgSpec<ConstCharRestrictedPtrPtr>, ArgSpec<ConstCharRestrictedPtrPtr>]
|
||||
>,
|
||||
]
|
||||
>;
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ def ConstCharPtr : ConstType<CharPtr>;
|
|||
def CharRestrictedPtr : RestrictedPtrType<CharType>;
|
||||
def CharRestrictedPtrPtr : RestrictedPtrType<CharPtr>;
|
||||
def ConstCharRestrictedPtr : ConstType<CharRestrictedPtr>;
|
||||
def ConstCharRestrictedPtrPtr : PtrType<ConstCharRestrictedPtr>;
|
||||
|
||||
def OnceFlagType : NamedType<"once_flag">;
|
||||
def OnceFlagTypePtr : PtrType<OnceFlagType>;
|
||||
|
@ -115,6 +116,8 @@ def FILERestrictedPtr : RestrictedPtrType<FILE>;
|
|||
def PThreadTType : NamedType<"pthread_t">;
|
||||
|
||||
def PidT : NamedType<"pid_t">;
|
||||
def RestrictedPidTPtr : RestrictedPtrType<PidT>;
|
||||
|
||||
def StructRUsage : NamedType<"struct rusage">;
|
||||
def StructRUsagePtr : PtrType<StructRUsage>;
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
|
||||
endif()
|
||||
|
||||
add_header_library(
|
||||
file_actions
|
||||
HDRS
|
||||
|
@ -63,3 +67,10 @@ add_entrypoint_object(
|
|||
libc.include.errno
|
||||
libc.include.spawn
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
posix_spawn
|
||||
ALIAS
|
||||
DEPENDS
|
||||
.${LIBC_TARGET_OS}.posix_spawn
|
||||
)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
add_entrypoint_object(
|
||||
posix_spawn
|
||||
SRCS
|
||||
posix_spawn.cpp
|
||||
HDRS
|
||||
../posix_spawn.h
|
||||
DEPENDS
|
||||
libc.include.fcntl
|
||||
libc.include.spawn
|
||||
libc.include.sys_syscall
|
||||
libc.src.__support.CPP.optional
|
||||
libc.src.__support.OSUtil.osutil
|
||||
libc.src.spawn.file_actions
|
||||
)
|
|
@ -0,0 +1,139 @@
|
|||
//===-- Linux implementation of posix_spawn -------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "src/spawn/posix_spawn.h"
|
||||
|
||||
#include "src/__support/CPP/optional.h"
|
||||
#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
|
||||
#include "src/__support/common.h"
|
||||
#include "src/spawn/file_actions.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/syscall.h> // For syscall numbers.
|
||||
|
||||
namespace __llvm_libc {
|
||||
|
||||
namespace {
|
||||
|
||||
pid_t fork() {
|
||||
// TODO: Use only the clone syscall and use a sperate small stack in the child
|
||||
// to avoid duplicating the complete stack from the parent. A new stack will
|
||||
// be created on exec anyway so duplicating the full stack is unnecessary.
|
||||
#ifdef SYS_fork
|
||||
return __llvm_libc::syscall_impl(SYS_fork);
|
||||
#elif defined(SYS_clone)
|
||||
return __llvm_libc::syscall_impl(SYS_clone, SIGCHLD, 0);
|
||||
#else
|
||||
#error "SYS_fork or SYS_clone not available."
|
||||
#endif
|
||||
}
|
||||
|
||||
cpp::optional<int> open(const char *path, int oflags, mode_t mode) {
|
||||
#ifdef SYS_open
|
||||
int fd = __llvm_libc::syscall_impl(SYS_open, path, oflags, mode);
|
||||
#else
|
||||
int fd = __llvm_libc::syscall_impl(SYS_openat, AT_FDCWD, path, oflags, mode);
|
||||
#endif
|
||||
if (fd > 0)
|
||||
return fd;
|
||||
// The open function is called as part of the child process' preparatory
|
||||
// steps. If an open fails, the child process just exits. So, unlike
|
||||
// the public open function, we do not need to set errno here.
|
||||
return cpp::nullopt;
|
||||
}
|
||||
|
||||
void close(int fd) { __llvm_libc::syscall_impl(SYS_close, fd); }
|
||||
|
||||
bool dup2(int fd, int newfd) {
|
||||
long ret = __llvm_libc::syscall_impl(SYS_dup2, fd, newfd);
|
||||
return ret < 0 ? false : true;
|
||||
}
|
||||
|
||||
// All exits from child_process are error exits. So, we use a simple
|
||||
// exit implementation which exits with code 127.
|
||||
void exit() {
|
||||
for (;;) {
|
||||
__llvm_libc::syscall_impl(SYS_exit_group, 127);
|
||||
__llvm_libc::syscall_impl(SYS_exit, 127);
|
||||
}
|
||||
}
|
||||
|
||||
void child_process(const char *__restrict path,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *__restrict, // For now unused
|
||||
char *const *__restrict argv, char *const *__restrict envp) {
|
||||
// TODO: In the code below, the child_process just exits on error during
|
||||
// processing |file_actions| and |attr|. The correct way would be to exit
|
||||
// after conveying the information about the failure to the parent process
|
||||
// (via a pipe for example).
|
||||
// TODO: Handle |attr|.
|
||||
|
||||
if (file_actions != nullptr) {
|
||||
auto *act = reinterpret_cast<BaseSpawnFileAction *>(file_actions->__front);
|
||||
while (act != nullptr) {
|
||||
switch (act->type) {
|
||||
case BaseSpawnFileAction::OPEN: {
|
||||
auto *open_act = reinterpret_cast<SpawnFileOpenAction *>(act);
|
||||
auto fd = open(open_act->path, open_act->oflag, open_act->mode);
|
||||
if (!fd)
|
||||
exit();
|
||||
int actual_fd = *fd;
|
||||
if (actual_fd != open_act->fd) {
|
||||
bool dup2_result = dup2(actual_fd, open_act->fd);
|
||||
close(actual_fd); // The old fd is not needed anymore.
|
||||
if (!dup2_result)
|
||||
exit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BaseSpawnFileAction::CLOSE: {
|
||||
auto *close_act = reinterpret_cast<SpawnFileCloseAction *>(act);
|
||||
close(close_act->fd);
|
||||
break;
|
||||
}
|
||||
case BaseSpawnFileAction::DUP2: {
|
||||
auto *dup2_act = reinterpret_cast<SpawnFileDup2Action *>(act);
|
||||
if (!dup2(dup2_act->fd, dup2_act->newfd))
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
act = act->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (__llvm_libc::syscall_impl(SYS_execve, path, argv, envp) < 0)
|
||||
exit();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
LLVM_LIBC_FUNCTION(int, posix_spawn,
|
||||
(pid_t *__restrict pid, const char *__restrict path,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *__restrict attr,
|
||||
char *const *__restrict argv,
|
||||
char *const *__restrict envp)) {
|
||||
pid_t cpid = fork();
|
||||
if (cpid == 0)
|
||||
child_process(path, file_actions, attr, argv, envp);
|
||||
else if (cpid < 0)
|
||||
return -cpid;
|
||||
|
||||
if (pid != nullptr)
|
||||
*pid = cpid;
|
||||
|
||||
// TODO: Before returning, one should wait for the child_process to startup
|
||||
// successfully. For now, we will just return. Future changes will add proper
|
||||
// wait (using pipes for example).
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace __llvm_libc
|
|
@ -0,0 +1,23 @@
|
|||
//===-- Implementation header for posix_spawn -------------------*- C++ -*-===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
|
||||
#define LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
|
||||
|
||||
#include <spawn.h>
|
||||
|
||||
namespace __llvm_libc {
|
||||
|
||||
int posix_spawn(pid_t *__restrict pid, const char *__restrict path,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *__restrict attr,
|
||||
char *const *__restrict argv, char *const *__restrict envp);
|
||||
|
||||
} // namespace __llvm_libc
|
||||
|
||||
#endif // LLVM_LIBC_SRC_SPAWN_POSIX_SPAWN_H
|
|
@ -1,5 +1,6 @@
|
|||
add_subdirectory(__support)
|
||||
add_subdirectory(pthread)
|
||||
add_subdirectory(spawn)
|
||||
add_subdirectory(stdio)
|
||||
add_subdirectory(stdlib)
|
||||
add_subdirectory(threads)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
add_custom_target(spawn-integration-tests)
|
||||
add_dependencies(libc-integration-tests spawn-integration-tests)
|
||||
|
||||
add_executable(
|
||||
libc_posix_spawn_test_binary
|
||||
EXCLUDE_FROM_ALL
|
||||
posix_spawn_test_binary.cpp
|
||||
test_binary_properties.h
|
||||
)
|
||||
set_target_properties(
|
||||
libc_posix_spawn_test_binary
|
||||
PROPERTIES
|
||||
OUTPUT_NAME libc_posix_spawn_test_binary
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_header_library(
|
||||
test_binary_properties
|
||||
HDRS
|
||||
test_binary_properties.h
|
||||
)
|
||||
|
||||
add_integration_test(
|
||||
posix_spawn_test
|
||||
SUITE
|
||||
spawn-integration-tests
|
||||
SRCS
|
||||
posix_spawn_test.cpp
|
||||
LOADER
|
||||
libc.loader.linux.crt1
|
||||
DEPENDS
|
||||
libc_posix_spawn_test_binary
|
||||
libc.test.integration.src.spawn.test_binary_properties
|
||||
libc.include.fcntl
|
||||
libc.include.signal
|
||||
libc.include.spawn
|
||||
libc.include.sys_wait
|
||||
libc.src.signal.raise
|
||||
libc.src.spawn.posix_spawn
|
||||
libc.src.spawn.posix_spawn_file_actions_addopen
|
||||
libc.src.spawn.posix_spawn_file_actions_destroy
|
||||
libc.src.spawn.posix_spawn_file_actions_init
|
||||
libc.src.sys.wait.waitpid
|
||||
)
|
||||
|
||||
add_subdirectory(testdata)
|
|
@ -0,0 +1,51 @@
|
|||
//===-- Unittests for posix_spawn -----------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "test_binary_properties.h"
|
||||
|
||||
#include "src/spawn/posix_spawn.h"
|
||||
#include "src/spawn/posix_spawn_file_actions_addopen.h"
|
||||
#include "src/spawn/posix_spawn_file_actions_destroy.h"
|
||||
#include "src/spawn/posix_spawn_file_actions_init.h"
|
||||
#include "src/sys/wait/waitpid.h"
|
||||
#include "utils/IntegrationTest/test.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <spawn.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
char arg0[] = "libc_posix_spawn_test_binary";
|
||||
char *argv[] = {
|
||||
arg0,
|
||||
nullptr,
|
||||
};
|
||||
|
||||
void spawn_and_wait_for_normal_exit(char **envp) {
|
||||
pid_t cpid;
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
ASSERT_EQ(__llvm_libc::posix_spawn_file_actions_init(&file_actions), 0);
|
||||
__llvm_libc::posix_spawn_file_actions_addopen(
|
||||
&file_actions, CHILD_FD, "testdata/posix_spawn.test", O_RDONLY, 0);
|
||||
ASSERT_EQ(
|
||||
__llvm_libc::posix_spawn(&cpid, arg0, &file_actions, nullptr, argv, envp),
|
||||
0);
|
||||
ASSERT_TRUE(cpid > 0);
|
||||
int status;
|
||||
ASSERT_EQ(__llvm_libc::waitpid(cpid, &status, 0), cpid);
|
||||
ASSERT_EQ(__llvm_libc::posix_spawn_file_actions_destroy(&file_actions), 0);
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
int exit_status = WEXITSTATUS(status);
|
||||
ASSERT_EQ(exit_status, 0);
|
||||
}
|
||||
|
||||
TEST_MAIN(int argc, char **argv, char **envp) {
|
||||
spawn_and_wait_for_normal_exit(envp);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#include "test_binary_properties.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 1)
|
||||
return 5;
|
||||
constexpr size_t bufsize = sizeof(TEXT);
|
||||
char buf[bufsize];
|
||||
ssize_t readsize = bufsize - 1;
|
||||
ssize_t len = read(CHILD_FD, buf, readsize);
|
||||
if (len != readsize) {
|
||||
return 1;
|
||||
}
|
||||
buf[readsize] = '\0'; // Null terminator
|
||||
if (close(CHILD_FD) != 0)
|
||||
return 2;
|
||||
if (strcmp(buf, TEXT) != 0)
|
||||
return 3;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//===-- Common definitions shared between test binary and test --*- C++ -*-===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
|
||||
#define LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
|
||||
|
||||
constexpr int CHILD_FD = 10;
|
||||
constexpr char TEXT[] = "Hello, posix_spawn";
|
||||
|
||||
#endif // LIBC_TEST_INTEGRATION_SRC_SPAWN_TEST_BINARY_PROPERTIES_H
|
|
@ -0,0 +1 @@
|
|||
file(GENERATE OUTPUT posix_spawn.test CONTENT "Hello, posix_spawn")
|
|
@ -1,5 +1,7 @@
|
|||
add_header_library(
|
||||
add_object_library(
|
||||
test
|
||||
SRCS
|
||||
test.cpp
|
||||
HDRS
|
||||
test.h
|
||||
DEPENDS
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//===-- Simple malloc and free for use with integration tests -------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Integration tests cannot use the SCUDO standalone allocator as SCUDO pulls
|
||||
// various other parts of the libc. Since SCUDO development does not use
|
||||
// LLVM libc build rules, it is very hard to keep track or pull all that SCUDO
|
||||
// requires. Hence, as a work around for this problem, we use a simple allocator
|
||||
// which just hands out continuous blocks from a statically allocated chunk of
|
||||
// memory.
|
||||
|
||||
static uint8_t memory[16384];
|
||||
static uint8_t *ptr = memory;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void *malloc(size_t s) {
|
||||
void *mem = ptr;
|
||||
ptr += s;
|
||||
return mem;
|
||||
}
|
||||
|
||||
void free(void *) {}
|
||||
|
||||
} // extern "C"
|
Loading…
Reference in New Issue