libretro: adjust some option text
[pcsx_rearmed.git] / deps / libretro-common / samples / streams / rzip / rzip.c
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
40 enum rzip_action_type
41 {
42         RZIP_ACTION_QUERY = 0,
43         RZIP_ACTION_COMPRESS,
44         RZIP_ACTION_EXTRACT
45 };
46
47 static 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
61 int 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
348 end:
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 }