root/core/chdk-dir.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. read_next_entry
  2. rewind_entry
  3. CHDKOpenDir
  4. CHDKCloseDir
  5. check_fn_char
  6. read_lfn_entry
  7. CHDKReadDir

   1 /*
   2  * FAT12/16/32 LFN-aware opendir, readdir, closedir implementation
   3  *
   4  * Copyright (C) 2014 srsa @ CHDK Forum
   5  *
   6  * This program is free software; you can redistribute it and/or modify
   7  * it under the terms of the GNU General Public License as published by
   8  * the Free Software Foundation; either version 2 of the License, or
   9  * (at your option) any later version.
  10  *
  11  * This program is distributed in the hope that it will be useful,
  12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14  * GNU General Public License for more details.
  15  *
  16  * You should have received a copy of the GNU General Public License
  17  * along with this program; if not, write to the Free Software
  18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  19  */
  20 
  21 /*
  22  * Only a restricted set of ascii characters is supported in long filenames, falls back to short names otherwise
  23  * Uses less memory than the cameras' ReadFDir functions
  24  * v 1.1: removed CHDK wrappers
  25  * v 1.2: - add basic filename and total path length limit check (return short names when they are exceeded)
  26  *        - bugfix: ignore terminating zero char of LFN
  27  */
  28 
  29 #include "stdlib.h"
  30 
  31 #define MIN(a,b) ((a) < (b) ? (a) : (b))
  32 #define MAX(a,b) ((a) > (b) ? (a) : (b))
  33 
  34 #define FNMAX 107
  35 #define DCSIZE 16384 // read cache size, 128 for testing, something like 16384 later
  36 
  37 typedef struct
  38 {
  39     int     fd;                 // directory file descriptor
  40     char*   dc;                 // allocated directory cache
  41     int     cp;                 // directory cache pointer used by the cache routines
  42     int     rc;                 // read count
  43 
  44     union
  45     {
  46         char          *fe;      // currently evaluated FAT entry
  47         unsigned char *feu;
  48     };
  49     int     islfn;              // long file name is being read (state)
  50     int     lfnpos;             // position in the long output string (starts from 1 due to the terminating zero char)
  51 
  52     int     mnl;                // maximum name length
  53 
  54     char    fn[FNMAX+1];        // current file name stored here by CHDKReadDir
  55 } myDIR_s;
  56 
  57 static int read_next_entry(myDIR_s* dir)
  58 {
  59     int rc = 0;
  60     if ( (dir->cp==-1) || (dir->cp>=MIN(DCSIZE, dir->rc)) ) // cache empty or fully read out
  61     {
  62         rc = read(dir->fd, dir->dc, DCSIZE);
  63         if ( rc < 32 ) // short read or error
  64         {
  65             return rc;
  66         }
  67         dir->rc = rc;
  68         dir->fe = dir->dc;
  69         dir->cp = 32;
  70         return 32;
  71     }
  72     else    // read from cache
  73     {
  74         int canread = MIN(dir->rc - dir->cp, 32);
  75         dir->fe = dir->dc + dir->cp;
  76         dir->cp += canread;
  77         return canread;
  78     }
  79 }
  80 
  81 static void rewind_entry(myDIR_s* dir)
  82 {
  83     dir->cp -= 32;
  84 }
  85 
  86 void *CHDKOpenDir(const char* name)
  87 {
  88     myDIR_s *dir = malloc( sizeof(myDIR_s) );
  89     if ( dir )
  90     {
  91         char *dirc = umalloc(DCSIZE);
  92         if ( dirc )
  93         {
  94             dir->fd = open(name, 0, 0x124);
  95             if ( dir->fd != -1 )
  96             {
  97                 dir->fn[0] = 0;
  98                 dir->dc = dirc;
  99                 dir->cp = -1;
 100                 // determine name length limit ('-1' is due to the extra '/' in a full filename)
 101                 dir->mnl = MIN(CAM_MAX_FNAME_LENGTH,CAM_MAX_PATH_LENGTH-strlen(name)-1);
 102                 return dir;
 103             }
 104             else
 105             {
 106                 free((void*)dir);
 107                 ufree((void*)dirc);
 108             }
 109         }
 110         else
 111         {
 112             free((void*)dir);
 113         }
 114     }
 115     return NULL; // failure
 116 }
 117 
 118 int CHDKCloseDir(void *d)
 119 {
 120     myDIR_s *dir = d;
 121     // if ( d ) // commented out, the wrapper already checks this
 122     {
 123         int ret = close(dir->fd);
 124         ufree(dir->dc);
 125         free(d);
 126         return ret;
 127     }
 128     return -1;
 129 }
 130 
 131 const unsigned char lfnchpos[]={30,28,24,22,20,18,16,14,9,7,5,3,1};
 132 
 133 int check_fn_char(int i)
 134 {
 135     if ( i & 0xffffff80 ) return -1;
 136     if (
 137         ((i >= '0') && (i <= '9')) ||
 138         ((i >= '@'/*'A'*/) && (i <= 'Z')) ||
 139         ((i >= 'a') && (i <= 'z')) ||
 140         ((i == '.') || (i == '-') || (i == '_') || (i == '(') || (i == ')') || (i == '$') || (i == '&'))
 141        )
 142     {
 143         return i;
 144     }
 145     if ( i != 0 ) return -1;
 146     return 0;
 147 }
 148 
 149 void read_lfn_entry(myDIR_s* dir)
 150 {
 151     int n;
 152     for (n=0; n<13; n++)
 153     {
 154         // read unicode char, reading as halfword is not working on armv5 due to alignment
 155         int uch = *(unsigned char*)(dir->fe+lfnchpos[n])+((*(unsigned char*)(dir->fe+lfnchpos[n]+1))<<8);
 156         if ((uch != 0xffff) && (uch != 0))  // unused space is filled with '0xffff' chars and zero or one '0x0' char
 157         {
 158             if ( (check_fn_char(uch) < 0) ) // disable lfn if any chars are outside 7bit ascii
 159             {
 160                 dir->islfn = 0;
 161                 break;
 162             }
 163             dir->fn[FNMAX-dir->lfnpos] = (char)uch;
 164             dir->lfnpos++;
 165         }
 166     }
 167 }
 168 
 169 int CHDKReadDir(void *d, void* dd)
 170 {
 171     myDIR_s *dir = d;
 172     int rd;
 173     int lfnchsum = 0;    // lfn checksum (only zeroed here to calm down the compiler)
 174     dir->islfn = 0;      // long file name is being read (state)
 175     dir->lfnpos = 0;     // position in the long output string (only zeroed here to calm down the compiler)
 176 
 177     //if ( (int)d && (int)dd ) // commented out, the wrapper already checks this
 178     {
 179         while (1)
 180         {
 181             rd=read_next_entry(dir);
 182             if ( (rd < 32) ) // either error or short read, return with null string
 183             {
 184                 break;
 185             }
 186             if ( dir->fe[0] == 0 ) // last entry, return with null string
 187             {
 188                 break;
 189             }
 190             if ( (dir->feu[0] == 0xe5) || ((dir->fe[11]&0xf) == 8) ) // erased entry or label, makes LFN invalid -> skip entry
 191             {
 192                 dir->islfn = 0;
 193                 continue;
 194             }
 195             if ( !dir->islfn && (dir->fe[11] != 0xf) ) // no LFN in progress, not LFN entry -> read short name, return
 196             {
 197                 int n, m;
 198                 m = 0; // position in output
 199                 for (n=0; n<8; n++) // name
 200                 {
 201                     if ( (n>0) && (dir->fe[n]==0x20) ) break;
 202                     dir->fn[m] = dir->fe[n];
 203                     m++;
 204                 }
 205                 if ( (dir->fe[8]!=0x20) ) // add dot only when there's extension
 206                 {
 207                     dir->fn[m] = '.';
 208                     m++;
 209                 }
 210                 for (n=8; n<11; n++) // extension
 211                 {
 212                     if ( (dir->fe[n]==0x20) ) break;
 213                     dir->fn[m] = dir->fe[n];
 214                     m++;
 215                 }
 216                 dir->fn[m] = 0;
 217                 strcpy(dd, dir->fn);
 218                 return (int)(dir->fn);
 219             }
 220             if ( (dir->islfn == 1) && (dir->fe[11] != 0xf) ) // lfn entries over, this must be the short filename entry
 221             {
 222                 // compute checksum
 223                 unsigned char cs = 0;
 224                 int n;
 225                 for (n = 0; n < 11; n++)
 226                 {
 227                     cs = (((cs & 1) << 7) | ((cs & 0xfe) >> 1)) + dir->feu[n];
 228                 }
 229                 // checksum computed
 230                 if ( (cs == lfnchsum) && (dir->lfnpos-1 <= dir->mnl) ) 
 231                 {
 232                     // lfn is valid, not too long, and belongs to this short name -> return
 233                     strcpy(dd, (dir->fn)+FNMAX-dir->lfnpos+1);
 234                     return (int)((dir->fn)+FNMAX-dir->lfnpos+1);
 235                 }
 236                 else // invalid checksum or name too long, try re-interpreting entry
 237                 {
 238                     rewind_entry(dir);
 239                     dir->islfn = 0;
 240                     continue;
 241                 }
 242             }
 243             else if ( dir->fe[11] == 0xf ) // lfn entry, process or skip
 244             {
 245                 if (dir->islfn) // already in an lfn block
 246                 {
 247                     if (                         // check for anomalies:
 248                          (dir->feu[13] != lfnchsum) || // checksum doesn't match
 249                          (dir->fe[0] & 0x40) ||       // first entry of an lfn block
 250                          (dir->fe[0] != dir->islfn-1)      // out of order entry
 251                        )
 252                     {
 253                         dir->islfn = 0;
 254                         continue;
 255                     }
 256                     dir->islfn = dir->fe[0]; // number of lfn entries left + 1
 257                     read_lfn_entry(dir);
 258                     if ( dir->lfnpos > 99+1 ) // CHDK limit (100 chars) hit, skip lfn
 259                     {
 260                         dir->islfn = 0;
 261                         continue;
 262                     }
 263                 }
 264                 else // lfn block start
 265                 {
 266                     if ( (dir->fe[0] & 0x40) && (dir->fe[0]-0x40 <= 8) ) // start must be valid and name not too long
 267                     {
 268                         dir->islfn = dir->fe[0] - 0x40; // number of lfn entries left + 1
 269                         memset(dir->fn, 0, FNMAX+1);
 270                         dir->lfnpos = 1; // name will be filled backwards, the last char of the buffer will remain 0 for safety
 271                         read_lfn_entry(dir);
 272                         lfnchsum = dir->feu[13];
 273                     }
 274                     else // invalid or too long, skip
 275                     {
 276                         continue;
 277                     }
 278                 }
 279             }
 280             else // lfn invalid, skip
 281             {
 282                 dir->islfn = 0;
 283             }
 284         }
 285 // problem or end-of-directory
 286         dir->fn[0] = 0;
 287         strcpy(dd, dir->fn);
 288     }
 289     return 0;
 290 }

/* [<][>][^][v][top][bottom][index][help] */