git subrepo clone https://github.com/libretro/libretro-common.git deps/libretro-common
[pcsx_rearmed.git] / deps / libretro-common / samples / streams / rzip / rzip.c
CommitLineData
3719602c
PC
1/* Copyright (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (config_file_test.c).
5 * ---------------------------------------------------------------------------------------
6 *
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23#include <stdlib.h>
24#include <string.h>
25#include <stdio.h>
26#include <ctype.h>
27#include <inttypes.h>
28#include <errno.h>
29#include <time.h>
30
31#include <string/stdstring.h>
32#include <file/file_path.h>
33#include <streams/interface_stream.h>
34#include <streams/file_stream.h>
35#include <streams/rzip_stream.h>
36#include <retro_miscellaneous.h>
37
38#define FILE_TRANSFER_CHUNK_SIZE 4096
39
40enum rzip_action_type
41{
42 RZIP_ACTION_QUERY = 0,
43 RZIP_ACTION_COMPRESS,
44 RZIP_ACTION_EXTRACT
45};
46
47static void rand_str(char *dst, size_t len)
48{
49 char charset[] = "0123456789"
50 "abcdefghijklmnopqrstuvwxyz"
51 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
52
53 while (len-- > 0)
54 {
55 size_t i = (double)rand() / RAND_MAX * (sizeof(charset) - 1);
56 *dst++ = charset[i];
57 }
58 *dst = '\0';
59}
60
61int main(int argc, char *argv[])
62{
63 char in_file_path[PATH_MAX_LENGTH];
64 char out_file_path[PATH_MAX_LENGTH];
65 enum rzip_action_type action = RZIP_ACTION_QUERY;
66 intfstream_t *in_file = NULL;
67 intfstream_t *out_file = NULL;
68 int64_t in_file_size = 0;
69 int64_t in_file_raw_size = 0;
70 int64_t out_file_size = 0;
71 int64_t file_size_diff = 0;
72 int64_t total_data_read = 0;
73 bool in_file_compressed = false;
74 bool valid_args = false;
75 bool in_place = false;
76 int ret = 1;
77
78 in_file_path[0] = '\0';
79 out_file_path[0] = '\0';
80
81 /* Parse arguments */
82 if ((argc > 1) && !string_is_empty(argv[1]))
83 {
84 valid_args = true;
85
86 if (string_is_equal(argv[1], "i"))
87 action = RZIP_ACTION_QUERY;
88 else if (string_is_equal(argv[1], "a"))
89 action = RZIP_ACTION_COMPRESS;
90 else if (string_is_equal(argv[1], "x"))
91 action = RZIP_ACTION_EXTRACT;
92 else
93 valid_args = false;
94 }
95
96 /* Get input file path */
97 if (valid_args && (argc > 2) && !string_is_empty(argv[2]))
98 {
99 strlcpy(in_file_path, argv[2], sizeof(in_file_path));
100 path_resolve_realpath(in_file_path, sizeof(in_file_path), true);
101 valid_args = valid_args && !string_is_empty(in_file_path);
102 }
103 else
104 valid_args = false;
105
106 /* Ensure arguments are valid */
107 if (!valid_args)
108 {
109 fprintf(stderr, "Usage:\n");
110 fprintf(stderr, "- Query file status: %s i <input file>\n", argv[0]);
111 fprintf(stderr, "- Compress file: %s a <input file> <output file (optional)>\n", argv[0]);
112 fprintf(stderr, "- Extract file: %s x <input file> <output file (optional)>\n", argv[0]);
113 fprintf(stderr, "Omitting <output file> will overwrite <input file>\n");
114 goto end;
115 }
116
117 /* Ensure that input file exists */
118 if (!path_is_valid(in_file_path))
119 {
120 fprintf(stderr, "ERROR: Input file does not exist: %s\n", in_file_path);
121 goto end;
122 }
123
124 /* Get output file path, if specified */
125 if ((argc > 3) && !string_is_empty(argv[3]))
126 {
127 strlcpy(out_file_path, argv[3], sizeof(out_file_path));
128 path_resolve_realpath(out_file_path, sizeof(out_file_path), true);
129 }
130
131 /* If we are compressing/extracting and an
132 * output file was not specified, generate a
133 * temporary output file path */
134 if ((action != RZIP_ACTION_QUERY) &&
135 string_is_empty(out_file_path))
136 {
137 const char *in_file_name = path_basename(in_file_path);
138 char in_file_dir[PATH_MAX_LENGTH];
139
140 in_file_dir[0] = '\0';
141
142 fill_pathname_parent_dir(in_file_dir, in_file_path, sizeof(in_file_dir));
143
144 if (string_is_empty(in_file_name))
145 {
146 fprintf(stderr, "ERROR: Invalid input file: %s\n", in_file_path);
147 goto end;
148 }
149
150 srand((unsigned int)time(NULL));
151
152 for (;;)
153 {
154 char tmp_str[10] = {0};
155
156 /* Generate 'random' file name */
157 rand_str(tmp_str, sizeof(tmp_str) - 1);
158 tmp_str[0] = '.';
159
160 if (!string_is_empty(in_file_dir))
161 fill_pathname_join_special(out_file_path, in_file_dir,
162 tmp_str, sizeof(out_file_path));
163 else
164 strlcpy(out_file_path, tmp_str, sizeof(out_file_path));
165
166 strlcat(out_file_path, ".", sizeof(out_file_path));
167 strlcat(out_file_path, in_file_name, sizeof(out_file_path));
168 path_resolve_realpath(out_file_path, sizeof(out_file_path), true);
169
170 if (!path_is_valid(out_file_path))
171 break;
172 }
173
174 in_place = true;
175 }
176
177 /* Ensure that input and output files
178 * are different */
179 if (string_is_equal(in_file_path, out_file_path))
180 {
181 fprintf(stderr, "ERROR: Input and output are the same file: %s\n", in_file_path);
182 goto end;
183 }
184
185 /* Get input file size */
186 in_file_size = (int64_t)path_get_size(in_file_path);
187
188 if (in_file_size < 1)
189 {
190 fprintf(stderr, "ERROR: Input file is empty: %s\n", in_file_path);
191 goto end;
192 }
193
194 /* Open input file
195 * > Always use RZIP interface */
196 in_file = intfstream_open_rzip_file(
197 in_file_path, RETRO_VFS_FILE_ACCESS_READ);
198
199 if (!in_file)
200 {
201 fprintf(stderr, "ERROR: Failed to open input file: %s\n", in_file_path);
202 goto end;
203 }
204
205 /* Get input file compression status */
206 in_file_compressed = intfstream_is_compressed(in_file);
207
208 /* Get raw (uncompressed) input file size */
209 in_file_raw_size = intfstream_get_size(in_file);
210
211 /* If this is a query operation, just
212 * print current state */
213 if (action == RZIP_ACTION_QUERY)
214 {
215 printf("%s: %s\n",
216 in_file_compressed ? "File is in RZIP format" : "File is NOT in RZIP format",
217 in_file_path);
218 printf(" Size on disk: %" PRIi64 " bytes\n", in_file_size);
219 if (in_file_compressed)
220 printf(" Uncompressed size: %" PRIi64 " bytes\n", in_file_raw_size);
221 goto end;
222 }
223
224 /* Check whether file is already in the
225 * requested state */
226 if ((in_file_compressed && (action == RZIP_ACTION_COMPRESS)) ||
227 (!in_file_compressed && (action == RZIP_ACTION_EXTRACT)))
228 {
229 printf("Input file is %s: %s\n",
230 in_file_compressed ?
231 "already in RZIP format - cannot compress" :
232 "not in RZIP format - cannot extract",
233 in_file_path);
234 goto end;
235 }
236
237 /* Check whether output file already exists */
238 if (path_is_valid(out_file_path))
239 {
240 char reply[8];
241
242 reply[0] = '\0';
243
244 printf("WARNING: Output file already exists: %s\n", out_file_path);
245 printf(" Overwrite? [Y/n]: ");
246 fgets(reply, sizeof(reply), stdin);
247 if (reply[0] != 'Y')
248 goto end;
249 }
250
251 /* Open output file */
252 if (in_file_compressed)
253 out_file = intfstream_open_file(
254 out_file_path, RETRO_VFS_FILE_ACCESS_WRITE,
255 RETRO_VFS_FILE_ACCESS_HINT_NONE);
256 else
257 out_file = intfstream_open_rzip_file(
258 out_file_path, RETRO_VFS_FILE_ACCESS_WRITE);
259
260 if (!out_file)
261 {
262 fprintf(stderr, "ERROR: Failed to open output file: %s\n", out_file_path);
263 goto end;
264 }
265
266 /* Start file transfer */
267 printf("%s file\n", in_file_compressed ? "Extracting" : "Compressing");
268 printf(" From: %s\n", in_file_path);
269 printf(" To: %s\n", in_place ? in_file_path : out_file_path);
270
271 for (;;)
272 {
273 int64_t data_written = 0;
274 uint8_t buffer[FILE_TRANSFER_CHUNK_SIZE];
275 /* Read a single chunk from input file */
276 int64_t data_read = intfstream_read(
277 in_file, buffer, sizeof(buffer));
278
279 if (data_read < 0)
280 {
281 fprintf(stderr, "ERROR: Failed to read from input file: %s\n", in_file_path);
282 goto end;
283 }
284
285 total_data_read += data_read;
286
287 /* Check whether we have reached the end of the file */
288 if (data_read == 0)
289 {
290 /* Close files */
291 intfstream_flush(out_file);
292 intfstream_close(out_file);
293 free(out_file);
294 out_file = NULL;
295
296 intfstream_close(in_file);
297 free(in_file);
298 in_file = NULL;
299
300 break;
301 }
302
303 /* Write chunk to backup file */
304 data_written = intfstream_write(out_file, buffer, data_read);
305
306 if (data_written != data_read)
307 {
308 fprintf(stderr, "ERROR: Failed to write to output file: %s\n", out_file_path);
309 goto end;
310 }
311
312 /* Update progress */
313 printf("\rProgress: %" PRIi64 " %%", total_data_read * 100 / in_file_raw_size);
314 fflush(stdout);
315 }
316 printf("\rProgress: 100 %%\n");
317
318 /* Display final status 'report' */
319 printf("%s complete:\n", in_file_compressed ? "Extraction" : "Compression");
320
321 out_file_size = (int64_t)path_get_size(out_file_path);
322 file_size_diff = (in_file_size > out_file_size) ?
323 (in_file_size - out_file_size) :
324 (out_file_size - in_file_size);
325
326 printf(" %" PRIi64 " -> %" PRIi64 " bytes [%" PRIi64 " %% %s]\n",
327 in_file_size, out_file_size,
328 file_size_diff * 100 / in_file_size,
329 (out_file_size >= in_file_size) ?
330 "increase" : "decrease");
331
332 /* If this was an in-place operation,
333 * replace input file with output file */
334 if (in_place)
335 {
336 filestream_delete(in_file_path);
337 if (filestream_rename(out_file_path, in_file_path))
338 {
339 fprintf(stderr, "ERROR: Failed to rename temporary file\n");
340 fprintf(stderr, " From: %s\n", out_file_path);
341 fprintf(stderr, " To: %s\n", in_file_path);
342 goto end;
343 }
344 }
345
346 ret = 0;
347
348end:
349 if (in_file)
350 {
351 intfstream_close(in_file);
352 free(in_file);
353 }
354
355 if (out_file)
356 {
357 intfstream_close(out_file);
358 free(out_file);
359 }
360
361 return ret;
362}