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