Skip to content

Commit

Permalink
v1.1
Browse files Browse the repository at this point in the history
API : Changes in `filecopy_error` enum. Internal changes to spelling and formatting.
Util : Changes to using argv[0] for program name and FILECOPY_VERSION => VERSION, plus formatting improvements and changes to handle `filecopy_error`s.
  • Loading branch information
a-p-jo authored Jun 16, 2022
1 parent d2ada24 commit b483dbd
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 63 deletions.
37 changes: 9 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
# filecopy v1.0
## Utility Usage

### About
filecopy is both a tiny utility + a tiny library/API for copying a stream's contents to another in a _simple , safe and efficient_ manner.

### Utility Usage
_If you can use `cp` , you can use `filecopy` !_

1. Clone the repo, compile all `.c` files in it. For example, on GNU/Linux or macOS , you might run :
1. Compile all `.c` files in the repo. For example, on \*nix systems , you might run :
```sh
git clone https://github.com/a-p-jo/filecopy
cc filecopy/*.c -o filecopy -Ofast -flto
git clone https://github.com/a-p-jo/filecopy.git
cc filecopy/*.c -o fcp -Ofast -flto # Compile to exe named "fcp"
rm -r filecopy # Repo no longer needed, delete it
```
2. Get the help menu :
```
$ filecopy
filecopy v1.0 : A simple, safe, performant and reliable file copying program.
$ ./fcp
fcp v1.0
Usage : filecopy <src> [dst (optional, stdout by default)] [overwrite dst? (y/n) (optional)]
Copies the contents of src to dst if given, else to stdout.
Copies the contents of src to dst (if given, else to stdout).
If dst does not exist, creates it. If it pre-exists, if 'y' is specified, continues.
If 'n' is specified, aborts. Else, asks for permission before overwriting.
Example : filecopy archlinux.iso /dev/sda y
```
NOTES :

- _`filecopy` will treat arguments *exactly* in the order specified above._

- _Any arguments beyond the first three, and the third if invalid, are ignored automatically_.

Examples :
```
filecopy ~/.zshrc /mnt/backups/zhrc.bak y
filecopy /dev/sda | gzip > drive.bak
filecopy settings.json D:\backups\settings.json
Example : fcp archlinux.iso /dev/sda y
```
27 changes: 15 additions & 12 deletions filecopy.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,27 @@ static inline uintmax_t divround(uintmax_t x, uintmax_t y) { return (x + y/2) /
typedef enum {
filesize_error_none,
filesize_error_unseekable, /* stream isn't a regular file, has no real "size" */
fileszie_error_spurious /* sudden failure, stream indeterminate. */
filesize_error_spurious /* sudden failure, stream indeterminate. */
} filesize_error;

typedef struct { uintmax_t nbytes; filesize_error err; } filesize_result;

static inline filesize_result filesize_result_err(filesize_error e) { return (filesize_result){.err = e}; }

/* Only meaningful if f is a regular file opened in binary mode */
static inline filesize_result filesize(FILE *f)
{
intmax_t curpos = filecopy_ftell(f);
if (curpos < 0 || filecopy_fseek(f, 0, SEEK_END) != 0)
return filesize_result_err(filesize_error_unseekable);
return (filesize_result) {.err = filesize_error_unseekable};

intmax_t endpos = filecopy_ftell(f);
int tryreset = filecopy_fseek(f, curpos, SEEK_SET);
if (endpos < 0) /* Is spurious only if trying to reset fails. */
return filesize_result_err(tryreset == 0? filesize_error_unseekable : fileszie_error_spurious);
return (filesize_result) {
.err = tryreset != 0? filesize_error_spurious : filesize_error_unseekable
};
else if (tryreset != 0)
return filesize_result_err(fileszie_error_spurious);
return (filesize_result) {.err = filesize_error_spurious};

return (filesize_result) {.nbytes = endpos-curpos};
}

Expand All @@ -50,8 +50,8 @@ filecopy_result filecopy(FILE *dst, FILE *src, uintmax_t nbytes, void(*cb)(uint_
/* If we have to callback to report progress, we need to know the stream's size. */
if (cb && !nbytes) {
filesize_result srcsz = filesize(src);
if (srcsz.err == fileszie_error_spurious) /* Stream unrecoverable */
return (filecopy_result) {.err = filecopy_error_seek};
if (srcsz.err == filesize_error_spurious) /* Stream unrecoverable */
return (filecopy_result) {.err = filecopy_error_src};
else
nbytes = srcsz.nbytes;
}
Expand All @@ -66,7 +66,10 @@ filecopy_result filecopy(FILE *dst, FILE *src, uintmax_t nbytes, void(*cb)(uint_
if (nbytes && nbytes < res.bytes_copied+sizeof(buf))
toread = nbytes - res.bytes_copied; /* In last loop read only as much as left */

if (fread(buf, 1, toread, src) == toread && fwrite(buf, 1, toread, dst) == toread) {
if (
fread(buf, 1, toread, src) == toread
&& fwrite(buf, 1, toread, dst) == toread
) {
res.bytes_copied += toread;
if (one_percent) {
uint_least8_t curprog = divround(res.bytes_copied, one_percent);
Expand All @@ -78,9 +81,9 @@ filecopy_result filecopy(FILE *dst, FILE *src, uintmax_t nbytes, void(*cb)(uint_
}

if (ferror(src))
res.err = filecopy_error_read;
res.err = filecopy_error_src;
else if (ferror(dst))
res.err = filecopy_error_write;
res.err = filecopy_error_dst;
else if (nbytes && res.bytes_copied < nbytes)
res.err = filecopy_error_early_eof;
return res;
Expand Down
12 changes: 7 additions & 5 deletions filecopy.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
#include <stdio.h> /* FILE */

typedef enum {
filecopy_error_none, /* Successful, operation complete. */
filecopy_error_none, /* Successful, operation complete. */
filecopy_error_early_eof, /* EOF on src before n bytes read, stopped. */
filecopy_error_seek, /* Seeking failed, src indeterminate. Only if callback !NULL && nbytes 0. */
/* IO error, dst and src indeterminate */
filecopy_error_read, /* ferror(src) is true */
filecopy_error_write /* ferror(dst) is true */
filecopy_error_src, /* Error reading/seeking src. */
filecopy_error_dst /* Error writing dst. */
} filecopy_error;

typedef struct { uintmax_t bytes_copied; filecopy_error err; } filecopy_result;
Expand All @@ -21,7 +20,10 @@ typedef struct { uintmax_t bytes_copied; filecopy_error err; } filecopy_result;
* If callback is not NULL, calls it with current progress %
* if nbytes is non-zero or src is seekable.
*/
filecopy_result filecopy(FILE *dst, FILE *src, uintmax_t nbytes, void (*callback)(uint_least8_t progress_percentage));
filecopy_result filecopy(
FILE *dst, FILE *src, uintmax_t nbytes,
void (*callback)(uint_least8_t progress_percentage)
);

#endif

41 changes: 23 additions & 18 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,46 @@
#include <stdbool.h>
#include "filecopy.h"

#define NAME "filecopy v1.0"
#define VERSION "1.1"

typedef struct { long double nunits; char suffix; } bytes_fmt_result;

static inline bytes_fmt_result bytes_fmt(uintmax_t bytes)
{
static const char suffix[] = {'K', 'M', 'G', 'T'};

bytes_fmt_result res = {bytes, 'B'};
for (uint_least8_t i = 0; i < sizeof(suffix) && res.nunits/1024 >= 1; res.nunits /= 1024, res.suffix = suffix[i++])
for (
uint_least8_t i = 0;
i < sizeof(suffix) && res.nunits/1024 >= 1;
res.nunits /= 1024, res.suffix = suffix[i++]
)
;
return res;
}

static inline bool streq(const char *a, const char *b) { return strcmp(a, b) == 0; }
static inline bool streq(const char *a, const char *b)
{
return strcmp(a, b) == 0;
}

static void print_progress(uint_least8_t percentage) { printf("%03u%%\r", percentage); fflush(stdout); }
static void print_progress(uint_least8_t percentage)
{
printf("%03u%%\r", percentage);
fflush(stdout);
}

int main(int argc, char **argv)
{
if (argc < 2 || streq("-h",argv[1]) || streq("--help",argv[1]))
fputs(
NAME" : A simple, safe, performant and reliable file copying program.\n"
fprintf(
stderr, "%s v"VERSION"\n"
"Usage : filecopy <src> [dst (optional, stdout by default)] [overwrite dst? (y/n) (optional)]\n"
"\n"
"Copies the contents of src to dst if given, else to stdout.\n"
"Copies the contents of src to dst (if given, else to stdout).\n"
"If dst does not exist, creates it. If it pre-exists, if 'y' is specified, continues.\n"
"If 'n' is specified, aborts. Else, asks for permission before overwriting.\n"
"\n"
"Example : filecopy archlinux.iso /dev/sda y\n", stderr
"Example : %s archlinux.iso /dev/sda y\n", argv[0], argv[0]
), exit(EXIT_FAILURE);

FILE *src = fopen(argv[1],"rb"), *dst = stdout;
Expand All @@ -50,7 +60,8 @@ int main(int argc, char **argv)
if (streq("y", argv[3]) || streq("Y", argv[3]))
;
else if (streq("n", argv[3]) || streq("N", argv[4]))
fprintf(stderr, "\"%s\" pre-exists. Aborted.\n", argv[2]), exit(EXIT_SUCCESS);
fprintf(stderr, "\"%s\" pre-exists. Aborted.\n", argv[2]),
exit(EXIT_SUCCESS);
else ask : {
fprintf(stderr, "\"%s\" pre-exists. Overwrite ? (y/n) : ", argv[2]);
int ch = getchar();
Expand Down Expand Up @@ -78,19 +89,13 @@ int main(int argc, char **argv)
bfmt.nunits, bfmt.suffix, res.bytes_copied
), exit(EXIT_SUCCESS);
}
case filecopy_error_seek :
fprintf(
stderr, "Error : Unable to seek in \"%s\"%s%s",
argv[1], errno? " : " : ".",
errno? strerror(errno) : ""
), exit(EXIT_FAILURE);
case filecopy_error_read :
case filecopy_error_src :
fprintf(
stderr, "Error : Unknown fatal error reading \"%s\" %s%s\n",
argv[1], errno? " : " : ".",
errno? strerror(errno) : ""
), exit(EXIT_FAILURE);
case filecopy_error_write :
case filecopy_error_dst :
fprintf(
stderr, "Error : Unknown fatal error writing to \"%s\" %s%s\n",
argv[2]? argv[2] : "stdout", errno? " : " : ".",
Expand Down

0 comments on commit b483dbd

Please sign in to comment.