* Copyright 2008, Salvatore Benedetto, salvatore.benedetto@gmail.com.
* Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net.
* Distributed under the terms of the MIT License.
*/
#include <Drivers.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <KernelExport.h>
#include <util/kernel_cpp.h>
#include "Icb.h"
#include "Recognition.h"
#include "Utils.h"
#include "Volume.h"
#undef TRACE
#undef TRACE_ERROR
#define UDF_KERNEL_INTERFACE_DEBUG
#ifdef UDF_KERNEL_INTERFACE_DEBUG
# define TRACE(x) dprintf x
# define TRACE_ERROR(x) dprintf x
#else
# define TRACE(x)
# define TRACE_ERROR(x) dprintf x
#endif
extern fs_volume_ops gUDFVolumeOps;
extern fs_vnode_ops gUDFVnodeOps;
static float
udf_identify_partition(int fd, partition_data *partition, void **_cookie)
{
TRACE(("udf_identify_partition: fd = %d, id = %d, offset = %d, size = %d "
"content_size = %d, block_size = %d\n", fd, partition->id,
partition->offset, partition->size, partition->content_size,
partition->block_size));
logical_volume_descriptor logicalVolumeDescriptor;
partition_descriptor partitionDescriptors[kMaxPartitionDescriptors];
uint8 descriptorCount = kMaxPartitionDescriptors;
uint32 blockShift;
status_t error = udf_recognize(fd, partition->offset, partition->size,
partition->block_size, blockShift, logicalVolumeDescriptor,
partitionDescriptors, descriptorCount);
if (error != B_OK)
return -1;
return 0.8f;
}
static status_t
udf_scan_partition(int fd, partition_data *partition, void *_cookie)
{
TRACE(("udf_scan_partition: fd = %d\n", fd));
#if 0
UdfString name(logicalVolumeDescriptor.logical_volume_identifier());
partition->content_name = strdup(name.Utf8());
#endif
return B_ERROR;
}
static status_t
udf_unmount(fs_volume *_volume)
{
TRACE(("udb_unmount: _volume = %p\n", _volume));
Volume *volume = (Volume *)_volume->private_volume;
delete volume;
return B_OK;
}
static status_t
udf_read_fs_stat(fs_volume *_volume, struct fs_info *info)
{
TRACE(("udf_read_fs_stat: _volume = %p, info = %p\n", _volume, info));
Volume *volume = (Volume *)_volume->private_volume;
info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY;
info->io_size = 65536;
info->block_size = volume->BlockSize();
info->total_blocks = volume->Length();
info->free_blocks = 0;
sprintf(info->volume_name, "%s", volume->Name());
strcpy(info->fsh_name, "udf");
return B_OK;
}
static status_t
udf_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *_node, int *_type,
uint32 *_flags, bool reenter)
{
TRACE(("udf_get_vnode: _volume = %p, _node = %p, reenter = %s\n",
_volume, _node, (reenter ? "true" : "false")));
Volume *volume = (Volume *)_volume->private_volume;
TRACE(("udf_get_vnode: id = %d, blockSize = %d\n", id, volume->BlockSize()));
Icb *icb = new(std::nothrow) Icb(volume,
to_long_address(id, volume->BlockSize()));
if (icb) {
if(icb->InitCheck() == B_OK) {
if (_node)
_node->private_node = icb;
_node->ops = &gUDFVnodeOps;
_flags = 0;
} else {
TRACE_ERROR(("udf_get_vnode: InitCheck failed\n"));
delete icb;
return B_ERROR;
}
} else
return B_NO_MEMORY;
return B_OK;
}
static status_t
udf_lookup(fs_volume *_volume, fs_vnode *_directory, const char *file,
ino_t *vnodeID)
{
TRACE(("udf_lookup: _directory = %p, filename = %s\n", _directory, file));
Volume *volume = (Volume *)_volume->private_volume;
Icb *dir = (Icb *)_directory->private_node;
Icb *node = NULL;
status_t status = B_OK;
if (strcmp(file, ".") == 0) {
TRACE(("udf_lookup: file = ./\n"));
*vnodeID = dir->Id();
status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&node);
if (status != B_OK)
return B_ENTRY_NOT_FOUND;
} else {
status = dir->Find(file, vnodeID);
if (status != B_OK)
return status;
Icb *icb;
status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&icb);
if (status != B_OK)
return B_ENTRY_NOT_FOUND;
}
TRACE(("udf_lookup: vnodeId = %Ld found!\n", *vnodeID));
return B_OK;
}
static status_t
udf_put_vnode(fs_volume *volume, fs_vnode *node, bool reenter)
{
TRACE(("udf_put_vnode: volume = %p, node = %p\n", volume, node));
#if !DEBUG_TO_FILE
DEBUG_INIT_ETC(NULL, ("node: %p", node));
#endif
Icb *icb = (Icb *)node->private_node;
delete icb;
#if !DEBUG_TO_FILE
RETURN(B_OK);
#else
return B_OK;
#endif
}
static status_t
udf_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
{
TRACE(("udf_remove_vnode: _volume = %p, _node = %p\n", _volume, _node));
return B_ERROR;
}
static status_t
udf_read_stat(fs_volume *_volume, fs_vnode *node, struct stat *stat)
{
TRACE(("udf_read_stat: _volume = %p, node = %p\n", _volume, node));
if (!_volume || !node || !stat)
return B_BAD_VALUE;
Volume *volume = (Volume *)_volume->private_volume;
Icb *icb = (Icb *)node->private_node;
stat->st_dev = volume->ID();
stat->st_ino = icb->Id();
stat->st_nlink = icb->FileLinkCount();
stat->st_blksize = volume->BlockSize();
TRACE(("udf_read_stat: st_dev = %d, st_ino = %d, st_blksize = %d\n",
stat->st_dev, stat->st_ino, stat->st_blksize));
stat->st_uid = icb->Uid();
stat->st_gid = icb->Gid();
stat->st_mode = icb->Mode();
stat->st_size = icb->Length();
stat->st_blocks = (stat->st_size + 511) / 512;
stat->st_atime = icb->AccessTime();
stat->st_mtime = stat->st_ctime = stat->st_crtime = icb->ModificationTime();
TRACE(("udf_read_stat: mode = 0x%lx, st_ino: %Ld\n", stat->st_mode,
stat->st_ino));
return B_OK;
}
static status_t
udf_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
{
TRACE(("udf_open: _volume = %p, _node = %p\n", _volume, _node));
return B_OK;
}
static status_t
udf_close(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
TRACE(("udf_close: _volume = %p, _node = %p\n", _volume, _node));
return B_OK;
}
static status_t
udf_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
TRACE(("udf_free_cookie: _volume = %p, _node = %p\n", _volume, _node));
return B_OK;
}
static status_t
udf_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
{
TRACE(("udf_access: _volume = %p, _node = %p\n", _volume, _node));
return B_OK;
}
static status_t
udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
void *buffer, size_t *length)
{
TRACE(("udf_read: ID = %ld, pos = %lld, length = %lu\n",
((Volume *)volume->private_volume)->ID(), pos, *length));
Icb *icb = (Icb *)vnode->private_node;
RETURN(icb->Read(pos, buffer, length));
}
static status_t
udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
{
TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode));
if (!volume || !vnode || !cookie)
RETURN(B_BAD_VALUE);
Icb *dir = (Icb *)vnode->private_node;
if (!dir->IsDirectory()) {
TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n",
dir->Type()));
return B_BAD_VALUE;
}
DirectoryIterator *iterator = NULL;
status_t status = dir->GetDirectoryIterator(&iterator);
if (status != B_OK) {
TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%lx, "
"`%s'\n", status, strerror(status)));
return status;
}
*cookie = (void *)iterator;
TRACE(("udf_open_dir: *cookie = %p\n", *cookie));
return B_OK;
}
static status_t
udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie)
{
TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node));
return B_OK;
}
static status_t
udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie)
{
TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node));
return B_OK;
}
static status_t
udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie,
struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n",
_volume, vnode, bufferSize));
if (!_volume || !vnode || !cookie || !_num || bufferSize < sizeof(dirent))
return B_BAD_VALUE;
Volume *volume = (Volume *)_volume->private_volume;
Icb *dir = (Icb *)vnode->private_node;
DirectoryIterator *iterator = (DirectoryIterator *)cookie;
if (dir != iterator->Parent()) {
TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given "
"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
return B_BAD_VALUE;
}
uint32 nameLength = bufferSize - sizeof(struct dirent) + 1;
ino_t id;
status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id);
TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength));
if (!status) {
*_num = 1;
dirent->d_dev = volume->ID();
dirent->d_ino = id;
dirent->d_reclen = sizeof(struct dirent) + nameLength - 1;
} else {
*_num = 0;
if (status == B_ENTRY_NOT_FOUND)
status = B_OK;
}
RETURN(status);
}
status_t
udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n",
volume, vnode, cookie));
if (!volume || !vnode || !cookie)
RETURN(B_BAD_VALUE);
Icb *dir = (Icb *)vnode->private_node;
DirectoryIterator *iterator = (DirectoryIterator *)cookie;
if (dir != iterator->Parent()) {
PRINT(("udf_rewind_dir: icb does not match parent Icb of given "
"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
return B_BAD_VALUE;
}
iterator->Rewind();
return B_OK;
}
\todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the
partition is. This won't work for handling multi-session semantics correctly.
To support them correctly in R5 I need either:
- A way to get the proper info (best)
- To ignore trying to find anchor volume descriptor pointers at
locations N-256 and N. (acceptable, perhaps, but not really correct)
Either way we should address this problem properly for OBOS::R1.
\todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e.
disk images), so I need to use stat or something else for those
instances.
*/
static status_t
udf_mount(fs_volume *_volume, const char *_device, uint32 flags,
const char *args, ino_t *_rootVnodeID)
{
TRACE(("udf_mount: device = %s\n", _device));
status_t status = B_OK;
Volume *volume = NULL;
off_t deviceOffset = 0;
off_t numBlock = 0;
partition_info info;
device_geometry geometry;
int device = open(_device, O_RDONLY);
status = device < B_OK ? device : B_OK;
if (!status) {
if (ioctl(device, B_GET_PARTITION_INFO, &info) == 0) {
TRACE(("partition_info:\n"));
TRACE(("\toffset: %Ld\n", info.offset));
TRACE(("\tsize: %Ld\n", info.size));
TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size));
TRACE(("\tsession: %ld\n", info.session));
TRACE(("\tpartition: %ld\n", info.partition));
TRACE(("\tdevice: `%s'\n", info.device));
_device = info.device;
deviceOffset = info.offset / info.logical_block_size;
numBlock = deviceOffset + info.size / info.logical_block_size;
} else if (ioctl(device, B_GET_GEOMETRY, &geometry) == 0) {
TRACE(("geometry_info:\n"));
TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track));
TRACE(("\tcylinder_count: %ld\n", geometry.cylinder_count));
TRACE(("\thead_count: %ld\n", geometry.head_count));
deviceOffset = 0;
numBlock = (off_t)geometry.sectors_per_track
* geometry.cylinder_count * geometry.head_count;
} else {
struct stat stat;
status = fstat(device, &stat) < 0 ? B_ERROR : B_OK;
if (!status) {
TRACE(("stat_info:\n"));
TRACE(("\tst_size: %Ld\n", stat.st_size));
deviceOffset = 0;
numBlock = stat.st_size / 2048;
}
}
close(device);
}
volume = new(std::nothrow) Volume(_volume);
status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags);
if (status != B_OK) {
delete volume;
return status;
}
_volume->private_volume = volume;
_volume->ops = &gUDFVolumeOps;
*_rootVnodeID = volume->RootIcb()->Id();
TRACE(("udf_mount: succefully mounted the partition\n"));
return B_OK;
}
static status_t
udf_std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
fs_volume_ops gUDFVolumeOps = {
&udf_unmount,
&udf_read_fs_stat,
NULL,
NULL,
&udf_get_vnode,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
fs_vnode_ops gUDFVnodeOps = {
&udf_lookup,
NULL,
&udf_put_vnode,
&udf_remove_vnode,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
&udf_access,
&udf_read_stat,
NULL,
NULL,
&udf_open,
&udf_close,
&udf_free_cookie,
&udf_read,
NULL,
NULL,
NULL,
&udf_open_dir,
&udf_close_dir,
&udf_free_dir_cookie,
&udf_read_dir,
&udf_rewind_dir,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
static file_system_module_info sUDFFileSystem = {
{
"file_systems/udf" B_CURRENT_FS_API_VERSION,
0,
udf_std_ops,
},
"udf",
"UDF File System",
0,
&udf_identify_partition,
&udf_scan_partition,
NULL,
NULL,
&udf_mount,
NULL,
};
module_info *modules[] = {
(module_info *)&sUDFFileSystem,
NULL,
};