]> git.sur5r.net Git - freertos/blob - FreeRTOS-Plus/Source/FreeRTOS-Plus-FAT-SL/fat_sl/common/file.c
Add FAT SL code and demo project.
[freertos] / FreeRTOS-Plus / Source / FreeRTOS-Plus-FAT-SL / fat_sl / common / file.c
1 /*\r
2  * FreeRTOS+FAT FS V1.0.0 (C) 2013 HCC Embedded\r
3  *\r
4  * The FreeRTOS+FAT SL license terms are different to the FreeRTOS license \r
5  * terms.\r
6  * \r
7  * FreeRTOS+FAT SL uses a dual license model that allows the software to be used\r
8  * under a pure GPL open source license (as opposed to the modified GPL licence\r
9  * under which FreeRTOS is distributed) or a commercial license.  Details of \r
10  * both license options follow:\r
11  * \r
12  * - Open source licensing -\r
13  * FreeRTOS+FAT SL is a free download and may be used, modified, evaluated and\r
14  * distributed without charge provided the user adheres to version two of the \r
15  * GNU General Public License (GPL) and does not remove the copyright notice or \r
16  * this text.  The GPL V2 text is available on the gnu.org web site, and on the\r
17  * following URL: http://www.FreeRTOS.org/gpl-2.0.txt.\r
18  * \r
19  * - Commercial licensing -\r
20  * Businesses and individuals who for commercial or other reasons cannot comply\r
21  * with the terms of the GPL V2 license must obtain a commercial license before \r
22  * incorporating FreeRTOS+FAT SL into proprietary software for distribution in \r
23  * any form.  Commercial licenses can be purchased from \r
24  * http://shop.freertos.org/fat_sl and do not require any source files to be \r
25  * changed.\r
26  *\r
27  * FreeRTOS+FAT SL is distributed in the hope that it will be useful.  You\r
28  * cannot use FreeRTOS+FAT SL unless you agree that you use the software 'as\r
29  * is'.  FreeRTOS+FAT SL is provided WITHOUT ANY WARRANTY; without even the\r
30  * implied warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A\r
31  * PARTICULAR PURPOSE. Real Time Engineers Ltd. and HCC Embedded disclaims all\r
32  * conditions and terms, be they implied, expressed, or statutory.\r
33  *\r
34  * http://www.FreeRTOS.org\r
35  * http://www.FreeRTOS.org/FreeRTOS-Plus\r
36  *\r
37  */\r
38 \r
39 #include "../../api/fat_sl.h"\r
40 #include "../../psp/include/psp_string.h"\r
41 \r
42 #include "util.h"\r
43 #include "volume.h"\r
44 #include "drv.h"\r
45 #include "fat.h"\r
46 #include "dir.h"\r
47 #include "file.h"\r
48 \r
49 #include "../../version/ver_fat_sl.h"\r
50 #if VER_FAT_SL_MAJOR != 3 || VER_FAT_SL_MINOR != 2\r
51  #error Incompatible FAT_SL version number!\r
52 #endif\r
53 \r
54 static unsigned char _f_emptywritebuffer ( void );\r
55 \r
56 \r
57 /****************************************************************************\r
58  *\r
59  * fn_filelength\r
60  *\r
61  * Get a file length\r
62  *\r
63  * INPUTS\r
64  *\r
65  * filename - file whose length is needed\r
66  *\r
67  * RETURNS\r
68  *\r
69  * length of the file\r
70  *\r
71  ***************************************************************************/\r
72 \r
73 long fn_filelength ( const char * filename )\r
74 {\r
75   F_POS        pos;\r
76   F_DIRENTRY * de;\r
77   F_NAME       fsname;\r
78 \r
79   if ( _f_setfsname( filename, &fsname ) )\r
80   {\r
81     return 0;                                     /*invalid name*/\r
82   }\r
83 \r
84   if ( _f_checknamewc( fsname.filename, fsname.fileext ) )\r
85   {\r
86     return 0;                                                    /*invalid name*/\r
87   }\r
88 \r
89   if ( _f_getvolume() )\r
90   {\r
91     return 0;                     /*can't get the size*/\r
92   }\r
93 \r
94   if ( !_f_findpath( &fsname, &pos ) )\r
95   {\r
96     return 0;\r
97   }\r
98 \r
99   if ( !_f_findfilewc( fsname.filename, fsname.fileext, &pos, &de, 0 ) )\r
100   {\r
101     return 0;\r
102   }\r
103 \r
104   if ( de->attr & F_ATTR_DIR )\r
105   {\r
106     return 0;                                           /*directory*/\r
107   }\r
108 \r
109   return (long)_f_getlong( &de->filesize );\r
110 } /* fn_filelength */\r
111 \r
112 \r
113 \r
114 \r
115 \r
116 /****************************************************************************\r
117  *\r
118  * _f_emptywritebuffer\r
119  *\r
120  * empty write buffer if it contains unwritten data\r
121  *\r
122  * RETURNS\r
123  * error code or zero if successful\r
124  *\r
125  ***************************************************************************/\r
126 \r
127 \r
128 static unsigned char _f_emptywritebuffer ( void )\r
129 {\r
130   unsigned char  ret;\r
131 \r
132   ret = _f_writeglsector( gl_file.pos.sector );\r
133   if ( ret )\r
134   {\r
135     return ret;\r
136   }\r
137 \r
138   gl_file.modified = 0;\r
139 \r
140   gl_file.pos.sector++;\r
141 \r
142   if ( gl_file.pos.sector >= gl_file.pos.sectorend )\r
143   {\r
144     unsigned long  value;\r
145 \r
146     gl_volume.fatsector = (unsigned long)-1;\r
147     ret = _f_getclustervalue( gl_file.pos.cluster, &value );\r
148     if ( ret )\r
149     {\r
150       return ret;\r
151     }\r
152 \r
153     if ( ( value >= 2 ) && ( value < F_CLUSTER_RESERVED ) ) /*we are in chain*/\r
154     {\r
155       gl_file.prevcluster = gl_file.pos.cluster;\r
156       _f_clustertopos( value, &gl_file.pos );    /*go to next cluster*/\r
157     }\r
158     else\r
159     {\r
160       unsigned long  nextcluster;\r
161 \r
162       ret = _f_alloccluster( &nextcluster );\r
163       if ( ret )\r
164       {\r
165         return ret;\r
166       }\r
167 \r
168       ret = _f_setclustervalue( nextcluster, F_CLUSTER_LAST );\r
169       if ( ret )\r
170       {\r
171         return ret;\r
172       }\r
173 \r
174       ret = _f_setclustervalue( gl_file.pos.cluster, nextcluster );\r
175       if ( ret )\r
176       {\r
177         return ret;\r
178       }\r
179 \r
180       gl_file.prevcluster = gl_file.pos.cluster;\r
181 \r
182       _f_clustertopos( nextcluster, &gl_file.pos );\r
183 \r
184       return _f_writefatsector();\r
185     }\r
186   }\r
187 \r
188 \r
189   return F_NO_ERROR;\r
190 } /* _f_emptywritebuffer */\r
191 \r
192 \r
193 \r
194 \r
195 /****************************************************************************\r
196  *\r
197  * _f_extend\r
198  *\r
199  * Extend file to a certain size\r
200  *\r
201  ***************************************************************************/\r
202 static unsigned char _f_extend ( long size )\r
203 {\r
204   unsigned long  _size;\r
205   unsigned char  rc;\r
206 \r
207   size -= gl_file.filesize;\r
208   _size = (unsigned long)size;\r
209 \r
210   rc = _f_getcurrsector();\r
211   if ( rc )\r
212   {\r
213     return rc;\r
214   }\r
215 \r
216   psp_memset( gl_sector + gl_file.relpos, 0, ( F_SECTOR_SIZE - gl_file.relpos ) );\r
217 \r
218   if ( gl_file.relpos + _size > F_SECTOR_SIZE )\r
219   {\r
220     _size -= ( F_SECTOR_SIZE - gl_file.relpos );\r
221     while ( _size )\r
222     {\r
223       if ( _f_emptywritebuffer() )\r
224       {\r
225         return F_ERR_WRITE;\r
226       }\r
227 \r
228       psp_memset( gl_sector, 0, F_SECTOR_SIZE );\r
229       _size -= ( _size > F_SECTOR_SIZE ? F_SECTOR_SIZE : _size );\r
230     }\r
231   }\r
232   else\r
233   {\r
234     _size += gl_file.relpos;\r
235   }\r
236 \r
237   gl_file.modified = 1;\r
238   gl_file.filesize += size;\r
239   gl_file.abspos = gl_file.filesize & ( ~( F_SECTOR_SIZE - 1 ) );\r
240   gl_file.relpos = _size;\r
241 \r
242   return F_NO_ERROR;\r
243 } /* _f_extend */\r
244 \r
245 \r
246 \r
247 /****************************************************************************\r
248  *\r
249  * _f_fseek\r
250  *\r
251  * subfunction for f_seek it moves position into given offset and read\r
252  * the current sector\r
253  *\r
254  * INPUTS\r
255  * offset - position from start\r
256  *\r
257  * RETURNS\r
258  *\r
259  * error code or zero if successful\r
260  *\r
261  ***************************************************************************/\r
262 static unsigned char _f_fseek ( long offset )\r
263 {\r
264   unsigned long  cluster;\r
265   unsigned long  tmp;\r
266   unsigned char  ret = F_NO_ERROR;\r
267   long           remain;\r
268 \r
269   if ( offset < 0 )\r
270   {\r
271     offset = 0;\r
272   }\r
273 \r
274   if ( ( (unsigned long) offset <= gl_file.filesize )\r
275        && ( (unsigned long) offset >= gl_file.abspos )\r
276        && ( (unsigned long) offset < gl_file.abspos + F_SECTOR_SIZE ) )\r
277   {\r
278     gl_file.relpos = (unsigned short)( offset - gl_file.abspos );\r
279   }\r
280   else\r
281   {\r
282     if ( gl_file.modified )\r
283     {\r
284       ret = _f_writeglsector( (unsigned long)-1 );\r
285       if ( ret )\r
286       {\r
287         gl_file.mode = F_FILE_CLOSE; /*cant accessed any more*/\r
288         return ret;\r
289       }\r
290     }\r
291 \r
292     if ( gl_file.startcluster )\r
293     {\r
294       gl_file.abspos = 0;\r
295       gl_file.relpos = 0;\r
296       gl_file.prevcluster = 0;\r
297       gl_file.pos.cluster = gl_file.startcluster;\r
298       remain = gl_file.filesize;\r
299 \r
300       tmp = gl_volume.bootrecord.sector_per_cluster;\r
301       tmp *= F_SECTOR_SIZE;   /* set to cluster size */\r
302 \r
303       /*calc cluster*/\r
304       gl_volume.fatsector = (unsigned long)-1;\r
305       while ( (unsigned long)offset >= tmp )\r
306       {\r
307         ret = _f_getclustervalue( gl_file.pos.cluster, &cluster );\r
308         if ( ret )\r
309         {\r
310           gl_file.mode = F_FILE_CLOSE;\r
311           return ret;\r
312         }\r
313 \r
314         if ( (long) tmp >= remain )\r
315         {\r
316           break;\r
317         }\r
318 \r
319         remain -= tmp;\r
320         offset -= tmp;\r
321         gl_file.abspos += tmp;\r
322         if ( cluster >= F_CLUSTER_RESERVED )\r
323         {\r
324           break;\r
325         }\r
326 \r
327         gl_file.prevcluster = gl_file.pos.cluster;\r
328         gl_file.pos.cluster = cluster;\r
329       }\r
330 \r
331       _f_clustertopos( gl_file.pos.cluster, &gl_file.pos );\r
332       if ( remain && offset )\r
333       {\r
334         while ( ( offset > (long) F_SECTOR_SIZE )\r
335                && ( remain > (long) F_SECTOR_SIZE ) )\r
336         {\r
337           gl_file.pos.sector++;\r
338           offset -= F_SECTOR_SIZE;\r
339           remain -= F_SECTOR_SIZE;\r
340           gl_file.abspos += F_SECTOR_SIZE;\r
341         }\r
342       }\r
343 \r
344       if ( remain < offset )\r
345       {\r
346         gl_file.relpos = (unsigned short)remain;\r
347         ret = _f_extend( gl_file.filesize + offset - remain );\r
348       }\r
349       else\r
350       {\r
351         gl_file.relpos = (unsigned short)offset;\r
352       }\r
353     }\r
354   }\r
355 \r
356   return ret;\r
357 } /* _f_fseek */\r
358 \r
359 \r
360 \r
361 /****************************************************************************\r
362  *\r
363  * fn_open\r
364  *\r
365  * open a file for reading/writing/appending\r
366  *\r
367  * INPUTS\r
368  *\r
369  * filename - which file need to be opened\r
370  * mode - string how to open ("r"-read, "w"-write, "w+"-overwrite, "a"-append\r
371  *\r
372  * RETURNS\r
373  *\r
374  * F_FILE pointer if successfully\r
375  * 0 - if any error\r
376  *\r
377  ***************************************************************************/\r
378 F_FILE * fn_open ( const char * filename, const char * mode )\r
379 {\r
380   F_DIRENTRY    * de;\r
381   F_NAME          fsname;\r
382   unsigned short  date;\r
383   unsigned short  time;\r
384   unsigned char   m_mode = F_FILE_CLOSE;\r
385 \r
386   if ( mode[1] == 0 )\r
387   {\r
388     if ( mode[0] == 'r' )\r
389     {\r
390       m_mode = F_FILE_RD;\r
391     }\r
392 \r
393     if ( mode[0] == 'w' )\r
394     {\r
395       m_mode = F_FILE_WR;\r
396     }\r
397 \r
398     if ( mode[0] == 'a' )\r
399     {\r
400       m_mode = F_FILE_A;\r
401     }\r
402   }\r
403 \r
404   if ( ( mode[1] == '+' ) && ( mode[2] == 0 ) )\r
405   {\r
406     if ( mode[0] == 'r' )\r
407     {\r
408       m_mode = F_FILE_RDP;\r
409     }\r
410 \r
411     if ( mode[0] == 'w' )\r
412     {\r
413       m_mode = F_FILE_WRP;\r
414     }\r
415 \r
416     if ( mode[0] == 'a' )\r
417     {\r
418       m_mode = F_FILE_AP;\r
419     }\r
420 \r
421   }\r
422 \r
423   if ( m_mode == F_FILE_CLOSE )\r
424   {\r
425     return 0;                                       /*invalid mode*/\r
426   }\r
427 \r
428   if ( _f_setfsname( filename, &fsname ) )\r
429   {\r
430     return 0;                                     /*invalid name*/\r
431   }\r
432 \r
433   if ( _f_checknamewc( fsname.filename, fsname.fileext ) )\r
434   {\r
435     return 0;                                                    /*invalid name*/\r
436   }\r
437 \r
438   if ( fsname.filename[0] == '.' )\r
439   {\r
440     return 0;\r
441   }\r
442 \r
443   if ( _f_getvolume() )\r
444   {\r
445     return 0;                     /*cant open any*/\r
446   }\r
447 \r
448   if ( gl_file.mode != F_FILE_CLOSE )\r
449   {\r
450     return 0;\r
451   }\r
452 \r
453   psp_memset( &gl_file, 0, 21 );\r
454 \r
455   if ( !_f_findpath( &fsname, &gl_file.dirpos ) )\r
456   {\r
457     return 0;\r
458   }\r
459 \r
460   switch ( m_mode )\r
461   {\r
462     case F_FILE_RDP:   /*r*/\r
463     case F_FILE_RD:   /*r*/\r
464       if ( !_f_findfilewc( fsname.filename, fsname.fileext, &gl_file.dirpos, &de, 0 ) )\r
465       {\r
466         return 0;\r
467       }\r
468 \r
469       if ( de->attr & F_ATTR_DIR )\r
470       {\r
471         return 0;                                      /*directory*/\r
472       }\r
473 \r
474       gl_file.startcluster = _f_getdecluster( de );\r
475 \r
476       if ( gl_file.startcluster )\r
477       {\r
478         _f_clustertopos( gl_file.startcluster, &gl_file.pos );\r
479         gl_file.filesize = _f_getlong( &de->filesize );\r
480         gl_file.abspos = (unsigned long) (-1 * (long) F_SECTOR_SIZE);\r
481         if ( _f_fseek( 0 ) )\r
482         {\r
483           return 0;\r
484         }\r
485       }\r
486 \r
487 #if F_FILE_CHANGED_EVENT\r
488       if ( m_mode == F_FILE_RDP )\r
489       {\r
490         _f_createfullname( gl_file.filename, sizeof( gl_file.filename ), fsname.path, fsname.filename, fsname.fileext );\r
491       }\r
492 \r
493 #endif\r
494 \r
495       break;\r
496 \r
497     case F_FILE_AP:\r
498     case F_FILE_A: /*a*/\r
499       psp_memcpy( &( gl_file.pos ), &( gl_file.dirpos ), sizeof( F_POS ) );\r
500       if ( _f_findfilewc( fsname.filename, fsname.fileext, &gl_file.dirpos, &de, 0 ) )\r
501       {\r
502         if ( de->attr & ( F_ATTR_DIR | F_ATTR_READONLY ) )\r
503         {\r
504           return 0;\r
505         }\r
506 \r
507         gl_file.startcluster = _f_getdecluster( de );\r
508         gl_file.filesize = _f_getlong( &de->filesize );\r
509 \r
510         if ( gl_file.startcluster )\r
511         {\r
512           _f_clustertopos( gl_file.startcluster, &gl_file.pos );\r
513           gl_file.abspos = (unsigned long) (-1 * (long) F_SECTOR_SIZE);   /*forcing seek to read 1st sector! abspos=0;*/\r
514           if ( _f_fseek( (long)gl_file.filesize ) )\r
515           {\r
516             gl_file.mode = F_FILE_CLOSE;\r
517             return 0;\r
518           }\r
519         }\r
520       }\r
521       else\r
522       {\r
523         psp_memcpy( &( gl_file.dirpos ), &( gl_file.pos ), sizeof( F_POS ) );\r
524         _f_clustertopos( gl_file.dirpos.cluster, &gl_file.pos );\r
525 \r
526         if ( _f_addentry( &fsname, &gl_file.dirpos, &de ) )\r
527         {\r
528           return 0;                                                  /*couldnt be added*/\r
529         }\r
530 \r
531         de->attr |= F_ATTR_ARC;         /*set as archiv*/\r
532         if ( _f_writeglsector( (unsigned long)-1 ) )\r
533         {\r
534           return 0;\r
535         }\r
536       }\r
537 \r
538  #if F_FILE_CHANGED_EVENT\r
539       _f_createfullname( gl_file.filename, sizeof( gl_file.filename ), fsname.path, fsname.filename, fsname.fileext );\r
540  #endif\r
541       break;\r
542 \r
543 \r
544     case F_FILE_WR:  /*w*/\r
545     case F_FILE_WRP: /*w+*/\r
546       _f_clustertopos( gl_file.dirpos.cluster, &gl_file.pos );\r
547       if ( _f_findfilewc( fsname.filename, fsname.fileext, &gl_file.pos, &de, 0 ) )\r
548       {\r
549         unsigned long  cluster = _f_getdecluster( de );    /*exist*/\r
550 \r
551         if ( de->attr & ( F_ATTR_DIR | F_ATTR_READONLY ) )\r
552         {\r
553           return 0;\r
554         }\r
555 \r
556         psp_memcpy( &( gl_file.dirpos ), &( gl_file.pos ), sizeof( F_POS ) );\r
557 \r
558         _f_setlong( de->filesize, 0 );  /*reset size;*/\r
559         de->attr |= F_ATTR_ARC;         /*set as archiv*/\r
560         _f_setword( de->clusterlo, 0 ); /*no points anywhere*/\r
561         _f_setword( de->clusterhi, 0 );\r
562 \r
563         if ( gl_volume.mediatype == F_FAT32_MEDIA )\r
564         {\r
565           f_igettimedate( &time, &date );\r
566           _f_setword( &de->crtdate, date );        /*if there is realtime clock then creation date could be set from*/\r
567           _f_setword( &de->crttime, time );        /*if there is realtime clock then creation time could be set from*/\r
568           _f_setword( &de->lastaccessdate, date ); /*if there is realtime clock then creation date could be set from*/\r
569         }\r
570 \r
571         if ( _f_writeglsector( (unsigned long)-1 ) )\r
572         {\r
573           return 0;\r
574         }\r
575 \r
576         if ( _f_removechain( cluster ) )\r
577         {\r
578           return 0;                                /*remove */\r
579         }\r
580       }\r
581       else\r
582       {\r
583         if ( _f_addentry( &fsname, &gl_file.dirpos, &de ) )\r
584         {\r
585           return 0;                                                  /*couldnt be added*/\r
586         }\r
587 \r
588         psp_memset( &gl_file, 0, 21 );\r
589         de->attr |= F_ATTR_ARC;         /*set as archiv*/\r
590         if ( _f_writeglsector( (unsigned long)-1 ) )\r
591         {\r
592           return 0;\r
593         }\r
594       }\r
595 \r
596  #if F_FILE_CHANGED_EVENT\r
597       _f_createfullname( gl_file.filename, sizeof( gl_file.filename ), fsname.path, fsname.filename, fsname.fileext );\r
598  #endif\r
599 \r
600       break;\r
601 \r
602     default:\r
603       return 0;        /*invalid mode*/\r
604   } /* switch */\r
605 \r
606   if ( ( m_mode != F_FILE_RD ) && ( gl_file.startcluster == 0 ) )\r
607   {\r
608     gl_volume.fatsector = (unsigned long)-1;\r
609     if ( _f_alloccluster( &( gl_file.startcluster ) ) )\r
610     {\r
611       return 0;\r
612     }\r
613 \r
614     _f_clustertopos( gl_file.startcluster, &gl_file.pos );\r
615     if ( _f_setclustervalue( gl_file.startcluster, F_CLUSTER_LAST ) )\r
616     {\r
617       return 0;\r
618     }\r
619 \r
620     if ( _f_writefatsector() )\r
621     {\r
622       return 0;\r
623     }\r
624   }\r
625 \r
626 \r
627   gl_file.mode = m_mode; /* lock it */\r
628   return (F_FILE *)1;\r
629 } /* fn_open */\r
630 \r
631 \r
632 /****************************************************************************\r
633  * _f_updatefileentry\r
634  * Updated a file directory entry or removes the entry\r
635  * and the fat chain belonging to it.\r
636  ***************************************************************************/\r
637 static unsigned char _f_updatefileentry ( int remove )\r
638 {\r
639   F_DIRENTRY    * de;\r
640   unsigned short  date;\r
641   unsigned short  time;\r
642 \r
643   de = (F_DIRENTRY *)( gl_sector + sizeof( F_DIRENTRY ) * gl_file.dirpos.pos );\r
644   if ( _f_readglsector( gl_file.dirpos.sector ) || remove )\r
645   {\r
646     _f_setdecluster( de, 0 );\r
647     _f_setlong( &de->filesize, 0 );\r
648     (void)_f_writeglsector( (unsigned long)-1 );\r
649     (void)_f_removechain( gl_file.startcluster );\r
650     return F_ERR_WRITE;\r
651   }\r
652 \r
653   _f_setdecluster( de, gl_file.startcluster );\r
654   _f_setlong( &de->filesize, gl_file.filesize );\r
655   f_igettimedate( &time, &date );\r
656   _f_setword( &de->cdate, date );  /*if there is realtime clock then creation date could be set from*/\r
657   _f_setword( &de->ctime, time );  /*if there is realtime clock then creation time could be set from*/\r
658   if ( gl_volume.mediatype == F_FAT32_MEDIA )\r
659   {\r
660     _f_setword( &de->lastaccessdate, date );  /*if there is realtime clock then creation date could be set from*/\r
661   }\r
662 \r
663   return _f_writeglsector( (unsigned long)-1 );\r
664 } /* _f_updatefileentry */\r
665 \r
666 \r
667 /****************************************************************************\r
668  *\r
669  * fn_close\r
670  *\r
671  * close a previously opened file\r
672  *\r
673  * INPUTS\r
674  *\r
675  * filehandle - which file needs to be closed\r
676  *\r
677  * RETURNS\r
678  *\r
679  * error code or zero if successful\r
680  *\r
681  ***************************************************************************/\r
682 unsigned char fn_close ( F_FILE * f )\r
683 {\r
684   unsigned char  ret;\r
685 \r
686 #if F_FILE_CHANGED_EVENT\r
687   unsigned char  mode;\r
688 #endif\r
689 \r
690   if ( !f )\r
691   {\r
692     return F_ERR_NOTOPEN;\r
693   }\r
694 \r
695   ret = _f_getvolume();\r
696   if ( ret )\r
697   {\r
698     return ret;\r
699   }\r
700 \r
701   if ( gl_file.mode == F_FILE_CLOSE )\r
702   {\r
703     return F_ERR_NOTOPEN;\r
704   }\r
705 \r
706   else if ( gl_file.mode == F_FILE_RD )\r
707   {\r
708     gl_file.mode = F_FILE_CLOSE;\r
709     return F_NO_ERROR;\r
710   }\r
711   else\r
712   {\r
713  #if F_FILE_CHANGED_EVENT\r
714     mode = f->mode;\r
715  #endif\r
716     gl_file.mode = F_FILE_CLOSE;\r
717 \r
718     if ( gl_file.modified )\r
719     {\r
720       if ( _f_writeglsector( (unsigned long)-1 ) )\r
721       {\r
722         (void)_f_updatefileentry( 1 );\r
723         return F_ERR_WRITE;\r
724       }\r
725     }\r
726 \r
727     ret = _f_updatefileentry( 0 );\r
728 \r
729  #if F_FILE_CHANGED_EVENT\r
730     if ( f_filechangedevent && !ret )\r
731     {\r
732       ST_FILE_CHANGED  fc;\r
733       if ( ( mode == F_FILE_WR )  || ( mode == F_FILE_WRP ) )\r
734       {\r
735         fc.action = FACTION_ADDED;\r
736         fc.flags  = FFLAGS_FILE_NAME;\r
737       }\r
738       else if ( ( mode == F_FILE_A )  || ( mode == F_FILE_RDP ) )\r
739       {\r
740         fc.action = FACTION_MODIFIED;\r
741         fc.flags  = FFLAGS_FILE_NAME | FFLAGS_SIZE;\r
742       }\r
743 \r
744       strcpy( fc.filename, f->filename );\r
745       if ( f->filename[0] )\r
746       {\r
747         f_filechangedevent( &fc );\r
748       }\r
749 \r
750       f->filename[0] = '\0';\r
751     }\r
752 \r
753  #endif /* if F_FILE_CHANGED_EVENT */\r
754     return ret;\r
755   }\r
756 } /* fn_close */\r
757 \r
758 \r
759 /****************************************************************************\r
760  *\r
761  * fn_flush\r
762  *\r
763  * flush a previously opened file\r
764  *\r
765  * INPUTS\r
766  *\r
767  * filehandle - which file needs to be closed\r
768  *\r
769  * RETURNS\r
770  *\r
771  * error code or zero if successful\r
772  *\r
773  ***************************************************************************/\r
774 unsigned char fn_flush ( F_FILE * f )\r
775 {\r
776   unsigned char  ret;\r
777 \r
778   if ( !f )\r
779   {\r
780     return F_ERR_NOTOPEN;\r
781   }\r
782 \r
783   ret = _f_getvolume();\r
784   if ( ret )\r
785   {\r
786     return ret;\r
787   }\r
788 \r
789   if ( gl_file.mode == F_FILE_CLOSE )\r
790   {\r
791     return F_ERR_NOTOPEN;\r
792   }\r
793   else if ( gl_file.mode != F_FILE_RD )\r
794   {\r
795     if ( gl_file.modified )\r
796     {\r
797       if ( _f_writeglsector( (unsigned long)-1 ) )\r
798       {\r
799         (void)_f_updatefileentry( 1 );\r
800         return F_ERR_WRITE;\r
801       }\r
802     }\r
803 \r
804     return _f_updatefileentry( 0 );\r
805   }\r
806 \r
807   return F_NO_ERROR;\r
808 } /* fn_flush */\r
809 \r
810 \r
811 /****************************************************************************\r
812  *\r
813  * fn_read\r
814  *\r
815  * read data from file\r
816  *\r
817  * INPUTS\r
818  *\r
819  * buf - where the store data\r
820  * size - size of items to be read\r
821  * _size_t - number of items need to be read\r
822  * filehandle - file where to read from\r
823  *\r
824  * RETURNS\r
825  *\r
826  * with the number of read bytes\r
827  *\r
828  ***************************************************************************/\r
829 long fn_read ( void * buf, long size, long _size_st, F_FILE * f )\r
830 {\r
831   char * buffer = (char *)buf;\r
832   long   retsize;\r
833 \r
834   if ( !f )\r
835   {\r
836     return 0;\r
837   }\r
838 \r
839   if ( ( gl_file.mode & ( F_FILE_RD | F_FILE_RDP | F_FILE_WRP | F_FILE_AP ) ) == 0 )\r
840   {\r
841     return 0;\r
842   }\r
843 \r
844   retsize = size;\r
845   size *= _size_st;\r
846   _size_st = retsize;\r
847   retsize = 0;\r
848 \r
849   if ( _f_getvolume() )\r
850   {\r
851     return 0;                     /*cant read any*/\r
852   }\r
853 \r
854   if ( size + gl_file.relpos + gl_file.abspos >= gl_file.filesize ) /*read len longer than the file*/\r
855   {\r
856     size = (long)( ( gl_file.filesize ) - ( gl_file.relpos ) - ( gl_file.abspos ) ); /*calculate new size*/\r
857   }\r
858 \r
859   if ( size <= 0 )\r
860   {\r
861     return 0;\r
862   }\r
863 \r
864   if ( _f_getcurrsector() )\r
865   {\r
866     gl_file.mode = F_FILE_CLOSE; /*no more read allowed*/\r
867     return 0;\r
868   }\r
869 \r
870   for( ; ; )\r
871   {\r
872     unsigned long  rdsize = (unsigned long)size;\r
873 \r
874     if ( gl_file.relpos == F_SECTOR_SIZE )\r
875     {\r
876       unsigned char  ret;\r
877 \r
878       gl_file.abspos += gl_file.relpos;\r
879       gl_file.relpos = 0;\r
880 \r
881       if ( gl_file.modified )\r
882       {\r
883         ret = _f_writeglsector( (unsigned long)-1 );     /*empty write buffer */\r
884         if ( ret )\r
885         {\r
886           gl_file.mode = F_FILE_CLOSE;         /*no more read allowed*/\r
887           return retsize;\r
888         }\r
889       }\r
890 \r
891       gl_file.pos.sector++;         /*goto next*/\r
892 \r
893       ret = _f_getcurrsector();\r
894       if ( ( ret == F_ERR_EOF ) && ( !size ) )\r
895       {\r
896         return retsize;\r
897       }\r
898 \r
899       if ( ret )\r
900       {\r
901         gl_file.mode = F_FILE_CLOSE;       /*no more read allowed*/\r
902         return retsize;\r
903       }\r
904     }\r
905 \r
906     if ( !size )\r
907     {\r
908       break;\r
909     }\r
910 \r
911     if ( rdsize >= F_SECTOR_SIZE - gl_file.relpos )\r
912     {\r
913       rdsize = (unsigned long)( F_SECTOR_SIZE - gl_file.relpos );\r
914     }\r
915 \r
916     psp_memcpy( buffer, gl_sector + gl_file.relpos, rdsize ); /*always less than 512*/\r
917 \r
918     buffer += rdsize;\r
919     gl_file.relpos += rdsize;\r
920     size -= rdsize;\r
921     retsize += rdsize;\r
922   }\r
923 \r
924   return retsize / _size_st;\r
925 } /* fn_read */\r
926 \r
927 \r
928 /****************************************************************************\r
929  *\r
930  * fn_write\r
931  *\r
932  * write data into file\r
933  *\r
934  * INPUTS\r
935  *\r
936  * buf - where the store data\r
937  * size - size of items to be read\r
938  * size_t - number of items need to be read\r
939  * filehandle - file where to read from\r
940  *\r
941  * RETURNS\r
942  *\r
943  * with the number of read bytes\r
944  *\r
945  ***************************************************************************/\r
946 \r
947 long fn_write ( const void * buf, long size, long _size_st, F_FILE * f )\r
948 {\r
949   char * buffer = (char *)buf;\r
950   long   retsize;\r
951   long   ret = 0;\r
952 \r
953   if ( !f )\r
954   {\r
955     return 0;\r
956   }\r
957 \r
958   if ( ( gl_file.mode & ( F_FILE_WR | F_FILE_A | F_FILE_RDP | F_FILE_WRP | F_FILE_AP ) ) == 0 )\r
959   {\r
960     return 0;\r
961   }\r
962 \r
963   retsize = size;\r
964   size *= _size_st;\r
965   _size_st = retsize;\r
966   retsize = 0;\r
967 \r
968   if ( _f_getvolume() )\r
969   {\r
970     return 0;                     /*can't write*/\r
971   }\r
972 \r
973   if ( ( gl_file.mode ) & ( F_FILE_A | F_FILE_AP ) )\r
974   {\r
975     if ( _f_fseek( (long)gl_file.filesize ) )\r
976     {\r
977       gl_file.mode = F_FILE_CLOSE;\r
978       return 0;\r
979     }\r
980   }\r
981 \r
982   if ( _f_getcurrsector() )\r
983   {\r
984     gl_file.mode = F_FILE_CLOSE;\r
985     return 0;\r
986   }\r
987 \r
988   for( ; ; )\r
989   {\r
990     unsigned long  wrsize = (unsigned long)size;\r
991 \r
992     if ( gl_file.relpos == F_SECTOR_SIZE )\r
993     {     /*now full*/\r
994       if ( gl_file.modified )\r
995       {\r
996         if ( _f_emptywritebuffer() )\r
997         {\r
998           gl_file.mode = F_FILE_CLOSE;\r
999           if ( _f_updatefileentry( 0 ) == 0 )\r
1000           {\r
1001             return retsize;\r
1002           }\r
1003           else\r
1004           {\r
1005             return 0;\r
1006           }\r
1007         }\r
1008       }\r
1009       else\r
1010       {\r
1011         gl_file.pos.sector++;       /*goto next*/\r
1012       }\r
1013 \r
1014       gl_file.abspos += gl_file.relpos;\r
1015       gl_file.relpos = 0;\r
1016 \r
1017       if ( wrsize && ( wrsize < F_SECTOR_SIZE ) )\r
1018       {\r
1019         ret = _f_getcurrsector();\r
1020 \r
1021         if ( ret )\r
1022         {\r
1023           if ( ret != F_ERR_EOF )\r
1024           {\r
1025             gl_file.mode = F_FILE_CLOSE;       /*no more read allowed*/\r
1026             return retsize;\r
1027           }\r
1028         }\r
1029       }\r
1030     }\r
1031 \r
1032     if ( !size )\r
1033     {\r
1034       break;\r
1035     }\r
1036 \r
1037     if ( wrsize >= F_SECTOR_SIZE - gl_file.relpos )\r
1038     {\r
1039       wrsize = (unsigned long)( F_SECTOR_SIZE - gl_file.relpos );\r
1040     }\r
1041 \r
1042 \r
1043     psp_memcpy( gl_sector + gl_file.relpos, buffer, wrsize );\r
1044     gl_file.modified = 1;    /*sector is modified*/\r
1045 \r
1046     buffer += wrsize;\r
1047     gl_file.relpos += wrsize;\r
1048     size -= wrsize;\r
1049     retsize += wrsize;\r
1050 \r
1051     if ( gl_file.filesize < gl_file.abspos + gl_file.relpos )\r
1052     {\r
1053       gl_file.filesize = gl_file.abspos + gl_file.relpos;\r
1054     }\r
1055   }\r
1056 \r
1057   return retsize / _size_st;\r
1058 } /* fn_write */\r
1059 \r
1060 \r
1061 \r
1062 /****************************************************************************\r
1063  *\r
1064  * fn_seek\r
1065  *\r
1066  * moves position into given offset in given file\r
1067  *\r
1068  * INPUTS\r
1069  *\r
1070  * filehandle - F_FILE structure which file position needed to be modified\r
1071  * offset - relative position\r
1072  * whence - where to calculate position (F_SEEK_SET,F_SEEK_CUR,F_SEEK_END)\r
1073  *\r
1074  * RETURNS\r
1075  *\r
1076  * 0 - if successfully\r
1077  * other - if any error\r
1078  *\r
1079  ***************************************************************************/\r
1080 \r
1081 \r
1082 unsigned char fn_seek ( F_FILE * f, long offset, unsigned char whence )\r
1083 {\r
1084   unsigned char  ret;\r
1085 \r
1086   if ( !f )\r
1087   {\r
1088     return F_ERR_NOTOPEN;\r
1089   }\r
1090 \r
1091   if ( ( gl_file.mode & ( F_FILE_RD | F_FILE_WR | F_FILE_A | F_FILE_RDP | F_FILE_WRP | F_FILE_AP ) ) == 0 )\r
1092   {\r
1093     return F_ERR_NOTOPEN;\r
1094   }\r
1095 \r
1096   ret = _f_getvolume();\r
1097   if ( ret )\r
1098   {\r
1099     return ret;\r
1100   }\r
1101 \r
1102   if ( whence == F_SEEK_CUR )\r
1103   {\r
1104     return _f_fseek( (long)( gl_file.abspos + gl_file.relpos + offset ) );\r
1105   }\r
1106   else if ( whence == F_SEEK_END )\r
1107   {\r
1108     return _f_fseek( (long)( gl_file.filesize + offset ) );\r
1109   }\r
1110   else if ( whence == F_SEEK_SET )\r
1111   {\r
1112     return _f_fseek( offset );\r
1113   }\r
1114 \r
1115   return F_ERR_NOTUSEABLE;\r
1116 } /* fn_seek */\r
1117 \r
1118 \r
1119 \r
1120 /****************************************************************************\r
1121  *\r
1122  * fn_tell\r
1123  *\r
1124  * Tells the current position of opened file\r
1125  *\r
1126  * INPUTS\r
1127  *\r
1128  * filehandle - which file needs the position\r
1129  *\r
1130  * RETURNS\r
1131  *\r
1132  * position in the file from start\r
1133  *\r
1134  ***************************************************************************/\r
1135 \r
1136 long fn_tell ( F_FILE * f )\r
1137 {\r
1138   if ( !f )\r
1139   {\r
1140     return 0;\r
1141   }\r
1142 \r
1143   if ( ( gl_file.mode & ( F_FILE_RD | F_FILE_WR | F_FILE_A | F_FILE_RDP | F_FILE_WRP | F_FILE_AP ) ) == 0 )\r
1144   {\r
1145     return 0;\r
1146   }\r
1147 \r
1148   return (long)( gl_file.abspos + gl_file.relpos );\r
1149 }\r
1150 \r
1151 \r
1152 \r
1153 /****************************************************************************\r
1154  *\r
1155  * fn_eof\r
1156  *\r
1157  * Tells if the current position is end of file or not\r
1158  *\r
1159  * INPUTS\r
1160  *\r
1161  * filehandle - which file needs the checking\r
1162  *\r
1163  * RETURNS\r
1164  *\r
1165  * 0 - if not EOF\r
1166  * other - if EOF or invalid file handle\r
1167  *\r
1168  ***************************************************************************/\r
1169 \r
1170 unsigned char fn_eof ( F_FILE * f )\r
1171 {\r
1172   if ( !f )\r
1173   {\r
1174     return F_ERR_NOTOPEN;          /*if error*/\r
1175   }\r
1176 \r
1177   if ( gl_file.abspos + gl_file.relpos < gl_file.filesize )\r
1178   {\r
1179     return 0;\r
1180   }\r
1181 \r
1182   return F_ERR_EOF;  /*EOF*/\r
1183 }\r
1184 \r
1185 \r
1186 \r
1187 \r
1188 /****************************************************************************\r
1189  *\r
1190  * fn_rewind\r
1191  *\r
1192  * set the fileposition in the opened file to the begining\r
1193  *\r
1194  * INPUTS\r
1195  *\r
1196  * filehandle - which file needs to be rewinded\r
1197  *\r
1198  * RETURNS\r
1199  *\r
1200  * error code or zero if successful\r
1201  *\r
1202  ***************************************************************************/\r
1203 \r
1204 unsigned char fn_rewind ( F_FILE * filehandle )\r
1205 {\r
1206   return fn_seek( filehandle, 0L, F_SEEK_SET );\r
1207 }\r
1208 \r
1209 \r
1210 \r
1211 /****************************************************************************\r
1212  *\r
1213  * fn_putc\r
1214  *\r
1215  * write a character into file\r
1216  *\r
1217  * INPUTS\r
1218  *\r
1219  * ch - what to write into file\r
1220  * filehandle - file where to write\r
1221  *\r
1222  * RETURNS\r
1223  *\r
1224  * with the number of written bytes (1-success, 0-not successfully)\r
1225  *\r
1226  ***************************************************************************/\r
1227 \r
1228 int fn_putc ( int ch, F_FILE * filehandle )\r
1229 {\r
1230   unsigned char  tmpch = (unsigned char)ch;\r
1231 \r
1232   if ( fn_write( &tmpch, 1, 1, filehandle ) )\r
1233   {\r
1234     return ch;\r
1235   }\r
1236   else\r
1237   {\r
1238     return -1;\r
1239   }\r
1240 }\r
1241 \r
1242 \r
1243 \r
1244 /****************************************************************************\r
1245  *\r
1246  * fn_getc\r
1247  *\r
1248  * get a character from file\r
1249  *\r
1250  * INPUTS\r
1251  *\r
1252  * filehandle - file where to read from\r
1253  *\r
1254  * RETURNS\r
1255  *\r
1256  * with the read character or -1 if read was not successfully\r
1257  *\r
1258  ***************************************************************************/\r
1259 int fn_getc ( F_FILE * filehandle )\r
1260 {\r
1261   unsigned char  ch;\r
1262 \r
1263   if ( !fn_read( &ch, 1, 1, filehandle ) )\r
1264   {\r
1265     return -1;\r
1266   }\r
1267 \r
1268   return (int)ch;\r
1269 }\r
1270 \r
1271 \r
1272 \r
1273 /****************************************************************************\r
1274  *\r
1275  * fn_delete\r
1276  *\r
1277  * delete a file\r
1278  *\r
1279  * INPUTS\r
1280  *\r
1281  * filename - file which wanted to be deleted (with or without path)\r
1282  *\r
1283  * RETURNS\r
1284  *\r
1285  * error code or zero if successful\r
1286  *\r
1287  ***************************************************************************/\r
1288 unsigned char fn_delete ( const char * filename )\r
1289 {\r
1290   F_POS          pos;\r
1291   F_DIRENTRY   * de;\r
1292   F_NAME         fsname;\r
1293   unsigned char  ret;\r
1294 \r
1295   if ( _f_setfsname( filename, &fsname ) )\r
1296   {\r
1297     return F_ERR_INVALIDNAME;                                     /*invalid name*/\r
1298   }\r
1299 \r
1300   if ( _f_checknamewc( fsname.filename, fsname.fileext ) )\r
1301   {\r
1302     return F_ERR_INVALIDNAME;                                                    /*invalid name*/\r
1303   }\r
1304 \r
1305   if ( fsname.filename[0] == '.' )\r
1306   {\r
1307     return F_ERR_NOTFOUND;\r
1308   }\r
1309 \r
1310   ret = _f_getvolume();\r
1311   if ( ret )\r
1312   {\r
1313     return ret;\r
1314   }\r
1315 \r
1316   if ( !( _f_findpath( &fsname, &pos ) ) )\r
1317   {\r
1318     return F_ERR_INVALIDDIR;\r
1319   }\r
1320 \r
1321   if ( !_f_findfilewc( fsname.filename, fsname.fileext, &pos, &de, 0 ) )\r
1322   {\r
1323     return F_ERR_NOTFOUND;\r
1324   }\r
1325 \r
1326   if ( de->attr & F_ATTR_DIR )\r
1327   {\r
1328     return F_ERR_INVALIDDIR;                                /*directory*/\r
1329   }\r
1330 \r
1331   if ( de->attr & F_ATTR_READONLY )\r
1332   {\r
1333     return F_ERR_ACCESSDENIED;                                      /*readonly*/\r
1334   }\r
1335 \r
1336   if ( ( gl_file.mode != F_FILE_CLOSE ) && ( gl_file.dirpos.sector == pos.sector ) && ( gl_file.dirpos.pos == pos.pos ) )\r
1337   {\r
1338     return F_ERR_LOCKED;\r
1339   }\r
1340 \r
1341   de->name[0] = (unsigned char)0xe5; /*removes it*/\r
1342   ret = _f_writeglsector( (unsigned long)-1 );\r
1343   if ( ret )\r
1344   {\r
1345     return ret;\r
1346   }\r
1347 \r
1348   ret = _f_removechain( _f_getdecluster( de ) );\r
1349 \r
1350  #if F_FILE_CHANGED_EVENT\r
1351   if ( f_filechangedevent && !ret )\r
1352   {\r
1353     ST_FILE_CHANGED  fc;\r
1354     fc.action = FACTION_REMOVED;\r
1355     fc.flags = FFLAGS_FILE_NAME;\r
1356 \r
1357     if ( !_f_createfullname( fc.filename, sizeof( fc.filename ), fsname.path, fsname.filename, fsname.fileext ) )\r
1358     {\r
1359       f_filechangedevent( &fc );\r
1360     }\r
1361   }\r
1362 \r
1363  #endif\r
1364 \r
1365   return ret;\r
1366 } /* fn_delete */\r
1367 \r
1368 \r
1369 \r
1370 \r
1371 /****************************************************************************\r
1372  *\r
1373  * _f_seteof\r
1374  *\r
1375  * Set end of file\r
1376  *\r
1377  * INPUT:       f - file pointer\r
1378  *              filesize - required new size\r
1379  * RETURN:      F_NO_ERROR - on success\r
1380  *              other if error\r
1381  *\r
1382  ***************************************************************************/\r
1383 unsigned char _f_seteof ( F_FILE * f, long filesize )\r
1384 {\r
1385   unsigned char  rc = F_NO_ERROR;\r
1386 \r
1387   if ( !f )\r
1388   {\r
1389     return F_ERR_NOTOPEN;        /*if error*/\r
1390   }\r
1391 \r
1392   if ( ( unsigned long) filesize < gl_file.filesize )\r
1393   {\r
1394     rc = _f_fseek( filesize );\r
1395     if ( rc == F_NO_ERROR )\r
1396     {\r
1397       unsigned long  cluster;\r
1398       rc = _f_getclustervalue( gl_file.pos.cluster, &cluster );\r
1399       if ( rc == F_NO_ERROR )\r
1400       {\r
1401         if ( cluster != F_CLUSTER_LAST )\r
1402         {\r
1403           rc = _f_removechain( cluster );\r
1404           if ( rc )\r
1405           {\r
1406             return rc;\r
1407           }\r
1408 \r
1409           rc = _f_setclustervalue( gl_file.pos.cluster, F_CLUSTER_LAST );\r
1410           if ( rc )\r
1411           {\r
1412             return rc;\r
1413           }\r
1414 \r
1415           rc = _f_writefatsector();\r
1416           if ( rc )\r
1417           {\r
1418             return rc;\r
1419           }\r
1420         }\r
1421 \r
1422         gl_file.filesize = (unsigned long)filesize;\r
1423       }\r
1424     }\r
1425   }\r
1426   else if ( (unsigned long) filesize > gl_file.filesize )\r
1427   {\r
1428     rc = _f_fseek( filesize );\r
1429   }\r
1430 \r
1431   return rc;\r
1432 } /* _f_seteof */\r
1433 \r
1434 \r
1435 /****************************************************************************\r
1436  *\r
1437  * fn_seteof\r
1438  *\r
1439  * Set end of file\r
1440  *\r
1441  * INPUT:       f - file pointer\r
1442  *              filesize - required new size\r
1443  * RETURN:      F_NO_ERROR - on success\r
1444  *              other if error\r
1445  *\r
1446  ***************************************************************************/\r
1447 unsigned char fn_seteof ( F_FILE * f )\r
1448 {\r
1449   unsigned char  rc = F_NO_ERROR;\r
1450 \r
1451   rc = _f_seteof( f, ( gl_file.abspos + gl_file.relpos ) );\r
1452 \r
1453   return rc;\r
1454 } /* fn_seteof */\r
1455 \r
1456 \r
1457 \r
1458 \r
1459 /****************************************************************************\r
1460  *\r
1461  * fn_truncate\r
1462  *\r
1463  * Open a file and set end of file\r
1464  *\r
1465  * INPUT:       filename - name of the file\r
1466  *              filesize - required new size\r
1467  * RETURN:      NULL on error, otherwise file pointer\r
1468  *\r
1469  ***************************************************************************/\r
1470 F_FILE * fn_truncate ( const char * filename, long filesize )\r
1471 {\r
1472   F_FILE       * f = fn_open( filename, "r+" );\r
1473   unsigned char  rc;\r
1474 \r
1475   if ( f != NULL )\r
1476   {\r
1477     rc = _f_fseek( (long)gl_file.filesize );\r
1478     if ( rc == F_NO_ERROR )\r
1479     {\r
1480       rc = _f_seteof( f, filesize );\r
1481     }\r
1482 \r
1483     if ( rc )\r
1484     {\r
1485       fn_close( f );\r
1486       f = NULL;\r
1487     }\r
1488   }\r
1489 \r
1490   return f;\r
1491 } /* fn_truncate */\r
1492 \r