]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
c30a15e5 DK |
2 | /* |
3 | * fat_write.c | |
4 | * | |
5 | * R/W (V)FAT 12/16/32 filesystem implementation by Donggeun Kim | |
c30a15e5 DK |
6 | */ |
7 | ||
8 | #include <common.h> | |
9 | #include <command.h> | |
10 | #include <config.h> | |
11 | #include <fat.h> | |
12 | #include <asm/byteorder.h> | |
13 | #include <part.h> | |
fb7e16cc | 14 | #include <linux/ctype.h> |
9e374e7b TR |
15 | #include <div64.h> |
16 | #include <linux/math64.h> | |
c30a15e5 DK |
17 | #include "fat.c" |
18 | ||
19 | static void uppercase(char *str, int len) | |
20 | { | |
21 | int i; | |
22 | ||
23 | for (i = 0; i < len; i++) { | |
fb7e16cc | 24 | *str = toupper(*str); |
c30a15e5 DK |
25 | str++; |
26 | } | |
27 | } | |
28 | ||
29 | static int total_sector; | |
079df722 | 30 | static int disk_write(__u32 block, __u32 nr_blocks, void *buf) |
c30a15e5 | 31 | { |
0a04ed86 ŁM |
32 | ulong ret; |
33 | ||
2a981dc2 | 34 | if (!cur_dev) |
c30a15e5 DK |
35 | return -1; |
36 | ||
079df722 DK |
37 | if (cur_part_info.start + block + nr_blocks > |
38 | cur_part_info.start + total_sector) { | |
c30a15e5 DK |
39 | printf("error: overflow occurs\n"); |
40 | return -1; | |
41 | } | |
42 | ||
2a981dc2 | 43 | ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf); |
0a04ed86 ŁM |
44 | if (nr_blocks && ret == 0) |
45 | return -1; | |
46 | ||
47 | return ret; | |
c30a15e5 DK |
48 | } |
49 | ||
50 | /* | |
51 | * Set short name in directory entry | |
52 | */ | |
53 | static void set_name(dir_entry *dirent, const char *filename) | |
54 | { | |
55 | char s_name[VFAT_MAXLEN_BYTES]; | |
56 | char *period; | |
57 | int period_location, len, i, ext_num; | |
58 | ||
59 | if (filename == NULL) | |
60 | return; | |
61 | ||
62 | len = strlen(filename); | |
63 | if (len == 0) | |
64 | return; | |
65 | ||
73dc8328 | 66 | strcpy(s_name, filename); |
c30a15e5 DK |
67 | uppercase(s_name, len); |
68 | ||
69 | period = strchr(s_name, '.'); | |
70 | if (period == NULL) { | |
71 | period_location = len; | |
72 | ext_num = 0; | |
73 | } else { | |
74 | period_location = period - s_name; | |
75 | ext_num = len - period_location - 1; | |
76 | } | |
77 | ||
78 | /* Pad spaces when the length of file name is shorter than eight */ | |
79 | if (period_location < 8) { | |
80 | memcpy(dirent->name, s_name, period_location); | |
81 | for (i = period_location; i < 8; i++) | |
82 | dirent->name[i] = ' '; | |
83 | } else if (period_location == 8) { | |
84 | memcpy(dirent->name, s_name, period_location); | |
85 | } else { | |
86 | memcpy(dirent->name, s_name, 6); | |
87 | dirent->name[6] = '~'; | |
88 | dirent->name[7] = '1'; | |
89 | } | |
90 | ||
91 | if (ext_num < 3) { | |
92 | memcpy(dirent->ext, s_name + period_location + 1, ext_num); | |
93 | for (i = ext_num; i < 3; i++) | |
94 | dirent->ext[i] = ' '; | |
95 | } else | |
96 | memcpy(dirent->ext, s_name + period_location + 1, 3); | |
97 | ||
98 | debug("name : %s\n", dirent->name); | |
99 | debug("ext : %s\n", dirent->ext); | |
100 | } | |
101 | ||
102 | /* | |
103 | * Write fat buffer into block device | |
104 | */ | |
3c0ed9c3 | 105 | static int flush_dirty_fat_buffer(fsdata *mydata) |
c30a15e5 DK |
106 | { |
107 | int getsize = FATBUFBLOCKS; | |
108 | __u32 fatlength = mydata->fatlength; | |
109 | __u8 *bufptr = mydata->fatbuf; | |
110 | __u32 startblock = mydata->fatbufnum * FATBUFBLOCKS; | |
111 | ||
3c0ed9c3 SB |
112 | debug("debug: evicting %d, dirty: %d\n", mydata->fatbufnum, |
113 | (int)mydata->fat_dirty); | |
114 | ||
115 | if ((!mydata->fat_dirty) || (mydata->fatbufnum == -1)) | |
116 | return 0; | |
117 | ||
6c1a8080 SB |
118 | /* Cap length if fatlength is not a multiple of FATBUFBLOCKS */ |
119 | if (startblock + getsize > fatlength) | |
120 | getsize = fatlength - startblock; | |
c30a15e5 | 121 | |
6c1a8080 | 122 | startblock += mydata->fat_sect; |
c30a15e5 DK |
123 | |
124 | /* Write FAT buf */ | |
125 | if (disk_write(startblock, getsize, bufptr) < 0) { | |
126 | debug("error: writing FAT blocks\n"); | |
127 | return -1; | |
128 | } | |
129 | ||
4ced2039 | 130 | if (mydata->fats == 2) { |
627182ea DK |
131 | /* Update corresponding second FAT blocks */ |
132 | startblock += mydata->fatlength; | |
133 | if (disk_write(startblock, getsize, bufptr) < 0) { | |
134 | debug("error: writing second FAT blocks\n"); | |
135 | return -1; | |
136 | } | |
137 | } | |
3c0ed9c3 | 138 | mydata->fat_dirty = 0; |
627182ea | 139 | |
c30a15e5 DK |
140 | return 0; |
141 | } | |
142 | ||
c30a15e5 DK |
143 | /* |
144 | * Set the file name information from 'name' into 'slotptr', | |
145 | */ | |
146 | static int str2slot(dir_slot *slotptr, const char *name, int *idx) | |
147 | { | |
148 | int j, end_idx = 0; | |
149 | ||
150 | for (j = 0; j <= 8; j += 2) { | |
151 | if (name[*idx] == 0x00) { | |
152 | slotptr->name0_4[j] = 0; | |
153 | slotptr->name0_4[j + 1] = 0; | |
154 | end_idx++; | |
155 | goto name0_4; | |
156 | } | |
157 | slotptr->name0_4[j] = name[*idx]; | |
158 | (*idx)++; | |
159 | end_idx++; | |
160 | } | |
161 | for (j = 0; j <= 10; j += 2) { | |
162 | if (name[*idx] == 0x00) { | |
163 | slotptr->name5_10[j] = 0; | |
164 | slotptr->name5_10[j + 1] = 0; | |
165 | end_idx++; | |
166 | goto name5_10; | |
167 | } | |
168 | slotptr->name5_10[j] = name[*idx]; | |
169 | (*idx)++; | |
170 | end_idx++; | |
171 | } | |
172 | for (j = 0; j <= 2; j += 2) { | |
173 | if (name[*idx] == 0x00) { | |
174 | slotptr->name11_12[j] = 0; | |
175 | slotptr->name11_12[j + 1] = 0; | |
176 | end_idx++; | |
177 | goto name11_12; | |
178 | } | |
179 | slotptr->name11_12[j] = name[*idx]; | |
180 | (*idx)++; | |
181 | end_idx++; | |
182 | } | |
183 | ||
184 | if (name[*idx] == 0x00) | |
185 | return 1; | |
186 | ||
187 | return 0; | |
188 | /* Not used characters are filled with 0xff 0xff */ | |
189 | name0_4: | |
190 | for (; end_idx < 5; end_idx++) { | |
191 | slotptr->name0_4[end_idx * 2] = 0xff; | |
192 | slotptr->name0_4[end_idx * 2 + 1] = 0xff; | |
193 | } | |
194 | end_idx = 5; | |
195 | name5_10: | |
196 | end_idx -= 5; | |
197 | for (; end_idx < 6; end_idx++) { | |
198 | slotptr->name5_10[end_idx * 2] = 0xff; | |
199 | slotptr->name5_10[end_idx * 2 + 1] = 0xff; | |
200 | } | |
201 | end_idx = 11; | |
202 | name11_12: | |
203 | end_idx -= 11; | |
204 | for (; end_idx < 2; end_idx++) { | |
205 | slotptr->name11_12[end_idx * 2] = 0xff; | |
206 | slotptr->name11_12[end_idx * 2 + 1] = 0xff; | |
207 | } | |
208 | ||
209 | return 1; | |
210 | } | |
211 | ||
4ced2039 | 212 | static int flush_dir_table(fat_itr *itr); |
c30a15e5 DK |
213 | |
214 | /* | |
215 | * Fill dir_slot entries with appropriate name, id, and attr | |
4ced2039 | 216 | * 'itr' will point to a next entry |
c30a15e5 | 217 | */ |
4ced2039 AT |
218 | static int |
219 | fill_dir_slot(fat_itr *itr, const char *l_name) | |
c30a15e5 | 220 | { |
7aa1a6b7 TFC |
221 | __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)]; |
222 | dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer; | |
8506eb8d | 223 | __u8 counter = 0, checksum; |
c30a15e5 | 224 | int idx = 0, ret; |
c30a15e5 | 225 | |
ed76f912 | 226 | /* Get short file name checksum value */ |
4ced2039 | 227 | checksum = mkcksum(itr->dent->name, itr->dent->ext); |
c30a15e5 DK |
228 | |
229 | do { | |
230 | memset(slotptr, 0x00, sizeof(dir_slot)); | |
231 | ret = str2slot(slotptr, l_name, &idx); | |
232 | slotptr->id = ++counter; | |
233 | slotptr->attr = ATTR_VFAT; | |
234 | slotptr->alias_checksum = checksum; | |
235 | slotptr++; | |
236 | } while (ret == 0); | |
237 | ||
238 | slotptr--; | |
239 | slotptr->id |= LAST_LONG_ENTRY_MASK; | |
240 | ||
241 | while (counter >= 1) { | |
4ced2039 | 242 | memcpy(itr->dent, slotptr, sizeof(dir_slot)); |
c30a15e5 DK |
243 | slotptr--; |
244 | counter--; | |
4ced2039 AT |
245 | if (!fat_itr_next(itr)) |
246 | if (!itr->dent && !itr->is_root && flush_dir_table(itr)) | |
c30a15e5 | 247 | return -1; |
c30a15e5 DK |
248 | } |
249 | ||
4ced2039 AT |
250 | if (!itr->dent && !itr->is_root) |
251 | /* | |
252 | * don't care return value here because we have already | |
253 | * finished completing an entry with name, only ending up | |
254 | * no more entry left | |
255 | */ | |
256 | flush_dir_table(itr); | |
c30a15e5 DK |
257 | |
258 | return 0; | |
259 | } | |
260 | ||
c30a15e5 | 261 | /* |
49abbd9c | 262 | * Set the entry at index 'entry' in a FAT (12/16/32) table. |
c30a15e5 DK |
263 | */ |
264 | static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value) | |
265 | { | |
49abbd9c PS |
266 | __u32 bufnum, offset, off16; |
267 | __u16 val1, val2; | |
c30a15e5 DK |
268 | |
269 | switch (mydata->fatsize) { | |
270 | case 32: | |
271 | bufnum = entry / FAT32BUFSIZE; | |
272 | offset = entry - bufnum * FAT32BUFSIZE; | |
273 | break; | |
274 | case 16: | |
275 | bufnum = entry / FAT16BUFSIZE; | |
276 | offset = entry - bufnum * FAT16BUFSIZE; | |
277 | break; | |
49abbd9c PS |
278 | case 12: |
279 | bufnum = entry / FAT12BUFSIZE; | |
280 | offset = entry - bufnum * FAT12BUFSIZE; | |
281 | break; | |
c30a15e5 DK |
282 | default: |
283 | /* Unsupported FAT size */ | |
284 | return -1; | |
285 | } | |
286 | ||
287 | /* Read a new block of FAT entries into the cache. */ | |
288 | if (bufnum != mydata->fatbufnum) { | |
289 | int getsize = FATBUFBLOCKS; | |
290 | __u8 *bufptr = mydata->fatbuf; | |
291 | __u32 fatlength = mydata->fatlength; | |
292 | __u32 startblock = bufnum * FATBUFBLOCKS; | |
293 | ||
6c1a8080 SB |
294 | /* Cap length if fatlength is not a multiple of FATBUFBLOCKS */ |
295 | if (startblock + getsize > fatlength) | |
296 | getsize = fatlength - startblock; | |
c30a15e5 | 297 | |
3c0ed9c3 SB |
298 | if (flush_dirty_fat_buffer(mydata) < 0) |
299 | return -1; | |
c30a15e5 | 300 | |
6c1a8080 SB |
301 | startblock += mydata->fat_sect; |
302 | ||
c30a15e5 DK |
303 | if (disk_read(startblock, getsize, bufptr) < 0) { |
304 | debug("Error reading FAT blocks\n"); | |
305 | return -1; | |
306 | } | |
307 | mydata->fatbufnum = bufnum; | |
308 | } | |
309 | ||
3c0ed9c3 SB |
310 | /* Mark as dirty */ |
311 | mydata->fat_dirty = 1; | |
312 | ||
c30a15e5 DK |
313 | /* Set the actual entry */ |
314 | switch (mydata->fatsize) { | |
315 | case 32: | |
316 | ((__u32 *) mydata->fatbuf)[offset] = cpu_to_le32(entry_value); | |
317 | break; | |
318 | case 16: | |
319 | ((__u16 *) mydata->fatbuf)[offset] = cpu_to_le16(entry_value); | |
49abbd9c PS |
320 | break; |
321 | case 12: | |
322 | off16 = (offset * 3) / 4; | |
323 | ||
324 | switch (offset & 0x3) { | |
325 | case 0: | |
326 | val1 = cpu_to_le16(entry_value) & 0xfff; | |
327 | ((__u16 *)mydata->fatbuf)[off16] &= ~0xfff; | |
328 | ((__u16 *)mydata->fatbuf)[off16] |= val1; | |
329 | break; | |
330 | case 1: | |
331 | val1 = cpu_to_le16(entry_value) & 0xf; | |
332 | val2 = (cpu_to_le16(entry_value) >> 4) & 0xff; | |
333 | ||
334 | ((__u16 *)mydata->fatbuf)[off16] &= ~0xf000; | |
335 | ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 12); | |
336 | ||
337 | ((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xff; | |
338 | ((__u16 *)mydata->fatbuf)[off16 + 1] |= val2; | |
339 | break; | |
340 | case 2: | |
341 | val1 = cpu_to_le16(entry_value) & 0xff; | |
342 | val2 = (cpu_to_le16(entry_value) >> 8) & 0xf; | |
343 | ||
344 | ((__u16 *)mydata->fatbuf)[off16] &= ~0xff00; | |
345 | ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 8); | |
346 | ||
347 | ((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xf; | |
348 | ((__u16 *)mydata->fatbuf)[off16 + 1] |= val2; | |
349 | break; | |
350 | case 3: | |
351 | val1 = cpu_to_le16(entry_value) & 0xfff; | |
352 | ((__u16 *)mydata->fatbuf)[off16] &= ~0xfff0; | |
353 | ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 4); | |
354 | break; | |
355 | default: | |
356 | break; | |
357 | } | |
358 | ||
c30a15e5 DK |
359 | break; |
360 | default: | |
361 | return -1; | |
362 | } | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | /* | |
49abbd9c | 368 | * Determine the next free cluster after 'entry' in a FAT (12/16/32) table |
ae1755be | 369 | * and link it to 'entry'. EOC marker is not set on returned entry. |
c30a15e5 DK |
370 | */ |
371 | static __u32 determine_fatent(fsdata *mydata, __u32 entry) | |
372 | { | |
373 | __u32 next_fat, next_entry = entry + 1; | |
374 | ||
375 | while (1) { | |
b8948d2a | 376 | next_fat = get_fatent(mydata, next_entry); |
c30a15e5 | 377 | if (next_fat == 0) { |
ae1755be | 378 | /* found free entry, link to entry */ |
c30a15e5 DK |
379 | set_fatent_value(mydata, entry, next_entry); |
380 | break; | |
381 | } | |
382 | next_entry++; | |
383 | } | |
384 | debug("FAT%d: entry: %08x, entry_value: %04x\n", | |
385 | mydata->fatsize, entry, next_entry); | |
386 | ||
387 | return next_entry; | |
388 | } | |
389 | ||
390 | /* | |
391 | * Write at most 'size' bytes from 'buffer' into the specified cluster. | |
392 | * Return 0 on success, -1 otherwise. | |
393 | */ | |
394 | static int | |
395 | set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer, | |
396 | unsigned long size) | |
397 | { | |
8133f43d | 398 | __u32 idx = 0; |
c30a15e5 | 399 | __u32 startsect; |
8133f43d | 400 | int ret; |
c30a15e5 DK |
401 | |
402 | if (clustnum > 0) | |
265edc03 | 403 | startsect = clust_to_sect(mydata, clustnum); |
c30a15e5 DK |
404 | else |
405 | startsect = mydata->rootdir_sect; | |
406 | ||
407 | debug("clustnum: %d, startsect: %d\n", clustnum, startsect); | |
408 | ||
8133f43d BT |
409 | if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) { |
410 | ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size); | |
411 | ||
412 | printf("FAT: Misaligned buffer address (%p)\n", buffer); | |
413 | ||
414 | while (size >= mydata->sect_size) { | |
415 | memcpy(tmpbuf, buffer, mydata->sect_size); | |
416 | ret = disk_write(startsect++, 1, tmpbuf); | |
417 | if (ret != 1) { | |
418 | debug("Error writing data (got %d)\n", ret); | |
419 | return -1; | |
420 | } | |
421 | ||
422 | buffer += mydata->sect_size; | |
423 | size -= mydata->sect_size; | |
424 | } | |
425 | } else if (size >= mydata->sect_size) { | |
426 | idx = size / mydata->sect_size; | |
427 | ret = disk_write(startsect, idx, buffer); | |
428 | if (ret != idx) { | |
429 | debug("Error writing data (got %d)\n", ret); | |
6b8f185f WJ |
430 | return -1; |
431 | } | |
8133f43d BT |
432 | |
433 | startsect += idx; | |
434 | idx *= mydata->sect_size; | |
435 | buffer += idx; | |
436 | size -= idx; | |
c30a15e5 DK |
437 | } |
438 | ||
8133f43d BT |
439 | if (size) { |
440 | ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size); | |
c30a15e5 | 441 | |
8133f43d BT |
442 | memcpy(tmpbuf, buffer, size); |
443 | ret = disk_write(startsect, 1, tmpbuf); | |
444 | if (ret != 1) { | |
445 | debug("Error writing data (got %d)\n", ret); | |
c30a15e5 DK |
446 | return -1; |
447 | } | |
c30a15e5 DK |
448 | } |
449 | ||
450 | return 0; | |
451 | } | |
452 | ||
453 | /* | |
454 | * Find the first empty cluster | |
455 | */ | |
456 | static int find_empty_cluster(fsdata *mydata) | |
457 | { | |
458 | __u32 fat_val, entry = 3; | |
459 | ||
460 | while (1) { | |
b8948d2a | 461 | fat_val = get_fatent(mydata, entry); |
c30a15e5 DK |
462 | if (fat_val == 0) |
463 | break; | |
464 | entry++; | |
465 | } | |
466 | ||
467 | return entry; | |
468 | } | |
469 | ||
470 | /* | |
4ced2039 | 471 | * Write directory entries in itr's buffer to block device |
c30a15e5 | 472 | */ |
4ced2039 | 473 | static int flush_dir_table(fat_itr *itr) |
c30a15e5 | 474 | { |
4ced2039 | 475 | fsdata *mydata = itr->fsdata; |
c30a15e5 | 476 | int dir_newclust = 0; |
4ced2039 | 477 | unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; |
c30a15e5 | 478 | |
4ced2039 AT |
479 | if (set_cluster(mydata, itr->clust, itr->block, bytesperclust) != 0) { |
480 | printf("error: writing directory entry\n"); | |
481 | return -1; | |
c30a15e5 DK |
482 | } |
483 | dir_newclust = find_empty_cluster(mydata); | |
4ced2039 | 484 | set_fatent_value(mydata, itr->clust, dir_newclust); |
c30a15e5 DK |
485 | if (mydata->fatsize == 32) |
486 | set_fatent_value(mydata, dir_newclust, 0xffffff8); | |
487 | else if (mydata->fatsize == 16) | |
488 | set_fatent_value(mydata, dir_newclust, 0xfff8); | |
49abbd9c PS |
489 | else if (mydata->fatsize == 12) |
490 | set_fatent_value(mydata, dir_newclust, 0xff8); | |
c30a15e5 | 491 | |
4ced2039 AT |
492 | itr->clust = dir_newclust; |
493 | itr->next_clust = dir_newclust; | |
c30a15e5 | 494 | |
3c0ed9c3 | 495 | if (flush_dirty_fat_buffer(mydata) < 0) |
4ced2039 AT |
496 | return -1; |
497 | ||
498 | memset(itr->block, 0x00, bytesperclust); | |
c30a15e5 | 499 | |
4ced2039 AT |
500 | itr->dent = (dir_entry *)itr->block; |
501 | itr->last_cluster = 1; | |
502 | itr->remaining = bytesperclust / sizeof(dir_entry) - 1; | |
c30a15e5 | 503 | |
4ced2039 | 504 | return 0; |
c30a15e5 DK |
505 | } |
506 | ||
507 | /* | |
508 | * Set empty cluster from 'entry' to the end of a file | |
509 | */ | |
510 | static int clear_fatent(fsdata *mydata, __u32 entry) | |
511 | { | |
512 | __u32 fat_val; | |
513 | ||
49abbd9c | 514 | while (!CHECK_CLUST(entry, mydata->fatsize)) { |
b8948d2a | 515 | fat_val = get_fatent(mydata, entry); |
c30a15e5 DK |
516 | if (fat_val != 0) |
517 | set_fatent_value(mydata, entry, 0); | |
518 | else | |
519 | break; | |
520 | ||
c30a15e5 DK |
521 | entry = fat_val; |
522 | } | |
523 | ||
524 | /* Flush fat buffer */ | |
3c0ed9c3 | 525 | if (flush_dirty_fat_buffer(mydata) < 0) |
c30a15e5 DK |
526 | return -1; |
527 | ||
528 | return 0; | |
529 | } | |
530 | ||
531 | /* | |
532 | * Write at most 'maxsize' bytes from 'buffer' into | |
533 | * the file associated with 'dentptr' | |
1ad0b98a SR |
534 | * Update the number of bytes written in *gotsize and return 0 |
535 | * or return -1 on fatal errors. | |
c30a15e5 DK |
536 | */ |
537 | static int | |
538 | set_contents(fsdata *mydata, dir_entry *dentptr, __u8 *buffer, | |
1ad0b98a | 539 | loff_t maxsize, loff_t *gotsize) |
c30a15e5 | 540 | { |
1ad0b98a | 541 | loff_t filesize = FAT2CPU32(dentptr->size); |
c30a15e5 DK |
542 | unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; |
543 | __u32 curclust = START(dentptr); | |
544 | __u32 endclust = 0, newclust = 0; | |
1ad0b98a | 545 | loff_t actsize; |
c30a15e5 | 546 | |
1ad0b98a SR |
547 | *gotsize = 0; |
548 | debug("Filesize: %llu bytes\n", filesize); | |
c30a15e5 DK |
549 | |
550 | if (maxsize > 0 && filesize > maxsize) | |
551 | filesize = maxsize; | |
552 | ||
1ad0b98a | 553 | debug("%llu bytes\n", filesize); |
c30a15e5 | 554 | |
1254b44a BT |
555 | if (!curclust) { |
556 | if (filesize) { | |
557 | debug("error: nonempty clusterless file!\n"); | |
558 | return -1; | |
559 | } | |
560 | return 0; | |
561 | } | |
562 | ||
c30a15e5 DK |
563 | actsize = bytesperclust; |
564 | endclust = curclust; | |
565 | do { | |
566 | /* search for consecutive clusters */ | |
567 | while (actsize < filesize) { | |
568 | newclust = determine_fatent(mydata, endclust); | |
569 | ||
570 | if ((newclust - 1) != endclust) | |
571 | goto getit; | |
572 | ||
573 | if (CHECK_CLUST(newclust, mydata->fatsize)) { | |
5e1a860e | 574 | debug("newclust: 0x%x\n", newclust); |
c30a15e5 | 575 | debug("Invalid FAT entry\n"); |
1ad0b98a | 576 | return 0; |
c30a15e5 DK |
577 | } |
578 | endclust = newclust; | |
579 | actsize += bytesperclust; | |
580 | } | |
c30a15e5 DK |
581 | |
582 | /* set remaining bytes */ | |
c30a15e5 | 583 | actsize = filesize; |
1d7f2ece | 584 | if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) { |
c30a15e5 DK |
585 | debug("error: writing cluster\n"); |
586 | return -1; | |
587 | } | |
1ad0b98a | 588 | *gotsize += actsize; |
c30a15e5 DK |
589 | |
590 | /* Mark end of file in FAT */ | |
49abbd9c PS |
591 | if (mydata->fatsize == 12) |
592 | newclust = 0xfff; | |
593 | else if (mydata->fatsize == 16) | |
c30a15e5 DK |
594 | newclust = 0xffff; |
595 | else if (mydata->fatsize == 32) | |
596 | newclust = 0xfffffff; | |
597 | set_fatent_value(mydata, endclust, newclust); | |
598 | ||
1ad0b98a | 599 | return 0; |
c30a15e5 DK |
600 | getit: |
601 | if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) { | |
602 | debug("error: writing cluster\n"); | |
603 | return -1; | |
604 | } | |
1ad0b98a | 605 | *gotsize += actsize; |
c30a15e5 DK |
606 | filesize -= actsize; |
607 | buffer += actsize; | |
608 | ||
5e1a860e BT |
609 | if (CHECK_CLUST(newclust, mydata->fatsize)) { |
610 | debug("newclust: 0x%x\n", newclust); | |
c30a15e5 | 611 | debug("Invalid FAT entry\n"); |
1ad0b98a | 612 | return 0; |
c30a15e5 DK |
613 | } |
614 | actsize = bytesperclust; | |
615 | curclust = endclust = newclust; | |
616 | } while (1); | |
617 | } | |
618 | ||
619 | /* | |
1254b44a | 620 | * Set start cluster in directory entry |
c30a15e5 | 621 | */ |
1254b44a BT |
622 | static void set_start_cluster(const fsdata *mydata, dir_entry *dentptr, |
623 | __u32 start_cluster) | |
c30a15e5 DK |
624 | { |
625 | if (mydata->fatsize == 32) | |
626 | dentptr->starthi = | |
627 | cpu_to_le16((start_cluster & 0xffff0000) >> 16); | |
628 | dentptr->start = cpu_to_le16(start_cluster & 0xffff); | |
1254b44a BT |
629 | } |
630 | ||
631 | /* | |
632 | * Fill dir_entry | |
633 | */ | |
634 | static void fill_dentry(fsdata *mydata, dir_entry *dentptr, | |
635 | const char *filename, __u32 start_cluster, __u32 size, __u8 attr) | |
636 | { | |
637 | set_start_cluster(mydata, dentptr, start_cluster); | |
c30a15e5 DK |
638 | dentptr->size = cpu_to_le32(size); |
639 | ||
640 | dentptr->attr = attr; | |
641 | ||
642 | set_name(dentptr, filename); | |
643 | } | |
644 | ||
645 | /* | |
646 | * Check whether adding a file makes the file system to | |
647 | * exceed the size of the block device | |
648 | * Return -1 when overflow occurs, otherwise return 0 | |
649 | */ | |
1ad0b98a | 650 | static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size) |
c30a15e5 | 651 | { |
9e374e7b | 652 | __u32 startsect, sect_num, offset; |
c30a15e5 DK |
653 | |
654 | if (clustnum > 0) { | |
265edc03 | 655 | startsect = clust_to_sect(mydata, clustnum); |
c30a15e5 DK |
656 | } else { |
657 | startsect = mydata->rootdir_sect; | |
658 | } | |
659 | ||
9e374e7b TR |
660 | sect_num = div_u64_rem(size, mydata->sect_size, &offset); |
661 | ||
662 | if (offset != 0) | |
c30a15e5 DK |
663 | sect_num++; |
664 | ||
76216211 | 665 | if (startsect + sect_num > total_sector) |
c30a15e5 | 666 | return -1; |
c30a15e5 DK |
667 | return 0; |
668 | } | |
669 | ||
c30a15e5 DK |
670 | /* |
671 | * Find a directory entry based on filename or start cluster number | |
672 | * If the directory entry is not found, | |
673 | * the new position for writing a directory entry will be returned | |
674 | */ | |
4ced2039 | 675 | static dir_entry *find_directory_entry(fat_itr *itr, char *filename) |
c30a15e5 | 676 | { |
4ced2039 | 677 | int match = 0; |
c30a15e5 | 678 | |
4ced2039 AT |
679 | while (fat_itr_next(itr)) { |
680 | /* check both long and short name: */ | |
681 | if (!strcasecmp(filename, itr->name)) | |
682 | match = 1; | |
683 | else if (itr->name != itr->s_name && | |
684 | !strcasecmp(filename, itr->s_name)) | |
685 | match = 1; | |
c30a15e5 | 686 | |
4ced2039 AT |
687 | if (!match) |
688 | continue; | |
c30a15e5 | 689 | |
4ced2039 | 690 | if (itr->dent->name[0] == '\0') |
c30a15e5 | 691 | return NULL; |
4ced2039 AT |
692 | else |
693 | return itr->dent; | |
694 | } | |
c30a15e5 | 695 | |
4ced2039 AT |
696 | if (!itr->dent && !itr->is_root && flush_dir_table(itr)) |
697 | /* indicate that allocating dent failed */ | |
698 | itr->dent = NULL; | |
c30a15e5 | 699 | |
4ced2039 AT |
700 | return NULL; |
701 | } | |
c30a15e5 | 702 | |
4ced2039 AT |
703 | static int split_filename(char *filename, char **dirname, char **basename) |
704 | { | |
705 | char *p, *last_slash, *last_slash_cont; | |
706 | ||
707 | again: | |
708 | p = filename; | |
709 | last_slash = NULL; | |
710 | last_slash_cont = NULL; | |
711 | while (*p) { | |
712 | if (ISDIRDELIM(*p)) { | |
713 | last_slash = p; | |
714 | last_slash_cont = p; | |
715 | /* continuous slashes */ | |
716 | while (ISDIRDELIM(*p)) | |
717 | last_slash_cont = p++; | |
718 | if (!*p) | |
719 | break; | |
c30a15e5 | 720 | } |
4ced2039 AT |
721 | p++; |
722 | } | |
c30a15e5 | 723 | |
4ced2039 AT |
724 | if (last_slash) { |
725 | if (last_slash_cont == (filename + strlen(filename) - 1)) { | |
726 | /* remove trailing slashes */ | |
727 | *last_slash = '\0'; | |
728 | goto again; | |
dd6d7967 WJ |
729 | } |
730 | ||
4ced2039 AT |
731 | if (last_slash == filename) { |
732 | /* avoid ""(null) directory */ | |
733 | *dirname = "/"; | |
734 | } else { | |
735 | *last_slash = '\0'; | |
736 | *dirname = filename; | |
c30a15e5 | 737 | } |
4ced2039 AT |
738 | |
739 | *last_slash_cont = '\0'; | |
740 | *basename = last_slash_cont + 1; | |
741 | } else { | |
742 | *dirname = "/"; /* root by default */ | |
743 | *basename = filename; | |
c30a15e5 DK |
744 | } |
745 | ||
4ced2039 | 746 | return 0; |
c30a15e5 DK |
747 | } |
748 | ||
25bb9dab AT |
749 | static int normalize_longname(char *l_filename, const char *filename) |
750 | { | |
751 | const char *p, legal[] = "!#$%&\'()-.@^`_{}~"; | |
752 | char c; | |
753 | int name_len; | |
754 | ||
755 | /* Check that the filename is valid */ | |
756 | for (p = filename; p < filename + strlen(filename); p++) { | |
757 | c = *p; | |
758 | ||
759 | if (('0' <= c) && (c <= '9')) | |
760 | continue; | |
761 | if (('A' <= c) && (c <= 'Z')) | |
762 | continue; | |
763 | if (('a' <= c) && (c <= 'z')) | |
764 | continue; | |
765 | if (strchr(legal, c)) | |
766 | continue; | |
767 | /* extended code */ | |
768 | if ((0x80 <= c) && (c <= 0xff)) | |
769 | continue; | |
770 | ||
771 | return -1; | |
772 | } | |
773 | ||
774 | /* Normalize it */ | |
775 | name_len = strlen(filename); | |
776 | if (name_len >= VFAT_MAXLEN_BYTES) | |
777 | /* should return an error? */ | |
778 | name_len = VFAT_MAXLEN_BYTES - 1; | |
779 | ||
780 | memcpy(l_filename, filename, name_len); | |
781 | l_filename[name_len] = 0; /* terminate the string */ | |
782 | downcase(l_filename, INT_MAX); | |
783 | ||
784 | return 0; | |
785 | } | |
786 | ||
1ad0b98a SR |
787 | static int do_fat_write(const char *filename, void *buffer, loff_t size, |
788 | loff_t *actwrite) | |
c30a15e5 | 789 | { |
4ced2039 | 790 | dir_entry *retdent; |
c30a15e5 | 791 | __u32 start_cluster; |
4ced2039 | 792 | fsdata datablock = { .fatbuf = NULL, }; |
c30a15e5 | 793 | fsdata *mydata = &datablock; |
4ced2039 | 794 | fat_itr *itr = NULL; |
25bb9dab | 795 | int ret = -1; |
4ced2039 | 796 | char *filename_copy, *parent, *basename; |
c30a15e5 DK |
797 | char l_filename[VFAT_MAXLEN_BYTES]; |
798 | ||
4ced2039 AT |
799 | filename_copy = strdup(filename); |
800 | if (!filename_copy) | |
801 | return -ENOMEM; | |
c30a15e5 | 802 | |
4ced2039 AT |
803 | split_filename(filename_copy, &parent, &basename); |
804 | if (!strlen(basename)) { | |
805 | ret = -EINVAL; | |
806 | goto exit; | |
c30a15e5 DK |
807 | } |
808 | ||
4ced2039 AT |
809 | filename = basename; |
810 | if (normalize_longname(l_filename, filename)) { | |
811 | printf("FAT: illegal filename (%s)\n", filename); | |
812 | ret = -EINVAL; | |
813 | goto exit; | |
c30a15e5 DK |
814 | } |
815 | ||
4ced2039 AT |
816 | itr = malloc_cache_aligned(sizeof(fat_itr)); |
817 | if (!itr) { | |
818 | ret = -ENOMEM; | |
819 | goto exit; | |
c30a15e5 DK |
820 | } |
821 | ||
4ced2039 AT |
822 | ret = fat_itr_root(itr, &datablock); |
823 | if (ret) | |
c30a15e5 | 824 | goto exit; |
c30a15e5 | 825 | |
4ced2039 AT |
826 | total_sector = datablock.total_sect; |
827 | ||
828 | ret = fat_itr_resolve(itr, parent, TYPE_DIR); | |
829 | if (ret) { | |
830 | printf("%s: doesn't exist (%d)\n", parent, ret); | |
25bb9dab AT |
831 | goto exit; |
832 | } | |
c30a15e5 | 833 | |
4ced2039 AT |
834 | retdent = find_directory_entry(itr, l_filename); |
835 | ||
c30a15e5 | 836 | if (retdent) { |
4ced2039 AT |
837 | if (fat_itr_isdir(itr)) { |
838 | ret = -EISDIR; | |
839 | goto exit; | |
840 | } | |
841 | ||
c30a15e5 DK |
842 | /* Update file size and start_cluster in a directory entry */ |
843 | retdent->size = cpu_to_le32(size); | |
e876be4b | 844 | start_cluster = START(retdent); |
c30a15e5 | 845 | |
1254b44a BT |
846 | if (start_cluster) { |
847 | if (size) { | |
848 | ret = check_overflow(mydata, start_cluster, | |
849 | size); | |
850 | if (ret) { | |
851 | printf("Error: %llu overflow\n", size); | |
f1149cea | 852 | ret = -ENOSPC; |
1254b44a BT |
853 | goto exit; |
854 | } | |
855 | } | |
856 | ||
857 | ret = clear_fatent(mydata, start_cluster); | |
858 | if (ret) { | |
859 | printf("Error: clearing FAT entries\n"); | |
f1149cea | 860 | ret = -EIO; |
1254b44a BT |
861 | goto exit; |
862 | } | |
c30a15e5 | 863 | |
1254b44a BT |
864 | if (!size) |
865 | set_start_cluster(mydata, retdent, 0); | |
866 | } else if (size) { | |
867 | ret = start_cluster = find_empty_cluster(mydata); | |
868 | if (ret < 0) { | |
869 | printf("Error: finding empty cluster\n"); | |
f1149cea | 870 | ret = -ENOSPC; |
1254b44a BT |
871 | goto exit; |
872 | } | |
873 | ||
874 | ret = check_overflow(mydata, start_cluster, size); | |
875 | if (ret) { | |
876 | printf("Error: %llu overflow\n", size); | |
f1149cea | 877 | ret = -ENOSPC; |
1254b44a BT |
878 | goto exit; |
879 | } | |
880 | ||
881 | set_start_cluster(mydata, retdent, start_cluster); | |
c30a15e5 | 882 | } |
c30a15e5 | 883 | } else { |
4ced2039 AT |
884 | /* Create a new file */ |
885 | ||
886 | if (itr->is_root) { | |
887 | /* root dir cannot have "." or ".." */ | |
888 | if (!strcmp(l_filename, ".") || | |
889 | !strcmp(l_filename, "..")) { | |
890 | ret = -EINVAL; | |
891 | goto exit; | |
892 | } | |
893 | } | |
894 | ||
895 | if (!itr->dent) { | |
896 | printf("Error: allocating new dir entry\n"); | |
897 | ret = -EIO; | |
898 | goto exit; | |
899 | } | |
900 | ||
901 | memset(itr->dent, 0, sizeof(*itr->dent)); | |
902 | ||
c30a15e5 | 903 | /* Set short name to set alias checksum field in dir_slot */ |
4ced2039 AT |
904 | set_name(itr->dent, filename); |
905 | if (fill_dir_slot(itr, filename)) { | |
906 | ret = -EIO; | |
907 | goto exit; | |
908 | } | |
c30a15e5 | 909 | |
1254b44a BT |
910 | if (size) { |
911 | ret = start_cluster = find_empty_cluster(mydata); | |
912 | if (ret < 0) { | |
913 | printf("Error: finding empty cluster\n"); | |
f1149cea | 914 | ret = -ENOSPC; |
1254b44a BT |
915 | goto exit; |
916 | } | |
c30a15e5 | 917 | |
1254b44a BT |
918 | ret = check_overflow(mydata, start_cluster, size); |
919 | if (ret) { | |
920 | printf("Error: %llu overflow\n", size); | |
f1149cea | 921 | ret = -ENOSPC; |
1254b44a BT |
922 | goto exit; |
923 | } | |
924 | } else { | |
925 | start_cluster = 0; | |
c30a15e5 DK |
926 | } |
927 | ||
4ced2039 AT |
928 | /* Set attribute as archive for regular file */ |
929 | fill_dentry(itr->fsdata, itr->dent, filename, | |
930 | start_cluster, size, 0x20); | |
c30a15e5 | 931 | |
4ced2039 | 932 | retdent = itr->dent; |
e876be4b | 933 | } |
c30a15e5 | 934 | |
e876be4b BT |
935 | ret = set_contents(mydata, retdent, buffer, size, actwrite); |
936 | if (ret < 0) { | |
937 | printf("Error: writing contents\n"); | |
f1149cea | 938 | ret = -EIO; |
e876be4b BT |
939 | goto exit; |
940 | } | |
941 | debug("attempt to write 0x%llx bytes\n", *actwrite); | |
c30a15e5 | 942 | |
e876be4b | 943 | /* Flush fat buffer */ |
3c0ed9c3 | 944 | ret = flush_dirty_fat_buffer(mydata); |
e876be4b BT |
945 | if (ret) { |
946 | printf("Error: flush fat buffer\n"); | |
f1149cea | 947 | ret = -EIO; |
e876be4b | 948 | goto exit; |
c30a15e5 DK |
949 | } |
950 | ||
e876be4b | 951 | /* Write directory table to device */ |
4ced2039 AT |
952 | ret = set_cluster(mydata, itr->clust, itr->block, |
953 | mydata->clust_size * mydata->sect_size); | |
f1149cea | 954 | if (ret) { |
e876be4b | 955 | printf("Error: writing directory entry\n"); |
f1149cea AT |
956 | ret = -EIO; |
957 | } | |
e876be4b | 958 | |
c30a15e5 | 959 | exit: |
4ced2039 | 960 | free(filename_copy); |
c30a15e5 | 961 | free(mydata->fatbuf); |
4ced2039 | 962 | free(itr); |
1ad0b98a | 963 | return ret; |
c30a15e5 DK |
964 | } |
965 | ||
1ad0b98a SR |
966 | int file_fat_write(const char *filename, void *buffer, loff_t offset, |
967 | loff_t maxsize, loff_t *actwrite) | |
c30a15e5 | 968 | { |
1ad0b98a | 969 | if (offset != 0) { |
0af49b95 | 970 | printf("Error: non zero offset is currently not supported.\n"); |
f1149cea | 971 | return -EINVAL; |
1ad0b98a SR |
972 | } |
973 | ||
c30a15e5 | 974 | printf("writing %s\n", filename); |
1ad0b98a | 975 | return do_fat_write(filename, buffer, maxsize, actwrite); |
c30a15e5 | 976 | } |