lazysphere/command/cp.c

315 lines
8.4 KiB
C
Raw Normal View History

2023-06-08 01:33:44 +00:00
/**
* file: cp.c
* command: cp
* author: Tyler Murphy
*/
2023-05-06 04:39:44 +00:00
#include "command.h"
#include "lslib.h"
2023-05-03 16:17:56 +00:00
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
2023-05-06 04:39:44 +00:00
#include <stdlib.h>
#include <string.h>
2023-05-03 16:17:56 +00:00
#include <sys/stat.h>
2023-05-15 01:43:02 +00:00
#include <errno.h>
2023-05-03 16:17:56 +00:00
#include <unistd.h>
2023-06-08 01:33:44 +00:00
/**
* Command flags to be used with cp
*/
2023-05-03 16:17:56 +00:00
static struct {
2023-06-08 01:33:44 +00:00
bool recurse; /* Recurse copy directorys */
bool preserve; /* Preserve file attributes */
bool sym_link; /* Symlink files instead of copy */
bool hard_link; /* Hardlink files instead of copy */
bool verbose; /* Verbose the output */
2023-05-03 16:17:56 +00:00
} flags;
2023-06-08 01:33:44 +00:00
/**
* Help function for cp
*/
2023-05-03 16:17:56 +00:00
static void help(void) {
printf("Usage: cp [-rplsv] SOURCE... DEST\n\n");
printf("Copy SOURCEs to DEST\n\n");
printf("\t-R,-r\tRecurse\n");
printf("\t-p\tPreserve file attributes if possible\n");
printf("\t-l,-s\tCreate (sym)links\n");
printf("\t-v\tVerbose\n");
}
2023-06-08 01:33:44 +00:00
/**
* Takes in each argument that has a single - and parses it
* @param c the character after the -
* @param next the next argument in argv that hasnt been parsed
* @reutrn if the next arg was used or if the arg was invalid
*/
2023-05-03 16:17:56 +00:00
static int short_arg(char c, char* next) {
UNUSED(next);
switch (c) {
case 'R':
case 'r':
flags.recurse = true;
break;
case 'p':
flags.preserve = true;
break;
case 'l':
flags.hard_link = true;
flags.sym_link = false;
break;
case 's':
flags.sym_link = true;
flags.hard_link = false;
break;
case 'v':
flags.verbose = true;
break;
default:
return ARG_INVALID;
}
return ARG_UNUSED;
}
2023-06-08 01:33:44 +00:00
/**
* Takes in two files paths and copies the data from one path to another
* @param from the file to copy from
* @param to the file to copy to
* @returns if the file successfully coppied
*/
2023-05-03 16:17:56 +00:00
static bool copy_file(char* from, char* to) {
2023-06-08 01:33:44 +00:00
#define BS 1024 /* The block size of copy with */
2023-05-04 20:10:37 +00:00
2023-06-08 01:33:44 +00:00
FILE *from_f, *to_f; /* file pointers to cope data from and to */
char buf[BS]; /* te file copy buffer */
int read; /* the amount of bytes read */
2023-05-04 20:10:37 +00:00
2023-06-08 01:33:44 +00:00
/* attempt to get a read stream from the src file */
2023-05-04 20:10:37 +00:00
from_f = get_file_s(from, "r");
2023-05-03 16:17:56 +00:00
if (from_f == NULL) { return false; }
2023-06-08 01:33:44 +00:00
/* attempt to get a write streams from the dest file */
2023-05-04 20:10:37 +00:00
to_f = get_file_s(to, "w");
2023-05-03 16:17:56 +00:00
if (to_f == NULL) { fclose(from_f); return false; }
2023-06-08 01:33:44 +00:00
/* while data is read copy the data to the dest */
2023-05-03 16:17:56 +00:00
while ((read = fread(buf, 1, BS, from_f)) > 0) {
fwrite(buf, 1, read, to_f);
}
2023-06-08 01:33:44 +00:00
/* output if verbose */
2023-05-03 16:17:56 +00:00
if (flags.verbose) {
output("copied '%s'", from);
}
return true;
}
2023-06-08 01:33:44 +00:00
/**
* Takes in two file paths and symlinks from one to the other path
* @param from the file to symlink
* @param to the path to put the symlink
* @returns if the file successfully symlinked
*/
2023-05-03 16:17:56 +00:00
static bool symlink_file(char* from, char* to) {
2023-06-08 01:33:44 +00:00
/* symlink the file and error if failed */
2023-05-03 16:17:56 +00:00
if (symlink(from, to) < 0) {
2023-05-15 01:43:02 +00:00
error_s("failed to symlink '%s'", from);
2023-05-03 16:17:56 +00:00
return false;
} else if (flags.verbose) {
2023-06-08 01:33:44 +00:00
output("symlinked '%s'", from); /* output if verbose */
2023-05-03 16:17:56 +00:00
}
return true;
}
2023-06-08 01:33:44 +00:00
/**
* Takes in two file paths and hardlinks from one to the other path
* @param from the file to hardlink
* @param the path to put the hardlink
* @returns fi the fule successfully hardlinked
*/
2023-05-03 16:17:56 +00:00
static bool hardlink_file(char* from, char* to) {
2023-06-08 01:33:44 +00:00
/* hard link the file and error if failed */
2023-05-03 16:17:56 +00:00
if (link(from, to) < 0) {
2023-05-15 01:43:02 +00:00
error_s("failed to hardlink '%s'", from);
2023-05-03 16:17:56 +00:00
return false;
} else if (flags.verbose) {
2023-06-08 01:33:44 +00:00
output("hardlinked '%s'", from); /* output if verbose */
2023-05-03 16:17:56 +00:00
}
return true;
}
2023-06-08 01:33:44 +00:00
/**
* Copies data from one path to another using the set path buffers and
* flags. Make sure to set these before running this function.
* @param s the stat of the file being coppied
*/
2023-05-03 16:17:56 +00:00
static void run_copy(struct stat* s) {
2023-06-08 01:33:44 +00:00
/* read file paths from the path buffers */
2023-05-03 16:17:56 +00:00
char* from = get_path_buffer();
char* to = get_path_buffer_2();
2023-06-08 01:33:44 +00:00
/* choose copy method based on given flags */
2023-05-03 16:17:56 +00:00
bool result;
2023-06-08 01:33:44 +00:00
if (flags.sym_link) {
result = symlink_file(from, to); /* symlink */
2023-05-03 16:17:56 +00:00
} else if (flags.hard_link) {
2023-06-08 01:33:44 +00:00
result = hardlink_file(from, to); /* hardlink */
2023-05-03 16:17:56 +00:00
} else {
2023-06-08 01:33:44 +00:00
result = copy_file(from, to); /* copy */
2023-05-03 16:17:56 +00:00
}
2023-06-08 01:33:44 +00:00
/* if failed return */
2023-05-03 16:17:56 +00:00
if (!result) return;
if (!flags.preserve) return;
2023-06-08 01:33:44 +00:00
/* if set to preserve file attribs, copy over ownership and permissions */
2023-05-03 16:17:56 +00:00
if (chmod(to, s->st_mode) < 0) {
2023-06-08 01:33:44 +00:00
error_s("cannot chmod '%s'", to); /* error if failed */
2023-05-03 16:17:56 +00:00
return;
}
if (chown(to, s->st_uid, s->st_gid) < 0) {
2023-06-08 01:33:44 +00:00
error_s("cannot chown '%s'", to); /* error if failed */
2023-05-03 16:17:56 +00:00
return;
}
}
2023-06-08 01:33:44 +00:00
/* predefine function definition */
2023-05-03 16:17:56 +00:00
static void cp_file(char* path);
2023-06-08 01:33:44 +00:00
/**
* Copies all files in a diretory specified in the path buffer, to the dest.
* Make sure both the src and dest path buffers are set before calling.
* @param s the stat of the directory
*/
2023-05-03 16:17:56 +00:00
static void cp_directory(struct stat* s) {
2023-05-04 20:10:37 +00:00
DIR* d;
struct dirent* file;
2023-06-08 01:33:44 +00:00
/* if were not recursing ignore directory and error */
2023-05-03 16:17:56 +00:00
if (!flags.recurse) {
error_s("-r not specified; omitting directory '%s'", get_path_buffer());
return;
}
2023-06-08 01:33:44 +00:00
/* if we failed to make the directory to copy data into error and return */
2023-05-03 16:17:56 +00:00
if (mkdir(get_path_buffer_2(), s->st_mode) < 0 && errno != EEXIST) {
2023-05-15 01:43:02 +00:00
error_s("cannot create directory '%s'", get_path_buffer_2());
2023-05-03 16:17:56 +00:00
return;
}
2023-06-08 01:33:44 +00:00
/* open directory, if failed error and return */
2023-05-04 20:10:37 +00:00
d = opendir(get_path_buffer());
2023-05-03 16:17:56 +00:00
if (d == NULL) {
2023-05-15 01:43:02 +00:00
error_s("cannot open directory '%s'", get_path_buffer());
2023-05-03 16:17:56 +00:00
return;
}
2023-06-08 01:33:44 +00:00
/* copy each file in the directory */
2023-05-03 16:17:56 +00:00
while ((file = readdir(d)) != NULL) {
if (is_dot_dir(file->d_name)) continue;
cp_file(file->d_name);
}
}
2023-06-08 01:33:44 +00:00
/**
* Copies a given file path to the destination given in the 2nd path buffer
* Make sure path buffer 2 is set before running this function
* @param path the file to copy
*/
2023-05-03 16:17:56 +00:00
static void cp_file(char* path) {
2023-06-08 01:33:44 +00:00
/* load src and dest into path buffers */
2023-05-03 16:17:56 +00:00
int save = push_path_buffer(path);
2023-06-08 01:33:44 +00:00
int save2 = push_path_buffer_2(get_last_component(path));
/* if we cannot get file info error and return */
2023-05-03 16:17:56 +00:00
struct stat s;
if (lstat(get_path_buffer(), &s) < 0) {
pop_path_buffer(save);
2023-05-15 01:43:02 +00:00
error_s("cannot stat '%s'", get_path_buffer());
2023-05-03 16:17:56 +00:00
return;
}
2023-06-08 01:33:44 +00:00
/* if the file is a directory copy it as a directory, else copy it as a file */
2023-05-03 16:17:56 +00:00
if (S_ISDIR(s.st_mode)) {
cp_directory(&s);
} else {
run_copy(&s);
}
2023-06-08 01:33:44 +00:00
/* cleanup */
2023-05-03 16:17:56 +00:00
pop_path_buffer(save);
pop_path_buffer_2(save2);
}
2023-06-08 01:33:44 +00:00
/**
* Copes file from a src path to a dest path
*/
2023-05-15 14:57:33 +00:00
COMMAND(cp_main) {
2023-05-03 16:17:56 +00:00
2023-05-04 20:10:37 +00:00
int start, i;
struct stat s;
2023-06-08 01:33:44 +00:00
/* define default flags */
2023-05-03 16:17:56 +00:00
flags.hard_link = false;
flags.sym_link = false;
flags.preserve = false;
flags.recurse = false;
flags.verbose = false;
2023-06-08 01:33:44 +00:00
/* parse arguments */
2023-05-04 20:10:37 +00:00
start = parse_args(argc, argv, help, short_arg, NULL);
2023-05-03 16:17:56 +00:00
2023-06-08 01:33:44 +00:00
/* if not enough arguments passed show help message and quit */
2023-05-03 16:17:56 +00:00
if (argc - start < 2) {
global_help(help);
}
2023-05-04 20:10:37 +00:00
/* only when 2 args and first is a file, the 2nd will be a file */
2023-05-03 16:17:56 +00:00
if (argc - start == 2) {
2023-06-08 01:33:44 +00:00
/* if cannot read file info error and exit */
2023-05-03 16:17:56 +00:00
struct stat s;
if (lstat(argv[start], &s) < 0) {
2023-05-15 01:43:02 +00:00
error("cannot stat '%s'", argv[start]);
2023-05-03 16:17:56 +00:00
}
2023-06-08 01:33:44 +00:00
/* load src and dest into path buffers */
2023-05-03 16:17:56 +00:00
push_path_buffer(argv[argc-2]);
push_path_buffer_2(argv[argc-1]);
2023-06-08 01:33:44 +00:00
/* if the file is a directory copy it as a directory, else copy it as a file */
2023-05-03 16:17:56 +00:00
if (!S_ISDIR(s.st_mode)) {
run_copy(&s);
} else {
cp_directory(&s);
}
2023-06-08 01:33:44 +00:00
/* no need to cleanup path buffers since command is done */
2023-05-03 16:17:56 +00:00
return EXIT_SUCCESS;
}
2023-06-08 01:33:44 +00:00
/* otherwise treat the dest argument as a directory */
2023-05-03 16:17:56 +00:00
2023-06-08 01:33:44 +00:00
/* push dest directory */
2023-05-03 16:17:56 +00:00
push_path_buffer_2(argv[argc-1]);
2023-06-08 01:33:44 +00:00
/* if we cannot read dest info error and return */
2023-05-03 16:17:56 +00:00
if (lstat(get_path_buffer_2(), &s) < 0) {
2023-05-15 01:43:02 +00:00
error("target: '%s'", get_path_buffer_2());
2023-05-03 16:17:56 +00:00
}
2023-06-08 01:33:44 +00:00
/* copy each file into dest directory */
2023-05-04 20:10:37 +00:00
for (i = start; i < argc - 1; i++) {
2023-05-03 16:17:56 +00:00
cp_file(argv[i]);
}
return EXIT_SUCCESS;
}