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