├── .circleci └── config.yml ├── .gitignore ├── Makefile ├── README.md └── main.c /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | workflows: 4 | version: 2 5 | compile: 6 | jobs: 7 | - build 8 | 9 | jobs: 10 | build: 11 | docker: 12 | - image: gcc:8.2 13 | steps: 14 | - checkout 15 | - run: make slice 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .DS_Store 3 | slice 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= clang 2 | 3 | .PHONY: all install uninstall clean 4 | 5 | all: slice 6 | 7 | slice: main.c 8 | $(CC) main.c -std=c99 -o slice -Wall -Wextra -O3 9 | 10 | install: slice 11 | cp slice /usr/local/bin/slice 12 | 13 | uninstall: 14 | rm -f /usr/local/bin/slice 15 | 16 | clean: 17 | rm -f slice 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍕 slice 2 | 3 | A no-nonsense tool to copy a blob of data from one file to another. 4 | 5 | Opens `input`, seeks to `start`, reads `length` bytes, and writes them to `output`. 6 | 7 | ## Options 8 | 9 | ``` 10 | -i,--input 11 | Source file path to read from. 12 | [required] 13 | 14 | -o,--output 15 | Destination file path to write to. 16 | [required] 17 | 18 | -s,--start 19 | Start offset in bytes. 20 | [optional, defaults to 0 if omitted] 21 | 22 | -n,--length 23 | Length of data to copy, in bytes. 24 | [optional, reads to the end of input if omitted] 25 | ``` 26 | 27 | ## Building 28 | 29 | On any \*nix system it _should_ be as simple as: 30 | 31 | ``` 32 | git clone https://github.com/lunasorcery/slice.git 33 | cd slice 34 | make slice && make install 35 | ``` 36 | 37 | This depends on a C compiler being available. If you encounter issues, please let me know. 38 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include /* how is this not a language feature? !*/ 4 | #include 5 | #include 6 | 7 | #define eprintf(...) fprintf(stderr, __VA_ARGS__) 8 | 9 | /* save ourselves an include */ 10 | int64_t min(int64_t a, int64_t b) { return a < b ? a : b; } 11 | 12 | int main(int argc, char** argv) 13 | { 14 | int const BUFFER_SIZE = 1024*1024; 15 | 16 | char const* pathIn = NULL; 17 | char const* pathOut = NULL; 18 | int64_t start = 0; 19 | int64_t length = 0; 20 | bool readToEnd = true; 21 | 22 | while (1) 23 | { 24 | static struct option long_options[] = { 25 | { "input", required_argument, 0, 'i' }, 26 | { "output", required_argument, 0, 'o' }, 27 | { "start", required_argument, 0, 's' }, 28 | { "length", required_argument, 0, 'n' }, 29 | { 0, 0, 0, 0 } 30 | }; 31 | 32 | int option_index = 0; 33 | int c = getopt_long(argc, argv, "i:o:s:n:", long_options, &option_index); 34 | 35 | if (c == -1) { 36 | break; 37 | } 38 | 39 | switch (c) { 40 | case 'i': { 41 | pathIn = optarg; 42 | break; 43 | } 44 | case 'o': { 45 | pathOut = optarg; 46 | break; 47 | } 48 | case 's': { 49 | start = strtol(optarg, NULL, 10); 50 | if (start < 0) { 51 | eprintf("Start offset must be greater than or equal to zero.\n"); 52 | return 1; 53 | } 54 | break; 55 | } 56 | case 'n': { 57 | length = strtol(optarg, NULL, 10); 58 | if (length <= 0) { 59 | eprintf("Length must be greater than zero.\n"); 60 | return 1; 61 | } 62 | readToEnd = false; 63 | break; 64 | } 65 | } 66 | } 67 | 68 | if (!pathIn || !pathOut) 69 | { 70 | eprintf("usage:\n"); 71 | eprintf("slice -i -o -s -n \n"); 72 | return 1; 73 | } 74 | 75 | FILE* fhIn = fopen(pathIn, "rb"); 76 | if (!fhIn) 77 | { 78 | eprintf("Failed to open '%s' for reading.\n", pathIn); 79 | return 1; 80 | } 81 | 82 | FILE* fhOut = fopen(pathOut, "wb"); 83 | if (!fhOut) 84 | { 85 | eprintf("Failed to open '%s' for writing.\n", pathOut); 86 | return 1; 87 | } 88 | 89 | fseek(fhIn, start, SEEK_SET); 90 | 91 | unsigned char buffer[BUFFER_SIZE]; 92 | 93 | int64_t totalBytesCopied = 0; 94 | if (readToEnd) 95 | { 96 | int64_t bytesRead; 97 | do { 98 | bytesRead = fread(buffer, 1, BUFFER_SIZE, fhIn); 99 | if (bytesRead > 0) 100 | { 101 | fwrite(buffer, 1, bytesRead, fhOut); 102 | totalBytesCopied += bytesRead; 103 | } 104 | } while (bytesRead != 0); 105 | 106 | printf( 107 | "Copied %lld bytes from '%s'+0x%llx to '%s'\n", 108 | totalBytesCopied, 109 | pathIn, 110 | start, 111 | pathOut 112 | ); 113 | } 114 | else 115 | { 116 | while (totalBytesCopied < length) 117 | { 118 | int64_t const bytesRead = fread(buffer, 1, BUFFER_SIZE, fhIn); 119 | if (bytesRead > 0) 120 | { 121 | int64_t const bytesToWrite = min(length - totalBytesCopied, bytesRead); 122 | fwrite(buffer, 1, bytesToWrite, fhOut); 123 | totalBytesCopied += bytesToWrite; 124 | } 125 | else 126 | { 127 | break; 128 | } 129 | } 130 | 131 | if (totalBytesCopied == length) 132 | { 133 | printf( 134 | "Copied %lld bytes from '%s'+0x%llx to '%s'\n", 135 | totalBytesCopied, 136 | pathIn, 137 | start, 138 | pathOut 139 | ); 140 | } 141 | else 142 | { 143 | if (totalBytesCopied == 0) 144 | { 145 | eprintf( 146 | "Attempted to copy %lld bytes from '%s'+0x%llx to '%s', but no data was available.\n", 147 | length, 148 | pathIn, 149 | start, 150 | pathOut 151 | ); 152 | } 153 | else 154 | { 155 | eprintf( 156 | "Attempted to copy %lld bytes from '%s'+0x%llx to '%s', but only %lld %s available.\n", 157 | length, 158 | pathIn, 159 | start, 160 | pathOut, 161 | totalBytesCopied, 162 | totalBytesCopied == 1 ? "byte was" : "bytes were" 163 | ); 164 | } 165 | fclose(fhIn); 166 | fclose(fhOut); 167 | return 1; 168 | } 169 | } 170 | 171 | fclose(fhIn); 172 | fclose(fhOut); 173 | } 174 | --------------------------------------------------------------------------------