* Copyright 2012, Jérôme Duval, korli@users.berlios.de.
* Copyright 2010, Michael Lotz, mmlr@mlotz.ch.
* 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 <io_requests.h>
#include "Icb.h"
#include "Recognition.h"
#include "Utils.h"
#include "Volume.h"
#undef TRACE
#undef TRACE_ERROR
#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;
struct identify_cookie {
struct logical_volume_descriptor logical_volume_descriptor;
};
static status_t
iterative_io_get_vecs_hook(void *cookie, io_request *request, off_t offset,
size_t size, struct file_io_vec *vecs, size_t *count)
{
Icb *icb = (Icb *)cookie;
return file_map_translate(icb->FileMap(), offset, size, vecs, count,
icb->GetVolume()->BlockSize());
}
static status_t
iterative_io_finished_hook(void *cookie, io_request *request, status_t status,
bool partialTransfer, size_t bytesTransferred)
{
return B_OK;
}
static float
udf_identify_partition(int fd, partition_data *partition, void **_cookie)
{
TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %lld, size = %lld "
"content_size = %lld, block_size = %lu\n", fd, partition->id,
partition->offset, partition->size, partition->content_size,
partition->block_size));
primary_volume_descriptor primaryVolumeDescriptor;
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, primaryVolumeDescriptor,
logicalVolumeDescriptor, partitionDescriptors, descriptorCount);
if (error != B_OK)
return -1;
identify_cookie *cookie = new(std::nothrow) identify_cookie;
if (cookie == NULL)
return -1;
cookie->logical_volume_descriptor = logicalVolumeDescriptor;
*_cookie = cookie;
return 0.8f;
}
static status_t
udf_scan_partition(int fd, partition_data *partition, void *_cookie)
{
TRACE(("udf_scan_partition: fd = %d\n", fd));
identify_cookie *cookie = (identify_cookie *)_cookie;
logical_volume_descriptor &volumeDescriptor
= cookie->logical_volume_descriptor;
partition->status = B_PARTITION_VALID;
partition->flags |= B_PARTITION_FILE_SYSTEM;
partition->content_size = partition->size;
partition->block_size = volumeDescriptor.logical_block_size();
UdfString name(volumeDescriptor.logical_volume_identifier());
partition->content_name = strdup(name.Utf8());
if (partition->content_name == NULL)
return B_NO_MEMORY;
return B_OK;
}
static void
udf_free_identify_partition_cookie(partition_data *partition, void *cookie)
{
delete (identify_cookie *)cookie;
}
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 = %lld, blockSize = %lu\n", id,
volume->BlockSize()));
Icb *icb = new(std::nothrow) Icb(volume,
to_long_address(id, volume->BlockSize()));
if (icb == NULL)
return B_NO_MEMORY;
if (icb->InitCheck() == B_OK) {
node->private_node = icb;
node->ops = &gUDFVnodeOps;
*_type = icb->Mode();
*_flags = 0;
return B_OK;
}
TRACE_ERROR(("udf_get_vnode: InitCheck failed\n"));
delete icb;
return B_ERROR;
}
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 = %lld 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 = %ld, st_ino = %lld, 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;
icb->GetAccessTime(stat->st_atim);
icb->GetModificationTime(stat->st_mtim);
icb->GetModificationTime(stat->st_ctim);
icb->GetModificationTime(stat->st_crtim);
TRACE(("udf_read_stat: mode = 0x%x, st_ino: %lld\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 = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
"length = %lu\n",
((Volume *)volume->private_volume)->ID(), pos, *length));
Icb *icb = (Icb *)vnode->private_node;
DEBUG_INIT_ETC("udf_read", ("ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
"length = %lu",
((Volume *)volume->private_volume)->ID(), pos, *length));
RETURN(icb->Read(pos, buffer, length));
}
static status_t
udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request)
{
if (io_request_is_write(request)) {
notify_io_request(request, B_READ_ONLY_DEVICE);
return B_READ_ONLY_DEVICE;
}
Icb *icb = (Icb *)vnode->private_node;
if (icb->FileCache() == NULL) {
notify_io_request(request, B_BAD_VALUE);
return B_BAD_VALUE;
}
return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(),
request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb);
}
static status_t
udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size,
struct file_io_vec *vecs, size_t *count)
{
Icb *icb = (Icb *)vnode->private_node;
return icb->GetFileMap(offset, size, vecs, count);
}
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));
DEBUG_INIT_ETC("udf_open_dir", ("ID = %" B_PRIdDEV,
((Volume *)volume->private_volume)->ID()));
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_NOT_A_DIRECTORY;
}
DirectoryIterator *iterator = NULL;
status_t status = dir->GetDirectoryIterator(&iterator);
if (status != B_OK) {
TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%"
B_PRIx32 ", `%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(struct dirent)) {
return B_BAD_VALUE;
}
Volume *volume = (Volume *)_volume->private_volume;
Icb *dir = (Icb *)vnode->private_node;
DirectoryIterator *iterator = (DirectoryIterator *)cookie;
DEBUG_INIT_ETC("udf_read_dir", ("ID = %" B_PRIdDEV , volume->ID()));
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 - offsetof(struct dirent, d_name);
ino_t id;
status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id);
if (!status) {
TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength));
*_num = 1;
dirent->d_dev = volume->ID();
dirent->d_ino = id;
dirent->d_reclen = offsetof(struct dirent, d_name) + 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));
DEBUG_INIT_ETC("udf_rewind_dir", ("ID = %" B_PRIdDEV,
((Volume *)volume->private_volume)->ID()));
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 Haiku::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, sizeof(partition_info)) == 0) {
TRACE(("partition_info:\n"));
TRACE(("\toffset: %lld\n", info.offset));
TRACE(("\tsize: %lld\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, sizeof(device_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: %lld\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:
init_entities();
return B_OK;
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,
&udf_io,
NULL,
&udf_get_file_map,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
&udf_access,
&udf_read_stat,
NULL,
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,
&udf_free_identify_partition_cookie,
NULL,
&udf_mount,
NULL,
};
module_info *modules[] = {
(module_info *)&sUDFFileSystem,
NULL,
};