1 /*
2  * This file has been modified for the cdrkit suite.
3  *
4  * The behaviour and appearence of the program code below can differ to a major
5  * extent from the version distributed by the original author(s).
6  *
7  * For details, see Changelog file distributed with the cdrkit package. If you
8  * received this file from another source then ask the distributing person for
9  * a log of modifications.
10  *
11  */
12 
13 /* @(#)name.c	1.28 04/03/05 joerg */
14 /*
15  * File name.c - map full Unix file names to unique 8.3 names that
16  * would be valid on DOS.
17  *
18  *
19  * Written by Eric Youngdale (1993).
20  * Almost totally rewritten by J. Schilling (2000).
21  *
22  * Copyright 1993 Yggdrasil Computing, Incorporated
23  * Copyright (c) 1999,2000 J. Schilling
24  *
25  * This program is free software; you can redistribute it and/or modify
26  * it under the terms of the GNU General Public License as published by
27  * the Free Software Foundation; either version 2, or (at your option)
28  * any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program; if not, write to the Free Software
37  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
38  */
39 
40 #include <mconfig.h>
41 #include "genisoimage.h"
42 #include <standard.h>
43 #include <schily.h>
44 #include <ctype.h>
45 
46 void	iso9660_check(struct iso_directory_record *idr,
47 						  struct directory_entry *ndr);
48 int	iso9660_file_length(const char *name,
49 								  struct directory_entry *sresult,
50 								  int dirflag);
51 
52 void
iso9660_check(struct iso_directory_record * idr,struct directory_entry * ndr)53 iso9660_check(struct iso_directory_record *idr,
54 				  struct directory_entry *ndr)
55 {
56 	int	nlen;
57 	char	schar;
58 	char	*p;
59 	char	*np;
60 
61 	nlen = idr->name_len[0];
62 	schar = idr->name[nlen];
63 
64 	if (nlen == 1 && (idr->name[0] == '\0' || idr->name[0] == '\001'))
65 		return;
66 
67 	idr->name[nlen] = '\0';		/* Make it null terminated */
68 	if ((p = strrchr(idr->name, ';')) != NULL) {
69 		*p = '\0';		/* Strip off old version # */
70 	}
71 	iso9660_file_length(idr->name, ndr,
72 				(idr->flags[0] & ISO_DIRECTORY) != 0);
73 
74 	if ((np = strrchr(ndr->isorec.name, ';')) != NULL) {
75 		*np = '\0';		/* Strip off new version # */
76 	}
77 	if (strcmp(idr->name, ndr->isorec.name)) {
78 		if (p)
79 			*p = ';';	/* Restore old version # */
80 		if (np)
81 			*np = ';';	/* Restore new version # */
82 		errmsgno(EX_BAD,
83 			"Old session has illegal name '%.*s' length %d\n",
84 			idr->name_len[0],
85 			idr->name,
86 			idr->name_len[0]);
87 		errmsgno(EX_BAD,
88 			"New session will use    name '%s'\n",
89 			ndr->isorec.name);
90 	}
91 	if (p)
92 		*p = ';';		/* Restore old version # */
93 	if (np)
94 		*np = ';';		/* Restore new version # */
95 	idr->name[nlen] = schar;	/* Restore old iso record*/
96 }
97 
98 /*
99  * Function:	iso9660_file_length
100  *
101  * Purpose:	Map file name to 8.3 format, return length
102  *		of result.
103  *
104  * Arguments:	name	file name we need to map.
105  *		sresult	directory entry structure to contain mapped name.
106  *		dirflag	flag indicating whether this is a directory or not.
107  *
108  * Note:	name being const * is a bug introduced by Eric but hard to
109  *		fix without going through the whole source.
110  */
111 int
iso9660_file_length(const char * name,struct directory_entry * sresult,int dirflag)112 iso9660_file_length(const char *name /* Not really const !!! */,
113 						  struct directory_entry *sresult, int dirflag)
114 {
115 	char		c;
116 	char		*cp;
117 	int		before_dot = 8;
118 	int		after_dot = 3;
119 	int		chars_after_dot = 0;
120 	int		chars_before_dot = 0;
121 	int		current_length = 0;
122 	int		extra = 0;
123 	int		ignore = 0;
124 	char		*last_dot;
125 	const char	*pnt;
126 	int		priority = 32767;
127 	char		*result;
128 	int		ochars_after_dot;
129 	int		ochars_before_dot;
130 	int		seen_dot = 0;
131 	int		seen_semic = 0;
132 	int		tildes = 0;
133 
134 	result = sresult->isorec.name;
135 
136 	if (sresult->priority)
137 		priority = sresult->priority;
138 
139 	/*
140 	 * For the '.' entry, generate the correct record, and return 1 for
141 	 * the length.
142 	 */
143 	if (strcmp(name, ".") == 0) {
144 		*result = 0;
145 		return (1);
146 	}
147 	/*
148 	 * For the '..' entry, generate the correct record, and return 1
149 	 * for the length.
150 	 */
151 	if (strcmp(name, "..") == 0) {
152 		*result++ = 1;
153 		*result++ = 0;
154 		return (1);
155 	}
156 	/*
157 	 * Now scan the directory one character at a time, and figure out
158 	 * what to do.
159 	 */
160 	pnt = name;
161 
162 	/*
163 	 * Find the '.' that we intend to use for the extension.
164 	 * Usually this is the last dot, but if we have . followed by nothing
165 	 * or a ~, we would consider this to be unsatisfactory, and we keep
166 	 * searching.
167 	 */
168 	last_dot = strrchr(pnt, '.');
169 	if ((last_dot != NULL) &&
170 	    ((last_dot[1] == '~') || (last_dot[1] == '\0'))) {
171 		cp = last_dot;
172 		*cp = '\0';
173 		last_dot = strrchr(pnt, '.');
174 		*cp = '.';
175 		/*
176 		 * If we found no better '.' back up to the last match.
177 		 */
178 		if (last_dot == NULL)
179 			last_dot = cp;
180 	}
181 
182 	if (last_dot != NULL) {
183 		ochars_after_dot = strlen(last_dot);	/* dot counts */
184 		ochars_before_dot = last_dot - pnt;
185 	} else {
186 		ochars_before_dot = 128;
187 		ochars_after_dot = 0;
188 	}
189 	/*
190 	 * If we have full names, the names we generate will not work
191 	 * on a DOS machine, since they are not guaranteed to be 8.3.
192 	 * Nonetheless, in many cases this is a useful option.  We
193 	 * still only allow one '.' character in the name, however.
194 	 */
195 	if (full_iso9660_filenames || iso9660_level > 1) {
196 		before_dot = iso9660_namelen;
197 		after_dot = before_dot - 1;
198 
199 		if (!dirflag) {
200 			if (ochars_after_dot > ((iso9660_namelen/2)+1)) {
201 				/*
202 				 * The minimum number of characters before
203 				 * the dot is 3 to allow renaming.
204 				 * Let us allow to have 15 characters after
205 				 * dot to give more rational filenames.
206 				 */
207 				before_dot = iso9660_namelen/2;
208 				after_dot = ochars_after_dot;
209 			} else {
210 				before_dot -= ochars_after_dot; /* dot counts */
211 				after_dot = ochars_after_dot;
212 			}
213 		}
214 	}
215 
216 	while (*pnt) {
217 #ifdef VMS
218 		if (strcmp(pnt, ".DIR;1") == 0) {
219 			break;
220 		}
221 #endif
222 
223 #ifdef		Eric_code_does_not_work
224 		/*
225 		 * XXX If we make this code active we get corrupted direcrory
226 		 * XXX trees with infinite loops.
227 		 */
228 		/*
229 		 * This character indicates a Unix style of backup file
230 		 * generated by some editors.  Lower the priority of the file.
231 		 */
232 		if (iso_translate && *pnt == '#') {
233 			priority = 1;
234 			pnt++;
235 			continue;
236 		}
237 		/*
238 		 * This character indicates a Unix style of backup file
239 		 * generated by some editors.  Lower the priority of the file.
240 		 */
241 		if (iso_translate && *pnt == '~') {
242 			priority = 1;
243 			tildes++;
244 			pnt++;
245 			continue;
246 		}
247 #endif
248 		/*
249 		 * This might come up if we had some joker already try and put
250 		 * iso9660 version numbers into the file names.  This would be
251 		 * a silly thing to do on a Unix box, but we check for it
252 		 * anyways.  If we see this, then we don't have to add our own
253 		 * version number at the end. UNLESS the ';' is part of the
254 		 * filename and no valid version number is following.
255 		 */
256 		if (use_fileversion && *pnt == ';' && seen_dot) {
257 			/*
258 			 * Check if a valid version number follows.
259 			 * The maximum valid version number is 32767.
260 			 */
261 			for (c = 1, cp = (char *)&pnt[1]; c < 6 && *cp; c++, cp++) {
262 				if (*cp < '0' || *cp > '9')
263 					break;
264 			}
265 			if (c <= 6 && *cp == '\0' && atoi(&pnt[1]) <= 32767)
266 				seen_semic++;
267 		}
268 		/*
269 		 * If we have a name with multiple '.' characters, we ignore
270 		 * everything after we have gotten the extension.
271 		 */
272 		if (ignore) {
273 			pnt++;
274 			continue;
275 		}
276 		if (current_length >= iso9660_namelen) {
277 #ifdef	nono
278 			/*
279 			 * Does not work as we may truncate before the dot.
280 			 */
281 			fprintf(stderr, "Truncating '%s' to '%.*s'.\n",
282 				name,
283 				current_length, sresult->isorec.name);
284 			ignore++;
285 #endif
286 			pnt++;
287 			continue;
288 		}
289 		/* Spin past any iso9660 version number we might have. */
290 		if (seen_semic) {
291 			if (seen_semic == 1) {
292 				seen_semic++;
293 				*result++ = ';';
294 			}
295 			if (*pnt >= '0' && *pnt <= '9') {
296 				*result++ = *pnt;
297 			}
298 			extra++;
299 			pnt++;
300 			continue;
301 		}
302 
303 		if (*pnt == '.') {
304 			if (!allow_multidot) {
305 				if (strcmp(pnt, ".tar.gz") == 0)
306 					pnt = last_dot = ".tgz";
307 				if (strcmp(pnt, ".ps.gz") == 0)
308 					pnt = last_dot = ".psz";
309 			}
310 
311 			if (!chars_before_dot && !allow_leading_dots) {
312 				/*
313 				 * DOS can't read files with dot first
314 				 */
315 				chars_before_dot++;
316 				*result++ = '_'; /* Substitute underscore */
317 
318 			} else if (pnt == last_dot) {
319 				if (seen_dot) {
320 					ignore++;
321 					continue;
322 				}
323 				*result++ = '.';
324 				seen_dot++;
325 			} else if (allow_multidot) {
326 				if (chars_before_dot < before_dot) {
327 					chars_before_dot++;
328 					*result++ = '.';
329 				}
330 			} else {
331 				/*
332 				 * If this isn't the dot that we use
333 				 * for the extension, then change the
334 				 * character into a '_' instead.
335 				 */
336 				if (chars_before_dot < before_dot) {
337 					chars_before_dot++;
338 					*result++ = '_';
339 				}
340 			}
341 		} else {
342 			if ((seen_dot && (chars_after_dot < after_dot) &&
343 						++chars_after_dot) ||
344 			    (!seen_dot && (chars_before_dot < before_dot) &&
345 			    ++chars_before_dot)) {
346 
347 				c = *pnt;
348 				if (c & 0x80) {
349 					/*
350 					 * We allow 8 bit chars if -iso-level
351 					 * is at least 4
352 					 *
353 					 * XXX We should check if the output
354 					 * XXX character set is a 7 Bit ASCI
355 					 * extension.
356 					 */
357 					if (iso9660_level >= 4) {
358 						c = conv_charset(c, in_nls, out_nls);
359 					} else {
360 						c = '_';
361 					}
362 				} else if (!allow_lowercase) {
363 					c = islower((unsigned char)c) ?
364 						toupper((unsigned char)c) : c;
365 				}
366 				if (relaxed_filenames) {
367 					/*
368 					 * Here we allow a more relaxed syntax.
369 					 */
370 					if (c == '/')
371 						c = '_';
372 					*result++ = c;
373 				} else switch (c) {
374 					/*
375 					 * Dos style filenames.
376 					 * We really restrict the names here.
377 					 */
378 
379 				default:
380 					*result++ = c;
381 					break;
382 
383 				/*
384 				 * Descriptions of DOS's 'Parse Filename'
385 				 * (function 29H) describes V1 and V2.0+
386 				 * separator and terminator characters. These
387 				 * characters in a DOS name make the file
388 				 * visible but un-manipulable (all useful
389 				 * operations error off.
390 				 */
391 				/* separators */
392 				case '+':
393 				case '=':
394 				case '%': /* not legal DOS */
395 						/* filename */
396 				case ':':
397 				case ';': /* already handled */
398 				case '.': /* already handled */
399 				case ',': /* already handled */
400 				case '\t':
401 				case ' ':
402 				/* V1 only separators */
403 				case '/':
404 				case '"':
405 				case '[':
406 				case ']':
407 				/* terminators */
408 				case '>':
409 				case '<':
410 				case '|':
411 				/*
412 				 * Other characters that are not valid ISO-9660
413 				 * characters.
414 				 */
415 				case '!':
416 /*				case '#':*/
417 				case '$':
418 				case '&':
419 				case '\'':
420 				case '(':
421 				case ')':
422 				case '*':
423 /*				case '-':*/
424 				case '?':
425 				case '@':
426 				case '\\':
427 				case '^':
428 				case '`':
429 				case '{':
430 				case '}':
431 /*				case '~':*/
432 				/*
433 				 * All characters below 32 (space) are not
434 				 * allowed too.
435 				 */
436 				case 1: case 2: case 3: case 4:
437 				case 5: case 6: case 7: case 8:
438 				/* case 9: */
439 				case 10: case 11: case 12:
440 				case 13: case 14: case 15:
441 				case 16: case 17: case 18:
442 				case 19: case 20: case 21:
443 				case 22: case 23: case 24:
444 				case 25: case 26: case 27:
445 				case 28: case 29: case 30:
446 				case 31:
447 
448 					/*
449 					 * Hmm - what to do here? Skip? Win95
450 					 * looks like it substitutes '_'
451 					 */
452 					*result++ = '_';
453 					break;
454 
455 				case '#':
456 				case '-':
457 				case '~':
458 					/*
459 					 * Check if we should allow these
460 					 * illegal characters used by
461 					 * Microsoft.
462 					 */
463 					if (iso_translate)
464 						*result++ = '_';
465 					else
466 						*result++ = c;
467 					break;
468 				}	/* switch (*pnt) */
469 			} else {	/* if (chars_{after,before}_dot) ... */
470 				pnt++;
471 				continue;
472 			}
473 		}	/* else *pnt == '.' */
474 		current_length++;
475 		pnt++;
476 	}	/* while (*pnt) */
477 
478 	/*
479 	 * OK, that wraps up the scan of the name.  Now tidy up a few other
480 	 * things.
481 	 * Look for emacs style of numbered backups, like foo.c.~3~.  If we
482 	 * see this, convert the version number into the priority number.
483 	 * In case of name conflicts, this is what would end up being used as
484 	 * the 'extension'.
485 	 */
486 	if (tildes == 2) {
487 		int	prio1 = 0;
488 
489 		pnt = name;
490 		while (*pnt && *pnt != '~') {
491 			pnt++;
492 		}
493 		if (*pnt) {
494 			pnt++;
495 		}
496 		while (*pnt && *pnt != '~') {
497 			prio1 = 10 * prio1 + *pnt - '0';
498 			pnt++;
499 		}
500 		priority = prio1;
501 	}
502 	/*
503 	 * If this is not a directory, force a '.' in case we haven't seen one,
504 	 * and add a version number if we haven't seen one of those either.
505 	 */
506 	if (!dirflag) {
507 		if (!seen_dot && !omit_period) {
508 			if (chars_before_dot >= (iso9660_namelen-1)) {
509 				chars_before_dot--;
510 				result--;
511 			}
512 			*result++ = '.';
513 			extra++;
514 		}
515 		if (!omit_version_number && !seen_semic) {
516 			*result++ = ';';
517 			*result++ = '1';
518 			extra += 2;
519 		}
520 	}
521 	*result++ = 0;
522 	sresult->priority = priority;
523 
524 /*#define	DEBBUG*/
525 #ifdef	DEBBUG
526 	fprintf(stderr, "NAME: '%s'\n", sresult->isorec.name);
527 	fprintf(stderr, "chars_before_dot %d chars_after_dot %d seen_dot %d extra %d\n",
528 		chars_before_dot, chars_after_dot, seen_dot, extra);
529 #endif
530 	return (chars_before_dot + chars_after_dot + seen_dot + extra);
531 }
532