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