/*
Copyright 1999-2001, Be Incorporated. All Rights Reserved.
This file may be used under the terms of the Be Sample Code License.
*/
#include "dir.h"
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <fs_cache.h>
#include <fs_info.h>
#include <KernelExport.h>
#include "iter.h"
#include "dosfs.h"
#include "attr.h"
#include "dlist.h"
#include "fat.h"
#include "util.h"
#include "vcache.h"
#include "file.h"
#include "encodings.h"
#define DPRINTF(a,b) if (debug_dir > (a)) dprintf b
// used here and in encodings.cpp
const char acceptable[]="!#$%&'()-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`{}~";
const char illegal[] = "\\/:*?\"<>|";
typedef struct dircookie {
uint32 current_index;
} dircookie;
static status_t findfile(nspace *vol, vnode *dir, const char *file,
ino_t *vnid, vnode **node, bool check_case, bool check_dups,
bool *dups_exist);
// private structure for returning data from _next_dirent_()
struct _dirent_info_ {
uint32 sindex;
uint32 eindex;
uint32 mode;
uint32 cluster;
uint32 size;
uint32 time;
uint32 creation_time;
};
//! Scans dir for the next entry, using the state stored in a struct diri.
static status_t
_next_dirent_(struct diri *iter, struct _dirent_info_ *oinfo, char *filename,
int len)
{
uint8 *buffer;
uint8 hash = 0;
uchar uni[1024];
uint16 *puni;
uint32 i;
// lfn state
uint32 start_index = 0xffff, filename_len = 0;
uint32 lfn_count = 0;
if (iter->current_block == NULL)
return ENOENT;
if (len < 15) {
DPRINTF(0, ("_next_dirent_: len too short (%x)\n", len));
return ENOMEM;
}
buffer = iter->current_block + ((iter->current_index)
% (iter->csi.vol->bytes_per_sector / 0x20)) * 0x20;
for (; buffer != NULL; buffer = diri_next_entry(iter)) {
DPRINTF(2, ("_next_dirent_: %" B_PRIu32 "/%" B_PRIu32 "/%" B_PRIu32
"\n", iter->csi.cluster, iter->csi.sector, iter->current_index));
if (buffer[0] == 0) { // quit if at end of table
if (start_index != 0xffff)
dprintf("lfn entry (%" B_PRIu32 ") with no alias\n", lfn_count);
return ENOENT;
}
if (buffer[0] == 0xe5) { // skip erased entries
if (start_index != 0xffff) {
dprintf("lfn entry (%" B_PRIu32 ") with intervening erased "
"entries\n", lfn_count);
start_index = 0xffff;
}
DPRINTF(2, ("entry erased, skipping...\n"));
continue;
}
if (buffer[0xb] == 0xf) { // long file name
if ((buffer[0xc] != 0) ||
(buffer[0x1a] != 0) || (buffer[0x1b] != 0)) {
dprintf("invalid long file name: reserved fields munged\n");
continue;
}
if (start_index == 0xffff) {
if ((buffer[0] & 0x40) == 0) {
dprintf("bad lfn start entry in directory\n");
continue;
}
hash = buffer[0xd];
lfn_count = buffer[0] & 0x1f;
start_index = iter->current_index;
puni = (uint16 *)(uni + 2*13*(lfn_count - 1));
for (i = 1; i < 0x20; i += 2) {
if (*(uint16 *)&buffer[i] == 0xffff)
break;
*puni++ = *(uint16 *)&buffer[i];
if (i == 0x9) i+=3;
if (i == 0x18) i+=2;
}
*puni++ = 0;
filename_len = (uchar *)(puni) - uni;
continue;
} else {
if (buffer[0xd] != hash) {
dprintf("error in long file name: hash values don't match\n");
start_index = 0xffff;
continue;
}
if (buffer[0] != --lfn_count) {
dprintf("bad lfn entry in directory\n");
start_index = 0xffff;
continue;
}
puni = (uint16 *)(uni + 2*13*(lfn_count - 1));
for (i = 1; i < 0x20; i += 2) {
if ((buffer[i] == 0xff) && (buffer[i+1] == 0xff)) {
dprintf("bad lfn entry in directory\n");
start_index = 0xffff;
break;
}
*puni++ = *(uint16 *)&buffer[i];
if (i == 0x9) i+=3;
if (i == 0x18) i+=2;
}
continue;
}
}
break;
}
// hit end of directory entries with no luck
if (buffer == NULL)
return ENOENT;
// process long name
if (start_index != 0xffff) {
if (lfn_count != 1) {
dprintf("unfinished lfn in directory\n");
start_index = 0xffff;
} else {
if (unicode_to_utf8(uni, filename_len, (uint8*)filename, len)) {
// rewind to beginning of call
dprintf("error: long file name too long\n");
diri_free(iter);
diri_init(iter->csi.vol, iter->starting_cluster, start_index,
iter);
return ENAMETOOLONG;
} else if (hash_msdos_name((const char *)buffer) != hash) {
dprintf("error: long file name (%s) hash and short file name "
"don't match\n", filename);
start_index = 0xffff;
}
}
}
// process short name
if (start_index == 0xffff) {
start_index = iter->current_index;
// korli : seen on FreeBSD /src/sys/fs/msdosfs/direntry.h
msdos_to_utf8(buffer, (uchar *)filename, len, buffer[0xc] & 0x18);
}
if (oinfo) {
oinfo->sindex = start_index;
oinfo->eindex = iter->current_index;
oinfo->mode = buffer[0xb];
oinfo->cluster = read16(buffer, 0x1a);
if (iter->csi.vol->fat_bits == 32)
oinfo->cluster += 0x10000 * read16(buffer, 0x14);
oinfo->size = read32(buffer, 0x1c);
oinfo->time = read32(buffer, 0x16);
oinfo->creation_time = read32(buffer, 0x0e);
}
diri_next_entry(iter);
return B_NO_ERROR;
}
static status_t
get_next_dirent(nspace *vol, vnode *dir, struct diri *iter, ino_t *vnid,
char *filename, int len)
{
struct _dirent_info_ info;
status_t result;
do {
result = _next_dirent_(iter, &info, filename, len);
if (result < 0)
return result;
// only hide volume label entries in the root directory
} while ((info.mode & FAT_VOLUME) && (dir->vnid == vol->root_vnode.vnid));
if (!strcmp(filename, ".")) {
// assign vnode based on parent
if (vnid) *vnid = dir->vnid;
} else if (!strcmp(filename, "..")) {
// assign vnode based on parent of parent
if (vnid) *vnid = dir->dir_vnid;
} else {
if (vnid) {
ino_t loc = (IS_DATA_CLUSTER(info.cluster))
? GENERATE_DIR_CLUSTER_VNID(dir->vnid, info.cluster)
: GENERATE_DIR_INDEX_VNID(dir->vnid, info.sindex);
bool added_to_vcache = false;
/* if it matches a loc in the lookup table, we are done. */
result = vcache_loc_to_vnid(vol, loc, vnid);
if (result == ENOENT) {
/* ...else check if it matches any vnid's in the lookup table */
if (find_vnid_in_vcache(vol, loc) == B_OK) {
/* if it does, create a random one since we can't reuse
* existing vnid's */
*vnid = generate_unique_vnid(vol);
/* and add it to the vcache */
if ((result = add_to_vcache(vol, *vnid, loc)) < 0)
return result;
added_to_vcache = true;
} else {
/* otherwise we are free to use it */
*vnid = loc;
}
} else if (result != B_OK) {
dprintf("get_next_dirent: unknown error (%s)\n",
strerror(result));
return result;
}
if (info.mode & FAT_SUBDIR) {
if (dlist_find(vol, info.cluster) == -1LL) {
if ((result = dlist_add(vol, *vnid)) < 0) {
if (added_to_vcache)
remove_from_vcache(vol, *vnid);
return result;
}
}
}
}
}
DPRINTF(2, ("get_next_dirent: found %s (vnid %" B_PRIdINO ")\n", filename,
vnid != NULL ? *vnid : (ino_t)0));
return B_NO_ERROR;
}
status_t
check_dir_empty(nspace *vol, vnode *dir)
{
uint32 i;
struct diri iter;
status_t result = B_ERROR;
if (diri_init(vol, dir->cluster, 0, &iter) == NULL) {
dprintf("check_dir_empty: error opening directory\n");
return B_ERROR;
}
i = (dir->vnid == vol->root_vnode.vnid) ? 2 : 0;
for (; i < 3; i++) {
char filename[512];
result = _next_dirent_(&iter, NULL, filename, 512);
if (result < 0) {
if (i == 2 && result == ENOENT)
result = B_OK;
break;
}
if ((i == 0 && strcmp(filename, "."))
|| (i == 1 && strcmp(filename, ".."))
// weird case where ./.. are stored as long file names
|| (i < 2 && iter.current_index != i + 1)) {
dprintf("check_dir_empty: malformed directory\n");
result = ENOTDIR;
break;
}
result = ENOTEMPTY;
}
diri_free(&iter);
return result;
}
status_t
findfile_case(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
vnode **node)
{
return findfile(vol, dir, file, vnid, node, true, false, NULL);
}
status_t
findfile_nocase(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
vnode **node)
{
return findfile(vol, dir, file, vnid, node, false, false, NULL);
}
status_t
findfile_nocase_duplicates(nspace *vol, vnode *dir, const char *file,
ino_t *vnid, vnode **node, bool *dups_exist)
{
return findfile(vol, dir, file, vnid, node, false, true, dups_exist);
}
status_t
findfile_case_duplicates(nspace *vol, vnode *dir, const char *file,
ino_t *vnid, vnode **node, bool *dups_exist)
{
return findfile(vol, dir, file, vnid, node, true, true, dups_exist);
}
static status_t
findfile(nspace *vol, vnode *dir, const char *file, ino_t *vnid,
vnode **node, bool check_case, bool check_dups, bool *dups_exist)
{
/* Starting at the base, find the file in the subdir
and return its vnode id */
/* The check_case flags determines whether or not the search
is done for the exact case or not. If it is not, it will
return the first occurance of the match. */
/* The check_dups flag instructs the function to find the
first filename match based on the case sensitivity in
check_case, but continue searching to see if there are
any other case-insensitive matches. If there are, the
dups_exist flag is set to true. */
int result = 0;
ino_t found_vnid = 0;
bool found_file = false;
// dprintf("findfile: %s in %Lx, case %d dups %d\n", file, dir->vnid, check_case, check_dups);
DPRINTF(1, ("findfile: %s in %" B_PRIdINO "\n", file, dir->vnid));
if (dups_exist != NULL)
*dups_exist = false;
else
check_dups = false;
if (strcmp(file,".") == 0 && dir->vnid == vol->root_vnode.vnid) {
found_file = true;
found_vnid = dir->vnid;
} else if (strcmp(file, "..") == 0 && dir->vnid == vol->root_vnode.vnid) {
found_file = true;
found_vnid = dir->dir_vnid;
} else {
struct diri diri;
// XXX: do it in a smarter way
if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
dprintf("findfile: error opening directory\n");
return ENOENT;
}
while (1) {
char filename[512];
ino_t _vnid;
result = get_next_dirent(vol, dir, &diri, &_vnid, filename, 512);
if (result != B_NO_ERROR)
break;
if (check_case) {
if (!found_file && !strcmp(filename, file)) {
found_file = true;
found_vnid = _vnid;
} else if (check_dups && !strcasecmp(filename, file)) {
*dups_exist = true;
}
} else {
if (!strcasecmp(filename, file)) {
if (check_dups && found_file)
*dups_exist = true;
found_file = true;
found_vnid = _vnid;
}
}
if (found_file && (!check_dups || (check_dups && *dups_exist)))
break;
}
diri_free(&diri);
}
if (found_file) {
if (vnid)
*vnid = found_vnid;
if (node)
result = get_vnode(vol->volume, found_vnid, (void **)node);
result = B_OK;
} else {
result = ENOENT;
}
#if 0
dprintf("findfile: returning %d", result);
if(dups_exist)
dprintf(" dups_exist %d\n", *dups_exist);
else
dprintf("\n");
#endif
return result;
}
status_t
erase_dir_entry(nspace *vol, vnode *node)
{
status_t result;
uint32 i;
char filename[512];
uint8 *buffer;
struct _dirent_info_ info;
struct diri diri;
DPRINTF(0, ("erasing directory entries %" B_PRIu32 " through %" B_PRIu32
"\n", node->sindex, node->eindex));
buffer = diri_init(vol,VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri);
// first pass: check if the entry is still valid
if (buffer == NULL) {
dprintf("erase_dir_entry: error reading directory\n");
return ENOENT;
}
result = _next_dirent_(&diri, &info, filename, 512);
diri_free(&diri);
if (result < 0)
return result;
if (info.sindex != node->sindex || info.eindex != node->eindex) {
// any other attributes may be in a state of flux due to wstat calls
dprintf("erase_dir_entry: directory entry doesn't match\n");
return B_ERROR;
}
// second pass: actually erase the entry
buffer = diri_init(vol, VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri);
for (i = node->sindex; i <= node->eindex && buffer;
buffer = diri_next_entry(&diri), i++) {
diri_make_writable(&diri);
buffer[0] = 0xe5; // mark entry erased
}
diri_free(&diri);
return 0;
}
/*! shrink directory to the size needed
errors here are neither likely nor problematic
w95 doesn't seem to do this, so it's possible to create a
really large directory that consumes all available space!
*/
status_t
compact_directory(nspace *vol, vnode *dir)
{
uint32 last = 0;
struct diri diri;
status_t error = B_ERROR; /* quiet warning */
DPRINTF(0, ("compacting directory with vnode id %" B_PRIdINO "\n",
dir->vnid));
// root directory can't shrink in fat12 and fat16
if (IS_FIXED_ROOT(dir->cluster))
return 0;
if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
dprintf("compact_directory: cannot open dir at cluster (%" B_PRIu32
")\n", dir->cluster);
return EIO;
}
while (diri.current_block) {
char filename[512];
struct _dirent_info_ info;
error = _next_dirent_(&diri, &info, filename, 512);
if (error == B_OK) {
// don't compact away volume labels in the root dir
if (!(info.mode & FAT_VOLUME) || (dir->vnid != vol->root_vnode.vnid))
last = diri.current_index;
} else if (error == ENOENT) {
uint32 clusters = (last + vol->bytes_per_sector / 0x20
* vol->sectors_per_cluster - 1) / (vol->bytes_per_sector / 0x20)
/ vol->sectors_per_cluster;
error = 0;
// special case for fat32 root directory; we don't want
// it to disappear
if (clusters == 0)
clusters = 1;
if (clusters * vol->bytes_per_sector * vol->sectors_per_cluster
< dir->st_size) {
DPRINTF(0, ("shrinking directory to %" B_PRIu32 " clusters\n",
clusters));
error = set_fat_chain_length(vol, dir, clusters, true);
dir->st_size = clusters * vol->bytes_per_sector
* vol->sectors_per_cluster;
dir->iteration++;
}
break;
} else {
dprintf("compact_directory: unknown error from _next_dirent_ (%s)\n",
strerror(error));
break;
}
}
diri_free(&diri);
return error;
}
//! name is array of char[11] as returned by findfile
static status_t
find_short_name(nspace *vol, vnode *dir, const uchar *name)
{
struct diri diri;
uint8 *buffer;
status_t result = ENOENT;
buffer = diri_init(vol, dir->cluster, 0, &diri);
while (buffer) {
if (buffer[0] == 0)
break;
if (buffer[0xb] != 0xf) { // not long file name
if (!memcmp(name, buffer, 11)) {
result = B_OK;
break;
}
}
buffer = diri_next_entry(&diri);
}
diri_free(&diri);
return result;
}
struct _entry_info_ {
uint32 mode;
uint32 cluster;
uint32 size;
time_t time;
time_t creation_time;
};
static status_t
_create_dir_entry_(nspace *vol, vnode *dir, struct _entry_info_ *info,
const char nshort[11], const char *nlong, uint32 len, uint32 *ns,
uint32 *ne)
{
status_t error = B_ERROR; /* quiet warning */
uint32 required_entries, i;
uint8 *buffer, hash;
bool last_entry;
struct diri diri;
// short name cannot be the same as that of a device
// this list was created by running strings on io.sys
const char *device_names[] = {
"CON ",
"AUX ",
"PRN ",
"CLOCK$ ",
"COM1 ",
"LPT1 ",
"LPT2 ",
"LPT3 ",
"COM2 ",
"COM3 ",
"COM4 ",
"CONFIG$ ",
NULL
};
// check short name against device names
for (i = 0; device_names[i]; i++) {
// only first 8 characters seem to matter
if (!memcmp(nshort, device_names[i], 8))
return EPERM;
}
if (info->cluster != 0 && !IS_DATA_CLUSTER(info->cluster)) {
dprintf("_create_dir_entry_ for bad cluster (%" B_PRIu32 ")\n",
info->cluster);
return EINVAL;
}
/* convert byte length of unicode name to directory entries */
required_entries = (len + 24) / 26 + 1;
// find a place to put the entries
*ns = 0;
last_entry = true;
if (diri_init(vol, dir->cluster, 0, &diri) == NULL) {
dprintf("_create_dir_entry_: cannot open dir at cluster (%" B_PRIu32
")\n", dir->cluster);
return EIO;
}
while (diri.current_block) {
char filename[512];
struct _dirent_info_ info;
error = _next_dirent_(&diri, &info, filename, 512);
if (error == B_OK) {
if (info.sindex - *ns >= required_entries) {
last_entry = false;
break;
}
*ns = diri.current_index;
} else if (error == ENOENT) {
// hit end of directory marker
break;
} else {
dprintf("_create_dir_entry_: unknown error from _next_dirent_ (%s)\n",
strerror(error));
break;
}
}
// if at end of directory, last_entry flag will be true as it should be
diri_free(&diri);
if (error != B_OK && error != ENOENT)
return error;
*ne = *ns + required_entries - 1;
for (i = *ns; i <= *ne; i++) {
ASSERT(find_loc_in_vcache(vol,
GENERATE_DIR_INDEX_VNID(dir->cluster, i)) == ENOENT);
}
DPRINTF(0, ("directory entry runs from %" B_PRIu32 " to %" B_PRIu32
" (dirsize = %" B_PRIdOFF ") (is%s last entry)\n", *ns, *ne,
dir->st_size, last_entry ? "" : "n't"));
// check if the directory needs to be expanded
if (*ne * 0x20 >= dir->st_size) {
uint32 clusters_needed;
// can't expand fat12 and fat16 root directories :(
if (IS_FIXED_ROOT(dir->cluster)) {
DPRINTF(0, ("_create_dir_entry_: out of space in root directory\n"));
return ENOSPC;
}
// otherwise grow directory to fit
clusters_needed = ((*ne + 1) * 0x20 +
vol->bytes_per_sector*vol->sectors_per_cluster - 1) /
vol->bytes_per_sector / vol->sectors_per_cluster;
DPRINTF(0, ("expanding directory from %" B_PRIdOFF " to %" B_PRIu32
" clusters\n", dir->st_size / vol->bytes_per_sector
/ vol->sectors_per_cluster, clusters_needed));
if ((error = set_fat_chain_length(vol, dir, clusters_needed, false))
< 0) {
return error;
}
dir->st_size = vol->bytes_per_sector*vol->sectors_per_cluster*clusters_needed;
dir->iteration++;
}
// starting blitting entries
buffer = diri_init(vol,dir->cluster, *ns, &diri);
if (buffer == NULL) {
dprintf("_create_dir_entry_: cannot open dir at (%" B_PRIu32 ", %"
B_PRIu32 ")\n", dir->cluster, *ns);
return EIO;
}
hash = hash_msdos_name(nshort);
// write lfn entries
for (i = 1; i < required_entries && buffer; i++) {
const char *p = nlong + (required_entries - i - 1) * 26;
// go to unicode offset
diri_make_writable(&diri);
memset(buffer, 0, 0x20);
buffer[0] = required_entries - i + ((i == 1) ? 0x40 : 0);
buffer[0x0b] = 0x0f;
buffer[0x0d] = hash;
memcpy(buffer+1,p,10);
memcpy(buffer+0x0e,p+10,12);
memcpy(buffer+0x1c,p+22,4);
buffer = diri_next_entry(&diri);
}
ASSERT(buffer != NULL);
if (buffer == NULL) { // this should never happen...
DPRINTF(0, ("_create_dir_entry_: the unthinkable has occured\n"));
diri_free(&diri);
return B_ERROR;
}
// write directory entry
diri_make_writable(&diri);
memcpy(buffer, nshort, 11);
buffer[0x0b] = info->mode;
memset(buffer+0xc, 0, 0x16-0xc);
i = time_t2dos(info->creation_time);
buffer[0x0e] = i & 0xff;
buffer[0x0f] = (i >> 8) & 0xff;
buffer[0x10] = (i >> 16) & 0xff;
buffer[0x11] = (i >> 24) & 0xff;
i = time_t2dos(info->time);
buffer[0x16] = i & 0xff;
buffer[0x17] = (i >> 8) & 0xff;
buffer[0x18] = (i >> 16) & 0xff;
buffer[0x19] = (i >> 24) & 0xff;
i = info->cluster;
if (info->size == 0) i = 0; // cluster = 0 for 0 byte files
buffer[0x1a] = i & 0xff;
buffer[0x1b] = (i >> 8) & 0xff;
if (vol->fat_bits == 32) {
buffer[0x14] = (i >> 16) & 0xff;
buffer[0x15] = (i >> 24) & 0xff;
}
i = (info->mode & FAT_SUBDIR) ? 0 : info->size;
buffer[0x1c] = i & 0xff;
buffer[0x1d] = (i >> 8) & 0xff;
buffer[0x1e] = (i >> 16) & 0xff;
buffer[0x1f] = (i >> 24) & 0xff;
if (last_entry) {
// add end of directory markers to the rest of the
// cluster; need to clear all the other entries or else
// scandisk will complain.
while ((buffer = diri_next_entry(&diri)) != NULL) {
diri_make_writable(&diri);
memset(buffer, 0, 0x20);
}
}
diri_free(&diri);
return 0;
}
//! doesn't do any name checking
status_t
create_volume_label(nspace *vol, const char name[11], uint32 *index)
{
status_t err;
uint32 dummy;
struct _entry_info_ info = {
FAT_ARCHIVE | FAT_VOLUME, 0, 0, 0
};
time(&info.time);
// check if name already exists
err = find_short_name(vol, &(vol->root_vnode), (uchar *)name);
if (err == B_OK)
return EEXIST;
if (err != ENOENT)
return err;
return _create_dir_entry_(vol, &(vol->root_vnode), &info, name, NULL,
0, index, &dummy);
}
bool
is_filename_legal(const char *name)
{
unsigned int i;
unsigned int len = strlen(name);
if (len <= 0)
return false;
// names ending with a dot are not allowed
if (name[len - 1] == '.')
return false;
// names ending with a space are not allowed
if (name[len - 1] == ' ')
return false;
// XXX illegal character search can be made faster
for (i = 0; i < len; i++) {
if (name[i] & 0x80)
continue; //belongs to an utf8 char
if (strchr(illegal, name[i]))
return false;
if ((unsigned char)name[i] < 32)
return false;
}
return true;
}
status_t
create_dir_entry(nspace *vol, vnode *dir, vnode *node, const char *name,
uint32 *ns, uint32 *ne)
{
status_t error;
int32 len;
unsigned char nlong[512], nshort[11];
int encoding;
struct _entry_info_ info;
// check name legality before doing anything
if (!is_filename_legal(name))
return EINVAL;
// check if name already exists
error = findfile_nocase(vol, dir, name, NULL, NULL);
if (error == B_OK) {
DPRINTF(0, ("%s already found in directory %" B_PRIdINO "\n", name,
dir->vnid));
return EEXIST;
}
if (error != ENOENT)
return error;
// check name legality while converting. we ignore the case conversion
// flag, i.e. (filename "blah" will always have a patched short name),
// because the whole case conversion system in dos is brain damaged;
// remanants of CP/M no less.
// existing names pose a problem; in these cases, we'll just live with
// two identical short names. not a great solution, but there's little
// we can do about it.
len = utf8_to_unicode(name, nlong, 512);
if (len <= 0) {
DPRINTF(0, ("Error converting utf8 name '%s' to unicode\n", name));
return len ? len : B_ERROR;
}
memset(nlong + len, 0xff, 512 - len); /* pad with 0xff */
error = generate_short_name((uchar *)name, nlong, len, nshort, &encoding);
if (error) {
DPRINTF(0, ("Error generating short name for '%s'\n", name));
return error;
}
// if there is a long name, patch short name if necessary and check for duplication
if (requires_long_name(name, nlong)) {
char tshort[11]; // temporary short name
int iter = 1;
memcpy(tshort, nshort, 11);
if (requires_munged_short_name((uchar *)name, nshort, encoding))
error = B_OK;
else
error = find_short_name(vol, dir, nshort);
if (error == B_OK) {
do {
memcpy(nshort, tshort, 11);
DPRINTF(0, ("trying short name %11.11s\n", nshort));
munge_short_name1(nshort, iter, encoding);
} while ((error = find_short_name(vol, dir, nshort)) == B_OK && ++iter < 10);
}
if (error != B_OK && error != ENOENT)
return error;
if (error == B_OK) {
// XXX: possible infinite loop here
do {
memcpy(nshort, tshort, 11);
DPRINTF(0, ("trying short name %11.11s\n", nshort));
munge_short_name2(nshort, encoding);
} while ((error = find_short_name(vol, dir, nshort)) == B_OK);
if (error != ENOENT)
return error;
}
} else {
len = 0; /* entry doesn't need a long name */
}
DPRINTF(0, ("creating directory entry (%11.11s)\n", nshort));
info.mode = node->mode;
if ((node->mode & FAT_SUBDIR) == 0)
info.mode |= FAT_ARCHIVE;
info.cluster = node->cluster;
info.size = node->st_size;
info.time = node->st_time;
info.creation_time = node->st_crtim;
return _create_dir_entry_(vol, dir, &info, (char *)nshort,
(char *)nlong, len, ns, ne);
}
status_t
dosfs_read_vnode(fs_volume *_vol, ino_t vnid, fs_vnode *_node, int *_type,
uint32 *_flags, bool reenter)
{
nspace *vol = (nspace*)_vol->private_volume;
int result = B_NO_ERROR;
ino_t loc, dir_vnid;
vnode *entry;
struct _dirent_info_ info;
struct diri iter;
char filename[512]; /* need this for setting mime type */
LOCK_VOL(vol);
_node->private_node = NULL;
_node->ops = &gFATVnodeOps;
*_flags = 0;
DPRINTF(0, ("dosfs_read_vnode (vnode id %" B_PRIdINO ")\n", vnid));
if (vnid == vol->root_vnode.vnid) {
dprintf("??? dosfs_read_vnode called on root node ???\n");
_node->private_node = (void *)&(vol->root_vnode);
*_type = make_mode(vol, &vol->root_vnode);
goto bi;
}
if (vcache_vnid_to_loc(vol, vnid, &loc) != B_OK)
loc = vnid;
if (IS_ARTIFICIAL_VNID(loc) || IS_INVALID_VNID(loc)) {
DPRINTF(0, ("dosfs_read_vnode: unknown vnid %" B_PRIdINO " (loc %"
B_PRIdINO ")\n", vnid, loc));
result = ENOENT;
goto bi;
}
if ((dir_vnid = dlist_find(vol, DIR_OF_VNID(loc))) == -1LL) {
DPRINTF(0, ("dosfs_read_vnode: unknown directory at cluster %" B_PRIu32
"\n", DIR_OF_VNID(loc)));
result = ENOENT;
goto bi;
}
if (diri_init(vol, DIR_OF_VNID(loc),
IS_DIR_CLUSTER_VNID(loc) ? 0 : INDEX_OF_DIR_INDEX_VNID(loc),
&iter) == NULL) {
dprintf("dosfs_read_vnode: error initializing directory for vnid %"
B_PRIdINO " (loc %" B_PRIdINO ")\n", vnid, loc);
result = ENOENT;
goto bi;
}
while (1) {
result = _next_dirent_(&iter, &info, filename, 512);
if (result < 0) {
dprintf("dosfs_read_vnode: error finding vnid %" B_PRIdINO
" (loc %" B_PRIdINO ") (%s)\n", vnid, loc, strerror(result));
goto bi2;
}
if (IS_DIR_CLUSTER_VNID(loc)) {
if (info.cluster == CLUSTER_OF_DIR_CLUSTER_VNID(loc))
break;
} else {
if (info.sindex == INDEX_OF_DIR_INDEX_VNID(loc))
break;
dprintf("dosfs_read_vnode: error finding vnid %" B_PRIdINO
" (loc %" B_PRIdINO ") (%s)\n", vnid, loc, strerror(result));
result = ENOENT;
goto bi2;
}
}
if ((entry = calloc(sizeof(struct vnode), 1)) == NULL) {
DPRINTF(0, ("dosfs_read_vnode: out of memory\n"));
result = ENOMEM;
goto bi2;
}
entry->vnid = vnid;
entry->dir_vnid = dir_vnid;
entry->disk_image = 0;
if (vol->respect_disk_image) {
if ((dir_vnid == vol->root_vnode.vnid) && !strcmp(filename, "BEOS")) {
vol->beos_vnid = vnid;
entry->disk_image = 1;
}
if ((dir_vnid == vol->beos_vnid) && !strcmp(filename, "IMAGE.BE")) {
entry->disk_image = 2;
}
}
entry->iteration = 0;
entry->sindex = info.sindex;
entry->eindex = info.eindex;
entry->cluster = info.cluster;
entry->mode = info.mode;
entry->st_size = info.size;
entry->dirty = false;
if (info.mode & FAT_SUBDIR) {
entry->st_size = count_clusters(vol,entry->cluster)
* vol->sectors_per_cluster * vol->bytes_per_sector;
}
if (entry->cluster) {
entry->end_cluster = get_nth_fat_entry(vol, info.cluster,
(entry->st_size + vol->bytes_per_sector * vol->sectors_per_cluster - 1) /
vol->bytes_per_sector / vol->sectors_per_cluster - 1);
} else
entry->end_cluster = 0;
entry->st_time = dos2time_t(info.time);
entry->st_crtim = dos2time_t(info.creation_time);
#if TRACK_FILENAME
entry->filename = malloc(sizeof(filename) + 1);
if (entry->filename) strcpy(entry->filename, filename);
#endif
entry->cache = file_cache_create(vol->id, vnid, entry->st_size);
entry->file_map = file_map_create(vol->id, vnid, entry->st_size);
if (!(entry->mode & FAT_SUBDIR))
set_mime_type(entry, filename);
_node->private_node = entry;
*_type = make_mode(vol, entry);
bi2:
diri_free(&iter);
bi:
UNLOCK_VOL(vol);
if (result != B_OK)
DPRINTF(0, ("dosfs_read_vnode (%s)\n", strerror(result)));
return result;
}
status_t
dosfs_walk(fs_volume *_vol, fs_vnode *_dir, const char *file, ino_t *_vnid)
{
/* Starting at the base, find file in the subdir, and return path
string and vnode id of file. */
nspace *vol = (nspace*)_vol->private_volume;
vnode *dir = (vnode*)_dir->private_node;
vnode *vnode = NULL;
status_t result = ENOENT;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_walk: find %" B_PRIdINO "/%s\n", dir->vnid, file));
result = findfile_case(vol, dir, file, _vnid, &vnode);
if (result != B_OK) {
DPRINTF(0, ("dosfs_walk (%s)\n", strerror(result)));
} else {
DPRINTF(0, ("dosfs_walk: found vnid %" B_PRIdINO "\n", *_vnid));
}
UNLOCK_VOL(vol);
return result;
}
status_t
dosfs_access(fs_volume *_vol, fs_vnode *_node, int mode)
{
status_t result = B_OK;
nspace *vol = (nspace *)_vol->private_volume;
vnode *node = (vnode *)_node->private_node;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_access (vnode id %" B_PRIdINO ", mode %o)\n", node->vnid,
mode));
if (mode & W_OK) {
if (vol->flags & B_FS_IS_READONLY) {
DPRINTF(0, ("dosfs_access: can't write on read-only volume\n"));
result = EROFS;
} else if (node->mode & FAT_READ_ONLY) {
DPRINTF(0, ("can't open read-only file for writing\n"));
result = EPERM;
} else if (node->disk_image != 0) {
DPRINTF(0, ("can't open disk image file for writing\n"));
result = EPERM;
}
}
UNLOCK_VOL(vol);
return result;
}
status_t
dosfs_readlink(fs_volume *_vol, fs_vnode *_node, char *buf, size_t *bufsize)
{
TOUCH(_vol); TOUCH(_node); TOUCH(buf); TOUCH(bufsize);
// no links in fat...
DPRINTF(0, ("dosfs_readlink called\n"));
return EINVAL;
}
status_t
dosfs_opendir(fs_volume *_vol, fs_vnode *_node, void **_cookie)
{
nspace *vol = (nspace*)_vol->private_volume;
vnode *node = (vnode*)_node->private_node;
dircookie *cookie = NULL;
int result;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_opendir (vnode id %" B_PRIdINO ")\n", node->vnid));
*_cookie = NULL;
if (!(node->mode & FAT_SUBDIR)) {
/* bash will try to opendir files unless OPENDIR_NOT_ROBUST is
* defined, so we'll suppress this message; it's more of a problem
* with the application than with the file system, anyway
*/
DPRINTF(0, ("dosfs_opendir error: vnode not a directory\n"));
result = ENOTDIR;
goto bi;
}
if ((cookie = (dircookie *)malloc(sizeof(dircookie))) == NULL) {
DPRINTF(0, ("dosfs_opendir: out of memory error\n"));
result = ENOMEM;
goto bi;
}
cookie->current_index = 0;
result = B_NO_ERROR;
bi:
*_cookie = (void*)cookie;
if (result != B_OK)
DPRINTF(0, ("dosfs_opendir (%s)\n", strerror(result)));
UNLOCK_VOL(vol);
return result;
}
status_t
dosfs_readdir(fs_volume *_vol, fs_vnode *_dir, void *_cookie,
struct dirent *entry, size_t bufsize, uint32 *num)
{
int result = ENOENT;
nspace* vol = (nspace*)_vol->private_volume;
vnode *dir = (vnode *)_dir->private_node;
dircookie* cookie = (dircookie*)_cookie;
struct diri diri;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_readdir: vnode id %" B_PRIdINO ", index %" B_PRIu32 "\n",
dir->vnid, cookie->current_index));
// simulate '.' and '..' entries for root directory
if (dir->vnid == vol->root_vnode.vnid) {
if (cookie->current_index >= 2) {
cookie->current_index -= 2;
} else {
if (cookie->current_index++ == 0) {
strcpy(entry->d_name, ".");
entry->d_reclen = sizeof(struct dirent) + 1;
} else {
strcpy(entry->d_name, "..");
entry->d_reclen = sizeof(struct dirent) + 2;
}
*num = 1;
entry->d_ino = vol->root_vnode.vnid;
entry->d_dev = vol->id;
result = B_NO_ERROR;
goto bi;
}
}
if (diri_init(vol, dir->cluster, cookie->current_index, &diri) == NULL) {
DPRINTF(0, ("dosfs_readdir: no more entries!\n"));
// When you get to the end, don't return an error, just return 0
// in *num.
*num = 0;
result = B_NO_ERROR;
goto bi;
}
result = get_next_dirent(vol, dir, &diri, &entry->d_ino, entry->d_name,
bufsize - sizeof(struct dirent) - 1);
cookie->current_index = diri.current_index;
diri_free(&diri);
if (dir->vnid == vol->root_vnode.vnid)
cookie->current_index += 2;
if (result == B_NO_ERROR) {
*num = 1;
entry->d_dev = vol->id;
entry->d_reclen = sizeof(struct dirent) + strlen(entry->d_name);
DPRINTF(0, ("dosfs_readdir: found file %s\n", entry->d_name));
} else if (result == ENOENT) {
// When you get to the end, don't return an error, just return 0
// in *num.
*num = 0;
result = B_NO_ERROR;
} else {
dprintf("dosfs_readdir: error returned by get_next_dirent (%s)\n",
strerror(result));
}
bi:
if (result != B_OK) DPRINTF(0, ("dosfs_readdir (%s)\n", strerror(result)));
UNLOCK_VOL(vol);
return result;
}
status_t
dosfs_rewinddir(fs_volume *_vol, fs_vnode *_node, void* _cookie)
{
nspace *vol = (nspace *)_vol->private_volume;
vnode *node = (vnode *)_node->private_node;
dircookie *cookie = (dircookie*)_cookie;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_rewinddir (vnode id %" B_PRIdINO ")\n", node->vnid));
cookie->current_index = 0;
UNLOCK_VOL(vol);
return B_OK;
}
status_t
dosfs_closedir(fs_volume *_vol, fs_vnode *_node, void *_cookie)
{
TOUCH(_vol); TOUCH(_node); TOUCH(_cookie);
DPRINTF(0, ("dosfs_closedir called\n"));
return 0;
}
status_t
dosfs_free_dircookie(fs_volume *_vol, fs_vnode *_node, void *_cookie)
{
nspace *vol = (nspace *)_vol->private_volume;
vnode *node = (vnode *)_node->private_node;
dircookie *cookie = _cookie;
LOCK_VOL(vol);
DPRINTF(0, ("dosfs_free_dircookie (vnode id %" B_PRIdINO ")\n",
node->vnid));
free(cookie);
UNLOCK_VOL(vol);
return 0;
}
↑ V547 Expression is always false.
↑ V547 Expression is always false.