]>
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 | ||
9c709c7b AT |
212 | static int new_dir_table(fat_itr *itr); |
213 | static int flush_dir(fat_itr *itr); | |
c30a15e5 DK |
214 | |
215 | /* | |
216 | * Fill dir_slot entries with appropriate name, id, and attr | |
4ced2039 | 217 | * 'itr' will point to a next entry |
c30a15e5 | 218 | */ |
4ced2039 AT |
219 | static int |
220 | fill_dir_slot(fat_itr *itr, const char *l_name) | |
c30a15e5 | 221 | { |
7aa1a6b7 TFC |
222 | __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)]; |
223 | dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer; | |
8506eb8d | 224 | __u8 counter = 0, checksum; |
c30a15e5 | 225 | int idx = 0, ret; |
c30a15e5 | 226 | |
ed76f912 | 227 | /* Get short file name checksum value */ |
4ced2039 | 228 | checksum = mkcksum(itr->dent->name, itr->dent->ext); |
c30a15e5 DK |
229 | |
230 | do { | |
231 | memset(slotptr, 0x00, sizeof(dir_slot)); | |
232 | ret = str2slot(slotptr, l_name, &idx); | |
233 | slotptr->id = ++counter; | |
234 | slotptr->attr = ATTR_VFAT; | |
235 | slotptr->alias_checksum = checksum; | |
236 | slotptr++; | |
237 | } while (ret == 0); | |
238 | ||
239 | slotptr--; | |
240 | slotptr->id |= LAST_LONG_ENTRY_MASK; | |
241 | ||
242 | while (counter >= 1) { | |
4ced2039 | 243 | memcpy(itr->dent, slotptr, sizeof(dir_slot)); |
c30a15e5 DK |
244 | slotptr--; |
245 | counter--; | |
9c709c7b AT |
246 | |
247 | if (itr->remaining == 0) | |
248 | flush_dir(itr); | |
249 | ||
cd2d727f | 250 | /* allocate a cluster for more entries */ |
4ced2039 | 251 | if (!fat_itr_next(itr)) |
cd2d727f AT |
252 | if (!itr->dent && |
253 | (!itr->is_root || itr->fsdata->fatsize == 32) && | |
254 | new_dir_table(itr)) | |
c30a15e5 | 255 | return -1; |
c30a15e5 DK |
256 | } |
257 | ||
c30a15e5 DK |
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 | ||
f105fe7b | 390 | /** |
a9f6706c | 391 | * set_sectors() - write data to sectors |
f105fe7b | 392 | * |
a9f6706c | 393 | * Write 'size' bytes from 'buffer' into the specified sector. |
f105fe7b HS |
394 | * |
395 | * @mydata: data to be written | |
a9f6706c | 396 | * @startsect: sector to be written to |
f105fe7b HS |
397 | * @buffer: data to be written |
398 | * @size: bytes to be written (but not more than the size of a cluster) | |
399 | * Return: 0 on success, -1 otherwise | |
c30a15e5 DK |
400 | */ |
401 | static int | |
a9f6706c | 402 | set_sectors(fsdata *mydata, u32 startsect, u8 *buffer, u32 size) |
c30a15e5 | 403 | { |
a9f6706c | 404 | u32 nsects = 0; |
8133f43d | 405 | int ret; |
c30a15e5 | 406 | |
a9f6706c | 407 | debug("startsect: %d\n", startsect); |
c30a15e5 | 408 | |
8133f43d BT |
409 | if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) { |
410 | ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size); | |
411 | ||
1c381ceb | 412 | debug("FAT: Misaligned buffer address (%p)\n", buffer); |
8133f43d BT |
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) { | |
a9f6706c AT |
426 | nsects = size / mydata->sect_size; |
427 | ret = disk_write(startsect, nsects, buffer); | |
428 | if (ret != nsects) { | |
8133f43d | 429 | debug("Error writing data (got %d)\n", ret); |
6b8f185f WJ |
430 | return -1; |
431 | } | |
8133f43d | 432 | |
a9f6706c AT |
433 | startsect += nsects; |
434 | buffer += nsects * mydata->sect_size; | |
435 | size -= nsects * mydata->sect_size; | |
c30a15e5 DK |
436 | } |
437 | ||
8133f43d BT |
438 | if (size) { |
439 | ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size); | |
f105fe7b HS |
440 | /* Do not leak content of stack */ |
441 | memset(tmpbuf, 0, mydata->sect_size); | |
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 | ||
a9f6706c AT |
453 | /** |
454 | * set_cluster() - write data to cluster | |
455 | * | |
456 | * Write 'size' bytes from 'buffer' into the specified cluster. | |
457 | * | |
458 | * @mydata: data to be written | |
459 | * @clustnum: cluster to be written to | |
460 | * @buffer: data to be written | |
461 | * @size: bytes to be written (but not more than the size of a cluster) | |
462 | * Return: 0 on success, -1 otherwise | |
463 | */ | |
464 | static int | |
465 | set_cluster(fsdata *mydata, u32 clustnum, u8 *buffer, u32 size) | |
466 | { | |
467 | return set_sectors(mydata, clust_to_sect(mydata, clustnum), | |
468 | buffer, size); | |
469 | } | |
470 | ||
471 | static int | |
472 | flush_dir(fat_itr *itr) | |
473 | { | |
474 | fsdata *mydata = itr->fsdata; | |
475 | u32 startsect, sect_offset, nsects; | |
476 | ||
477 | if (!itr->is_root || mydata->fatsize == 32) | |
478 | return set_cluster(mydata, itr->clust, itr->block, | |
479 | mydata->clust_size * mydata->sect_size); | |
480 | ||
481 | sect_offset = itr->clust * mydata->clust_size; | |
482 | startsect = mydata->rootdir_sect + sect_offset; | |
483 | /* do not write past the end of rootdir */ | |
484 | nsects = min_t(u32, mydata->clust_size, | |
485 | mydata->rootdir_size - sect_offset); | |
486 | ||
487 | return set_sectors(mydata, startsect, itr->block, | |
488 | nsects * mydata->sect_size); | |
489 | } | |
490 | ||
cb8af8af AT |
491 | static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN); |
492 | ||
493 | /* | |
494 | * Read and modify data on existing and consecutive cluster blocks | |
495 | */ | |
496 | static int | |
497 | get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer, | |
498 | loff_t size, loff_t *gotsize) | |
499 | { | |
500 | unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; | |
501 | __u32 startsect; | |
502 | loff_t wsize; | |
503 | int clustcount, i, ret; | |
504 | ||
505 | *gotsize = 0; | |
506 | if (!size) | |
507 | return 0; | |
508 | ||
509 | assert(pos < bytesperclust); | |
510 | startsect = clust_to_sect(mydata, clustnum); | |
511 | ||
512 | debug("clustnum: %d, startsect: %d, pos: %lld\n", | |
513 | clustnum, startsect, pos); | |
514 | ||
515 | /* partial write at beginning */ | |
516 | if (pos) { | |
517 | wsize = min(bytesperclust - pos, size); | |
518 | ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster); | |
519 | if (ret != mydata->clust_size) { | |
520 | debug("Error reading data (got %d)\n", ret); | |
521 | return -1; | |
522 | } | |
523 | ||
524 | memcpy(tmpbuf_cluster + pos, buffer, wsize); | |
525 | ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster); | |
526 | if (ret != mydata->clust_size) { | |
527 | debug("Error writing data (got %d)\n", ret); | |
528 | return -1; | |
529 | } | |
530 | ||
531 | size -= wsize; | |
532 | buffer += wsize; | |
533 | *gotsize += wsize; | |
534 | ||
535 | startsect += mydata->clust_size; | |
536 | ||
537 | if (!size) | |
538 | return 0; | |
539 | } | |
540 | ||
541 | /* full-cluster write */ | |
542 | if (size >= bytesperclust) { | |
543 | clustcount = lldiv(size, bytesperclust); | |
544 | ||
545 | if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) { | |
546 | wsize = clustcount * bytesperclust; | |
547 | ret = disk_write(startsect, | |
548 | clustcount * mydata->clust_size, | |
549 | buffer); | |
550 | if (ret != clustcount * mydata->clust_size) { | |
551 | debug("Error writing data (got %d)\n", ret); | |
552 | return -1; | |
553 | } | |
554 | ||
555 | size -= wsize; | |
556 | buffer += wsize; | |
557 | *gotsize += wsize; | |
558 | ||
559 | startsect += clustcount * mydata->clust_size; | |
560 | } else { | |
561 | for (i = 0; i < clustcount; i++) { | |
562 | memcpy(tmpbuf_cluster, buffer, bytesperclust); | |
563 | ret = disk_write(startsect, | |
564 | mydata->clust_size, | |
565 | tmpbuf_cluster); | |
566 | if (ret != mydata->clust_size) { | |
567 | debug("Error writing data (got %d)\n", | |
568 | ret); | |
569 | return -1; | |
570 | } | |
571 | ||
572 | size -= bytesperclust; | |
573 | buffer += bytesperclust; | |
574 | *gotsize += bytesperclust; | |
575 | ||
576 | startsect += mydata->clust_size; | |
577 | } | |
578 | } | |
579 | } | |
580 | ||
581 | /* partial write at end */ | |
582 | if (size) { | |
583 | wsize = size; | |
584 | ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster); | |
585 | if (ret != mydata->clust_size) { | |
586 | debug("Error reading data (got %d)\n", ret); | |
587 | return -1; | |
588 | } | |
589 | memcpy(tmpbuf_cluster, buffer, wsize); | |
590 | ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster); | |
591 | if (ret != mydata->clust_size) { | |
592 | debug("Error writing data (got %d)\n", ret); | |
593 | return -1; | |
594 | } | |
595 | ||
596 | size -= wsize; | |
597 | buffer += wsize; | |
598 | *gotsize += wsize; | |
599 | } | |
600 | ||
601 | assert(!size); | |
602 | ||
603 | return 0; | |
604 | } | |
605 | ||
c30a15e5 DK |
606 | /* |
607 | * Find the first empty cluster | |
608 | */ | |
609 | static int find_empty_cluster(fsdata *mydata) | |
610 | { | |
611 | __u32 fat_val, entry = 3; | |
612 | ||
613 | while (1) { | |
b8948d2a | 614 | fat_val = get_fatent(mydata, entry); |
c30a15e5 DK |
615 | if (fat_val == 0) |
616 | break; | |
617 | entry++; | |
618 | } | |
619 | ||
620 | return entry; | |
621 | } | |
622 | ||
623 | /* | |
9c709c7b | 624 | * Allocate a cluster for additional directory entries |
c30a15e5 | 625 | */ |
9c709c7b | 626 | static int new_dir_table(fat_itr *itr) |
c30a15e5 | 627 | { |
4ced2039 | 628 | fsdata *mydata = itr->fsdata; |
c30a15e5 | 629 | int dir_newclust = 0; |
4ced2039 | 630 | unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; |
c30a15e5 | 631 | |
c30a15e5 | 632 | dir_newclust = find_empty_cluster(mydata); |
4ced2039 | 633 | set_fatent_value(mydata, itr->clust, dir_newclust); |
c30a15e5 DK |
634 | if (mydata->fatsize == 32) |
635 | set_fatent_value(mydata, dir_newclust, 0xffffff8); | |
636 | else if (mydata->fatsize == 16) | |
637 | set_fatent_value(mydata, dir_newclust, 0xfff8); | |
49abbd9c PS |
638 | else if (mydata->fatsize == 12) |
639 | set_fatent_value(mydata, dir_newclust, 0xff8); | |
c30a15e5 | 640 | |
4ced2039 AT |
641 | itr->clust = dir_newclust; |
642 | itr->next_clust = dir_newclust; | |
c30a15e5 | 643 | |
3c0ed9c3 | 644 | if (flush_dirty_fat_buffer(mydata) < 0) |
4ced2039 AT |
645 | return -1; |
646 | ||
647 | memset(itr->block, 0x00, bytesperclust); | |
c30a15e5 | 648 | |
4ced2039 AT |
649 | itr->dent = (dir_entry *)itr->block; |
650 | itr->last_cluster = 1; | |
651 | itr->remaining = bytesperclust / sizeof(dir_entry) - 1; | |
c30a15e5 | 652 | |
4ced2039 | 653 | return 0; |
c30a15e5 DK |
654 | } |
655 | ||
656 | /* | |
657 | * Set empty cluster from 'entry' to the end of a file | |
658 | */ | |
659 | static int clear_fatent(fsdata *mydata, __u32 entry) | |
660 | { | |
661 | __u32 fat_val; | |
662 | ||
49abbd9c | 663 | while (!CHECK_CLUST(entry, mydata->fatsize)) { |
b8948d2a | 664 | fat_val = get_fatent(mydata, entry); |
c30a15e5 DK |
665 | if (fat_val != 0) |
666 | set_fatent_value(mydata, entry, 0); | |
667 | else | |
668 | break; | |
669 | ||
c30a15e5 DK |
670 | entry = fat_val; |
671 | } | |
672 | ||
673 | /* Flush fat buffer */ | |
3c0ed9c3 | 674 | if (flush_dirty_fat_buffer(mydata) < 0) |
c30a15e5 DK |
675 | return -1; |
676 | ||
677 | return 0; | |
678 | } | |
679 | ||
704df6aa AT |
680 | /* |
681 | * Set start cluster in directory entry | |
682 | */ | |
683 | static void set_start_cluster(const fsdata *mydata, dir_entry *dentptr, | |
684 | __u32 start_cluster) | |
685 | { | |
686 | if (mydata->fatsize == 32) | |
687 | dentptr->starthi = | |
688 | cpu_to_le16((start_cluster & 0xffff0000) >> 16); | |
689 | dentptr->start = cpu_to_le16(start_cluster & 0xffff); | |
690 | } | |
691 | ||
692 | /* | |
693 | * Check whether adding a file makes the file system to | |
694 | * exceed the size of the block device | |
695 | * Return -1 when overflow occurs, otherwise return 0 | |
696 | */ | |
697 | static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size) | |
698 | { | |
699 | __u32 startsect, sect_num, offset; | |
700 | ||
701 | if (clustnum > 0) | |
702 | startsect = clust_to_sect(mydata, clustnum); | |
703 | else | |
704 | startsect = mydata->rootdir_sect; | |
705 | ||
706 | sect_num = div_u64_rem(size, mydata->sect_size, &offset); | |
707 | ||
708 | if (offset != 0) | |
709 | sect_num++; | |
710 | ||
711 | if (startsect + sect_num > total_sector) | |
712 | return -1; | |
713 | return 0; | |
714 | } | |
715 | ||
c30a15e5 DK |
716 | /* |
717 | * Write at most 'maxsize' bytes from 'buffer' into | |
718 | * the file associated with 'dentptr' | |
1ad0b98a SR |
719 | * Update the number of bytes written in *gotsize and return 0 |
720 | * or return -1 on fatal errors. | |
c30a15e5 DK |
721 | */ |
722 | static int | |
704df6aa AT |
723 | set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer, |
724 | loff_t maxsize, loff_t *gotsize) | |
c30a15e5 | 725 | { |
c30a15e5 DK |
726 | unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; |
727 | __u32 curclust = START(dentptr); | |
728 | __u32 endclust = 0, newclust = 0; | |
7274b763 HS |
729 | u64 cur_pos, filesize; |
730 | loff_t offset, actsize, wsize; | |
c30a15e5 | 731 | |
1ad0b98a | 732 | *gotsize = 0; |
cb8af8af | 733 | filesize = pos + maxsize; |
c30a15e5 | 734 | |
1ad0b98a | 735 | debug("%llu bytes\n", filesize); |
c30a15e5 | 736 | |
cb8af8af AT |
737 | if (!filesize) { |
738 | if (!curclust) | |
739 | return 0; | |
740 | if (!CHECK_CLUST(curclust, mydata->fatsize) || | |
741 | IS_LAST_CLUST(curclust, mydata->fatsize)) { | |
742 | clear_fatent(mydata, curclust); | |
743 | set_start_cluster(mydata, dentptr, 0); | |
744 | return 0; | |
745 | } | |
746 | debug("curclust: 0x%x\n", curclust); | |
747 | debug("Invalid FAT entry\n"); | |
748 | return -1; | |
749 | } | |
750 | ||
751 | if (!curclust) { | |
752 | assert(pos == 0); | |
753 | goto set_clusters; | |
754 | } | |
755 | ||
756 | /* go to cluster at pos */ | |
757 | cur_pos = bytesperclust; | |
758 | while (1) { | |
759 | if (pos <= cur_pos) | |
760 | break; | |
761 | if (IS_LAST_CLUST(curclust, mydata->fatsize)) | |
762 | break; | |
763 | ||
764 | newclust = get_fatent(mydata, curclust); | |
765 | if (!IS_LAST_CLUST(newclust, mydata->fatsize) && | |
766 | CHECK_CLUST(newclust, mydata->fatsize)) { | |
767 | debug("curclust: 0x%x\n", curclust); | |
768 | debug("Invalid FAT entry\n"); | |
1254b44a BT |
769 | return -1; |
770 | } | |
cb8af8af AT |
771 | |
772 | cur_pos += bytesperclust; | |
773 | curclust = newclust; | |
774 | } | |
775 | if (IS_LAST_CLUST(curclust, mydata->fatsize)) { | |
776 | assert(pos == cur_pos); | |
777 | goto set_clusters; | |
704df6aa AT |
778 | } |
779 | ||
cb8af8af AT |
780 | assert(pos < cur_pos); |
781 | cur_pos -= bytesperclust; | |
704df6aa | 782 | |
cb8af8af AT |
783 | /* overwrite */ |
784 | assert(IS_LAST_CLUST(curclust, mydata->fatsize) || | |
785 | !CHECK_CLUST(curclust, mydata->fatsize)); | |
786 | ||
787 | while (1) { | |
788 | /* search for allocated consecutive clusters */ | |
789 | actsize = bytesperclust; | |
790 | endclust = curclust; | |
791 | while (1) { | |
792 | if (filesize <= (cur_pos + actsize)) | |
793 | break; | |
794 | ||
795 | newclust = get_fatent(mydata, endclust); | |
796 | ||
797 | if (IS_LAST_CLUST(newclust, mydata->fatsize)) | |
798 | break; | |
799 | if (CHECK_CLUST(newclust, mydata->fatsize)) { | |
800 | debug("curclust: 0x%x\n", curclust); | |
801 | debug("Invalid FAT entry\n"); | |
802 | return -1; | |
803 | } | |
804 | ||
805 | actsize += bytesperclust; | |
806 | endclust = newclust; | |
807 | } | |
808 | ||
809 | /* overwrite to <curclust..endclust> */ | |
810 | if (pos < cur_pos) | |
811 | offset = 0; | |
812 | else | |
813 | offset = pos - cur_pos; | |
814 | wsize = min(cur_pos + actsize, filesize) - pos; | |
815 | if (get_set_cluster(mydata, curclust, offset, | |
816 | buffer, wsize, &actsize)) { | |
817 | printf("Error get-and-setting cluster\n"); | |
818 | return -1; | |
819 | } | |
820 | buffer += wsize; | |
821 | *gotsize += wsize; | |
822 | cur_pos += offset + wsize; | |
823 | ||
824 | if (filesize <= cur_pos) | |
825 | break; | |
826 | ||
827 | /* CHECK: newclust = get_fatent(mydata, endclust); */ | |
828 | ||
829 | if (IS_LAST_CLUST(newclust, mydata->fatsize)) | |
830 | /* no more clusters */ | |
831 | break; | |
832 | ||
833 | curclust = newclust; | |
834 | } | |
835 | ||
836 | if (filesize <= cur_pos) { | |
837 | /* no more write */ | |
838 | newclust = get_fatent(mydata, endclust); | |
839 | if (!IS_LAST_CLUST(newclust, mydata->fatsize)) { | |
840 | /* truncate the rest */ | |
841 | clear_fatent(mydata, newclust); | |
842 | ||
843 | /* Mark end of file in FAT */ | |
844 | if (mydata->fatsize == 12) | |
845 | newclust = 0xfff; | |
846 | else if (mydata->fatsize == 16) | |
847 | newclust = 0xffff; | |
848 | else if (mydata->fatsize == 32) | |
849 | newclust = 0xfffffff; | |
850 | set_fatent_value(mydata, endclust, newclust); | |
851 | } | |
852 | ||
853 | return 0; | |
854 | } | |
855 | ||
856 | curclust = endclust; | |
857 | filesize -= cur_pos; | |
7274b763 | 858 | assert(!do_div(cur_pos, bytesperclust)); |
cb8af8af AT |
859 | |
860 | set_clusters: | |
861 | /* allocate and write */ | |
862 | assert(!pos); | |
863 | ||
864 | /* Assure that curclust is valid */ | |
865 | if (!curclust) { | |
866 | curclust = find_empty_cluster(mydata); | |
867 | set_start_cluster(mydata, dentptr, curclust); | |
868 | } else { | |
869 | newclust = get_fatent(mydata, curclust); | |
870 | ||
871 | if (IS_LAST_CLUST(newclust, mydata->fatsize)) { | |
872 | newclust = determine_fatent(mydata, curclust); | |
873 | set_fatent_value(mydata, curclust, newclust); | |
874 | curclust = newclust; | |
875 | } else { | |
876 | debug("error: something wrong\n"); | |
877 | return -1; | |
878 | } | |
879 | } | |
880 | ||
881 | /* TODO: already partially written */ | |
704df6aa AT |
882 | if (check_overflow(mydata, curclust, filesize)) { |
883 | printf("Error: no space left: %llu\n", filesize); | |
884 | return -1; | |
1254b44a BT |
885 | } |
886 | ||
c30a15e5 DK |
887 | actsize = bytesperclust; |
888 | endclust = curclust; | |
889 | do { | |
890 | /* search for consecutive clusters */ | |
891 | while (actsize < filesize) { | |
892 | newclust = determine_fatent(mydata, endclust); | |
893 | ||
894 | if ((newclust - 1) != endclust) | |
704df6aa | 895 | /* write to <curclust..endclust> */ |
c30a15e5 DK |
896 | goto getit; |
897 | ||
898 | if (CHECK_CLUST(newclust, mydata->fatsize)) { | |
5e1a860e | 899 | debug("newclust: 0x%x\n", newclust); |
c30a15e5 | 900 | debug("Invalid FAT entry\n"); |
1ad0b98a | 901 | return 0; |
c30a15e5 DK |
902 | } |
903 | endclust = newclust; | |
904 | actsize += bytesperclust; | |
905 | } | |
c30a15e5 DK |
906 | |
907 | /* set remaining bytes */ | |
c30a15e5 | 908 | actsize = filesize; |
f105fe7b | 909 | if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) { |
c30a15e5 DK |
910 | debug("error: writing cluster\n"); |
911 | return -1; | |
912 | } | |
1ad0b98a | 913 | *gotsize += actsize; |
c30a15e5 DK |
914 | |
915 | /* Mark end of file in FAT */ | |
49abbd9c PS |
916 | if (mydata->fatsize == 12) |
917 | newclust = 0xfff; | |
918 | else if (mydata->fatsize == 16) | |
c30a15e5 DK |
919 | newclust = 0xffff; |
920 | else if (mydata->fatsize == 32) | |
921 | newclust = 0xfffffff; | |
922 | set_fatent_value(mydata, endclust, newclust); | |
923 | ||
1ad0b98a | 924 | return 0; |
c30a15e5 | 925 | getit: |
f105fe7b | 926 | if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) { |
c30a15e5 DK |
927 | debug("error: writing cluster\n"); |
928 | return -1; | |
929 | } | |
1ad0b98a | 930 | *gotsize += actsize; |
c30a15e5 DK |
931 | filesize -= actsize; |
932 | buffer += actsize; | |
933 | ||
5e1a860e BT |
934 | if (CHECK_CLUST(newclust, mydata->fatsize)) { |
935 | debug("newclust: 0x%x\n", newclust); | |
c30a15e5 | 936 | debug("Invalid FAT entry\n"); |
1ad0b98a | 937 | return 0; |
c30a15e5 DK |
938 | } |
939 | actsize = bytesperclust; | |
940 | curclust = endclust = newclust; | |
941 | } while (1); | |
c30a15e5 | 942 | |
704df6aa | 943 | return 0; |
1254b44a BT |
944 | } |
945 | ||
946 | /* | |
947 | * Fill dir_entry | |
948 | */ | |
949 | static void fill_dentry(fsdata *mydata, dir_entry *dentptr, | |
950 | const char *filename, __u32 start_cluster, __u32 size, __u8 attr) | |
951 | { | |
952 | set_start_cluster(mydata, dentptr, start_cluster); | |
c30a15e5 DK |
953 | dentptr->size = cpu_to_le32(size); |
954 | ||
955 | dentptr->attr = attr; | |
956 | ||
957 | set_name(dentptr, filename); | |
958 | } | |
959 | ||
c30a15e5 DK |
960 | /* |
961 | * Find a directory entry based on filename or start cluster number | |
962 | * If the directory entry is not found, | |
963 | * the new position for writing a directory entry will be returned | |
964 | */ | |
4ced2039 | 965 | static dir_entry *find_directory_entry(fat_itr *itr, char *filename) |
c30a15e5 | 966 | { |
4ced2039 | 967 | int match = 0; |
c30a15e5 | 968 | |
4ced2039 AT |
969 | while (fat_itr_next(itr)) { |
970 | /* check both long and short name: */ | |
971 | if (!strcasecmp(filename, itr->name)) | |
972 | match = 1; | |
973 | else if (itr->name != itr->s_name && | |
974 | !strcasecmp(filename, itr->s_name)) | |
975 | match = 1; | |
c30a15e5 | 976 | |
4ced2039 AT |
977 | if (!match) |
978 | continue; | |
c30a15e5 | 979 | |
4ced2039 | 980 | if (itr->dent->name[0] == '\0') |
c30a15e5 | 981 | return NULL; |
4ced2039 AT |
982 | else |
983 | return itr->dent; | |
984 | } | |
c30a15e5 | 985 | |
cd2d727f AT |
986 | /* allocate a cluster for more entries */ |
987 | if (!itr->dent && | |
988 | (!itr->is_root || itr->fsdata->fatsize == 32) && | |
989 | new_dir_table(itr)) | |
4ced2039 AT |
990 | /* indicate that allocating dent failed */ |
991 | itr->dent = NULL; | |
c30a15e5 | 992 | |
4ced2039 AT |
993 | return NULL; |
994 | } | |
c30a15e5 | 995 | |
4ced2039 AT |
996 | static int split_filename(char *filename, char **dirname, char **basename) |
997 | { | |
998 | char *p, *last_slash, *last_slash_cont; | |
999 | ||
1000 | again: | |
1001 | p = filename; | |
1002 | last_slash = NULL; | |
1003 | last_slash_cont = NULL; | |
1004 | while (*p) { | |
1005 | if (ISDIRDELIM(*p)) { | |
1006 | last_slash = p; | |
1007 | last_slash_cont = p; | |
1008 | /* continuous slashes */ | |
1009 | while (ISDIRDELIM(*p)) | |
1010 | last_slash_cont = p++; | |
1011 | if (!*p) | |
1012 | break; | |
c30a15e5 | 1013 | } |
4ced2039 AT |
1014 | p++; |
1015 | } | |
c30a15e5 | 1016 | |
4ced2039 AT |
1017 | if (last_slash) { |
1018 | if (last_slash_cont == (filename + strlen(filename) - 1)) { | |
1019 | /* remove trailing slashes */ | |
1020 | *last_slash = '\0'; | |
1021 | goto again; | |
dd6d7967 WJ |
1022 | } |
1023 | ||
4ced2039 AT |
1024 | if (last_slash == filename) { |
1025 | /* avoid ""(null) directory */ | |
1026 | *dirname = "/"; | |
1027 | } else { | |
1028 | *last_slash = '\0'; | |
1029 | *dirname = filename; | |
c30a15e5 | 1030 | } |
4ced2039 AT |
1031 | |
1032 | *last_slash_cont = '\0'; | |
1033 | *basename = last_slash_cont + 1; | |
1034 | } else { | |
1035 | *dirname = "/"; /* root by default */ | |
1036 | *basename = filename; | |
c30a15e5 DK |
1037 | } |
1038 | ||
4ced2039 | 1039 | return 0; |
c30a15e5 DK |
1040 | } |
1041 | ||
7b437807 HS |
1042 | /** |
1043 | * normalize_longname() - check long file name and convert to lower case | |
1044 | * | |
1045 | * We assume here that the FAT file system is using an 8bit code page. | |
1046 | * Linux typically uses CP437, EDK2 assumes CP1250. | |
1047 | * | |
1048 | * @l_filename: preallocated buffer receiving the normalized name | |
1049 | * @filename: filename to normalize | |
1050 | * Return: 0 on success, -1 on failure | |
1051 | */ | |
25bb9dab AT |
1052 | static int normalize_longname(char *l_filename, const char *filename) |
1053 | { | |
7b437807 | 1054 | const char *p, illegal[] = "<>:\"/\\|?*"; |
25bb9dab | 1055 | |
7b437807 | 1056 | if (strlen(filename) >= VFAT_MAXLEN_BYTES) |
25bb9dab | 1057 | return -1; |
25bb9dab | 1058 | |
7b437807 HS |
1059 | for (p = filename; *p; ++p) { |
1060 | if ((unsigned char)*p < 0x20) | |
1061 | return -1; | |
1062 | if (strchr(illegal, *p)) | |
1063 | return -1; | |
1064 | } | |
25bb9dab | 1065 | |
7b437807 HS |
1066 | strcpy(l_filename, filename); |
1067 | downcase(l_filename, VFAT_MAXLEN_BYTES); | |
25bb9dab AT |
1068 | |
1069 | return 0; | |
1070 | } | |
1071 | ||
704df6aa AT |
1072 | int file_fat_write_at(const char *filename, loff_t pos, void *buffer, |
1073 | loff_t size, loff_t *actwrite) | |
c30a15e5 | 1074 | { |
4ced2039 | 1075 | dir_entry *retdent; |
4ced2039 | 1076 | fsdata datablock = { .fatbuf = NULL, }; |
c30a15e5 | 1077 | fsdata *mydata = &datablock; |
4ced2039 | 1078 | fat_itr *itr = NULL; |
25bb9dab | 1079 | int ret = -1; |
4ced2039 | 1080 | char *filename_copy, *parent, *basename; |
c30a15e5 DK |
1081 | char l_filename[VFAT_MAXLEN_BYTES]; |
1082 | ||
704df6aa AT |
1083 | debug("writing %s\n", filename); |
1084 | ||
4ced2039 AT |
1085 | filename_copy = strdup(filename); |
1086 | if (!filename_copy) | |
1087 | return -ENOMEM; | |
c30a15e5 | 1088 | |
4ced2039 AT |
1089 | split_filename(filename_copy, &parent, &basename); |
1090 | if (!strlen(basename)) { | |
1091 | ret = -EINVAL; | |
1092 | goto exit; | |
c30a15e5 DK |
1093 | } |
1094 | ||
4ced2039 AT |
1095 | filename = basename; |
1096 | if (normalize_longname(l_filename, filename)) { | |
1097 | printf("FAT: illegal filename (%s)\n", filename); | |
1098 | ret = -EINVAL; | |
1099 | goto exit; | |
c30a15e5 DK |
1100 | } |
1101 | ||
4ced2039 AT |
1102 | itr = malloc_cache_aligned(sizeof(fat_itr)); |
1103 | if (!itr) { | |
1104 | ret = -ENOMEM; | |
1105 | goto exit; | |
c30a15e5 DK |
1106 | } |
1107 | ||
4ced2039 AT |
1108 | ret = fat_itr_root(itr, &datablock); |
1109 | if (ret) | |
c30a15e5 | 1110 | goto exit; |
c30a15e5 | 1111 | |
4ced2039 AT |
1112 | total_sector = datablock.total_sect; |
1113 | ||
1114 | ret = fat_itr_resolve(itr, parent, TYPE_DIR); | |
1115 | if (ret) { | |
1116 | printf("%s: doesn't exist (%d)\n", parent, ret); | |
25bb9dab AT |
1117 | goto exit; |
1118 | } | |
c30a15e5 | 1119 | |
4ced2039 AT |
1120 | retdent = find_directory_entry(itr, l_filename); |
1121 | ||
c30a15e5 | 1122 | if (retdent) { |
4ced2039 AT |
1123 | if (fat_itr_isdir(itr)) { |
1124 | ret = -EISDIR; | |
1125 | goto exit; | |
1126 | } | |
1127 | ||
cb8af8af AT |
1128 | /* A file exists */ |
1129 | if (pos == -1) | |
1130 | /* Append to the end */ | |
1131 | pos = FAT2CPU32(retdent->size); | |
1132 | if (pos > retdent->size) { | |
1133 | /* No hole allowed */ | |
1134 | ret = -EINVAL; | |
1135 | goto exit; | |
1136 | } | |
1137 | ||
704df6aa AT |
1138 | /* Update file size in a directory entry */ |
1139 | retdent->size = cpu_to_le32(pos + size); | |
c30a15e5 | 1140 | } else { |
4ced2039 AT |
1141 | /* Create a new file */ |
1142 | ||
1143 | if (itr->is_root) { | |
1144 | /* root dir cannot have "." or ".." */ | |
1145 | if (!strcmp(l_filename, ".") || | |
1146 | !strcmp(l_filename, "..")) { | |
1147 | ret = -EINVAL; | |
1148 | goto exit; | |
1149 | } | |
1150 | } | |
1151 | ||
1152 | if (!itr->dent) { | |
1153 | printf("Error: allocating new dir entry\n"); | |
1154 | ret = -EIO; | |
1155 | goto exit; | |
1156 | } | |
1157 | ||
cb8af8af AT |
1158 | if (pos) { |
1159 | /* No hole allowed */ | |
1160 | ret = -EINVAL; | |
1161 | goto exit; | |
1162 | } | |
1163 | ||
4ced2039 AT |
1164 | memset(itr->dent, 0, sizeof(*itr->dent)); |
1165 | ||
9c709c7b | 1166 | /* Calculate checksum for short name */ |
4ced2039 | 1167 | set_name(itr->dent, filename); |
9c709c7b AT |
1168 | |
1169 | /* Set long name entries */ | |
4ced2039 AT |
1170 | if (fill_dir_slot(itr, filename)) { |
1171 | ret = -EIO; | |
1172 | goto exit; | |
1173 | } | |
c30a15e5 | 1174 | |
9c709c7b | 1175 | /* Set short name entry */ |
704df6aa | 1176 | fill_dentry(itr->fsdata, itr->dent, filename, 0, size, 0x20); |
c30a15e5 | 1177 | |
4ced2039 | 1178 | retdent = itr->dent; |
e876be4b | 1179 | } |
c30a15e5 | 1180 | |
704df6aa | 1181 | ret = set_contents(mydata, retdent, pos, buffer, size, actwrite); |
e876be4b BT |
1182 | if (ret < 0) { |
1183 | printf("Error: writing contents\n"); | |
f1149cea | 1184 | ret = -EIO; |
e876be4b BT |
1185 | goto exit; |
1186 | } | |
1187 | debug("attempt to write 0x%llx bytes\n", *actwrite); | |
c30a15e5 | 1188 | |
e876be4b | 1189 | /* Flush fat buffer */ |
3c0ed9c3 | 1190 | ret = flush_dirty_fat_buffer(mydata); |
e876be4b BT |
1191 | if (ret) { |
1192 | printf("Error: flush fat buffer\n"); | |
f1149cea | 1193 | ret = -EIO; |
e876be4b | 1194 | goto exit; |
c30a15e5 DK |
1195 | } |
1196 | ||
e876be4b | 1197 | /* Write directory table to device */ |
a9f6706c | 1198 | ret = flush_dir(itr); |
f1149cea | 1199 | if (ret) { |
e876be4b | 1200 | printf("Error: writing directory entry\n"); |
f1149cea AT |
1201 | ret = -EIO; |
1202 | } | |
e876be4b | 1203 | |
c30a15e5 | 1204 | exit: |
4ced2039 | 1205 | free(filename_copy); |
c30a15e5 | 1206 | free(mydata->fatbuf); |
4ced2039 | 1207 | free(itr); |
1ad0b98a | 1208 | return ret; |
c30a15e5 DK |
1209 | } |
1210 | ||
1ad0b98a SR |
1211 | int file_fat_write(const char *filename, void *buffer, loff_t offset, |
1212 | loff_t maxsize, loff_t *actwrite) | |
c30a15e5 | 1213 | { |
704df6aa | 1214 | return file_fat_write_at(filename, offset, buffer, maxsize, actwrite); |
c30a15e5 | 1215 | } |
31a18d57 | 1216 | |
f8240ce9 AT |
1217 | static int fat_dir_entries(fat_itr *itr) |
1218 | { | |
1219 | fat_itr *dirs; | |
1220 | fsdata fsdata = { .fatbuf = NULL, }, *mydata = &fsdata; | |
1221 | /* for FATBUFSIZE */ | |
1222 | int count; | |
1223 | ||
1224 | dirs = malloc_cache_aligned(sizeof(fat_itr)); | |
1225 | if (!dirs) { | |
1226 | debug("Error: allocating memory\n"); | |
1227 | count = -ENOMEM; | |
1228 | goto exit; | |
1229 | } | |
1230 | ||
1231 | /* duplicate fsdata */ | |
1232 | fat_itr_child(dirs, itr); | |
1233 | fsdata = *dirs->fsdata; | |
1234 | ||
1235 | /* allocate local fat buffer */ | |
1236 | fsdata.fatbuf = malloc_cache_aligned(FATBUFSIZE); | |
1237 | if (!fsdata.fatbuf) { | |
1238 | debug("Error: allocating memory\n"); | |
1239 | count = -ENOMEM; | |
1240 | goto exit; | |
1241 | } | |
1242 | fsdata.fatbufnum = -1; | |
1243 | dirs->fsdata = &fsdata; | |
1244 | ||
1245 | for (count = 0; fat_itr_next(dirs); count++) | |
1246 | ; | |
1247 | ||
1248 | exit: | |
1249 | free(fsdata.fatbuf); | |
1250 | free(dirs); | |
1251 | return count; | |
1252 | } | |
1253 | ||
1254 | static int delete_dentry(fat_itr *itr) | |
1255 | { | |
1256 | fsdata *mydata = itr->fsdata; | |
1257 | dir_entry *dentptr = itr->dent; | |
1258 | ||
1259 | /* free cluster blocks */ | |
1260 | clear_fatent(mydata, START(dentptr)); | |
1261 | if (flush_dirty_fat_buffer(mydata) < 0) { | |
1262 | printf("Error: flush fat buffer\n"); | |
1263 | return -EIO; | |
1264 | } | |
1265 | ||
1266 | /* | |
1267 | * update a directory entry | |
1268 | * TODO: | |
1269 | * - long file name support | |
1270 | * - find and mark the "new" first invalid entry as name[0]=0x00 | |
1271 | */ | |
1272 | memset(dentptr, 0, sizeof(*dentptr)); | |
1273 | dentptr->name[0] = 0xe5; | |
1274 | ||
a9f6706c | 1275 | if (flush_dir(itr)) { |
f8240ce9 AT |
1276 | printf("error: writing directory entry\n"); |
1277 | return -EIO; | |
1278 | } | |
1279 | ||
1280 | return 0; | |
1281 | } | |
1282 | ||
1283 | int fat_unlink(const char *filename) | |
1284 | { | |
1285 | fsdata fsdata = { .fatbuf = NULL, }; | |
1286 | fat_itr *itr = NULL; | |
1287 | int n_entries, ret; | |
1288 | char *filename_copy, *dirname, *basename; | |
1289 | ||
1290 | filename_copy = strdup(filename); | |
0d532e91 HS |
1291 | if (!filename_copy) { |
1292 | printf("Error: allocating memory\n"); | |
1293 | ret = -ENOMEM; | |
1294 | goto exit; | |
1295 | } | |
f8240ce9 AT |
1296 | split_filename(filename_copy, &dirname, &basename); |
1297 | ||
1298 | if (!strcmp(dirname, "/") && !strcmp(basename, "")) { | |
1299 | printf("Error: cannot remove root\n"); | |
1300 | ret = -EINVAL; | |
1301 | goto exit; | |
1302 | } | |
1303 | ||
1304 | itr = malloc_cache_aligned(sizeof(fat_itr)); | |
1305 | if (!itr) { | |
1306 | printf("Error: allocating memory\n"); | |
0d532e91 HS |
1307 | ret = -ENOMEM; |
1308 | goto exit; | |
f8240ce9 AT |
1309 | } |
1310 | ||
1311 | ret = fat_itr_root(itr, &fsdata); | |
1312 | if (ret) | |
1313 | goto exit; | |
1314 | ||
1315 | total_sector = fsdata.total_sect; | |
1316 | ||
1317 | ret = fat_itr_resolve(itr, dirname, TYPE_DIR); | |
1318 | if (ret) { | |
1319 | printf("%s: doesn't exist (%d)\n", dirname, ret); | |
1320 | ret = -ENOENT; | |
1321 | goto exit; | |
1322 | } | |
1323 | ||
1324 | if (!find_directory_entry(itr, basename)) { | |
1325 | printf("%s: doesn't exist\n", basename); | |
1326 | ret = -ENOENT; | |
1327 | goto exit; | |
1328 | } | |
1329 | ||
1330 | if (fat_itr_isdir(itr)) { | |
1331 | n_entries = fat_dir_entries(itr); | |
1332 | if (n_entries < 0) { | |
1333 | ret = n_entries; | |
1334 | goto exit; | |
1335 | } | |
1336 | if (n_entries > 2) { | |
1337 | printf("Error: directory is not empty: %d\n", | |
1338 | n_entries); | |
1339 | ret = -EINVAL; | |
1340 | goto exit; | |
1341 | } | |
1342 | } | |
1343 | ||
1344 | ret = delete_dentry(itr); | |
1345 | ||
1346 | exit: | |
1347 | free(fsdata.fatbuf); | |
1348 | free(itr); | |
1349 | free(filename_copy); | |
1350 | ||
1351 | return ret; | |
1352 | } | |
1353 | ||
31a18d57 AT |
1354 | int fat_mkdir(const char *new_dirname) |
1355 | { | |
1356 | dir_entry *retdent; | |
1357 | fsdata datablock = { .fatbuf = NULL, }; | |
1358 | fsdata *mydata = &datablock; | |
1359 | fat_itr *itr = NULL; | |
1360 | char *dirname_copy, *parent, *dirname; | |
1361 | char l_dirname[VFAT_MAXLEN_BYTES]; | |
1362 | int ret = -1; | |
1363 | loff_t actwrite; | |
1364 | unsigned int bytesperclust; | |
1365 | dir_entry *dotdent = NULL; | |
1366 | ||
1367 | dirname_copy = strdup(new_dirname); | |
1368 | if (!dirname_copy) | |
1369 | goto exit; | |
1370 | ||
1371 | split_filename(dirname_copy, &parent, &dirname); | |
1372 | if (!strlen(dirname)) { | |
1373 | ret = -EINVAL; | |
1374 | goto exit; | |
1375 | } | |
1376 | ||
1377 | if (normalize_longname(l_dirname, dirname)) { | |
1378 | printf("FAT: illegal filename (%s)\n", dirname); | |
1379 | ret = -EINVAL; | |
1380 | goto exit; | |
1381 | } | |
1382 | ||
1383 | itr = malloc_cache_aligned(sizeof(fat_itr)); | |
1384 | if (!itr) { | |
1385 | ret = -ENOMEM; | |
1386 | goto exit; | |
1387 | } | |
1388 | ||
1389 | ret = fat_itr_root(itr, &datablock); | |
1390 | if (ret) | |
1391 | goto exit; | |
1392 | ||
1393 | total_sector = datablock.total_sect; | |
1394 | ||
1395 | ret = fat_itr_resolve(itr, parent, TYPE_DIR); | |
1396 | if (ret) { | |
1397 | printf("%s: doesn't exist (%d)\n", parent, ret); | |
1398 | goto exit; | |
1399 | } | |
1400 | ||
1401 | retdent = find_directory_entry(itr, l_dirname); | |
1402 | ||
1403 | if (retdent) { | |
1404 | printf("%s: already exists\n", l_dirname); | |
1405 | ret = -EEXIST; | |
1406 | goto exit; | |
1407 | } else { | |
1408 | if (itr->is_root) { | |
1409 | /* root dir cannot have "." or ".." */ | |
1410 | if (!strcmp(l_dirname, ".") || | |
1411 | !strcmp(l_dirname, "..")) { | |
1412 | ret = -EINVAL; | |
1413 | goto exit; | |
1414 | } | |
1415 | } | |
1416 | ||
1417 | if (!itr->dent) { | |
1418 | printf("Error: allocating new dir entry\n"); | |
1419 | ret = -EIO; | |
1420 | goto exit; | |
1421 | } | |
1422 | ||
1423 | memset(itr->dent, 0, sizeof(*itr->dent)); | |
1424 | ||
1425 | /* Set short name to set alias checksum field in dir_slot */ | |
1426 | set_name(itr->dent, dirname); | |
1427 | fill_dir_slot(itr, dirname); | |
1428 | ||
1429 | /* Set attribute as archive for regular file */ | |
1430 | fill_dentry(itr->fsdata, itr->dent, dirname, 0, 0, | |
1431 | ATTR_DIR | ATTR_ARCH); | |
1432 | ||
1433 | retdent = itr->dent; | |
1434 | } | |
1435 | ||
1436 | /* Default entries */ | |
1437 | bytesperclust = mydata->clust_size * mydata->sect_size; | |
1438 | dotdent = malloc_cache_aligned(bytesperclust); | |
1439 | if (!dotdent) { | |
1440 | ret = -ENOMEM; | |
1441 | goto exit; | |
1442 | } | |
1443 | memset(dotdent, 0, bytesperclust); | |
1444 | ||
1445 | memcpy(dotdent[0].name, ". ", 8); | |
1446 | memcpy(dotdent[0].ext, " ", 3); | |
1447 | dotdent[0].attr = ATTR_DIR | ATTR_ARCH; | |
1448 | ||
1449 | memcpy(dotdent[1].name, ".. ", 8); | |
1450 | memcpy(dotdent[1].ext, " ", 3); | |
1451 | dotdent[1].attr = ATTR_DIR | ATTR_ARCH; | |
1452 | set_start_cluster(mydata, &dotdent[1], itr->start_clust); | |
1453 | ||
1454 | ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent, | |
1455 | bytesperclust, &actwrite); | |
1456 | if (ret < 0) { | |
1457 | printf("Error: writing contents\n"); | |
1458 | goto exit; | |
1459 | } | |
1460 | /* Write twice for "." */ | |
1461 | set_start_cluster(mydata, &dotdent[0], START(retdent)); | |
1462 | ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent, | |
1463 | bytesperclust, &actwrite); | |
1464 | if (ret < 0) { | |
1465 | printf("Error: writing contents\n"); | |
1466 | goto exit; | |
1467 | } | |
1468 | ||
1469 | /* Flush fat buffer */ | |
1470 | ret = flush_dirty_fat_buffer(mydata); | |
1471 | if (ret) { | |
1472 | printf("Error: flush fat buffer\n"); | |
1473 | goto exit; | |
1474 | } | |
1475 | ||
1476 | /* Write directory table to device */ | |
a9f6706c | 1477 | ret = flush_dir(itr); |
31a18d57 AT |
1478 | if (ret) |
1479 | printf("Error: writing directory entry\n"); | |
1480 | ||
1481 | exit: | |
1482 | free(dirname_copy); | |
1483 | free(mydata->fatbuf); | |
1484 | free(itr); | |
1485 | free(dotdent); | |
1486 | return ret; | |
1487 | } |