1 /*
2  * fsdevice-filename.c - File system device.
3  *
4  * Written by
5  *  groepaz <groepaz@gmx.net>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 /* #define DEBUGFILENAME */
28 
29 #include "vice.h"
30 
31 #include <string.h>
32 
33 #include "charset.h"
34 #include "fsdevice-filename.h"
35 #include "fsdevicetypes.h"
36 #include "ioutil.h"
37 #include "lib.h"
38 #include "log.h"
39 #include "resources.h"
40 #include "vdrive.h"
41 
42 #ifdef DEBUGFILENAME
43 #define DBG(x)  printf x
44 #else
45 #define DBG(x)
46 #endif
47 
48 /* A lot of programs will not work right with filenames that are longer than 16
49    characters, so we must shorten them somehow. unfortunately this is less than
50    trivial :/
51 
52    - when creating a new file, we can just pad the name to 16 characters. this
53      is pretty much what a real CBM drive would do and should cause no side
54      effects.
55 
56    - when listing the directory, we create a short filename using a simple
57      algorithm:
58 
59      - count the number of files that are the same for the first 14 chars
60      - replace the last two chars by a) a marker that represents the counter for
61        the current file and b) a character that is valid in a petscii filename,
62        but invalid on the host filesystem.
63 
64     for example:
65 
66         1234567890123456789012              1234567890123456
67         testfoobartestest.prg   becomes     testfoobartest0/
68         testfoobartestAB.prg    becomes     testfoobartest1/
69 
70    - when opening an existing file, we iterate through the current work
71      directory, convert each filename to a short name using the algorithm above,
72      and then compare if the result matches the filename we want to open. if so,
73      we can use the long name of the file to open it.
74 
75     all functions below should be completely transparent (ie not change the
76     provided names in any way) when "FSDeviceLongNames" is set to "1".
77 
78 */
79 
80 /*
81     convert real (long) name into shortened representation
82 
83     mode    0 - name is ASCII
84             1 - name is PETSCII
85 */
86 
87 #define MAXDIRPOSMARK (10+26+26)
88 
_limit_longname(struct ioutil_dir_s * ioutil_dir,vdrive_t * vdrive,char * longname,int mode)89 static int _limit_longname(struct ioutil_dir_s *ioutil_dir, vdrive_t *vdrive, char *longname, int mode)
90 {
91     char *direntry;
92     char *newname;
93     int longnames;
94     int dirpos = 0;
95     int tmppos;
96     char *dirposmark = { "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" };
97 
98     DBG(("limit_longname enter '%s' mode: %d\n", longname, mode));
99     if (resources_get_int("FSDeviceLongNames", &longnames) < 0) {
100         return -1;
101     }
102 
103     /* get a buffer for the new name */
104     newname = lib_malloc(ioutil_maxpathlen());
105 
106     if (!longnames) {
107         if (strlen(longname) > 16) {
108             tmppos = ioutil_getdirpos(ioutil_dir);
109             ioutil_resetdir(ioutil_dir);
110 
111             while(1) {
112                 direntry = ioutil_readdir(ioutil_dir);
113                 if (direntry == NULL) {
114                     break;
115                 }
116                 strcpy(newname, direntry);
117                 if (mode) {
118                     charset_petconvstring((uint8_t *)newname, 0);   /* ASCII name to PETSCII */
119                 }
120                 if (!strncmp(newname, longname, 14)) {
121                     dirpos++;
122                     /* handle max count */
123                     if (dirpos == MAXDIRPOSMARK) {
124                         log_error(LOG_DEFAULT, "could not make a unique short name for '%s'", longname);
125                         ioutil_setdirpos(ioutil_dir, tmppos);
126                         return -1;
127                     }
128                     DBG(("limit_longname found partial '%s'\n", longname));
129                 }
130                 DBG(("limit_longname>%d '%s'->'%s' (%s)\n", dirpos, direntry, newname, longname));
131                 if (!strcmp(newname, longname)) {
132                     DBG(("limit_longname found full '%s'\n", longname));
133                     longname[14] = dirposmark[dirpos];
134                     longname[15] = '/';     /* FIXME: use macro */
135                     longname[16] = 0;
136                     break;
137                 }
138             }
139             ioutil_setdirpos(ioutil_dir, tmppos);
140         }
141     }
142     DBG(("limit_longname return '%s'\n", longname));
143     lib_free(newname);
144 
145     return 0;
146 }
147 
limit_longname(vdrive_t * vdrive,char * longname,int mode)148 static int limit_longname(vdrive_t *vdrive, char *longname, int mode)
149 {
150     struct ioutil_dir_s *ioutil_dir;
151     char *prefix;
152     int ret = -1;
153 
154     prefix = fsdevice_get_path(vdrive->unit);
155     DBG(("limit_longname path '%s'\n", prefix));
156 
157     ioutil_dir = ioutil_opendir(prefix, IOUTIL_OPENDIR_ALL_FILES);
158     ret = _limit_longname(ioutil_dir, vdrive, longname, mode);
159     ioutil_closedir(ioutil_dir);
160 
161     return ret;
162 }
163 
164 /*
165     convert shortened name into the actual (long) name
166 
167     mode    0 - name is ASCII
168             1 - name is PETSCII
169 */
170 
expand_shortname(vdrive_t * vdrive,char * shortname,int mode)171 static char *expand_shortname(vdrive_t *vdrive, char *shortname, int mode)
172 {
173     struct ioutil_dir_s *ioutil_dir;
174     char *direntry;
175     char *prefix;
176     char *longname;
177     int longnames;
178 
179     if (resources_get_int("FSDeviceLongNames", &longnames) < 0) {
180         longnames = 0;
181     }
182 
183     DBG(("expand_shortname shortname '%s' mode: %d\n", shortname, mode));
184 
185     /* get a buffer for the new name */
186     longname = lib_malloc(ioutil_maxpathlen());
187 
188     if (!longnames) {
189         prefix = fsdevice_get_path(vdrive->unit);
190         DBG(("expand_shortname path '%s'\n", prefix));
191 
192         ioutil_dir = ioutil_opendir(prefix, IOUTIL_OPENDIR_ALL_FILES);
193 
194         while(1) {
195             direntry = ioutil_readdir(ioutil_dir);
196             if (direntry == NULL) {
197                 break;
198             }
199             /* create the short name for this entry and see if it matches */
200             strcpy(longname, direntry);
201             _limit_longname(ioutil_dir, vdrive, longname, 0);
202             if (mode) {
203                 charset_petconvstring((uint8_t *)longname, 0);   /* ASCII name to PETSCII */
204             }
205             DBG(("expand_shortname>'%s'->'%s'('%s')\n", direntry, longname, shortname));
206             if (!strcmp(longname, shortname)) {
207                 strcpy(longname, direntry);
208                 if (mode) {
209                     charset_petconvstring((uint8_t *)longname, 0);   /* ASCII name to PETSCII */
210                 }
211                 ioutil_closedir(ioutil_dir);
212                 return longname;
213             }
214         }
215         ioutil_closedir(ioutil_dir);
216     }
217     /* copy original string to the new name */
218     strcpy(longname, shortname);
219     DBG(("expand_shortname return '%s'\n", longname));
220     return longname;
221 }
222 
223 
224 /* takes a short name and returns a pointer to a long name
225 
226     shortname: pointer to PETSCII string (filename)
227 */
fsdevice_expand_shortname(vdrive_t * vdrive,char * name)228 char *fsdevice_expand_shortname(vdrive_t *vdrive, char *name)
229 {
230     return expand_shortname(vdrive, name, 1);
231 }
232 
233 /* takes a short name and returns a pointer to a long name
234 
235     shortname: pointer to ASCII string (filename)
236 */
fsdevice_expand_shortname_ascii(vdrive_t * vdrive,char * name)237 char *fsdevice_expand_shortname_ascii(vdrive_t *vdrive, char *name)
238 {
239     return expand_shortname(vdrive, name, 0);
240 }
241 
242 
243 /* limit a filename length to 16 characters. works in-place, ie it changes
244    the input string
245 
246    used when listing the directory, in this case we must create a unique short
247    name that can be expanded to the full long name later.
248 
249    name: pointer to PETSCII string (filename)
250 */
fsdevice_limit_namelength(vdrive_t * vdrive,uint8_t * name)251 int fsdevice_limit_namelength(vdrive_t *vdrive, uint8_t *name)
252 {
253     return limit_longname(vdrive, (char*)name, 1);
254 }
255 
256 /* limit a filename length to 16 characters. works in-place, ie it changes
257    the input string
258 
259    used when listing the directory, in this case we must create a unique short
260    name that can be expanded to the full long name later.
261 
262    name: pointer to ASCII string (filename)
263 */
fsdevice_limit_namelength_ascii(vdrive_t * vdrive,char * name)264 int fsdevice_limit_namelength_ascii(vdrive_t *vdrive, char *name)
265 {
266     return limit_longname(vdrive, name, 0);
267 }
268 
269 /* limit a filename length to 16 characters. works in-place, ie it changes
270    the input string
271 
272     used when creating a file. in this case we can simply cut off the long
273     name after 16 chars - just like a real CBM drive would do.
274 */
fsdevice_limit_createnamelength(vdrive_t * vdrive,char * name)275 int fsdevice_limit_createnamelength(vdrive_t *vdrive, char *name)
276 {
277     int longnames;
278 
279     if (resources_get_int("FSDeviceLongNames", &longnames) < 0) {
280         return -1;
281     }
282 
283     if (!longnames) {
284         if (strlen((char*)name) > 16) {
285             name[16] = 0;
286         }
287     }
288     return 0;
289 }
290