1 /*---------------------------------------------------------------------------*
2   Project:  TwlSDK - FS - libraries
3   File:     fs_archive.c
4 
5   Copyright 2007-2009 Nintendo. All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law. They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Date:: 2009-08-27#$
14   $Rev: 11020 $
15   $Author: yosizaki $
16 
17  *---------------------------------------------------------------------------*/
18 
19 
20 #include <nitro/fs.h>
21 
22 #include <nitro/mi/memory.h>
23 #include <nitro/std.h>
24 
25 
26 #include "../include/util.h"
27 #include "../include/command.h"
28 
29 
30 #if defined(FS_IMPLEMENT)
31 
32 
33 /*---------------------------------------------------------------------------*/
34 /* Constants */
35 
36 // Enable when internally supporting long archive names
37 #define FS_SUPPORT_LONG_ARCNAME
38 
39 
40 /*---------------------------------------------------------------------------*/
41 /* Variables */
42 
43 // Archive list registered to the file system
44 static FSArchive   *arc_list = NULL;
45 
46 // Current directory
47 static FSDirPos     current_dir_pos;
48 static char         current_dir_path[FS_ENTRY_LONGNAME_MAX];
49 
50 #if defined(FS_SUPPORT_LONG_ARCNAME)
51 // Static buffer for internal support of long archive names
52 #define FS_LONG_ARCNAME_LENGTH_MAX  15
53 #define FS_LONG_ARCNAME_TABLE_MAX   16
54 static char FSiLongNameTable[FS_LONG_ARCNAME_TABLE_MAX][FS_LONG_ARCNAME_LENGTH_MAX + 1];
55 #endif
56 
57 
58 /*---------------------------------------------------------------------------*/
59 /* Functions */
60 
FSi_GetArchiveChain(void)61 FSArchive* FSi_GetArchiveChain(void)
62 {
63     return arc_list;
64 }
65 
66 /*---------------------------------------------------------------------------*
67   Name:         FSi_IsEventCommand
68 
69   Description:  Determines whether the command is an event notification.
70 
71   Arguments:    command: Command type
72 
73   Returns:      TRUE when command is event notification.
74  *---------------------------------------------------------------------------*/
FSi_IsEventCommand(FSCommandType command)75 static BOOL FSi_IsEventCommand(FSCommandType command)
76 {
77     return
78         ((command == FS_COMMAND_ACTIVATE) ||
79         (command == FS_COMMAND_IDLE) ||
80         (command == FS_COMMAND_SUSPEND) ||
81         (command == FS_COMMAND_RESUME) ||
82         (command == FS_COMMAND_MOUNT) ||
83         (command == FS_COMMAND_UNMOUNT) ||
84         (command == FS_COMMAND_INVALID));
85 }
86 
87 /*---------------------------------------------------------------------------*
88   Name:         FSi_EndCommand
89 
90   Description:  Completes the command and returns if there is an idle thread.
91 
92   Arguments:    file: Completed command
93                 ret: Command result value
94 
95   Returns:      None.
96  *---------------------------------------------------------------------------*/
FSi_EndCommand(FSFile * file,FSResult ret)97 static void FSi_EndCommand(FSFile *file, FSResult ret)
98 {
99     OSIntrMode bak_psr = OS_DisableInterrupts();
100     // Delete command from the list
101     FSArchive *const arc = file->arc;
102     if (arc)
103     {
104         FSFile    **pp = &arc->list;
105         for (; *pp; pp = &(*pp)->next)
106         {
107             if (*pp == file)
108             {
109                 *pp = file->next;
110                 break;
111             }
112         }
113         file->next = NULL;
114     }
115     // Store results
116     {
117         FSCommandType   command = FSi_GetCurrentCommand(file);
118         if (!FSi_IsEventCommand(command) && (arc != NULL))
119         {
120             arc->command = command;
121             arc->result = ret;
122         }
123         file->error = ret;
124         file->stat &= ~(FS_FILE_STATUS_CANCEL | FS_FILE_STATUS_BUSY |
125                         FS_FILE_STATUS_BLOCKING | FS_FILE_STATUS_OPERATING |
126                         FS_FILE_STATUS_ASYNC_DONE | FS_FILE_STATUS_UNICODE_MODE);
127     }
128     // Notify if there is an idling thread
129     OS_WakeupThread(file->queue);
130     (void)OS_RestoreInterrupts(bak_psr);
131 }
132 
133 
134 // The following two temporarily publicized for procedures
135 // Commands recursively calling from the procedures are all synchronous, so it is acceptable for applying only the FSi_WaitForArchiveCompletion function by directly calling via the vtbl.
136 //
137 
FSi_WaitForArchiveCompletion(FSFile * file,FSResult result)138 FSResult FSi_WaitForArchiveCompletion(FSFile *file, FSResult result)
139 {
140     if (result == FS_RESULT_PROC_ASYNC)
141     {
142         FSi_WaitConditionOn(&file->stat, FS_FILE_STATUS_ASYNC_DONE, file->queue);
143         file->stat &= ~FS_FILE_STATUS_ASYNC_DONE;
144         result = file->error;
145     }
146     return result;
147 }
148 
149 /*---------------------------------------------------------------------------*
150   Name:         FSi_InvokeCommand
151 
152   Description:  Issues a command to an archive.
153                 Command list management is resolved before calling this function.
154 
155   Arguments:    file: FSFile structure that stores the argument
156                 Command: Command ID
157 
158   Returns:      The processing result for the command.
159  *---------------------------------------------------------------------------*/
FSi_InvokeCommand(FSFile * file,FSCommandType command)160 static FSResult FSi_InvokeCommand(FSFile *file, FSCommandType command)
161 {
162     FSResult            result = FS_RESULT_UNSUPPORTED;
163     FSArchive   * const arc = file->arc;
164 
165     {
166         const void   *(*table) = (const void*)arc->vtbl;
167         // Undefined command out of range
168         if ((command < 0) || (command >= FS_COMMAND_MAX))
169         {
170             OS_TWarning("undefined command (%d)\n", command);
171             result = FS_RESULT_UNSUPPORTED;
172         }
173         // Call by restoring the argument if it is not a command whose implementation was abandoned by the driver side
174         else if (table[command] == NULL)
175         {
176             result = FS_RESULT_UNSUPPORTED;
177         }
178         else
179         {
180 #define FS_DECLARE_ARGUMENT_(type)                              \
181             type *arg = (type*) file->argument;                 \
182             (void)arg
183 
184 #define FS_INVOKE_METHOD_(command, ...)                         \
185             do                                                  \
186             {                                                   \
187                 FS_DECLARE_ARGUMENT_(FSArgumentFor ## command); \
188                 result = arc->vtbl->command(__VA_ARGS__);       \
189             }                                                   \
190             while(0)
191 #define FS_NOTIFY_EVENT_(command, ...)                          \
192             do                                                  \
193             {                                                   \
194                 FS_DECLARE_ARGUMENT_(FSArgumentFor ## command); \
195                 (void)arc->vtbl->command(__VA_ARGS__);          \
196                 return FS_RESULT_SUCCESS;                       \
197             }                                                   \
198             while(0)
199             switch (command)
200             {
201             case FS_COMMAND_READFILE:
202                 FS_INVOKE_METHOD_(ReadFile, arc, file, arg->buffer, &arg->length);
203                 break;
204             case FS_COMMAND_WRITEFILE:
205                 FS_INVOKE_METHOD_(WriteFile, arc, file, arg->buffer, &arg->length);
206                 break;
207             case FS_COMMAND_SEEKDIR:
208                 FS_INVOKE_METHOD_(SeekDirectory, arc, file, arg->id, arg->position);
209                 break;
210             case FS_COMMAND_READDIR:
211                 FS_INVOKE_METHOD_(ReadDirectory, arc, file, arg->info);
212                 break;
213             case FS_COMMAND_FINDPATH:
214                 FS_INVOKE_METHOD_(FindPath, arc, arg->baseid, arg->relpath, &arg->target_id, arg->target_is_directory);
215                 break;
216             case FS_COMMAND_GETPATH:
217                 FS_INVOKE_METHOD_(GetPath, arc, file, arg->is_directory, arg->buffer, &arg->length);
218                 break;
219             case FS_COMMAND_OPENFILEFAST:
220                 FS_INVOKE_METHOD_(OpenFileFast, arc, file, arg->id, arg->mode);
221                 break;
222             case FS_COMMAND_OPENFILEDIRECT:
223                 FS_INVOKE_METHOD_(OpenFileDirect, arc, file, arg->top, arg->bottom, &arg->id);
224                 break;
225             case FS_COMMAND_CLOSEFILE:
226                 FS_INVOKE_METHOD_(CloseFile, arc, file);
227                 break;
228             case FS_COMMAND_ACTIVATE:
229                 FS_NOTIFY_EVENT_(Activate, arc);
230                 break;
231             case FS_COMMAND_IDLE:
232                 FS_NOTIFY_EVENT_(Idle, arc);
233                 break;
234             case FS_COMMAND_SUSPEND:
235                 FS_NOTIFY_EVENT_(Suspend, arc);
236                 break;
237             case FS_COMMAND_RESUME:
238                 FS_NOTIFY_EVENT_(Resume, arc);
239                 break;
240             case FS_COMMAND_OPENFILE:
241                 FS_INVOKE_METHOD_(OpenFile, arc, file, arg->baseid, arg->relpath, arg->mode);
242                 break;
243             case FS_COMMAND_SEEKFILE:
244                 FS_INVOKE_METHOD_(SeekFile, arc, file, &arg->offset, arg->from);
245                 break;
246             case FS_COMMAND_GETFILELENGTH:
247                 FS_INVOKE_METHOD_(GetFileLength, arc, file, &arg->length);
248                 break;
249             case FS_COMMAND_GETFILEPOSITION:
250                 FS_INVOKE_METHOD_(GetFilePosition, arc, file, &arg->position);
251                 break;
252                 // From here the extended command
253             case FS_COMMAND_MOUNT:
254                 FS_NOTIFY_EVENT_(Mount, arc);
255                 break;
256             case FS_COMMAND_UNMOUNT:
257                 FS_NOTIFY_EVENT_(Unmount, arc);
258                 break;
259             case FS_COMMAND_GETARCHIVECAPS:
260                 FS_INVOKE_METHOD_(GetArchiveCaps, arc, &arg->caps);
261                 break;
262             case FS_COMMAND_CREATEFILE:
263                 FS_INVOKE_METHOD_(CreateFile, arc, arg->baseid, arg->relpath, arg->permit);
264                 break;
265             case FS_COMMAND_DELETEFILE:
266                 FS_INVOKE_METHOD_(DeleteFile, arc, arg->baseid, arg->relpath);
267                 break;
268             case FS_COMMAND_RENAMEFILE:
269                 FS_INVOKE_METHOD_(RenameFile, arc, arg->baseid_src, arg->relpath_src, arg->baseid_dst, arg->relpath_dst);
270                 break;
271             case FS_COMMAND_GETPATHINFO:
272                 FS_INVOKE_METHOD_(GetPathInfo, arc, arg->baseid, arg->relpath, arg->info);
273                 break;
274             case FS_COMMAND_SETPATHINFO:
275                 FS_INVOKE_METHOD_(SetPathInfo, arc, arg->baseid, arg->relpath, arg->info);
276                 break;
277             case FS_COMMAND_CREATEDIRECTORY:
278                 FS_INVOKE_METHOD_(CreateDirectory, arc, arg->baseid, arg->relpath, arg->permit);
279                 break;
280             case FS_COMMAND_DELETEDIRECTORY:
281                 FS_INVOKE_METHOD_(DeleteDirectory, arc, arg->baseid, arg->relpath);
282                 break;
283             case FS_COMMAND_RENAMEDIRECTORY:
284                 FS_INVOKE_METHOD_(RenameDirectory, arc, arg->baseid_src, arg->relpath_src, arg->baseid_dst, arg->relpath_dst);
285                 break;
286             case FS_COMMAND_GETARCHIVERESOURCE:
287                 FS_INVOKE_METHOD_(GetArchiveResource, arc, arg->resource);
288                 break;
289             case FS_COMMAND_FLUSHFILE:
290                 FS_INVOKE_METHOD_(FlushFile, arc, file);
291                 break;
292             case FS_COMMAND_SETFILELENGTH:
293                 FS_INVOKE_METHOD_(SetFileLength, arc, file, arg->length);
294                 break;
295             case FS_COMMAND_OPENDIRECTORY:
296                 FS_INVOKE_METHOD_(OpenDirectory, arc, file, arg->baseid, arg->relpath, arg->mode);
297                 break;
298             case FS_COMMAND_CLOSEDIRECTORY:
299                 FS_INVOKE_METHOD_(CloseDirectory, arc, file);
300                 break;
301             case FS_COMMAND_SETSEEKCACHE:
302                 FS_INVOKE_METHOD_(SetSeekCache, arc, file, arg->buf, arg->buf_size);
303                 break;
304             default:
305                 result = FS_RESULT_UNSUPPORTED;
306             }
307 #undef FS_DECLARE_ARGUMENT_
308 #undef FS_INVOKE_METHOD_
309 #undef FS_NOTIFY_EVENT_
310         }
311     }
312     // If this is a normal command with no event notification, return to satisfy conditions when issued
313     if (!FSi_IsEventCommand(command))
314     {
315         // Because there are many cases where the path name is incorrect and unsupported, if there is the possibility, a warning will be output
316         //
317         if (result == FS_RESULT_UNSUPPORTED)
318         {
319             OS_TWarning("archive \"%s:\" cannot support command %d.\n", FS_GetArchiveName(arc), command);
320         }
321         // If necessary, apply blocking
322         if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
323         {
324             result = FSi_WaitForArchiveCompletion(file, result);
325         }
326         // Release here if completed while nobody is blocking
327         else if (result != FS_RESULT_PROC_ASYNC)
328         {
329             FSi_EndCommand(file, result);
330         }
331     }
332     return result;
333 }
334 
335 /*---------------------------------------------------------------------------*
336   Name:         FSi_NextCommand
337 
338   Description:  Archive selects the next command that should be processed.
339                 If an asynchronous command is selected, return that pointer.
340                 If anything other than NULL is returned, it is necessary to process at the source of the call.
341 
342   Arguments:    arc: Archive that gets the next command
343                 owner: TRUE if source of call already has command execution rights
344 
345   Returns:      The next command that must be processed here.
346  *---------------------------------------------------------------------------*/
FSi_NextCommand(FSArchive * arc,BOOL owner)347 static FSFile *FSi_NextCommand(FSArchive *arc, BOOL owner)
348 {
349     FSFile  *next = NULL;
350     // First, check all command cancel requests
351     {
352         OSIntrMode bak_psr = OS_DisableInterrupts();
353         if ((arc->flag & FS_ARCHIVE_FLAG_CANCELING) != 0)
354         {
355             FSFile *p = arc->list;
356             arc->flag &= ~FS_ARCHIVE_FLAG_CANCELING;
357             while (p != NULL)
358             {
359                 FSFile *q = p->next;
360                 // Commands whose processes have already started cannot be externally canceled.
361                 if (FS_IsCanceling(p) && ((p->stat & FS_FILE_STATUS_OPERATING) == 0))
362                 {
363                     FSi_EndCommand(p, FS_RESULT_CANCELED);
364                     if (!q)
365                     {
366                         q = arc->list;
367                     }
368                 }
369                 p = q;
370             }
371         }
372         (void)OS_RestoreInterrupts(bak_psr);
373     }
374     // Determine whether the next command can be executed
375     {
376         OSIntrMode  bak_psr = OS_DisableInterrupts();
377         if (((arc->flag & FS_ARCHIVE_FLAG_SUSPENDING) == 0) &&
378             ((arc->flag & FS_ARCHIVE_FLAG_SUSPEND) == 0) && arc->list)
379         {
380             // Start up here if archive is stopped
381             const BOOL  is_started = owner && ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0);
382             if (is_started)
383             {
384                 arc->flag |= FS_ARCHIVE_FLAG_RUNNING;
385             }
386             (void)OS_RestoreInterrupts(bak_psr);
387             if (is_started)
388             {
389                 (void)FSi_InvokeCommand(arc->list, FS_COMMAND_ACTIVATE);
390             }
391             bak_psr = OS_DisableInterrupts();
392             // Command at the top of the list gets execution rights
393             if (owner || is_started)
394             {
395                 next = arc->list;
396                 next->stat |= FS_FILE_STATUS_OPERATING;
397             }
398             // If command issuing source is blocking, transfer execution rights
399             if (owner && ((next->stat & FS_FILE_STATUS_BLOCKING) != 0))
400             {
401                 OS_WakeupThread(next->queue);
402                 next = NULL;
403             }
404             (void)OS_RestoreInterrupts(bak_psr);
405         }
406         // Suspend or idle state
407         else
408         {
409             // If does not have execution rights, stop and go to idle state
410             if (owner)
411             {
412                 if ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) != 0)
413                 {
414                     FSFile  tmp;
415                     FS_InitFile(&tmp);
416                     tmp.arc = arc;
417                     arc->flag &= ~FS_ARCHIVE_FLAG_RUNNING;
418                     (void)FSi_InvokeCommand(&tmp, FS_COMMAND_IDLE);
419                 }
420                 // If suspend is executing, notify the source of the suspend function call
421                 if ((arc->flag & FS_ARCHIVE_FLAG_SUSPENDING) != 0)
422                 {
423                     arc->flag &= ~FS_ARCHIVE_FLAG_SUSPENDING;
424                     arc->flag |= FS_ARCHIVE_FLAG_SUSPEND;
425                     OS_WakeupThread(&arc->queue);
426                 }
427             }
428             (void)OS_RestoreInterrupts(bak_psr);
429         }
430     }
431     return next;
432 }
433 
434 /*---------------------------------------------------------------------------*
435   Name:         FSi_ExecuteAsyncCommand
436 
437   Description:  Executes an asynchronous command.
438                 The first call is made from the user thread with interrupts enabled.
439                 As long as the archive is operating synchronously, command processing will repeat here. If even a single asynchronous process occurs, the rest is performed with NotifyAsyncEnd().
440 
441 
442                 Therefore, it is necessary to be aware of the NotifyAsyncEnd() calling environment when archive processing switches between synchronous and asynchronous.
443 
444 
445   Arguments:    file: FSFile structure that stores the asynchronous command to execute
446 
447   Returns:      None.
448  *---------------------------------------------------------------------------*/
FSi_ExecuteAsyncCommand(FSFile * file)449 static void FSi_ExecuteAsyncCommand(FSFile *file)
450 {
451     FSArchive   * const arc = file->arc;
452     while (file)
453     {
454         // If call source is waiting on completion, wake that up and leave it to the process
455         {
456             OSIntrMode bak_psr = OS_DisableInterrupts();
457             file->stat |= FS_FILE_STATUS_OPERATING;
458             if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
459             {
460                 OS_WakeupThread(file->queue);
461                 file = NULL;
462             }
463             (void)OS_RestoreInterrupts(bak_psr);
464         }
465         if (!file)
466         {
467             break;
468         }
469         // If processing is asynchronous, quit for the time being
470         else if (FSi_InvokeCommand(file, FSi_GetCurrentCommand(file)) == FS_RESULT_PROC_ASYNC)
471         {
472             break;
473         }
474         // If the result is a synchronous completion, select the continuation here
475         else
476         {
477             file = FSi_NextCommand(arc, TRUE);
478         }
479     }
480 }
481 
482 /*---------------------------------------------------------------------------*
483   Name:         FSi_ExecuteSyncCommand
484 
485   Description:  Executes a command that was blocked.
486 
487   Arguments:    file: FSFile structure that stores the command to execute
488 
489   Returns:      None.
490  *---------------------------------------------------------------------------*/
FSi_ExecuteSyncCommand(FSFile * file)491 static void FSi_ExecuteSyncCommand(FSFile *file)
492 {
493     // If necessary, wait for turn, then process the command
494     FSi_WaitConditionChange(&file->stat, FS_FILE_STATUS_OPERATING, FS_FILE_STATUS_BUSY, file->queue);
495     // If you do not have the execution rights for some reason (for example, the command has already been cancelled), do nothing
496     if ((file->stat & FS_FILE_STATUS_OPERATING) != 0)
497     {
498         FSArchive   * const arc = file->arc;
499         FSResult            result;
500         result = FSi_InvokeCommand(file, FSi_GetCurrentCommand(file));
501         FSi_EndCommand(file, result);
502         // If there is an asynchronous type command to be processed here, execute instead
503         file = FSi_NextCommand(arc, TRUE);
504         if (file)
505         {
506             FSi_ExecuteAsyncCommand(file);
507         }
508     }
509 }
510 
511 /*---------------------------------------------------------------------------*
512   Name:         FSi_SendCommand
513 
514   Description:  Issues a command to an archive.
515                 Adjust start timing and block here if synchronous.
516 
517   Arguments:    file: FSFile structure in which the command argument has been specified
518                 Command: Command ID
519                 blocking: TRUE if blocking
520 
521   Returns:      TRUE if the command is successful.
522  *---------------------------------------------------------------------------*/
FSi_SendCommand(FSFile * file,FSCommandType command,BOOL blocking)523 BOOL FSi_SendCommand(FSFile *file, FSCommandType command, BOOL blocking)
524 {
525     BOOL                retval = FALSE;
526     FSArchive   * const arc = file->arc;
527     BOOL                owner = FALSE;
528 
529     if (FS_IsBusy(file))
530     {
531         OS_TPanic("specified file is now still proceccing previous command!");
532     }
533     if (!arc)
534     {
535         OS_TWarning("specified handle is not related by any archive\n");
536         file->error = FS_RESULT_INVALID_PARAMETER;
537         return FALSE;
538     }
539 
540     // Initialize command
541     file->error = FS_RESULT_BUSY;
542     file->stat &= ~(FS_FILE_STATUS_CMD_MASK << FS_FILE_STATUS_CMD_SHIFT);
543     file->stat |= (command << FS_FILE_STATUS_CMD_SHIFT);
544     file->stat |= FS_FILE_STATUS_BUSY;
545     file->next = NULL;
546     if (blocking)
547     {
548         file->stat |= FS_FILE_STATUS_BLOCKING;
549     }
550     // If unloading, cancel the process and if not, add to the end of the list
551     {
552         OSIntrMode          bak_psr = OS_DisableInterrupts();
553         if ((arc->flag & FS_ARCHIVE_FLAG_UNLOADING) != 0)
554         {
555             FSi_EndCommand(file, FS_RESULT_CANCELED);
556         }
557         else
558         {
559             FSFile    **pp;
560             for (pp = &arc->list; *pp; pp = &(*pp)->next)
561             {
562             }
563             *pp = file;
564         }
565         owner = (arc->list == file) && ((arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0);
566         (void)OS_RestoreInterrupts(bak_psr);
567     }
568     // If added to the list, check the command execution rights
569     if (file->error != FS_RESULT_CANCELED)
570     {
571         // If there is a command that should be processed here, execute that instead
572         FSFile *next = FSi_NextCommand(arc, owner);
573         // If necessary, block here
574         if (blocking)
575         {
576             FSi_ExecuteSyncCommand(file);
577             retval = FS_IsSucceeded(file);
578         }
579         // Added to the list, so will be processed by someone
580         else
581         {
582             if (next != NULL)
583             {
584                 FSi_ExecuteAsyncCommand(next);
585             }
586             retval = TRUE;
587         }
588     }
589 
590     return retval;
591 }
592 
593 /*---------------------------------------------------------------------------*
594   Name:         FSi_EndArchive
595 
596   Description:  End all archives and release.
597 
598   Arguments:    None.
599 
600   Returns:      None.
601  *---------------------------------------------------------------------------*/
FSi_EndArchive(void)602 void FSi_EndArchive(void)
603 {
604     OSIntrMode bak_psr = OS_DisableInterrupts();
605     while (arc_list)
606     {
607         FSArchive *p_arc = arc_list;
608         arc_list = arc_list->next;
609         (void)FS_UnloadArchive(p_arc);
610         FS_ReleaseArchiveName(p_arc);
611     }
612     (void)OS_RestoreInterrupts(bak_psr);
613 }
614 
615 /*---------------------------------------------------------------------------*
616   Name:         FS_FindArchive
617 
618   Description:  Searches for archive name.
619                 If there is no matching name, returns NULL.
620 
621   Arguments:    name: String of the archive name to search for
622                 name_len: Length of name string
623 
624   Returns:      Pointer to the archive that was found, or NULL.
625  *---------------------------------------------------------------------------*/
FS_FindArchive(const char * name,int name_len)626 FSArchive *FS_FindArchive(const char *name, int name_len)
627 {
628     OSIntrMode  bak_psr = OS_DisableInterrupts();
629     FSArchive  *arc = arc_list;
630     for (; arc; arc = arc->next)
631     {
632         if (FS_IsArchiveLoaded(arc))
633         {
634             const char *arcname = FS_GetArchiveName(arc);
635             if ((STD_CompareNString(arcname, name, name_len) == 0) && (arcname[name_len] == '\0'))
636             {
637                 break;
638             }
639         }
640     }
641     (void)OS_RestoreInterrupts(bak_psr);
642     return arc;
643 }
644 
645 /*---------------------------------------------------------------------------*
646   Name:         FS_GetArchiveResultCode
647 
648   Description:  Gets latest error code for the specified archive.
649 
650   Arguments:    path_or_archive  Specify targeted archive
651                                  FSArchive structure or path string
652 
653   Returns:      Latest error code for the specified archive.
654                 If the corresponding archive does not exist, FS_RESULT_ERROR.
655  *---------------------------------------------------------------------------*/
FS_GetArchiveResultCode(const void * path_or_archive)656 FSResult FS_GetArchiveResultCode(const void *path_or_archive)
657 {
658     OSIntrMode  bak_psr = OS_DisableInterrupts();
659     FSArchive  *arc = arc_list;
660     while (arc && (arc != (const FSArchive *)path_or_archive))
661     {
662         arc = arc->next;
663     }
664     if (!arc)
665     {
666         arc = FS_NormalizePath((const char *)path_or_archive, NULL, NULL);
667     }
668     (void)OS_RestoreInterrupts(bak_psr);
669     return arc ? arc->result : FS_RESULT_ERROR;
670 }
671 
672 /*---------------------------------------------------------------------------*
673   Name:         FS_GetCurrentDirectoryPath
674 
675   Description:  Gets current directory with path name.
676 
677   Arguments:    None.
678 
679   Returns:      String that represents current directory.
680  *---------------------------------------------------------------------------*/
FS_GetCurrentDirectory(void)681 const char *FS_GetCurrentDirectory(void)
682 {
683     return current_dir_path;
684 }
685 
686 /*---------------------------------------------------------------------------*
687   Name:         FS_SetCurrentDirectory
688 
689   Description:  Changes the current directory.
690 
691   Arguments:    path: Path name
692 
693   Returns:      TRUE if successful.
694  *---------------------------------------------------------------------------*/
FS_SetCurrentDirectory(const char * path)695 BOOL FS_SetCurrentDirectory(const char *path)
696 {
697     BOOL        retval = FALSE;
698     FSArchive  *arc = NULL;
699     u32         baseid = 0;
700     char        relpath[FS_ENTRY_LONGNAME_MAX];
701 
702     SDK_NULL_ASSERT(path);
703     SDK_ASSERT(OS_GetProcMode() != OS_PROCMODE_IRQ);
704 
705     // Get normalized path name
706     arc = FS_NormalizePath(path, &baseid, relpath);
707     if (arc)
708     {
709         // In any case, the setting was successful
710         current_dir_pos.arc = arc;
711         current_dir_pos.own_id = 0;
712         current_dir_pos.index = 0;
713         current_dir_pos.pos = 0;
714         (void)STD_CopyLString(current_dir_path, relpath, sizeof(current_dir_path));
715         // If possible, try to get directory ID
716         if (arc->vtbl->FindPath != NULL)
717         {
718             FSFile                  dir[1];
719             FSArgumentForFindPath   arg[1];
720             FS_InitFile(dir);
721             dir->arc = arc;
722             dir->argument = arg;
723             arg->baseid = baseid;
724             arg->relpath = relpath;
725             arg->target_is_directory = TRUE;
726             if (FSi_SendCommand(dir, FS_COMMAND_FINDPATH, TRUE))
727             {
728                 current_dir_pos.own_id = (u16)arg->target_id;
729                 (void)STD_CopyLString(current_dir_path, relpath, sizeof(current_dir_path));
730             }
731         }
732         retval = TRUE;
733     }
734     return retval;
735 }
736 
737 /*---------------------------------------------------------------------------*
738   Name:         FSi_CopySafeString
739 
740   Description:  Checks buffer size and copies string.
741 
742   Arguments:    dst: Transfer destination buffer
743                 dstlen: Transfer destination size
744                 src: Transfer source buffer
745                 srclen: Transfer destination size
746                 stickyFailure: FALSE, if truncated at transfer origin
747 
748   Returns:      Archive pointer or NULL.
749  *---------------------------------------------------------------------------*/
FSi_CopySafeString(char * dst,int dstlen,const char * src,int srclen,BOOL * stickyFailure)750 static int FSi_CopySafeString(char *dst, int dstlen, const char *src, int srclen, BOOL *stickyFailure)
751 {
752     int     i;
753     int     n = (dstlen - 1 < srclen) ? (dstlen - 1) : srclen;
754     for (i = 0; (i < n) && src[i]; ++i)
755     {
756         dst[i] = src[i];
757     }
758     if ((i < srclen) && src[i])
759     {
760         *stickyFailure = TRUE;
761     }
762     dst[i] = '\0';
763     return i;
764 }
765 
766 /*---------------------------------------------------------------------------*
767   Name:         FS_NormalizePath
768 
769   Description:  Normalizes to format of (Standard Directory ID) + (Path Name) considering the current directory in the specified path.
770 
771 
772 
773   Arguments:    path: Non-normalized path string
774                 baseid: Standard directory ID storage destination or NULL
775                 relpath: Path name storage destination after conversion or NULL
776 
777   Returns:      Archive pointer or NULL.
778  *---------------------------------------------------------------------------*/
FS_NormalizePath(const char * path,u32 * baseid,char * relpath)779 FSArchive* FS_NormalizePath(const char *path, u32 *baseid, char *relpath)
780 {
781     FSArchive  *arc = NULL;
782     int         pathlen = 0;
783     int         pathmax = FS_ENTRY_LONGNAME_MAX;
784     BOOL        stickyFailure = FALSE;
785     // Reevaluate when current directory is not specified
786     if (current_dir_pos.arc == NULL)
787     {
788         current_dir_pos.arc = arc_list;
789         current_dir_pos.own_id = 0;
790         current_dir_pos.pos = 0;
791         current_dir_pos.index = 0;
792         current_dir_path[0] = '\0';
793     }
794     // With "/path" make the current archive route the base
795     if (FSi_IsSlash((u8)*path))
796     {
797         arc = current_dir_pos.arc;
798         ++path;
799         if (baseid)
800         {
801             *baseid = 0;
802         }
803     }
804     else
805     {
806         int     i;
807         for (i = 0; ; i = FSi_IncrementSjisPosition(path, i))
808         {
809             u32     c = (u8)path[i];
810             // With "(path/)*path" the relevant path from the current position
811             if (!c || FSi_IsSlash(c))
812             {
813                 arc = current_dir_pos.arc;
814                 if (baseid)
815                 {
816                     *baseid = current_dir_pos.own_id;
817                 }
818                 if(relpath)
819                 {
820                     // If directory is retained only by the path name, link it
821                     if ((current_dir_pos.own_id == 0) && (current_dir_path[0] != '\0'))
822                     {
823                         pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
824                                                       current_dir_path, FS_ENTRY_LONGNAME_MAX, &stickyFailure);
825                         pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
826                                                       "/", 1, &stickyFailure);
827                     }
828                 }
829                 break;
830             }
831             // With "arc:/path," the full path
832             else if (c == ':')
833             {
834                 arc = FS_FindArchive(path, i);
835                 if (!arc)
836                 {
837                     OS_TWarning("archive \"%*s\" is not found.", i, path);
838                 }
839                 path += i + 1;
840                 if (FSi_IsSlash((u8)*path))
841                 {
842                     ++path;
843                 }
844                 if (baseid)
845                 {
846                     *baseid = 0;
847                 }
848                 break;
849             }
850         }
851     }
852     if(relpath)
853     {
854         // Normalize relative paths, being careful of specialized entry names.
855         int     curlen = 0;
856         while (!stickyFailure)
857         {
858             char    c = path[curlen];
859             if ((c != '\0') && !FSi_IsSlash((u8)c))
860             {
861                 curlen += STD_IsSjisCharacter(&path[curlen]) ? 2 : 1;
862             }
863             else
864             {
865                 // Ignore empty directory
866                 if (curlen == 0)
867                 {
868                 }
869                 // Ignore "." (current directory)
870                 else if ((curlen == 1) && (path[0] == '.'))
871                 {
872                 }
873                 // ".." (parent directory) raises the root one level as the upper limit
874                 else if ((curlen == 2) && (path[0] == '.') && (path[1] == '.'))
875                 {
876                     if (pathlen > 0)
877                     {
878                         --pathlen;
879                     }
880                     pathlen = FSi_DecrementSjisPositionToSlash(relpath, pathlen) + 1;
881                 }
882                 // Add entry for anything else
883                 else
884                 {
885                     pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
886                                                   path, curlen, &stickyFailure);
887                     if (c != '\0')
888                     {
889                         pathlen += FSi_CopySafeString(&relpath[pathlen], pathmax - pathlen,
890                                                       "/", 1, &stickyFailure);
891                     }
892                 }
893                 if (c == '\0')
894                 {
895                     break;
896                 }
897                 path += curlen + 1;
898                 curlen = 0;
899             }
900         }
901         relpath[pathlen] = '\0';
902         pathlen = FSi_TrimSjisTrailingSlash(relpath);
903     }
904     return stickyFailure ? NULL : arc;
905 }
906 
907 /*---------------------------------------------------------------------------*
908   Name:         FS_InitArchive
909 
910   Description:  Initializes archive structure.
911 
912   Arguments:    p_arc: Archive to initialize
913 
914   Returns:      None.
915  *---------------------------------------------------------------------------*/
FS_InitArchive(FSArchive * p_arc)916 void FS_InitArchive(FSArchive *p_arc)
917 {
918     SDK_NULL_ASSERT(p_arc);
919     MI_CpuClear8(p_arc, sizeof(FSArchive));
920     OS_InitThreadQueue(&p_arc->queue);
921 }
922 
923 /*---------------------------------------------------------------------------*
924   Name:         FS_RegisterArchiveName
925 
926   Description:  Registers the archive name in the file system and associates it.
927                 The archive itself is not yet loaded into the file system.
928                 The archive name "rom" is reserved by the file system.
929 
930   Arguments:    p_arc: Archive to associate with the name
931                 name: String of the name to register
932                 name_len: Length of name string
933 
934   Returns:      None.
935  *---------------------------------------------------------------------------*/
FS_RegisterArchiveName(FSArchive * p_arc,const char * name,u32 name_len)936 BOOL FS_RegisterArchiveName(FSArchive *p_arc, const char *name, u32 name_len)
937 {
938     BOOL    retval = FALSE;
939 
940     SDK_ASSERT(FS_IsAvailable());
941     SDK_NULL_ASSERT(p_arc);
942     SDK_NULL_ASSERT(name);
943 
944     {
945         OSIntrMode bak_intr = OS_DisableInterrupts();
946         if (!FS_FindArchive(name, (s32)name_len))
947         {
948             // Adds to the end of the list
949             FSArchive **pp;
950             for (pp = &arc_list; *pp; pp = &(*pp)->next)
951             {
952             }
953             *pp = p_arc;
954             // With the current specifications, the archive has a maximum of 3 characters
955             if (name_len <= FS_ARCHIVE_NAME_LEN_MAX)
956             {
957                 p_arc->name.pack = 0;
958                 (void)STD_CopyLString(p_arc->name.ptr, name, (int)(name_len + 1));
959             }
960             else
961             {
962 #if defined(FS_SUPPORT_LONG_ARCNAME)
963             // Feature for expanding the limit of the number of characters in an archive name
964             // It is necessary to allocate external memory to support without changing the FSArchive structure size
965             //
966                 if (name_len <= FS_LONG_ARCNAME_LENGTH_MAX)
967                 {
968                     int i;
969                     for (i = 0; ; ++i)
970                     {
971                         if (i >= FS_LONG_ARCNAME_TABLE_MAX)
972                         {
973                             OS_TPanic("failed to allocate memory for long archive-name(%.*s)!", name_len, name);
974                         }
975                         else if (FSiLongNameTable[i][0] == '\0')
976                         {
977                             (void)STD_CopyLString(FSiLongNameTable[i], name, (int)(name_len + 1));
978                             p_arc->name.pack = (u32)FSiLongNameTable[i];
979                             break;
980                         }
981                     }
982                 }
983 #endif
984                 // Cannot register archive names that are too long
985                 else
986                 {
987                     OS_TPanic("too long archive-name(%.*s)!", name_len, name);
988                 }
989             }
990             p_arc->flag |= FS_ARCHIVE_FLAG_REGISTER;
991             retval = TRUE;
992         }
993         (void)OS_RestoreInterrupts(bak_intr);
994     }
995     return retval;
996 }
997 
998 /*---------------------------------------------------------------------------*
999   Name:         FS_ReleaseArchiveName
1000 
1001   Description:  Release registered archive names.
1002                 They must be unloaded from the file system.
1003 
1004   Arguments:    p_arc: Archive with name to release
1005 
1006   Returns:      None.
1007  *---------------------------------------------------------------------------*/
FS_ReleaseArchiveName(FSArchive * p_arc)1008 void FS_ReleaseArchiveName(FSArchive *p_arc)
1009 {
1010     SDK_ASSERT(FS_IsAvailable());
1011     SDK_NULL_ASSERT(p_arc);
1012 
1013 	if(p_arc == arc_list)
1014     {
1015         OS_TPanic("[file-system] cannot modify \"rom\" archive.\n");
1016     }
1017 
1018     if (p_arc->name.pack)
1019     {
1020         OSIntrMode bak_psr = OS_DisableInterrupts();
1021         // Cut from the list
1022         FSArchive **pp;
1023         for (pp = &arc_list; *pp; pp = &(*pp)->next)
1024         {
1025             if(*pp == p_arc)
1026             {
1027                 *pp = (*pp)->next;
1028                 break;
1029             }
1030         }
1031 #if defined(FS_SUPPORT_LONG_ARCNAME)
1032         // Deallocate buffer to the system if there is a long archive name
1033         if (p_arc->name.ptr[3] != '\0')
1034         {
1035             ((char *)p_arc->name.pack)[0] = '\0';
1036         }
1037 #endif
1038         p_arc->name.pack = 0;
1039         p_arc->next = NULL;
1040         p_arc->flag &= ~FS_ARCHIVE_FLAG_REGISTER;
1041         // Deallocate if it is the current archive
1042         if (current_dir_pos.arc == p_arc)
1043         {
1044             current_dir_pos.arc = NULL;
1045         }
1046         (void)OS_RestoreInterrupts(bak_psr);
1047     }
1048 }
1049 
1050 /*---------------------------------------------------------------------------*
1051   Name:         FS_GetArchiveName
1052 
1053   Description:  Gets archive name.
1054 
1055   Arguments:    p_arc: Archive whose name to get
1056 
1057   Returns:      Archive name registered in the file system.
1058  *---------------------------------------------------------------------------*/
FS_GetArchiveName(const FSArchive * arc)1059 const char *FS_GetArchiveName(const FSArchive *arc)
1060 {
1061 #if defined(FS_SUPPORT_LONG_ARCNAME)
1062     return (arc->name.ptr[3] != '\0') ? (const char *)arc->name.pack : arc->name.ptr;
1063 #else
1064     return arc->name.ptr;
1065 #endif
1066 }
1067 
1068 /*---------------------------------------------------------------------------*
1069   Name:         FS_MountArchive
1070 
1071   Description:  Mounts the archive.
1072 
1073   Arguments:    arc: Archive to mount
1074                 userdata: User-defined variable that associates with an archive
1075                 vtbl: Command interface
1076                 reserved: Reserved for future use (always specify 0)
1077 
1078 
1079   Returns:      TRUE if mounting is successful.
1080  *---------------------------------------------------------------------------*/
FS_MountArchive(FSArchive * arc,void * userdata,const FSArchiveInterface * vtbl,u32 reserved)1081 BOOL FS_MountArchive(FSArchive *arc, void *userdata,
1082                      const FSArchiveInterface *vtbl, u32 reserved)
1083 {
1084     (void)reserved;
1085     SDK_ASSERT(FS_IsAvailable());
1086     SDK_NULL_ASSERT(arc);
1087     SDK_ASSERT(!FS_IsArchiveLoaded(arc));
1088     // Initialize archive with new specifications
1089     arc->userdata = userdata;
1090     arc->vtbl = vtbl;
1091     // Issue mounting notification event
1092     {
1093         FSFile  tmp[1];
1094         FS_InitFile(tmp);
1095         tmp->arc = arc;
1096         (void)FSi_InvokeCommand(tmp, FS_COMMAND_MOUNT);
1097     }
1098     arc->flag |= FS_ARCHIVE_FLAG_LOADED;
1099     return TRUE;
1100 }
1101 
1102 /*---------------------------------------------------------------------------*
1103   Name:         FS_UnmountArchive
1104 
1105   Description:  Unmounts the archive.
1106 
1107   Arguments:    arc: Archive to unmount
1108 
1109   Returns:      TRUE if unmounting is successful.
1110  *---------------------------------------------------------------------------*/
FS_UnmountArchive(FSArchive * arc)1111 BOOL    FS_UnmountArchive(FSArchive *arc)
1112 {
1113     SDK_ASSERT(FS_IsAvailable());
1114     SDK_NULL_ASSERT(arc);
1115 
1116     {
1117         OSIntrMode bak_psr = OS_DisableInterrupts();
1118         // Ignore if it is not loaded
1119         if (FS_IsArchiveLoaded(arc))
1120         {
1121             // Cancel all idling commands
1122             {
1123                 BOOL    bak_state = FS_SuspendArchive(arc);
1124                 FSFile *file = arc->list;
1125                 arc->flag |= FS_ARCHIVE_FLAG_UNLOADING;
1126                 while (file)
1127                 {
1128                     FSFile *next = file->next;
1129                     FSi_EndCommand(file, FS_RESULT_CANCELED);
1130                     file = next;
1131                 }
1132                 arc->list = NULL;
1133                 if (bak_state)
1134                 {
1135                     (void)FS_ResumeArchive(arc);
1136                 }
1137             }
1138             // Issue unmounting notification event
1139             {
1140                 FSFile  tmp[1];
1141                 FS_InitFile(tmp);
1142                 tmp->arc = arc;
1143                 (void)FSi_InvokeCommand(tmp, FS_COMMAND_UNMOUNT);
1144             }
1145             arc->flag &= ~(FS_ARCHIVE_FLAG_CANCELING |
1146                            FS_ARCHIVE_FLAG_LOADED | FS_ARCHIVE_FLAG_UNLOADING);
1147         }
1148         (void)OS_RestoreInterrupts(bak_psr);
1149     }
1150     return TRUE;
1151 
1152 }
1153 
1154 /*---------------------------------------------------------------------------*
1155   Name:         FS_SuspendArchive
1156 
1157   Description:  Stops archive processing mechanism itself.
1158                 If a process is currently executing, waits for it to complete.
1159 
1160   Arguments:    p_arc: Archive to suspend
1161 
1162   Returns:      TRUE if it was not in a suspended state before the call.
1163  *---------------------------------------------------------------------------*/
FS_SuspendArchive(FSArchive * p_arc)1164 BOOL FS_SuspendArchive(FSArchive *p_arc)
1165 {
1166     BOOL    retval = FALSE;
1167 
1168     SDK_ASSERT(FS_IsAvailable());
1169     SDK_NULL_ASSERT(p_arc);
1170 
1171     {
1172         OSIntrMode bak_psr = OS_DisableInterrupts();
1173         retval = !FS_IsArchiveSuspended(p_arc);
1174         if (retval)
1175         {
1176             if ((p_arc->flag & FS_ARCHIVE_FLAG_RUNNING) == 0)
1177             {
1178                 p_arc->flag |= FS_ARCHIVE_FLAG_SUSPEND;
1179             }
1180             else
1181             {
1182                 p_arc->flag |= FS_ARCHIVE_FLAG_SUSPENDING;
1183                 FSi_WaitConditionOff(&p_arc->flag, FS_ARCHIVE_FLAG_SUSPENDING, &p_arc->queue);
1184             }
1185         }
1186         (void)OS_RestoreInterrupts(bak_psr);
1187     }
1188     return retval;
1189 }
1190 
1191 /*---------------------------------------------------------------------------*
1192   Name:         FS_ResumeArchive
1193 
1194   Description:  Resumes the suspended archive processing.
1195 
1196   Arguments:    arc: Archive to reopen
1197 
1198   Returns:      TRUE if it was not in a suspended state before the call.
1199  *---------------------------------------------------------------------------*/
FS_ResumeArchive(FSArchive * arc)1200 BOOL FS_ResumeArchive(FSArchive *arc)
1201 {
1202     BOOL    retval;
1203     SDK_ASSERT(FS_IsAvailable());
1204     SDK_NULL_ASSERT(arc);
1205     {
1206         OSIntrMode bak_irq = OS_DisableInterrupts();
1207         retval = !FS_IsArchiveSuspended(arc);
1208         if (!retval)
1209         {
1210             arc->flag &= ~FS_ARCHIVE_FLAG_SUSPEND;
1211         }
1212         (void)OS_RestoreInterrupts(bak_irq);
1213     }
1214     {
1215         FSFile *file = NULL;
1216         file = FSi_NextCommand(arc, TRUE);
1217         if (file)
1218         {
1219             FSi_ExecuteAsyncCommand(file);
1220         }
1221     }
1222     return retval;
1223 }
1224 
1225 
1226 /*---------------------------------------------------------------------------*
1227   Name:         FS_NotifyArchiveAsyncEnd
1228 
1229   Description:  This is called from the archive implementation to send a notification when asynchronous archive processing is complete.
1230 
1231 
1232   Arguments:    arc: Archive for which to notify completion
1233                 ret: Processing result
1234 
1235   Returns:      None.
1236  *---------------------------------------------------------------------------*/
FS_NotifyArchiveAsyncEnd(FSArchive * arc,FSResult ret)1237 void FS_NotifyArchiveAsyncEnd(FSArchive *arc, FSResult ret)
1238 {
1239     FSFile     *file = arc->list;
1240     if ((file->stat & FS_FILE_STATUS_BLOCKING) != 0)
1241     {
1242         OSIntrMode bak_psr = OS_DisableInterrupts();
1243         file->stat |= FS_FILE_STATUS_ASYNC_DONE;
1244         file->error = ret;
1245         OS_WakeupThread(file->queue);
1246         (void)OS_RestoreInterrupts(bak_psr);
1247     }
1248     else
1249     {
1250         FSi_EndCommand(file, ret);
1251         file = FSi_NextCommand(arc, TRUE);
1252         if (file)
1253         {
1254             FSi_ExecuteAsyncCommand(file);
1255         }
1256     }
1257 }
1258 
1259 
1260 /*---------------------------------------------------------------------------*
1261   Name:         FS_WaitAsync
1262 
1263   Description:  Waits for end of asynchronous function and returns result.
1264 
1265   Arguments:    File to wait
1266 
1267   Returns:      If succeeded, TRUE.
1268  *---------------------------------------------------------------------------*/
FS_WaitAsync(FSFile * file)1269 BOOL FS_WaitAsync(FSFile *file)
1270 {
1271     SDK_NULL_ASSERT(file);
1272     SDK_ASSERT(FS_IsAvailable());
1273     SDK_ASSERT(OS_GetProcMode() != OS_PROCMODE_IRQ);
1274 
1275     {
1276         BOOL    is_owner = FALSE;
1277         OSIntrMode bak_psr = OS_DisableInterrupts();
1278         if (FS_IsBusy(file))
1279         {
1280             // Take on processing here if an unprocessed asynchronous mode
1281             is_owner = !(file->stat & (FS_FILE_STATUS_BLOCKING | FS_FILE_STATUS_OPERATING));
1282             if (is_owner)
1283             {
1284                 file->stat |= FS_FILE_STATUS_BLOCKING;
1285             }
1286         }
1287         (void)OS_RestoreInterrupts(bak_psr);
1288         if (is_owner)
1289         {
1290             FSi_ExecuteSyncCommand(file);
1291         }
1292         else
1293         {
1294             FSi_WaitConditionOff(&file->stat, FS_FILE_STATUS_BUSY, file->queue);
1295         }
1296     }
1297 
1298     return FS_IsSucceeded(file);
1299 }
1300 
1301 
1302 #endif /* FS_IMPLEMENT */
1303