1/*
2	Copyright 1999-2001, Be Incorporated.   All Rights Reserved.
3	This file may be used under the terms of the Be Sample Code License.
4*/
5
6#include "dir.h"
7
8#include "system_dependencies.h"
9
10#include "iter.h"
11#include "dosfs.h"
12#include "attr.h"
13#include "dlist.h"
14#include "fat.h"
15#include "util.h"
16#include "vcache.h"
17#include "file.h"
18
19#include "encodings.h"
20
21#define DPRINTF(a,b) if (debug_dir > (a)) dprintf b
22
23
24typedef struct dircookie {
25	uint32		current_index;
26} dircookie;
27
28static status_t	findfile(nspace *vol, vnode *dir, const char *file,
29	ino_t *vnid, vnode **node, bool check_case, bool check_dups,
30	bool *dups_exist);
31
32
33// private structure for returning data from _next_dirent_()
34struct _dirent_info_ {
35	uint32 sindex;
36	uint32 eindex;
37	uint32 mode;
38	uint32 cluster;
39	uint32 size;
40	uint32 time;
41	uint32 creation_time;
42};
43
44
45//!	Scans dir for the next entry, using the state stored in a struct diri.
46static status_t
47_next_dirent_(struct diri *iter, struct _dirent_info_ *oinfo, char *filename,
48	int len)
49{
50	uint8 *buffer;
51	uint8 hash = 0;
52	uchar uni[1024];
53	uint16 *puni;
54	uint32 i;
55
56	// lfn state
57	uint32 start_index = 0xffff, filename_len = 0;
58	uint32 lfn_count = 0;
59
60	if (iter->current_block == NULL)
61		return ENOENT;
62
63	if (len < 15) {
64		DPRINTF(0, ("_next_dirent_: len too short (%x)\n", len));
65		return ENOMEM;
66	}
67
68	buffer = iter->current_block + ((iter->current_index)
69		% (iter->csi.vol->bytes_per_sector / 0x20)) * 0x20;
70
71	for (; buffer != NULL; buffer = diri_next_entry(iter)) {
72		DPRINTF(2, ("_next_dirent_: %" B_PRIu32 "/%" B_PRIu32 "/%" B_PRIu32
73			"\n", iter->csi.cluster, iter->csi.sector, iter->current_index));
74		if (buffer[0] == 0) { // quit if at end of table
75			if (start_index != 0xffff)
76				dprintf("lfn entry (%" B_PRIu32 ") with no alias\n", lfn_count);
77			return ENOENT;
78		}
79
80		if (buffer[0] == 0xe5) { // skip erased entries
81			if (start_index != 0xffff) {
82				dprintf("lfn entry (%" B_PRIu32 ") with intervening erased "
83					"entries\n", lfn_count);
84				start_index = 0xffff;
85			}
86			DPRINTF(2, ("entry erased, skipping...\n"));
87			continue;
88		}
89
90		if (buffer[0xb] == 0xf) { // long file name
91			if ((buffer[0xc] != 0) ||
92				(buffer[0x1a] != 0) || (buffer[0x1b] != 0)) {
93				dprintf("invalid long file name: reserved fields munged\n");
94				continue;
95			}
96			if (start_index == 0xffff) {
97				if ((buffer[0] & 0x40) == 0) {
98					dprintf("bad lfn start entry in directory\n");
99					continue;
100				}
101				hash = buffer[0xd];
102				lfn_count = buffer[0] & 0x1f;
103				start_index = iter->current_index;
104				puni = (uint16 *)(uni + 2*13*(lfn_count - 1));
105				for (i = 1; i < 0x20; i += 2) {
106					if (*(uint16 *)&buffer[i] == 0xffff)
107						break;
108					*puni++ = *(uint16 *)&buffer[i];
109					if (i == 0x9) i+=3;
110					if (i == 0x18) i+=2;
111				}
112				*puni++ = 0;
113				filename_len = (uchar *)(puni) - uni;
114
115				continue;
116			} else {
117				if (buffer[0xd] != hash) {
118					dprintf("error in long file name: hash values don't match\n");
119					start_index = 0xffff;
120					continue;
121				}
122				if (buffer[0] != --lfn_count) {
123					dprintf("bad lfn entry in directory\n");
124					start_index = 0xffff;
125					continue;
126				}
127
128				puni = (uint16 *)(uni + 2*13*(lfn_count - 1));
129				for (i = 1; i < 0x20; i += 2) {
130					if ((buffer[i] == 0xff) && (buffer[i+1] == 0xff)) {
131						dprintf("bad lfn entry in directory\n");
132						start_index = 0xffff;
133						break;
134					}
135					*puni++ = *(uint16 *)&buffer[i];
136					if (i == 0x9) i+=3;
137					if (i == 0x18) i+=2;
138				}
139				continue;
140			}
141		}
142
143		break;
144	}
145
146	// hit end of directory entries with no luck
147	if (buffer == NULL)
148		return ENOENT;
149
150	// process long name
151	if (start_index != 0xffff) {
152		if (lfn_count != 1) {
153			dprintf("unfinished lfn in directory\n");
154			start_index = 0xffff;
155		} else {
156			if (unicode_to_utf8(uni, filename_len, (uint8*)filename, len)) {
157				// rewind to beginning of call
158				dprintf("error: long file name too long\n");
159
160				diri_init(iter->csi.vol, iter->starting_cluster, start_index,
161					iter);
162				return ENAMETOOLONG;
163			} else if (hash_msdos_name((const char *)buffer) != hash) {
164				dprintf("error: long file name (%s) hash and short file name "
165					"don't match\n", filename);
166				start_index = 0xffff;
167			}
168		}
169	}
170
171	// process short name
172	if (start_index == 0xffff) {
173		start_index = iter->current_index;
174		// korli : seen on FreeBSD /src/sys/fs/msdosfs/direntry.h
175		msdos_to_utf8(buffer, (uchar *)filename, len, buffer[0xc] & 0x18);
176	}
177
178	if (oinfo) {
179		oinfo->sindex = start_index;
180		oinfo->eindex = iter->current_index;
181		oinfo->mode = buffer[0xb];
182		oinfo->cluster = read16(buffer, 0x1a);
183		if (iter->csi.vol->fat_bits == 32)
184			oinfo->cluster += 0x10000 * read16(buffer, 0x14);
185		oinfo->size = read32(buffer, 0x1c);
186		oinfo->time = read32(buffer, 0x16);
187		oinfo->creation_time = read32(buffer, 0x0e);
188	}
189
190	diri_next_entry(iter);
191
192	return B_NO_ERROR;
193}
194
195
196static status_t
197get_next_dirent(nspace *vol, vnode *dir, struct diri *iter, ino_t *vnid,
198	char *filename, int len)
199{
200	struct _dirent_info_ info;
201	status_t result;
202
203	do {
204		result = _next_dirent_(iter, &info, filename, len);
205		if (result < 0)
206			return result;
207		// only hide volume label entries in the root directory
208	} while ((info.mode & FAT_VOLUME) && (dir->vnid == vol->root_vnode.vnid));
209
210	if (!strcmp(filename, ".")) {
211		// assign vnode based on parent
212		if (vnid) *vnid = dir->vnid;
213	} else if (!strcmp(filename, "..")) {
214		// assign vnode based on parent of parent
215		if (vnid) *vnid = dir->dir_vnid;
216	} else {
217		if (vnid) {
218			ino_t loc = (IS_DATA_CLUSTER(info.cluster))
219				? GENERATE_DIR_CLUSTER_VNID(dir->vnid, info.cluster)
220				: GENERATE_DIR_INDEX_VNID(dir->vnid, info.sindex);
221			bool added_to_vcache = false;
222
223			/* if it matches a loc in the lookup table, we are done. */
224			result = vcache_loc_to_vnid(vol, loc, vnid);
225			if (result == ENOENT) {
226				/* ...else check if it matches any vnid's in the lookup table */
227				if (find_vnid_in_vcache(vol, loc) == B_OK) {
228					/* if it does, create a random one since we can't reuse
229					 * existing vnid's */
230					*vnid = generate_unique_vnid(vol);
231					/* and add it to the vcache */
232					if ((result = add_to_vcache(vol, *vnid, loc)) < 0)
233						return result;
234					added_to_vcache = true;
235				} else {
236					/* otherwise we are free to use it */
237					*vnid = loc;
238				}
239			} else if (result != B_OK) {
240				dprintf("get_next_dirent: unknown error (%s)\n",
241					strerror(result));
242				return result;
243			}
244
245			if (info.mode & FAT_SUBDIR) {
246				if (dlist_find(vol, info.cluster) == -1LL) {
247					if ((result = dlist_add(vol, *vnid)) < 0) {
248						if (added_to_vcache)
249							remove_from_vcache(vol, *vnid);
250						return result;
251					}
252				}
253			}
254		}
255	}
256
257	DPRINTF(2, ("get_next_dirent: found %s (vnid %" B_PRIdINO ")\n", filename,
258		vnid != NULL ? *vnid : (ino_t)0));
259
260	return B_NO_ERROR;
261}
262
263
264status_t
265check_dir_empty(nspace *vol, vnode *dir)
266{
267	uint32 i;
268	struct diri iter;
269	status_t result = B_ERROR;
270
271	if (diri_init(vol, dir->cluster, 0, &iter) == NULL) {
272		dprintf("check_dir_empty: error opening directory\n");
273		return B_ERROR;
274	}
275
276	i = (dir->vnid == vol->root_vnode.vnid) ? 2 : 0;
277
278	for (; i < 3; i++) {
279		char filename[512];
280		result = _next_dirent_(&iter, NULL, filename, 512);
281		if (result < 0) {
282			if (i == 2 && result == ENOENT)
283				result = B_OK;
284			break;
285		}
286
287		if ((i == 0 && strcmp(filename, "."))
288			|| (i == 1 && strcmp(filename, ".."))
289			// weird case where ./.. are stored as long file names
290			|| (i < 2 && iter.current_index != i + 1)) {
291			dprintf("check_dir_empty: malformed directory\n");
292			result = ENOTDIR;
293			break;
294		}
295
296		result = ENOTEMPTY;
297	}
298
299	return result;
300}
301
302
303status_t
304findfile_case(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
305	vnode **node)
306{
307	return findfile(vol, dir, file, vnid, node, true, false, NULL);
308}
309
310
311status_t
312findfile_nocase(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
313	vnode **node)
314{
315	return findfile(vol, dir, file, vnid, node, false, false, NULL);
316}
317
318
319status_t
320findfile_nocase_duplicates(nspace *vol, vnode *dir, const char *file,
321	ino_t *vnid, vnode **node, bool *dups_exist)
322{
323	return findfile(vol, dir, file, vnid, node, false, true, dups_exist);
324}
325
326
327status_t
328findfile_case_duplicates(nspace *vol, vnode *dir, const char *file,
329	ino_t *vnid, vnode **node, bool *dups_exist)
330{
331	return findfile(vol, dir, file, vnid, node, true, true, dups_exist);
332}
333
334
335static status_t
336findfile(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
337	vnode **node, bool check_case, bool check_dups, bool *dups_exist)
338{
339	/* Starting at the base, find the file in the subdir
340	   and return its vnode id */
341	/* The check_case flags determines whether or not the search
342	   is done for the exact case or not. If it is not, it will
343	   return the first occurance of the match. */
344	/* The check_dups flag instructs the function to find the
345	   first filename match based on the case sensitivity in
346	   check_case, but continue searching to see if there are
347	   any other case-insensitive matches. If there are, the
348	   dups_exist flag is set to true. */
349	int		result = 0;
350	ino_t	found_vnid = 0;
351	bool found_file = false;
352
353//	dprintf("findfile: %s in %Lx, case %d dups %d\n", file, dir->vnid, check_case, check_dups);
354
355	DPRINTF(1, ("findfile: %s in %" B_PRIdINO "\n", file, dir->vnid));
356
357	if (dups_exist != NULL)
358		*dups_exist = false;
359	else
360		check_dups = false;
361
362	if (strcmp(file,".") == 0 && dir->vnid == vol->root_vnode.vnid) {
363		found_file = true;
364		found_vnid = dir->vnid;
365	} else if (strcmp(file, "..") == 0 && dir->vnid == vol->root_vnode.vnid) {
366		found_file = true;
367		found_vnid = dir->dir_vnid;
368	} else {
369		struct diri diri;
370
371		// XXX: do it in a smarter way
372		if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
373			dprintf("findfile: error opening directory\n");
374			return ENOENT;
375		}
376
377		while (1) {
378			char filename[512];
379			ino_t _vnid;
380
381			result = get_next_dirent(vol, dir, &diri, &_vnid, filename, 512);
382			if (result != B_NO_ERROR)
383				break;
384
385			if (check_case) {
386				if (!found_file && !strcmp(filename, file)) {
387					found_file = true;
388					found_vnid = _vnid;
389				} else if (check_dups && !strcasecmp(filename, file)) {
390					*dups_exist = true;
391				}
392			} else {
393				if (!strcasecmp(filename, file)) {
394					if (check_dups && found_file)
395						*dups_exist = true;
396
397					found_file = true;
398					found_vnid = _vnid;
399				}
400			}
401
402			if (found_file && (!check_dups || (check_dups && *dups_exist)))
403				break;
404		}
405	}
406	if (found_file) {
407		if (vnid)
408			*vnid = found_vnid;
409		if (node)
410			result = get_vnode(vol->volume, found_vnid, (void **)node);
411		result = B_OK;
412	} else {
413		result = ENOENT;
414	}
415#if 0
416	dprintf("findfile: returning %d", result);
417	if(dups_exist)
418		dprintf(" dups_exist %d\n", *dups_exist);
419	else
420		dprintf("\n");
421#endif
422	return result;
423}
424
425
426status_t
427erase_dir_entry(nspace *vol, vnode *node)
428{
429	status_t result;
430	uint32 i;
431	char filename[512];
432	uint8 *buffer;
433	struct _dirent_info_ info;
434	struct diri diri;
435
436	DPRINTF(0, ("erasing directory entries %" B_PRIu32 " through %" B_PRIu32
437		"\n", node->sindex, node->eindex));
438	buffer = diri_init(vol,VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri);
439
440	// first pass: check if the entry is still valid
441	if (buffer == NULL) {
442		dprintf("erase_dir_entry: error reading directory\n");
443		return ENOENT;
444	}
445
446	result = _next_dirent_(&diri, &info, filename, 512);
447
448	if (result < 0)
449		return result;
450
451	if (info.sindex != node->sindex || info.eindex != node->eindex) {
452		// any other attributes may be in a state of flux due to wstat calls
453		dprintf("erase_dir_entry: directory entry doesn't match\n");
454		return B_ERROR;
455	}
456
457	// second pass: actually erase the entry
458	buffer = diri_init(vol, VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri);
459	for (i = node->sindex; i <= node->eindex && buffer;
460			buffer = diri_next_entry(&diri), i++) {
461		diri_make_writable(&diri);
462		buffer[0] = 0xe5; // mark entry erased
463	}
464
465	return 0;
466}
467
468
469/*!	shrink directory to the size needed
470	errors here are neither likely nor problematic
471	w95 doesn't seem to do this, so it's possible to create a
472	really large directory that consumes all available space!
473*/
474status_t
475compact_directory(nspace *vol, vnode *dir)
476{
477	uint32 last = 0;
478	struct diri diri;
479	status_t error = B_ERROR; /* quiet warning */
480
481	DPRINTF(0, ("compacting directory with vnode id %" B_PRIdINO "\n",
482		dir->vnid));
483
484	// root directory can't shrink in fat12 and fat16
485	if (IS_FIXED_ROOT(dir->cluster))
486		return 0;
487
488	if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
489		dprintf("compact_directory: cannot open dir at cluster (%" B_PRIu32
490			")\n", dir->cluster);
491		return EIO;
492	}
493	while (diri.current_block) {
494		char filename[512];
495		struct _dirent_info_ info;
496
497		error = _next_dirent_(&diri, &info, filename, 512);
498
499		if (error == B_OK) {
500			// don't compact away volume labels in the root dir
501			if (!(info.mode & FAT_VOLUME) || (dir->vnid != vol->root_vnode.vnid))
502				last = diri.current_index;
503		} else if (error == ENOENT) {
504			uint32 clusters = (last + vol->bytes_per_sector / 0x20
505				* vol->sectors_per_cluster - 1) / (vol->bytes_per_sector / 0x20)
506				/ vol->sectors_per_cluster;
507			error = 0;
508
509			// special case for fat32 root directory; we don't want
510			// it to disappear
511			if (clusters == 0)
512				clusters = 1;
513
514			if (clusters * vol->bytes_per_sector * vol->sectors_per_cluster
515					< dir->st_size) {
516				DPRINTF(0, ("shrinking directory to %" B_PRIu32 " clusters\n",
517					clusters));
518				error = set_fat_chain_length(vol, dir, clusters, true);
519				dir->st_size = clusters * vol->bytes_per_sector
520					* vol->sectors_per_cluster;
521				dir->iteration++;
522			}
523			break;
524		} else {
525			dprintf("compact_directory: unknown error from _next_dirent_ (%s)\n",
526				strerror(error));
527			break;
528		}
529	}
530
531	return error;
532}
533
534
535//! name is array of char[11] as returned by findfile
536static status_t
537find_short_name(nspace *vol, vnode *dir, const uchar *name)
538{
539	struct diri diri;
540	uint8 *buffer;
541	status_t result = ENOENT;
542
543	buffer = diri_init(vol, dir->cluster, 0, &diri);
544	while (buffer) {
545		if (buffer[0] == 0)
546			break;
547
548		if (buffer[0xb] != 0xf) { // not long file name
549			if (!memcmp(name, buffer, 11)) {
550				result = B_OK;
551				break;
552			}
553		}
554
555		buffer = diri_next_entry(&diri);
556	}
557
558	return result;
559}
560
561
562struct _entry_info_ {
563	uint32 mode;
564	uint32 cluster;
565	uint32 size;
566	time_t time;
567	time_t creation_time;
568};
569
570
571static status_t
572_create_dir_entry_(nspace *vol, vnode *dir, struct _entry_info_ *info,
573	const char nshort[11], const char *nlong, uint32 len, uint32 *ns,
574	uint32 *ne)
575{
576	status_t error = B_ERROR; /* quiet warning */
577	uint32 required_entries, i;
578	uint8 *buffer, hash;
579	bool last_entry;
580	struct diri diri;
581
582	// short name cannot be the same as that of a device
583	// this list was created by running strings on io.sys
584	const char *device_names[] = {
585		"CON        ",
586		"AUX        ",
587		"PRN        ",
588		"CLOCK$     ",
589		"COM1       ",
590		"LPT1       ",
591		"LPT2       ",
592		"LPT3       ",
593		"COM2       ",
594		"COM3       ",
595		"COM4       ",
596		"CONFIG$    ",
597		NULL
598	};
599
600	// check short name against device names
601	for (i = 0; device_names[i]; i++) {
602		// only first 8 characters seem to matter
603		if (!memcmp(nshort, device_names[i], 8))
604			return EPERM;
605	}
606
607	if (info->cluster != 0 && !IS_DATA_CLUSTER(info->cluster)) {
608		dprintf("_create_dir_entry_ for bad cluster (%" B_PRIu32 ")\n",
609			info->cluster);
610		return EINVAL;
611	}
612
613	/* convert byte length of unicode name to directory entries */
614	required_entries = (len + 24) / 26 + 1;
615
616	// find a place to put the entries
617	*ns = 0;
618	last_entry = true;
619	if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
620		dprintf("_create_dir_entry_: cannot open dir at cluster (%" B_PRIu32
621			")\n", dir->cluster);
622		return EIO;
623	}
624
625	while (diri.current_block) {
626		char filename[512];
627		struct _dirent_info_ info;
628		error = _next_dirent_(&diri, &info, filename, 512);
629		if (error == B_OK) {
630			if (info.sindex - *ns >= required_entries) {
631				last_entry = false;
632				break;
633			}
634			*ns = diri.current_index;
635		} else if (error == ENOENT) {
636			// hit end of directory marker
637			break;
638		} else {
639			dprintf("_create_dir_entry_: unknown error from _next_dirent_ (%s)\n",
640				strerror(error));
641			break;
642		}
643	}
644
645	// if at end of directory, last_entry flag will be true as it should be
646
647	if (error != B_OK && error != ENOENT)
648		return error;
649
650	*ne = *ns + required_entries - 1;
651
652	for (i = *ns; i <= *ne; i++) {
653		ASSERT(find_loc_in_vcache(vol,
654			GENERATE_DIR_INDEX_VNID(dir->cluster, i)) == ENOENT);
655	}
656
657	DPRINTF(0, ("directory entry runs from %" B_PRIu32 " to %" B_PRIu32
658		" (dirsize = %" B_PRIdOFF ") (is%s last entry)\n", *ns, *ne,
659		dir->st_size, last_entry ? "" : "n't"));
660
661	// check if the directory needs to be expanded
662	if (*ne * 0x20 >= dir->st_size) {
663		uint32 clusters_needed;
664
665		// can't expand fat12 and fat16 root directories :(
666		if (IS_FIXED_ROOT(dir->cluster)) {
667			DPRINTF(0, ("_create_dir_entry_: out of space in root directory\n"));
668			return ENOSPC;
669		}
670
671		// otherwise grow directory to fit
672		clusters_needed = ((*ne + 1) * 0x20 +
673			vol->bytes_per_sector*vol->sectors_per_cluster - 1) /
674			vol->bytes_per_sector / vol->sectors_per_cluster;
675
676		DPRINTF(0, ("expanding directory from %" B_PRIdOFF " to %" B_PRIu32
677			" clusters\n", dir->st_size / vol->bytes_per_sector
678				/ vol->sectors_per_cluster, clusters_needed));
679		if ((error = set_fat_chain_length(vol, dir, clusters_needed, false))
680				< 0) {
681			return error;
682		}
683
684		dir->st_size = vol->bytes_per_sector*vol->sectors_per_cluster*clusters_needed;
685		dir->iteration++;
686	}
687
688	// starting blitting entries
689	buffer = diri_init(vol,dir->cluster, *ns, &diri);
690	if (buffer == NULL) {
691		dprintf("_create_dir_entry_: cannot open dir at (%" B_PRIu32 ", %"
692			B_PRIu32 ")\n", dir->cluster, *ns);
693		return EIO;
694	}
695	hash = hash_msdos_name(nshort);
696
697	// write lfn entries
698	for (i = 1; i < required_entries && buffer; i++) {
699		const char *p = nlong + (required_entries - i - 1) * 26;
700			// go to unicode offset
701		diri_make_writable(&diri);
702		memset(buffer, 0, 0x20);
703		buffer[0] = required_entries - i + ((i == 1) ? 0x40 : 0);
704		buffer[0x0b] = 0x0f;
705		buffer[0x0d] = hash;
706		memcpy(buffer+1,p,10);
707		memcpy(buffer+0x0e,p+10,12);
708		memcpy(buffer+0x1c,p+22,4);
709		buffer = diri_next_entry(&diri);
710	}
711
712	ASSERT(buffer != NULL);
713	if (buffer == NULL)	{	// this should never happen...
714		DPRINTF(0, ("_create_dir_entry_: the unthinkable has occured\n"));
715		return B_ERROR;
716	}
717
718	// write directory entry
719	diri_make_writable(&diri);
720	memcpy(buffer, nshort, 11);
721	buffer[0x0b] = info->mode;
722	memset(buffer+0xc, 0, 0x16-0xc);
723	i = time_t2dos(info->creation_time);
724	buffer[0x0e] = i & 0xff;
725	buffer[0x0f] = (i >> 8) & 0xff;
726	buffer[0x10] = (i >> 16) & 0xff;
727	buffer[0x11] = (i >> 24) & 0xff;
728	i = time_t2dos(info->time);
729	buffer[0x16] = i & 0xff;
730	buffer[0x17] = (i >> 8) & 0xff;
731	buffer[0x18] = (i >> 16) & 0xff;
732	buffer[0x19] = (i >> 24) & 0xff;
733	i = info->cluster;
734	if (info->size == 0) i = 0;		// cluster = 0 for 0 byte files
735	buffer[0x1a] = i & 0xff;
736	buffer[0x1b] = (i >> 8) & 0xff;
737	if (vol->fat_bits == 32) {
738		buffer[0x14] = (i >> 16) & 0xff;
739		buffer[0x15] = (i >> 24) & 0xff;
740	}
741	i = (info->mode & FAT_SUBDIR) ? 0 : info->size;
742	buffer[0x1c] = i & 0xff;
743	buffer[0x1d] = (i >> 8) & 0xff;
744	buffer[0x1e] = (i >> 16) & 0xff;
745	buffer[0x1f] = (i >> 24) & 0xff;
746
747	if (last_entry) {
748		// add end of directory markers to the rest of the
749		// cluster; need to clear all the other entries or else
750		// scandisk will complain.
751		while ((buffer = diri_next_entry(&diri)) != NULL) {
752			diri_make_writable(&diri);
753			memset(buffer, 0, 0x20);
754		}
755	}
756
757	return 0;
758}
759
760
761//! doesn't do any name checking
762status_t
763create_volume_label(nspace *vol, const char name[11], uint32 *index)
764{
765	status_t err;
766	uint32 dummy;
767	struct _entry_info_ info = {
768		FAT_ARCHIVE | FAT_VOLUME, 0, 0, 0
769	};
770	time(&info.time);
771
772	// check if name already exists
773	err = find_short_name(vol, &(vol->root_vnode), (uchar *)name);
774	if (err == B_OK)
775		return EEXIST;
776	if (err != ENOENT)
777		return err;
778
779	return _create_dir_entry_(vol, &(vol->root_vnode), &info, name, NULL,
780		0, index, &dummy);
781}
782
783
784bool
785is_filename_legal(const char *name)
786{
787	unsigned int i;
788	unsigned int len = strlen(name);
789
790	if (len <= 0)
791		return false;
792
793	// names ending with a dot are not allowed
794	if (name[len - 1] == '.')
795		return false;
796	// names ending with a space are not allowed
797	if (name[len - 1] == ' ')
798		return false;
799
800	// XXX illegal character search can be made faster
801	for (i = 0; i < len; i++) {
802		if (name[i] & 0x80)
803			continue; //belongs to an utf8 char
804		if (strchr(sIllegal, name[i]))
805			return false;
806		if ((unsigned char)name[i] < 32)
807			return false;
808	}
809	return true;
810}
811
812
813status_t
814create_dir_entry(nspace *vol, vnode *dir, vnode *node, const char *name,
815	uint32 *ns, uint32 *ne)
816{
817	status_t error;
818	int32 len;
819	unsigned char nlong[512], nshort[11];
820	int encoding;
821	struct _entry_info_ info;
822
823	// check name legality before doing anything
824	if (!is_filename_legal(name))
825		return EINVAL;
826
827	// check if name already exists
828	error = findfile_nocase(vol, dir, name, NULL, NULL);
829	if (error == B_OK) {
830		DPRINTF(0, ("%s already found in directory %" B_PRIdINO "\n", name,
831			dir->vnid));
832		return EEXIST;
833	}
834	if (error != ENOENT)
835		return error;
836
837	// check name legality while converting. we ignore the case conversion
838	// flag, i.e. (filename "blah" will always have a patched short name),
839	// because the whole case conversion system in dos is brain damaged;
840	// remanants of CP/M no less.
841
842	// existing names pose a problem; in these cases, we'll just live with
843	// two identical short names. not a great solution, but there's little
844	// we can do about it.
845	len = utf8_to_unicode(name, nlong, 512);
846	if (len <= 0) {
847		DPRINTF(0, ("Error converting utf8 name '%s' to unicode\n", name));
848		return len ? len : B_ERROR;
849	}
850	memset(nlong + len, 0xff, 512 - len); /* pad with 0xff */
851
852	error = generate_short_name((uchar *)name, nlong, len, nshort, &encoding);
853	if (error) {
854		DPRINTF(0, ("Error generating short name for '%s'\n", name));
855		return error;
856	}
857
858	// if there is a long name, patch short name if necessary and check for duplication
859	if (requires_long_name(name, nlong)) {
860		char tshort[11]; // temporary short name
861		int iter = 1;
862
863		memcpy(tshort, nshort, 11);
864
865		if (requires_munged_short_name((uchar *)name, nshort, encoding))
866			error = B_OK;
867		else
868			error = find_short_name(vol, dir, nshort);
869
870		if (error == B_OK) {
871			do {
872				memcpy(nshort, tshort, 11);
873				DPRINTF(0, ("trying short name %11.11s\n", nshort));
874				munge_short_name1(nshort, iter, encoding);
875			} while ((error = find_short_name(vol, dir, nshort)) == B_OK && ++iter < 10);
876		}
877
878		if (error != B_OK && error != ENOENT)
879			return error;
880
881		if (error == B_OK) {
882			// XXX: possible infinite loop here
883			do {
884				memcpy(nshort, tshort, 11);
885				DPRINTF(0, ("trying short name %11.11s\n", nshort));
886				munge_short_name2(nshort, encoding);
887			} while ((error = find_short_name(vol, dir, nshort)) == B_OK);
888
889			if (error != ENOENT)
890				return error;
891		}
892	} else {
893		len = 0; /* entry doesn't need a long name */
894	}
895
896	DPRINTF(0, ("creating directory entry (%11.11s)\n", nshort));
897
898	info.mode = node->mode;
899	if ((node->mode & FAT_SUBDIR) == 0)
900		info.mode |= FAT_ARCHIVE;
901	info.cluster = node->cluster;
902	info.size = node->st_size;
903	info.time = node->st_time;
904	info.creation_time = node->st_crtim;
905
906	return _create_dir_entry_(vol, dir, &info, (char *)nshort,
907		(char *)nlong, len, ns, ne);
908}
909
910
911status_t
912dosfs_read_vnode(fs_volume *_vol, ino_t vnid, fs_vnode *_node, int *_type,
913	uint32 *_flags, bool reenter)
914{
915	nspace *vol = (nspace *)_vol->private_volume;
916	int result = B_NO_ERROR;
917	ino_t loc, dir_vnid;
918	vnode *entry;
919	struct _dirent_info_ info;
920	struct diri iter;
921	char filename[512]; /* need this for setting mime type */
922
923	RecursiveLocker lock(vol->vlock);
924
925	_node->private_node = NULL;
926	_node->ops = &gFATVnodeOps;
927	*_flags = 0;
928
929	DPRINTF(0, ("dosfs_read_vnode (vnode id %" B_PRIdINO ")\n", vnid));
930
931	if (vnid == vol->root_vnode.vnid) {
932		dprintf("??? dosfs_read_vnode called on root node ???\n");
933		_node->private_node = (void *)&(vol->root_vnode);
934		*_type = make_mode(vol, &vol->root_vnode);
935		return result;
936	}
937
938	if (vcache_vnid_to_loc(vol, vnid, &loc) != B_OK)
939		loc = vnid;
940
941	if (IS_ARTIFICIAL_VNID(loc) || IS_INVALID_VNID(loc)) {
942		DPRINTF(0, ("dosfs_read_vnode: unknown vnid %" B_PRIdINO " (loc %"
943			B_PRIdINO ")\n", vnid, loc));
944		return ENOENT;
945	}
946
947	if ((dir_vnid = dlist_find(vol, DIR_OF_VNID(loc))) == -1LL) {
948		DPRINTF(0, ("dosfs_read_vnode: unknown directory at cluster %" B_PRIu32
949			"\n", DIR_OF_VNID(loc)));
950		return ENOENT;
951	}
952
953	if (diri_init(vol, DIR_OF_VNID(loc),
954			IS_DIR_CLUSTER_VNID(loc) ? 0 : INDEX_OF_DIR_INDEX_VNID(loc),
955			&iter) == NULL) {
956		dprintf("dosfs_read_vnode: error initializing directory for vnid %"
957			B_PRIdINO " (loc %" B_PRIdINO ")\n", vnid, loc);
958		return ENOENT;
959	}
960
961	while (1) {
962		result = _next_dirent_(&iter, &info, filename, 512);
963		if (result < 0) {
964			dprintf("dosfs_read_vnode: error finding vnid %" B_PRIdINO
965				" (loc %" B_PRIdINO ") (%s)\n", vnid, loc, strerror(result));
966			return result;
967		}
968
969		if (IS_DIR_CLUSTER_VNID(loc)) {
970			if (info.cluster == CLUSTER_OF_DIR_CLUSTER_VNID(loc))
971				break;
972		} else {
973			if (info.sindex == INDEX_OF_DIR_INDEX_VNID(loc))
974				break;
975			dprintf("dosfs_read_vnode: error finding vnid %" B_PRIdINO
976				" (loc %" B_PRIdINO ") (%s)\n", vnid, loc, strerror(result));
977			return ENOENT;
978		}
979	}
980
981	if ((entry = (vnode *)calloc(sizeof(struct vnode), 1)) == NULL) {
982		DPRINTF(0, ("dosfs_read_vnode: out of memory\n"));
983		return ENOMEM;
984	}
985
986	entry->vnid = vnid;
987	entry->dir_vnid = dir_vnid;
988	entry->disk_image = 0;
989	if (vol->respect_disk_image) {
990		if ((dir_vnid == vol->root_vnode.vnid) && !strcmp(filename, "BEOS")) {
991			vol->beos_vnid = vnid;
992			entry->disk_image = 1;
993		}
994		if ((dir_vnid == vol->beos_vnid) && !strcmp(filename, "IMAGE.BE")) {
995			entry->disk_image = 2;
996		}
997	}
998	entry->iteration = 0;
999	entry->sindex = info.sindex;
1000	entry->eindex = info.eindex;
1001	entry->cluster = info.cluster;
1002	entry->mode = info.mode;
1003	entry->st_size = info.size;
1004	entry->dirty = false;
1005	if (info.mode & FAT_SUBDIR) {
1006		entry->st_size = count_clusters(vol,entry->cluster)
1007			* vol->sectors_per_cluster * vol->bytes_per_sector;
1008	}
1009	if (entry->cluster) {
1010		entry->end_cluster = get_nth_fat_entry(vol, info.cluster,
1011			(entry->st_size + vol->bytes_per_sector * vol->sectors_per_cluster - 1) /
1012			vol->bytes_per_sector / vol->sectors_per_cluster - 1);
1013	} else
1014		entry->end_cluster = 0;
1015	entry->st_time = dos2time_t(info.time);
1016	entry->st_crtim = dos2time_t(info.creation_time);
1017
1018	entry->filename = (char *)malloc(sizeof(filename) + 1);
1019	if (entry->filename) strcpy(entry->filename, filename);
1020
1021	entry->cache = file_cache_create(vol->id, vnid, entry->st_size);
1022	entry->file_map = file_map_create(vol->id, vnid, entry->st_size);
1023	if (!(entry->mode & FAT_SUBDIR))
1024		set_mime_type(entry, filename);
1025
1026	_node->private_node = entry;
1027	*_type = make_mode(vol, entry);
1028
1029	return B_OK;
1030}
1031
1032
1033status_t
1034dosfs_walk(fs_volume *_vol, fs_vnode *_dir, const char *file, ino_t *_vnid)
1035{
1036	/* Starting at the base, find file in the subdir, and return path
1037		string and vnode id of file. */
1038	nspace	*vol = (nspace *)_vol->private_volume;
1039	vnode	*dir = (vnode *)_dir->private_node;
1040	vnode	*vnode = NULL;
1041	status_t result = ENOENT;
1042
1043	RecursiveLocker lock(vol->vlock);
1044
1045	DPRINTF(0, ("dosfs_walk: find %" B_PRIdINO "/%s\n", dir->vnid, file));
1046
1047	result = findfile_case(vol, dir, file, _vnid, &vnode);
1048	if (result != B_OK) {
1049		DPRINTF(0, ("dosfs_walk (%s)\n", strerror(result)));
1050	} else {
1051		DPRINTF(0, ("dosfs_walk: found vnid %" B_PRIdINO "\n", *_vnid));
1052	}
1053
1054	return result;
1055}
1056
1057
1058status_t
1059dosfs_access(fs_volume *_vol, fs_vnode *_node, int mode)
1060{
1061	status_t result = B_OK;
1062	nspace *vol = (nspace *)_vol->private_volume;
1063	vnode *node = (vnode *)_node->private_node;
1064
1065	RecursiveLocker lock(vol->vlock);
1066
1067	DPRINTF(0, ("dosfs_access (vnode id %" B_PRIdINO ", mode %o)\n", node->vnid,
1068		mode));
1069
1070	if (mode & W_OK) {
1071		if (vol->flags & B_FS_IS_READONLY) {
1072			DPRINTF(0, ("dosfs_access: can't write on read-only volume\n"));
1073			result = EROFS;
1074		} else if (node->mode & FAT_READ_ONLY) {
1075			DPRINTF(0, ("can't open read-only file for writing\n"));
1076			result = EPERM;
1077		} else if (node->disk_image != 0) {
1078			DPRINTF(0, ("can't open disk image file for writing\n"));
1079			result = EPERM;
1080		}
1081	}
1082
1083	return result;
1084}
1085
1086
1087status_t
1088dosfs_opendir(fs_volume *_vol, fs_vnode *_node, void **_cookie)
1089{
1090	nspace *vol = (nspace *)_vol->private_volume;
1091	vnode *node = (vnode *)_node->private_node;
1092
1093	RecursiveLocker lock(vol->vlock);
1094
1095	DPRINTF(0, ("dosfs_opendir (vnode id %" B_PRIdINO ")\n", node->vnid));
1096
1097	*_cookie = NULL;
1098
1099	if (!(node->mode & FAT_SUBDIR)) {
1100		/* bash will try to opendir files unless OPENDIR_NOT_ROBUST is
1101		 * defined, so we'll suppress this message; it's more of a problem
1102		 * with the application than with the file system, anyway
1103		 */
1104		DPRINTF(0, ("dosfs_opendir error: vnode not a directory\n"));
1105		return ENOTDIR;
1106	}
1107
1108	dircookie *cookie = (dircookie *)malloc(sizeof(dircookie));
1109	if (cookie == NULL) {
1110		DPRINTF(0, ("dosfs_opendir: out of memory error\n"));
1111		return ENOMEM;
1112	}
1113
1114	cookie->current_index = 0;
1115	*_cookie = (void *)cookie;
1116	return B_OK;
1117}
1118
1119
1120status_t
1121dosfs_readdir(fs_volume *_vol, fs_vnode *_dir, void *_cookie,
1122	struct dirent *entry, size_t bufsize, uint32 *num)
1123{
1124	int 		result = ENOENT;
1125	nspace* 	vol = (nspace *)_vol->private_volume;
1126	vnode		*dir = (vnode *)_dir->private_node;
1127	dircookie* 	cookie = (dircookie *)_cookie;
1128	struct		diri diri;
1129
1130	RecursiveLocker lock(vol->vlock);
1131
1132	DPRINTF(0, ("dosfs_readdir: vnode id %" B_PRIdINO ", index %" B_PRIu32 "\n",
1133		dir->vnid, cookie->current_index));
1134
1135	// simulate '.' and '..' entries for root directory
1136	if (dir->vnid == vol->root_vnode.vnid) {
1137		if (cookie->current_index >= 2) {
1138			cookie->current_index -= 2;
1139		} else {
1140			if (cookie->current_index++ == 0) {
1141				strcpy(entry->d_name, ".");
1142				entry->d_reclen = offsetof(struct dirent, d_name) + 2;
1143			} else {
1144				strcpy(entry->d_name, "..");
1145				entry->d_reclen = offsetof(struct dirent, d_name) + 3;
1146			}
1147			*num = 1;
1148			entry->d_ino = vol->root_vnode.vnid;
1149			entry->d_dev = vol->id;
1150			return B_NO_ERROR;
1151		}
1152	}
1153
1154	if (diri_init(vol, dir->cluster, cookie->current_index, &diri) == NULL) {
1155		DPRINTF(0, ("dosfs_readdir: no more entries!\n"));
1156		// When you get to the end, don't return an error, just return 0
1157		// in *num.
1158		*num = 0;
1159		return B_NO_ERROR;
1160	}
1161
1162	result = get_next_dirent(vol, dir, &diri, &entry->d_ino, entry->d_name,
1163		bufsize - offsetof(struct dirent, d_name) - 1);
1164
1165	cookie->current_index = diri.current_index;
1166
1167	if (dir->vnid == vol->root_vnode.vnid)
1168		cookie->current_index += 2;
1169
1170	if (result == B_NO_ERROR) {
1171		*num = 1;
1172		entry->d_dev = vol->id;
1173		entry->d_reclen = offsetof(struct dirent, d_name) + strlen(entry->d_name) + 1;
1174		DPRINTF(0, ("dosfs_readdir: found file %s\n", entry->d_name));
1175	} else if (result == ENOENT) {
1176		// When you get to the end, don't return an error, just return 0
1177		// in *num.
1178		*num = 0;
1179		return B_OK;
1180	} else {
1181		dprintf("dosfs_readdir: error returned by get_next_dirent (%s)\n",
1182			strerror(result));
1183	}
1184
1185	return B_OK;
1186}
1187
1188
1189status_t
1190dosfs_rewinddir(fs_volume *_vol, fs_vnode *_node, void* _cookie)
1191{
1192	nspace		*vol = (nspace *)_vol->private_volume;
1193	vnode		*node = (vnode *)_node->private_node;
1194	dircookie	*cookie = (dircookie *)_cookie;
1195
1196	RecursiveLocker lock(vol->vlock);
1197
1198	DPRINTF(0, ("dosfs_rewinddir (vnode id %" B_PRIdINO ")\n", node->vnid));
1199
1200	cookie->current_index = 0;
1201
1202	return B_OK;
1203}
1204
1205
1206status_t
1207dosfs_closedir(fs_volume *_vol, fs_vnode *_node, void *_cookie)
1208{
1209	DPRINTF(0, ("dosfs_closedir called\n"));
1210
1211	return B_OK;
1212}
1213
1214
1215status_t
1216dosfs_free_dircookie(fs_volume *_vol, fs_vnode *_node, void *_cookie)
1217{
1218	nspace *vol = (nspace *)_vol->private_volume;
1219	vnode *node = (vnode *)_node->private_node;
1220	dircookie *cookie = (dircookie *)_cookie;
1221
1222	RecursiveLocker lock(vol->vlock);
1223
1224	DPRINTF(0, ("dosfs_free_dircookie (vnode id %" B_PRIdINO ")\n",
1225		node->vnid));
1226
1227	free(cookie);
1228
1229	return 0;
1230}
1231