I have done very little systems programming, but I'm working through CS631 - Advanced Programming in the UNIX Environment on my own time (I am not actually a student in the class). This is my solution for the first homework assignment: implement bbcp, an extremely basic file copy utility.
I originally wrote this solution for NetBSD, but it should also work on Linux. It passes the tests from this test script (of which I am not the author).
bbcp.c
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#ifdef __linux__
#include <bsd/stdlib.h>
#else
#include <stdlib.h>
#endif
#include <unistd.h>
#ifndef BUFFSIZE
#define BUFFSIZE 32768
#endif
/*
* Check whether the given file status refers to a directory.
*/
static bool
is_dir(struct stat *stat) {
return S_ISDIR(stat->st_mode);
}
/*
* Check whether the give file status refers to a regular file or FIFO.
*/
bool
is_reg_or_fifo(struct stat *stat) {
mode_t mode = stat->st_mode;
return S_ISREG(mode) || S_ISFIFO(mode);
}
/*
* Check whether the given path points to a regular file or FIFO.
*/
bool
path_is_reg_or_fifo(char *path) {
struct stat path_stat;
if (stat(path, &path_stat) == -1) {
err(EXIT_FAILURE, "Unable to stat destination at %s", path);
}
return is_reg_or_fifo(&path_stat);
}
/*
* Replace the contents of the file at the dest_fd file descriptor with the
* contents of the file at the source_fd file descriptor.
*/
void
replace_content(int source_fd, int dest_fd) {
int n;
char buf[BUFFSIZE];
if (ftruncate(dest_fd, 0) == -1) {
err(EXIT_FAILURE, "Unable to truncate destination file\n");
}
while ((n = read(source_fd, buf, BUFFSIZE)) > 0) {
if (write(dest_fd, buf, n) != n) {
err(EXIT_FAILURE, "Error while writing");
}
}
if (n == -1) {
err(EXIT_FAILURE, "Error while reading");
}
}
/*
* Check whether two file descriptors describe the same file.
*/
bool
is_same_file(int fd1, int fd2) {
struct stat stat1;
struct stat stat2;
if (fstat(fd1, &stat1) == -1) {
err(EXIT_FAILURE, "Unable to stat file descriptor");
}
if (fstat(fd2, &stat2) == -1) {
err(EXIT_FAILURE, "Unable to stat file descriptor");
}
return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
}
int
main(int argc, char **argv) {
setprogname(argv[0]);
if (argc != 3) {
fprintf(stderr, "usage: %s source target\n", getprogname());
exit(EXIT_FAILURE);
}
// check that first argument is a file, not a directory
char *source_path = argv[1];
struct stat source_stat;
if (!path_is_reg_or_fifo(source_path)) {
fprintf(stderr, "Error: %s is not a file\n", source_path);
exit(EXIT_FAILURE);
}
int source = open(source_path, O_RDONLY);
if (source == -1) {
err(EXIT_FAILURE, "Unable to open source file at %s", source_path);
}
// check that second argument is a valid file or directory
char *dest_path = argv[2];
int dest;
struct stat dest_stat;
if (stat(dest_path, &dest_stat) == -1) {
if (errno != ENOENT) {
err(EXIT_FAILURE, "Unable to stat destination at %s", dest_path);
}
dest = open(dest_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (dest == -1) {
err(EXIT_FAILURE, "Unable to open destination file %s", dest_path);
}
} else if (is_dir(&dest_stat)) {
int dest_dir = open(dest_path, O_RDONLY);
if (dest_dir == -1) {
err(EXIT_FAILURE, "Unable to open destination directory %s", dest_path);
}
char *dest_filename = basename(source_path);
dest = openat(dest_dir, dest_filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (dest == -1) {
err(EXIT_FAILURE, "Unable to open destination file %s in %s", dest_filename, dest_path);
}
close(dest_dir);
} else if (is_reg_or_fifo(&dest_stat)) {
dest = open(dest_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (dest == -1) {
err(EXIT_FAILURE, "Unable to open destination file %s", dest_path);
}
}
if (is_same_file(source, dest)) {
fprintf(stderr, "Source and destination files cannot be the same\n");
exit(EXIT_FAILURE);
}
replace_content(source, dest);
close(source);
close(dest);
exit(EXIT_SUCCESS);
}
Makefile
CC=cc
CFLAGS=-Wall -Werror -Wextra -lbsd
bbcp: bbcp.c
$(CC) -o bbcp bbcp.c
run: bbcp
./bbcp
bsd/stdlib.hinstead ofstdlib.hon Linux? \$\endgroup\$getprognameandsetprognameare not available on Linux without including headers from libbsd. \$\endgroup\$bbcprecipe at all; you can definebbcp: bbcp.cwithout a recipe and Make will do the right thing (including applyingCFLAGS). \$\endgroup\$