cp: Add -r/--recursive flag

This commit is contained in:
Bananymous 2025-11-23 02:25:05 +02:00
parent e3e2e7b4df
commit 1f9b296ae7
2 changed files with 204 additions and 103 deletions

View File

@ -3,7 +3,6 @@ set(SOURCES
) )
add_executable(cp ${SOURCES}) add_executable(cp ${SOURCES})
banan_include_headers(cp ban)
banan_link_library(cp libc) banan_link_library(cp libc)
install(TARGETS cp OPTIONAL) install(TARGETS cp OPTIONAL)

View File

@ -1,143 +1,245 @@
#include <BAN/String.h> #include <dirent.h>
#include <BAN/Vector.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#define STR_STARTS_WITH(str, arg) (strncmp(str, arg, sizeof(arg) - 1) == 0) static int copy_file(const char* src, const char* dst)
#define STR_EQUAL(str, arg) (strcmp(str, arg) == 0)
bool copy_file(const BAN::String& source, BAN::String destination)
{ {
struct stat st; struct stat src_st;
if (stat(source.data(), &st) == -1) if (stat(src, &src_st) == -1)
return errno;
struct stat dst_st;
if (stat(dst, &dst_st) == 0)
{ {
fprintf(stderr, "%s: ", source.data()); if (S_ISDIR(dst_st.st_mode))
perror("stat"); return EINVAL;
return false; if (unlinkat(AT_FDCWD, dst, 0) == -1)
} return errno;
if (S_ISDIR(st.st_mode))
{
fprintf(stderr, "%s: is a directory\n", source.data());
return false;
} }
if (stat(destination.data(), &st) != -1 && S_ISDIR(st.st_mode)) if (S_ISREG(src_st.st_mode))
{ {
MUST(destination.push_back('/')); const int src_fd = open(src, O_RDONLY);
MUST(destination.append(MUST(source.sv().split('/')).back())); const int dst_fd = open(dst, O_RDWR | O_CREAT | O_EXCL, src_st.st_mode);
}
int src_fd = open(source.data(), O_RDONLY); if (src_fd == -1 || dst_fd == -1)
if (src_fd == -1)
{ {
fprintf(stderr, "%s: ", source.data()); if (src_fd != -1)
perror("open");
return false;
}
int dest_fd = open(destination.data(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (dest_fd == -1)
{
fprintf(stderr, "%s: ", destination.data());
perror("open");
close(src_fd); close(src_fd);
return false; if (dst_fd != -1)
close(dst_fd);
return errno;
} }
bool ret = true; int result = 0;
char buffer[1024];
while (ssize_t nread = read(src_fd, buffer, sizeof(buffer))) char buffer[512];
for (;;)
{ {
if (nread < 0) const ssize_t nread = read(src_fd, buffer, 512);
if (nread <= 0)
{ {
fprintf(stderr, "%s: ", source.data()); if (nread == -1)
perror("read"); result = errno;
ret = false;
break; break;
} }
ssize_t written = 0; ssize_t total_written = 0;
while (written < nread) while (total_written < nread)
{ {
ssize_t nwrite = write(dest_fd, buffer, nread - written); const ssize_t nwrite = write(dst_fd, buffer + total_written, nread - total_written);
if (nwrite < 0) if (nwrite < 0)
{ {
fprintf(stderr, "%s: ", destination.data()); result = errno;
perror("write");
ret = false;
}
if (nwrite <= 0)
break; break;
written += nwrite;
} }
total_written += nwrite;
if (written < nread) }
break;
} }
close(src_fd); close(src_fd);
close(dest_fd); close(dst_fd);
return ret; return result;
}
if (S_ISLNK(src_st.st_mode))
{
char* buffer = static_cast<char*>(malloc(512));
if (buffer == nullptr)
return errno;
ssize_t buffer_size = 512;
ssize_t link_len;
while ((link_len = readlink(src, buffer, buffer_size)) == buffer_size)
{
buffer_size *= 2;
void* new_buffer = realloc(buffer, buffer_size);
if (new_buffer == nullptr)
{
free(buffer);
return errno;
}
buffer = static_cast<char*>(new_buffer);
}
int result = 0;
if (link_len == -1)
result = errno;
if (result == 0 && symlink(buffer, dst) == -1)
result = errno;
free(buffer);
return result;
}
fprintf(stddbg, "move file with mode %07o to another filesystem\n", src_st.st_mode);
return ENOTSUP;
} }
bool copy_file_to_directory(const BAN::String& source, const BAN::String& destination) static int copy_directory(const char* src, const char* dst)
{ {
auto temp = destination; struct stat src_st;
MUST(temp.append(MUST(source.sv().split('/')).back())); if (stat(src, &src_st) == -1)
return copy_file(source, destination); return errno;
}
void usage(const char* argv0, int ret) struct stat dst_st;
{ if (stat(dst, &dst_st) == 0)
FILE* out = (ret == 0) ? stdout : stderr; {
fprintf(out, "usage: %s [OPTIONS]... SOURCE... DEST\n", argv0); if (!S_ISDIR(dst_st.st_mode))
fprintf(out, "Copies files SOURCE... to DEST\n"); return ENOTDIR;
fprintf(out, "OPTIONS:\n"); if (rmdir(dst) == -1)
fprintf(out, " -h, --help\n"); return errno;
fprintf(out, " Show this message and exit\n"); }
exit(ret);
if (mkdir(dst, src_st.st_mode) == -1)
return errno;
DIR* dirp = opendir(src);
if (dirp == nullptr)
return errno;
int result = 0;
dirent* dent;
while ((dent = readdir(dirp)))
{
if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
continue;
bool name_too_long = false;
char src_buffer[PATH_MAX];
if (snprintf(src_buffer, PATH_MAX, "%s/%s", src, dent->d_name) > PATH_MAX)
name_too_long = true;
char dst_buffer[PATH_MAX];
if (snprintf(dst_buffer, PATH_MAX, "%s/%s", dst, dent->d_name) > PATH_MAX)
name_too_long = true;
if (name_too_long)
result = ENAMETOOLONG;
else
{
auto* copy_func = (dent->d_type == DT_DIR) ? copy_directory : copy_file;
if (int ret = copy_func(src_buffer, dst_buffer))
result = ret;
}
}
closedir(dirp);
return result;
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
BAN::Vector<BAN::StringView> src; bool recursive = false;
BAN::StringView dest;
int i = 1; for (;;)
for (; i < argc; i++)
{
if (STR_EQUAL(argv[i], "-h") || STR_EQUAL(argv[i], "--help"))
{
usage(argv[0], 0);
}
else if (argv[i][0] == '-')
{
fprintf(stderr, "Unknown argument %s\n", argv[i]);
usage(argv[0], 1);
}
else
{ {
static option long_options[] {
{ "help", no_argument, nullptr, 'h' },
{ "recursive", no_argument, nullptr, 'r' },
};
int ch = getopt_long(argc, argv, "hr", long_options, nullptr);
if (ch == -1)
break; break;
}
}
for (; i < argc - 1; i++) switch (ch)
MUST(src.push_back(argv[i]));
dest = argv[argc - 1];
if (src.empty())
{ {
fprintf(stderr, "Missing destination operand\n"); case 'h':
usage(argv[0], 1); printf("usage: %s [OPTIONS]... SOURCE... DEST\n", argv[0]);
printf("Copies files SOURCE... to DEST\n");
printf("OPTIONS:\n");
printf(" -h, --help Show this message and exit\n");
return 0;
case 'r':
recursive = true;
break;
case '?':
fprintf(stderr, "invalid option %c\n", optopt);
fprintf(stderr, "see '%s --help' for usage\n", argv[0]);
return 1;
}
} }
int ret = 0; const int src_count = argc - optind - 1;
for (auto file_path : src) if (src_count < 1)
if (!copy_file(file_path, dest)) {
ret = 1; fprintf(stderr, "missing destination operand\n");
return 1;
}
return ret; const char* dest = argv[argc - 1];
if (src_count >= 2)
{
struct stat st;
if (stat(dest, &st) == -1 || !S_ISDIR(st.st_mode))
{
fprintf(stderr, "destination is not a directory\n");
return 1;
}
}
int result = 0;
for (int i = optind; i < argc - 1; i++)
{
struct stat src_st;
if (stat(argv[i], &src_st) == -1)
{
fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
continue;
}
if (!recursive && S_ISDIR(src_st.st_mode))
{
fprintf(stderr, "%s: %s\n", argv[0], strerror(EISDIR));
continue;
}
struct stat dst_st;
if (stat(dest, &dst_st) == 0 && S_ISDIR(dst_st.st_mode))
{
static char buffer[PATH_MAX];
if (snprintf(buffer, PATH_MAX, "%s/%s", dest, basename(argv[i])) > PATH_MAX)
return ENAMETOOLONG;
dest = buffer;
}
auto* copy_func = S_ISDIR(src_st.st_mode) ? copy_directory : copy_file;
if (int ret = copy_func(argv[i], dest); ret != 0)
{
fprintf(stderr, "%s: %s\n", argv[0], strerror(ret));
result = 1;
}
}
return result;
} }