releasing package exfatprogs version 1.2.8-1
[sven/exfatprogs.git] / fsck / repair.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  *  Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
4  */
5 #include <stdio.h>
6 #include <string.h>
7 #include <stdarg.h>
8 #include <stdlib.h>
9 #include <errno.h>
10
11 #include "exfat_ondisk.h"
12 #include "libexfat.h"
13 #include "repair.h"
14 #include "exfat_fs.h"
15 #include "fsck.h"
16
17 struct exfat_repair_problem {
18         er_problem_code_t       prcode;
19         unsigned int            flags;
20         unsigned int            prompt_type;
21         unsigned int            default_number;
22         unsigned int            bypass_number;
23         unsigned int            max_number;
24 };
25
26 /* Problem flags */
27 #define ERF_PREEN_YES           0x00000001
28 #define ERF_DEFAULT_YES         0x00000002
29 #define ERF_DEFAULT_NO          0x00000004
30
31 /* Prompt types */
32 #define ERP_FIX                 0x00000001
33 #define ERP_TRUNCATE            0x00000002
34 #define ERP_DELETE              0x00000003
35 #define ERP_RENAME              0x00000004
36
37 static const char *prompts[] = {
38         "Repair",
39         "Fix",
40         "Truncate",
41         "Delete",
42         "Select",
43 };
44
45 static struct exfat_repair_problem problems[] = {
46         {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
47         {ER_BS_BOOT_REGION, 0, ERP_FIX, 0, 0, 0},
48         {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0},
49         {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0},
50         {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0},
51         {ER_DE_UPCASE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
52         {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
53         {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0},
54         {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0},
55         {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
56         {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
57         {ER_DE_DOT_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
58         {ER_DE_DUPLICATED_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
59         {ER_DE_INVALID_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
60         {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
61         {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
62         {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
63         {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
64         {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
65         {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
66         {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
67         {ER_VENDOR_GUID, ERF_DEFAULT_NO, ERP_FIX, 0, 0, 0},
68 };
69
70 static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
71 {
72         unsigned int i;
73
74         for (i = 0; i < sizeof(problems)/sizeof(problems[0]); i++) {
75                 if (problems[i].prcode == prcode) {
76                         return &problems[i];
77                 }
78         }
79         return NULL;
80 }
81
82 static int ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr)
83 {
84         int repair = 0;
85         char answer[8];
86
87         if (fsck->options & FSCK_OPTS_REPAIR_NO ||
88             pr->flags & ERF_DEFAULT_NO)
89                 repair = 0;
90         else if (fsck->options & FSCK_OPTS_REPAIR_YES ||
91                  pr->flags & ERF_DEFAULT_YES)
92                 repair = 1;
93         else {
94                 if (fsck->options & FSCK_OPTS_REPAIR_ASK) {
95                         do {
96                                 if (pr->prompt_type & ERP_RENAME) {
97                                         printf("%s (Number: ?) ",
98                                                prompts[pr->prompt_type]);
99                                 } else {
100                                         printf(". %s (y/N)? ",
101                                                prompts[pr->prompt_type]);
102                                 }
103                                 fflush(stdout);
104
105                                 if (!fgets(answer, sizeof(answer), stdin))
106                                         continue;
107
108                                 if (pr->prompt_type & ERP_RENAME) {
109                                         unsigned int number = atoi(answer);
110
111                                         if (number > 0 && number < pr->max_number)
112                                                 return number;
113                                 } else {
114                                         if (strcasecmp(answer, "Y\n") == 0)
115                                                 return 1;
116                                         else if (strcasecmp(answer, "\n") == 0 ||
117                                                  strcasecmp(answer, "N\n") == 0)
118                                                 return 0;
119                                 }
120                         } while (1);
121                 } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO &&
122                            pr->flags & ERF_PREEN_YES)
123                         repair = 1;
124         }
125
126         if (pr->prompt_type & ERP_RENAME) {
127                 int print_num = repair ? pr->default_number : pr->bypass_number;
128
129                 printf("%s (Number : %d)\n", prompts[pr->prompt_type],
130                        print_num);
131                 repair = print_num;
132         } else {
133                 printf(". %s (y/N)? %c\n", prompts[pr->prompt_type],
134                        repair ? 'y' : 'n');
135         }
136         return repair;
137 }
138
139 int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode,
140                      const char *desc, ...)
141 {
142         struct exfat_repair_problem *pr = NULL;
143         va_list ap;
144         int repair;
145
146         pr = find_problem(prcode);
147         if (!pr) {
148                 exfat_err("unknown problem code. %#x\n", prcode);
149                 return 0;
150         }
151
152         va_start(ap, desc);
153         vprintf(desc, ap);
154         va_end(ap);
155
156         repair = ask_repair(fsck, pr);
157         if (repair) {
158                 if (pr->prompt_type & ERP_TRUNCATE)
159                         fsck->dirty_fat = true;
160                 fsck->dirty = true;
161         }
162         return repair;
163 }
164
165 static int get_rename_from_user(struct exfat_de_iter *iter,
166                 __le16 *utf16_name, int name_size)
167 {
168         int len = 0;
169         char *rename = malloc(ENTRY_NAME_MAX + 2);
170
171         if (!rename)
172                 return -ENOMEM;
173
174 retry:
175         /* +2 means LF(Line Feed) and NULL terminator */
176         memset(rename, 0x1, ENTRY_NAME_MAX + 2);
177         printf("New name: ");
178         if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) {
179                 int err;
180                 struct exfat_lookup_filter filter;
181
182                 len = strlen(rename);
183                 /* Remove LF in filename */
184                 rename[len - 1] = '\0';
185
186                 memset(utf16_name, 0, name_size);
187                 len = exfat_utf16_enc(rename, utf16_name, name_size);
188                 if (len < 0)
189                         goto out;
190
191                 err = exfat_check_name(utf16_name, len >> 1);
192                 if (err != len >> 1) {
193                         printf("filename contain invalid character(%c)\n",
194                                         le16_to_cpu(utf16_name[err]));
195                         goto retry;
196                 }
197
198                 exfat_de_iter_flush(iter);
199                 err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter);
200                 if (!err) {
201                         printf("file(%s) already exists, retry to insert name\n", rename);
202                         goto retry;
203                 }
204         }
205
206 out:
207         free(rename);
208
209         return len;
210 }
211
212 static int generate_rename(struct exfat_de_iter *iter, __le16 *utf16_name,
213                 int name_size)
214 {
215         int err;
216         char *rename;
217
218         if (iter->invalid_name_num > INVALID_NAME_NUM_MAX)
219                 return -ERANGE;
220
221         rename = malloc(ENTRY_NAME_MAX + 1);
222         if (!rename)
223                 return -ENOMEM;
224
225         while (1) {
226                 struct exfat_lookup_filter filter;
227
228                 snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK",
229                          iter->invalid_name_num++);
230                 err = exfat_lookup_file(iter->exfat, iter->parent, rename,
231                                         &filter);
232                 if (!err)
233                         continue;
234                 break;
235         }
236
237         memset(utf16_name, 0, name_size);
238         err = exfat_utf16_enc(rename, utf16_name, name_size);
239         free(rename);
240
241         return err;
242 }
243
244 int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter,
245                 __le16 *uname, er_problem_code_t prcode, char *error_msg)
246 {
247         int num;
248         char old_name[PATH_MAX + 1] = {0};
249
250         if (exfat_utf16_dec(uname, NAME_BUFFER_SIZE, old_name, PATH_MAX) <= 0) {
251                 exfat_err("failed to decode filename\n");
252                 return -EINVAL;
253         }
254
255 ask_again:
256         num = exfat_repair_ask(fsck, prcode, "ERROR: '%s' %s.\n%s",
257                         old_name, error_msg,
258                         " [1] Insert the name you want to rename.\n"
259                         " [2] Automatically renames filename.\n"
260                         " [3] Bypass this check(No repair)\n");
261         if (num) {
262                 __le16 utf16_name[ENTRY_NAME_MAX];
263                 __u16 hash;
264                 struct exfat_dentry *dentry;
265                 int ret;
266
267                 switch (num) {
268                 case 1:
269                         ret = get_rename_from_user(iter, utf16_name,
270                                         sizeof(utf16_name));
271                         break;
272                 case 2:
273                         ret = generate_rename(iter, utf16_name,
274                                         sizeof(utf16_name));
275                         break;
276                 case 3:
277                         return -EINVAL;
278                 default:
279                         exfat_info("select 1 or 2 number instead of %d\n", num);
280                         goto ask_again;
281                 }
282
283                 if (ret < 0)
284                         return -EINVAL;
285
286                 exfat_de_iter_get_dirty(iter, 2, &dentry);
287
288                 ret >>= 1;
289                 memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2);
290                 hash = exfat_calc_name_hash(iter->exfat, utf16_name, ret);
291                 exfat_de_iter_get_dirty(iter, 1, &dentry);
292                 dentry->stream_name_len = (__u8)ret;
293                 dentry->stream_name_hash = cpu_to_le16(hash);
294                 return 1;
295
296         }
297
298         return 0;
299 }