Start of a CDDA file system (replacement for cdda as part of BeOS):
* doesn't do anything useful yet, but it compiles (and should be mountable). * CDDA test app "cdda_text" will dump the table of contents as well as eventually existing CD-Text - if I had known only so few titles in my collection would come with those, I probably wouldn't have gone through it (if the samples I used are representable, it's about 1/10th of the CDs I have) :-) * cdda_text compiles on BeOS, so if you have known CD-Text CDs, please test it and report eventual problems - the test app already tries to beautify the names as much as possible. Usage is "cdda_text <path-to-raw-device>". * The test app will be removed later, so better test now ;) git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21109 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
6f709e745c
commit
ff2a7d86f8
@ -2,6 +2,7 @@ SubDir HAIKU_TOP src add-ons kernel file_systems ;
|
||||
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems dos ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems bfs ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems cdda ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems googlefs ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems iso9660 ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel file_systems nfs ;
|
||||
|
13
src/add-ons/kernel/file_systems/cdda/Jamfile
Normal file
13
src/add-ons/kernel/file_systems/cdda/Jamfile
Normal file
@ -0,0 +1,13 @@
|
||||
SubDir HAIKU_TOP src add-ons kernel file_systems cdda ;
|
||||
|
||||
UsePrivateHeaders [ FDirName kernel ] ;
|
||||
UsePrivateHeaders [ FDirName kernel disk_device_manager ] ;
|
||||
UsePrivateHeaders [ FDirName storage ] ;
|
||||
|
||||
KernelAddon cdda :
|
||||
kernel_interface.cpp
|
||||
;
|
||||
|
||||
SimpleTest cdda_text :
|
||||
cdda.cpp
|
||||
: be ;
|
96
src/add-ons/kernel/file_systems/cdda/Lock.h
Normal file
96
src/add-ons/kernel/file_systems/cdda/Lock.h
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2001-2007, Axel Dörfler, axeld@pinc-software.de.
|
||||
* This file may be used under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef LOCK_H
|
||||
#define LOCK_H
|
||||
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
|
||||
#define USE_BENAPHORE
|
||||
// if defined, benaphores are used for the Semaphore class
|
||||
|
||||
class Semaphore {
|
||||
public:
|
||||
Semaphore(const char *name)
|
||||
:
|
||||
#ifdef USE_BENAPHORE
|
||||
fSemaphore(create_sem(0, name)),
|
||||
fCount(1)
|
||||
#else
|
||||
fSemaphore(create_sem(1, name))
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
~Semaphore()
|
||||
{
|
||||
delete_sem(fSemaphore);
|
||||
}
|
||||
|
||||
status_t InitCheck()
|
||||
{
|
||||
if (fSemaphore < B_OK)
|
||||
return fSemaphore;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
status_t Lock()
|
||||
{
|
||||
#ifdef USE_BENAPHORE
|
||||
if (atomic_add(&fCount, -1) <= 0)
|
||||
#endif
|
||||
return acquire_sem(fSemaphore);
|
||||
#ifdef USE_BENAPHORE
|
||||
return B_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
status_t Unlock()
|
||||
{
|
||||
#ifdef USE_BENAPHORE
|
||||
if (atomic_add(&fCount, 1) < 0)
|
||||
#endif
|
||||
return release_sem(fSemaphore);
|
||||
#ifdef USE_BENAPHORE
|
||||
return B_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
sem_id fSemaphore;
|
||||
#ifdef USE_BENAPHORE
|
||||
vint32 fCount;
|
||||
#endif
|
||||
};
|
||||
|
||||
// a convenience class to lock a Semaphore object
|
||||
|
||||
class Locker {
|
||||
public:
|
||||
Locker(Semaphore &lock)
|
||||
: fLock(lock)
|
||||
{
|
||||
fStatus = lock.Lock();
|
||||
}
|
||||
|
||||
~Locker()
|
||||
{
|
||||
if (fStatus == B_OK)
|
||||
fLock.Unlock();
|
||||
}
|
||||
|
||||
status_t Status() const
|
||||
{
|
||||
return fStatus;
|
||||
}
|
||||
|
||||
private:
|
||||
Semaphore &fLock;
|
||||
status_t fStatus;
|
||||
};
|
||||
|
||||
#endif /* LOCK_H */
|
532
src/add-ons/kernel/file_systems/cdda/cdda.cpp
Normal file
532
src/add-ons/kernel/file_systems/cdda/cdda.cpp
Normal file
@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Copyright 2007, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include <bus/scsi/scsi_cmds.h>
|
||||
#include <device/scsi.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define kFramesPerSecond 75
|
||||
#define kFramesPerMinute (kFramesPerSecond * 60)
|
||||
|
||||
struct cdtext_pack_data {
|
||||
uint8 id;
|
||||
uint8 track;
|
||||
uint8 number;
|
||||
uint8 character_position : 4;
|
||||
uint8 block_number : 3;
|
||||
uint8 double_byte : 1;
|
||||
char text[12];
|
||||
uint8 crc[2];
|
||||
} _PACKED;
|
||||
|
||||
|
||||
static const uint8 kMaxTracks = 0x63;
|
||||
enum {
|
||||
kTrackID = 0x80,
|
||||
kArtistID = 0x81,
|
||||
kMessageID = 0x85,
|
||||
};
|
||||
|
||||
struct cdtext {
|
||||
char *artist;
|
||||
char *album;
|
||||
char *titles[kMaxTracks];
|
||||
char *artists[kMaxTracks];
|
||||
uint8 track_count;
|
||||
char *genre;
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - string functions
|
||||
|
||||
|
||||
bool
|
||||
is_garbage(char c)
|
||||
{
|
||||
return isspace(c) || c == '-' || c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
sanitize_string(char *string)
|
||||
{
|
||||
if (string == NULL)
|
||||
return;
|
||||
|
||||
// strip garbage at the start
|
||||
|
||||
uint32 length = strlen(string);
|
||||
uint32 garbage = 0;
|
||||
while (is_garbage(string[garbage])) {
|
||||
garbage++;
|
||||
}
|
||||
|
||||
length -= garbage;
|
||||
if (garbage)
|
||||
memmove(string, string + garbage, length + 1);
|
||||
|
||||
// strip garbage from the end
|
||||
|
||||
while (length > 1 && isspace(string[length - 1])) {
|
||||
string[--length] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! Finds the first occurrence of \a find in \a string, ignores case.
|
||||
static char*
|
||||
find_string(const char *string, const char *find)
|
||||
{
|
||||
if (string == NULL || find == NULL)
|
||||
return NULL;
|
||||
|
||||
char first = tolower(find[0]);
|
||||
if (first == '\0')
|
||||
return (char *)string;
|
||||
|
||||
int32 findLength = strlen(find) - 1;
|
||||
find++;
|
||||
|
||||
for (; string[0]; string++) {
|
||||
if (tolower(string[0]) != first)
|
||||
continue;
|
||||
if (strncasecmp(string + 1, find, findLength) == 0)
|
||||
return (char *)string;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
cut_string(char *string, char *cut)
|
||||
{
|
||||
if (string == NULL || cut == NULL)
|
||||
return;
|
||||
|
||||
char *found = find_string(string, cut);
|
||||
if (found != NULL) {
|
||||
uint32 foundLength = strlen(found);
|
||||
uint32 cutLength = strlen(cut);
|
||||
memmove(found, found + cutLength, foundLength + 1 - cutLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
sanitize_album(cdtext &text)
|
||||
{
|
||||
cut_string(text.album, text.artist);
|
||||
sanitize_string(text.album);
|
||||
|
||||
if ((text.artist == NULL || !text.artist[0]) && text.album != NULL) {
|
||||
// try to extract artist from album
|
||||
char *space = strstr(text.album, " ");
|
||||
if (space != NULL) {
|
||||
space[0] = '\0';
|
||||
text.artist = text.album;
|
||||
text.album = strdup(space + 2);
|
||||
|
||||
sanitize_string(text.artist);
|
||||
sanitize_string(text.album);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
sanitize_titles(cdtext &text)
|
||||
{
|
||||
for (uint8 i = 0; i < text.track_count; i++) {
|
||||
cut_string(text.titles[i], "(Album Version)");
|
||||
sanitize_string(text.titles[i]);
|
||||
sanitize_string(text.artists[i]);
|
||||
if (!strcasecmp(text.artists[i], text.artist)) {
|
||||
// if the title artist is the same as the main artist, remove it
|
||||
free(text.artists[i]);
|
||||
text.artists[i] = NULL;
|
||||
}
|
||||
|
||||
if (text.titles[i] != NULL && text.titles[i][0] == '\t' && i > 0)
|
||||
text.titles[i] = strdup(text.titles[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
single_case(const char *string, bool &upper, bool &first)
|
||||
{
|
||||
if (string == NULL)
|
||||
return true;
|
||||
|
||||
while (string[0]) {
|
||||
while (!isalpha(string[0])) {
|
||||
string++;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
upper = isupper(string[0]) != 0;
|
||||
first = false;
|
||||
} else if ((isupper(string[0]) != 0) ^ upper)
|
||||
return false;
|
||||
|
||||
string++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
capitalize_string(char *string)
|
||||
{
|
||||
if (string == NULL)
|
||||
return;
|
||||
|
||||
bool newWord = isalpha(string[0]) || isspace(string[0]);
|
||||
while (string[0]) {
|
||||
if (isalpha(string[0])) {
|
||||
if (newWord) {
|
||||
string[0] = toupper(string[0]);
|
||||
newWord = false;
|
||||
} else
|
||||
string[0] = tolower(string[0]);
|
||||
} else if (string[0] != '\'')
|
||||
newWord = true;
|
||||
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
correct_case(cdtext &text)
|
||||
{
|
||||
// check if all titles share a single case
|
||||
bool first = true;
|
||||
bool upper;
|
||||
if (!single_case(text.album, upper, first)
|
||||
|| !single_case(text.artist, upper, first))
|
||||
return;
|
||||
|
||||
for (int32 i = 0; i < text.track_count; i++) {
|
||||
if (!single_case(text.titles[i], upper, first)
|
||||
|| !single_case(text.artists[i], upper, first))
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, everything has a single case; we fix that
|
||||
// and capitalize each word
|
||||
|
||||
capitalize_string(text.album);
|
||||
capitalize_string(text.artist);
|
||||
for (int32 i = 0; i < text.track_count; i++) {
|
||||
capitalize_string(text.titles[i]);
|
||||
capitalize_string(text.artists[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - CD-Text
|
||||
|
||||
|
||||
bool
|
||||
is_string_id(uint8 id)
|
||||
{
|
||||
return id >= kTrackID && id <= kMessageID;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft,
|
||||
cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state,
|
||||
char *buffer, size_t &length)
|
||||
{
|
||||
if (packLeft < sizeof(cdtext_pack_data))
|
||||
return false;
|
||||
|
||||
uint8 number = pack->number;
|
||||
size_t size = length;
|
||||
|
||||
if (state != 0) {
|
||||
// we had a terminated string and a missing track
|
||||
track++;
|
||||
memcpy(buffer, lastPack->text + state, 12 - state);
|
||||
if (pack->track - track == 1)
|
||||
state = 0;
|
||||
else
|
||||
state += strnlen(buffer, 12 - state);
|
||||
return true;
|
||||
}
|
||||
|
||||
id = pack->id;
|
||||
track = pack->track;
|
||||
buffer[0] = '\0';
|
||||
length = 0;
|
||||
|
||||
size_t position = pack->character_position;
|
||||
if (position > 0 && lastPack != NULL) {
|
||||
memcpy(buffer, &lastPack->text[12 - position], position);
|
||||
length = position;
|
||||
}
|
||||
|
||||
while (id == pack->id && track == pack->track) {
|
||||
#if 1
|
||||
printf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number,
|
||||
pack->double_byte, pack->block_number, pack->character_position);
|
||||
for (int32 i = 0; i < 12; i++) {
|
||||
if (isprint(pack->text[i]))
|
||||
putchar(pack->text[i]);
|
||||
}
|
||||
putchar('\n');
|
||||
#endif
|
||||
if (is_string_id(id)) {
|
||||
// TODO: support double byte characters
|
||||
if (length + 12 < size) {
|
||||
memcpy(buffer + length, pack->text, 12);
|
||||
length += 12;
|
||||
}
|
||||
}
|
||||
|
||||
packLeft -= sizeof(cdtext_pack_data);
|
||||
if (packLeft < sizeof(cdtext_pack_data))
|
||||
return false;
|
||||
|
||||
lastPack = pack;
|
||||
number++;
|
||||
pack++;
|
||||
|
||||
if (pack->number != number)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id == pack->id) {
|
||||
length -= pack->character_position;
|
||||
if (length >= size)
|
||||
length = size - 1;
|
||||
buffer[length] = '\0';
|
||||
|
||||
if (pack->track > lastPack->track + 1) {
|
||||
// there is a missing track
|
||||
for (int32 i = 0; i < 12; i++) {
|
||||
if (lastPack->text[i] == '\0') {
|
||||
state = i + (lastPack->double_byte ? 2 : 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: convert text to UTF-8
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
dump_cdtext(cdtext_pack_data *pack, size_t packLength)
|
||||
{
|
||||
cdtext_pack_data *lastPack = NULL;
|
||||
char buffer[256];
|
||||
uint8 state = 0;
|
||||
cdtext text;
|
||||
|
||||
memset(&text, 0, sizeof(cdtext));
|
||||
|
||||
while (true) {
|
||||
size_t length = sizeof(buffer);
|
||||
uint8 id, track;
|
||||
|
||||
if (!parse_pack_data(pack, packLength, lastPack, id, track,
|
||||
state, buffer, length))
|
||||
break;
|
||||
|
||||
switch (id) {
|
||||
case kTrackID:
|
||||
if (track == 0) {
|
||||
if (text.album == NULL)
|
||||
text.album = strdup(buffer);
|
||||
} else if (track <= kMaxTracks) {
|
||||
if (text.titles[track - 1] == NULL)
|
||||
text.titles[track - 1] = strdup(buffer);
|
||||
if (track > text.track_count)
|
||||
text.track_count = track;
|
||||
}
|
||||
break;
|
||||
|
||||
case kArtistID:
|
||||
if (track == 0) {
|
||||
if (text.artist == NULL)
|
||||
text.artist = strdup(buffer);
|
||||
} else if (track <= kMaxTracks) {
|
||||
if (text.artists[track - 1] == NULL)
|
||||
text.artists[track - 1] = strdup(buffer);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_string_id(id))
|
||||
printf("UNKNOWN %u: \"%s\"\n", id, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sanitize_string(text.artist);
|
||||
sanitize_album(text);
|
||||
sanitize_titles(text);
|
||||
correct_case(text);
|
||||
|
||||
if (text.album)
|
||||
printf("Album: \"%s\"\n", text.album);
|
||||
if (text.artist)
|
||||
printf("Artist: \"%s\"\n", text.artist);
|
||||
for (uint8 i = 0; i < text.track_count; i++) {
|
||||
printf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i],
|
||||
text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "",
|
||||
text.artists[i] ? ")" : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
dump_toc(scsi_toc_toc *toc)
|
||||
{
|
||||
int32 numTracks = toc->last_track + 1 - toc->first_track;
|
||||
|
||||
for (int32 i = 0; i < numTracks; i++) {
|
||||
scsi_toc_track& track = toc->tracks[i];
|
||||
scsi_cd_msf& next = toc->tracks[i + 1].start.time;
|
||||
// the last track is always lead-out
|
||||
scsi_cd_msf& start = toc->tracks[i].start.time;
|
||||
scsi_cd_msf length;
|
||||
|
||||
uint64 diff = next.minute * kFramesPerMinute
|
||||
+ next.second * kFramesPerSecond + next.frame
|
||||
- start.minute * kFramesPerMinute
|
||||
- start.second * kFramesPerSecond - start.frame;
|
||||
length.minute = diff / kFramesPerMinute;
|
||||
length.second = (diff % kFramesPerMinute) / kFramesPerSecond;
|
||||
length.frame = diff % kFramesPerSecond;
|
||||
|
||||
printf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n",
|
||||
track.track_number, start.minute, start.second, start.frame,
|
||||
length.minute, length.second, length.frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer,
|
||||
size_t bufferSize)
|
||||
{
|
||||
raw_device_command raw;
|
||||
uint8 senseData[1024];
|
||||
|
||||
memset(&raw, 0, sizeof(raw_device_command));
|
||||
memset(senseData, 0, sizeof(senseData));
|
||||
memset(buffer, 0, bufferSize);
|
||||
|
||||
scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command;
|
||||
toc.opcode = SCSI_OP_READ_TOC;
|
||||
toc.time = 1;
|
||||
toc.format = format;
|
||||
toc.track = track;
|
||||
toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize);
|
||||
|
||||
raw.command_length = 10;
|
||||
raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL
|
||||
| B_RAW_DEVICE_SHORT_READ_VALID;
|
||||
raw.scsi_status = 0;
|
||||
raw.cam_status = 0;
|
||||
raw.data = buffer;
|
||||
raw.data_length = bufferSize;
|
||||
raw.timeout = 10000000LL; // 10 secs
|
||||
raw.sense_data = senseData;
|
||||
raw.sense_data_length = sizeof(senseData);
|
||||
|
||||
if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0) {
|
||||
if (raw.scsi_status == 0 && raw.cam_status == 1) {
|
||||
puts("success!\n");
|
||||
} else {
|
||||
puts("failure!\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
dump_block(const uint8 *buffer, int size, const char *prefix)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size;)
|
||||
{
|
||||
int start = i;
|
||||
|
||||
printf(prefix);
|
||||
for (; i < start+16; i++)
|
||||
{
|
||||
if (!(i % 4))
|
||||
printf(" ");
|
||||
|
||||
if (i >= size)
|
||||
printf(" ");
|
||||
else
|
||||
printf("%02x", *(unsigned char *)(buffer + i));
|
||||
}
|
||||
printf(" ");
|
||||
|
||||
for (i = start; i < start + 16; i++)
|
||||
{
|
||||
if (i < size)
|
||||
{
|
||||
char c = buffer[i];
|
||||
|
||||
if (c < 30)
|
||||
printf(".");
|
||||
else
|
||||
printf("%c", c);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
int fd = open(argv[1], O_RDONLY);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
uint8 buffer[1024];
|
||||
scsi_toc_general *header = (scsi_toc_general *)buffer;
|
||||
|
||||
read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC, buffer, sizeof(buffer));
|
||||
header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length);
|
||||
printf("TOC header %u, %d, %d\n", header->data_length, header->first, header->last);
|
||||
//dump_block(buffer, header->data_length + 2, "TOC");
|
||||
dump_toc((scsi_toc_toc *)buffer);
|
||||
|
||||
read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer));
|
||||
read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer));
|
||||
header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length);
|
||||
printf("TEXT header %u, %d, %d\n", header->data_length, header->first, header->last);
|
||||
//dump_block(buffer, header->data_length + 2, "TEXT");
|
||||
|
||||
dump_cdtext((cdtext_pack_data *)(buffer + 4), header->data_length - 2);
|
||||
|
||||
close(fd);
|
||||
}
|
826
src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp
Normal file
826
src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp
Normal file
@ -0,0 +1,826 @@
|
||||
/*
|
||||
* Copyright 2007, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "Lock.h"
|
||||
|
||||
#include <fs_info.h>
|
||||
#include <fs_interface.h>
|
||||
#include <KernelExport.h>
|
||||
|
||||
#include <util/kernel_cpp.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
//#define TRACE_CDDA
|
||||
#ifdef TRACE_CDDA
|
||||
# define TRACE(x) dprintf x
|
||||
#else
|
||||
# define TRACE(x)
|
||||
#endif
|
||||
|
||||
|
||||
class Volume;
|
||||
class Inode;
|
||||
struct dir_cookie;
|
||||
|
||||
class Volume {
|
||||
public:
|
||||
Volume(mount_id id);
|
||||
~Volume();
|
||||
|
||||
status_t InitCheck();
|
||||
mount_id ID() const { return fID; }
|
||||
Inode &RootNode() const { return *fRootNode; }
|
||||
|
||||
status_t Mount(const char* device);
|
||||
int Device() const { return fDevice; }
|
||||
vnode_id GetNextNodeID() { return fNextID++; }
|
||||
|
||||
const char *Name() const { return fName; }
|
||||
status_t SetName(const char *name);
|
||||
|
||||
Semaphore &Lock();
|
||||
|
||||
Inode *Find(vnode_id id);
|
||||
Inode *Find(const char *name);
|
||||
|
||||
Inode *FirstEntry() const { return fFirstEntry; }
|
||||
off_t NumBlocks() const { return fNumBlocks; }
|
||||
|
||||
private:
|
||||
Inode *_CreateNode(Inode *parent, const char *name, int32 type);
|
||||
|
||||
Semaphore fLock;
|
||||
int fDevice;
|
||||
mount_id fID;
|
||||
Inode *fRootNode;
|
||||
vnode_id fNextID;
|
||||
char *fName;
|
||||
off_t fNumBlocks;
|
||||
|
||||
// root directory contents - we don't support other directories
|
||||
Inode *fFirstEntry;
|
||||
};
|
||||
|
||||
|
||||
class Inode {
|
||||
public:
|
||||
Inode(Volume *volume, Inode *parent, const char *name, int32 type);
|
||||
~Inode();
|
||||
|
||||
status_t InitCheck();
|
||||
vnode_id ID() const { return fID; }
|
||||
|
||||
const char *Name() const { return fName; }
|
||||
status_t SetName(const char* name);
|
||||
|
||||
int32 Type() const
|
||||
{ return fType; }
|
||||
gid_t GroupID() const
|
||||
{ return fGroupID; }
|
||||
uid_t UserID() const
|
||||
{ return fUserID; }
|
||||
time_t CreationTime() const
|
||||
{ return fCreationTime; }
|
||||
time_t ModificationTime() const
|
||||
{ return fModificationTime; }
|
||||
off_t Size() const
|
||||
{ return fSize; }
|
||||
|
||||
Inode *Next() const { return fNext; }
|
||||
void SetNext(Inode *inode) { fNext = inode; }
|
||||
|
||||
private:
|
||||
Inode *fNext;
|
||||
vnode_id fID;
|
||||
int32 fType;
|
||||
const char *fName;
|
||||
gid_t fGroupID;
|
||||
uid_t fUserID;
|
||||
time_t fCreationTime;
|
||||
time_t fModificationTime;
|
||||
off_t fSize;
|
||||
};
|
||||
|
||||
|
||||
struct dir_cookie {
|
||||
Inode *current;
|
||||
int state; // iteration state
|
||||
};
|
||||
|
||||
// directory iteration states
|
||||
enum {
|
||||
ITERATION_STATE_DOT = 0,
|
||||
ITERATION_STATE_DOT_DOT = 1,
|
||||
ITERATION_STATE_OTHERS = 2,
|
||||
ITERATION_STATE_BEGIN = ITERATION_STATE_DOT,
|
||||
};
|
||||
|
||||
struct file_cookie {
|
||||
int open_mode;
|
||||
};
|
||||
|
||||
|
||||
Volume::Volume(mount_id id)
|
||||
:
|
||||
fLock("cdda"),
|
||||
fDevice(-1),
|
||||
fID(id),
|
||||
fRootNode(NULL),
|
||||
fNextID(1),
|
||||
fName(NULL),
|
||||
fNumBlocks(0),
|
||||
fFirstEntry(NULL)
|
||||
{
|
||||
// create the root vnode
|
||||
fRootNode = _CreateNode(NULL, "", S_IFDIR | 0777);
|
||||
}
|
||||
|
||||
|
||||
Volume::~Volume()
|
||||
{
|
||||
close(fDevice);
|
||||
|
||||
// put_vnode on the root to release the ref to it
|
||||
if (fRootNode)
|
||||
put_vnode(ID(), fRootNode->ID());
|
||||
|
||||
Inode *inode, *next;
|
||||
|
||||
for (inode = fFirstEntry; inode != NULL; inode = next) {
|
||||
next = inode->Next();
|
||||
delete inode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Volume::InitCheck()
|
||||
{
|
||||
if (fLock.InitCheck() < B_OK
|
||||
|| fRootNode == NULL)
|
||||
return B_ERROR;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Volume::Mount(const char* device)
|
||||
{
|
||||
fDevice = open(device, O_RDONLY);
|
||||
if (fDevice < 0)
|
||||
return errno;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
Semaphore&
|
||||
Volume::Lock()
|
||||
{
|
||||
return fLock;
|
||||
}
|
||||
|
||||
|
||||
Inode *
|
||||
Volume::_CreateNode(Inode *parent, const char *name, int32 type)
|
||||
{
|
||||
Inode *inode = new Inode(this, parent, name, type);
|
||||
if (inode == NULL)
|
||||
return NULL;
|
||||
|
||||
if (inode->InitCheck() != B_OK) {
|
||||
delete inode;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (S_ISREG(type)) {
|
||||
inode->SetNext(fFirstEntry);
|
||||
fFirstEntry = inode;
|
||||
}
|
||||
|
||||
publish_vnode(ID(), inode->ID(), inode);
|
||||
return inode;
|
||||
}
|
||||
|
||||
|
||||
Inode *
|
||||
Volume::Find(vnode_id id)
|
||||
{
|
||||
for (Inode *inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
|
||||
if (inode->ID() == id)
|
||||
return inode;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Inode *
|
||||
Volume::Find(const char *name)
|
||||
{
|
||||
if (!strcmp(name, ".")
|
||||
|| !strcmp(name, ".."))
|
||||
return fRootNode;
|
||||
|
||||
for (Inode *inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
|
||||
if (!strcmp(inode->Name(), name))
|
||||
return inode;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Volume::SetName(const char *name)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
Inode::Inode(Volume *volume, Inode *parent, const char *name, int32 type)
|
||||
:
|
||||
fNext(NULL)
|
||||
{
|
||||
fName = strdup(name);
|
||||
if (fName == NULL)
|
||||
return;
|
||||
|
||||
fID = volume->GetNextNodeID();
|
||||
fType = type;
|
||||
|
||||
fUserID = geteuid();
|
||||
fGroupID = parent ? parent->GroupID() : getegid();
|
||||
|
||||
fCreationTime = fModificationTime = time(NULL);
|
||||
}
|
||||
|
||||
|
||||
Inode::~Inode()
|
||||
{
|
||||
free(const_cast<char *>(fName));
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::InitCheck()
|
||||
{
|
||||
if (fName == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::SetName(const char* name)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - module API
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_mount(mount_id id, const char *device, uint32 flags, const char *args,
|
||||
fs_volume *_volume, vnode_id *_rootVnodeID)
|
||||
{
|
||||
TRACE(("cdda_mount: entry\n"));
|
||||
|
||||
Volume *volume = new Volume(id);
|
||||
if (volume == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
status_t status = volume->InitCheck();
|
||||
if (status == B_OK)
|
||||
status = volume->Mount(device);
|
||||
|
||||
if (status < B_OK) {
|
||||
delete volume;
|
||||
return status;
|
||||
}
|
||||
|
||||
*_rootVnodeID = volume->RootNode().ID();
|
||||
*_volume = volume;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_unmount(fs_volume _volume)
|
||||
{
|
||||
struct Volume *volume = (struct Volume *)_volume;
|
||||
|
||||
TRACE(("cdda_unmount: entry fs = %p\n", _volume));
|
||||
delete volume;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_read_fs_stat(fs_volume _volume, struct fs_info *info)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Locker locker(volume->Lock());
|
||||
|
||||
// File system flags.
|
||||
info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME
|
||||
| B_FS_IS_REMOVABLE;
|
||||
info->io_size = 65536;
|
||||
|
||||
info->block_size = 2048;
|
||||
info->total_blocks = volume->NumBlocks();
|
||||
info->free_blocks = 0;
|
||||
|
||||
// Volume name
|
||||
strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
|
||||
|
||||
// File system name
|
||||
strlcpy(info->fsh_name, "cdda", sizeof(info->fsh_name));
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_write_fs_stat(fs_volume _volume, const struct fs_info *info, uint32 mask)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Locker locker(volume->Lock());
|
||||
|
||||
status_t status = B_BAD_VALUE;
|
||||
|
||||
if (mask & FS_WRITE_FSINFO_NAME)
|
||||
status = volume->SetName(info->volume_name);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_sync(fs_volume fs)
|
||||
{
|
||||
TRACE(("cdda_sync: entry\n"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_lookup(fs_volume _volume, fs_vnode _dir, const char *name, vnode_id *_id, int *_type)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
status_t status;
|
||||
|
||||
TRACE(("cdda_lookup: entry dir %p, name '%s'\n", _dir, name));
|
||||
|
||||
Inode *directory = (Inode *)_dir;
|
||||
if (!S_ISDIR(directory->Type()))
|
||||
return B_NOT_A_DIRECTORY;
|
||||
|
||||
Locker _(volume->Lock());
|
||||
|
||||
Inode *inode = volume->Find(name);
|
||||
if (inode == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
Inode *dummy;
|
||||
status = get_vnode(volume->ID(), inode->ID(), (fs_vnode *)&dummy);
|
||||
if (status < B_OK)
|
||||
return status;
|
||||
|
||||
*_id = inode->ID();
|
||||
*_type = inode->Type();
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_get_vnode_name(fs_volume _volume, fs_vnode _node, char *buffer, size_t bufferSize)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Inode *inode = (Inode *)_node;
|
||||
|
||||
TRACE(("cdda_get_vnode_name(): inode = %p\n", inode));
|
||||
|
||||
Locker _(volume->Lock());
|
||||
strlcpy(buffer, inode->Name(), bufferSize);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_get_vnode(fs_volume _volume, vnode_id id, fs_vnode *_inode, bool reenter)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Inode *inode;
|
||||
|
||||
TRACE(("cdda_getvnode: asking for vnode 0x%Lx, r %d\n", id, reenter));
|
||||
|
||||
inode = volume->Find(id);
|
||||
if (inode == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
*_inode = inode;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_put_vnode(fs_volume _volume, fs_vnode _node, bool reenter)
|
||||
{
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_open(fs_volume _volume, fs_vnode _node, int openMode, fs_cookie *_cookie)
|
||||
{
|
||||
TRACE(("cdda_open(): node = %p, openMode = %d\n", _node, openMode));
|
||||
|
||||
file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie));
|
||||
if (cookie == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
TRACE((" open cookie = %p\n", cookie));
|
||||
cookie->open_mode = openMode;
|
||||
|
||||
*_cookie = (void *)cookie;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_close(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
|
||||
{
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_free_cookie(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
|
||||
{
|
||||
file_cookie *cookie = (file_cookie *)_cookie;
|
||||
|
||||
TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _node, _cookie));
|
||||
|
||||
free(cookie);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_fsync(fs_volume _volume, fs_vnode _v)
|
||||
{
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_read(fs_volume _volume, fs_vnode _node, fs_cookie _cookie, off_t offset,
|
||||
void *buffer, size_t *_length)
|
||||
{
|
||||
file_cookie *cookie = (file_cookie *)_cookie;
|
||||
Inode *inode = (Inode *)_node;
|
||||
|
||||
TRACE(("cdda_read(vnode = %p, offset %Ld, length = %lu, mode = %d)\n",
|
||||
_node, offset, *_length, cookie->open_mode));
|
||||
|
||||
if (S_ISDIR(inode->Type()))
|
||||
return B_IS_A_DIRECTORY;
|
||||
if ((cookie->open_mode & O_RWMASK) != O_RDONLY)
|
||||
return B_NOT_ALLOWED;
|
||||
|
||||
// TODO: read!
|
||||
*_length = 0;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
cdda_can_page(fs_volume _volume, fs_vnode _v, fs_cookie cookie)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_read_pages(fs_volume _volume, fs_vnode _v, fs_cookie cookie, off_t pos,
|
||||
const iovec *vecs, size_t count, size_t *_numBytes, bool reenter)
|
||||
{
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_write_pages(fs_volume _volume, fs_vnode _v, fs_cookie cookie, off_t pos,
|
||||
const iovec *vecs, size_t count, size_t *_numBytes, bool reenter)
|
||||
{
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_read_stat(fs_volume _volume, fs_vnode _node, struct stat *stat)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Inode *inode = (Inode *)_node;
|
||||
|
||||
TRACE(("cdda_read_stat: vnode %p (0x%Lx), stat %p\n", inode, inode->ID(), stat));
|
||||
|
||||
stat->st_dev = volume->ID();
|
||||
stat->st_ino = inode->ID();
|
||||
|
||||
stat->st_size = inode->Size();
|
||||
stat->st_mode = inode->Type();
|
||||
|
||||
stat->st_nlink = 1;
|
||||
stat->st_blksize = 2048;
|
||||
|
||||
stat->st_uid = inode->UserID();
|
||||
stat->st_gid = inode->GroupID();
|
||||
|
||||
stat->st_atime = time(NULL);
|
||||
stat->st_mtime = stat->st_ctime = inode->ModificationTime();
|
||||
stat->st_crtime = inode->CreationTime();
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
cdda_rename(fs_volume _volume, void *_oldDir, const char *oldName, void *_newDir,
|
||||
const char *newName)
|
||||
{
|
||||
if (_volume == NULL || _oldDir == NULL || _newDir == NULL
|
||||
|| oldName == NULL || *oldName == '\0'
|
||||
|| newName == NULL || *newName == '\0'
|
||||
|| !strcmp(oldName, ".") || !strcmp(oldName, "..")
|
||||
|| !strcmp(newName, ".") || !strcmp(newName, "..")
|
||||
|| strchr(newName, '/') != NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - directory functions
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_open_dir(fs_volume _volume, fs_vnode _node, fs_cookie *_cookie)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
|
||||
TRACE(("cdda_open_dir(): vnode = %p\n", _node));
|
||||
|
||||
Inode *inode = (Inode *)_node;
|
||||
if (!S_ISDIR(inode->Type()))
|
||||
return B_BAD_VALUE;
|
||||
|
||||
if (inode != &volume->RootNode())
|
||||
panic("pipefs: found directory that's not the root!");
|
||||
|
||||
dir_cookie *cookie = (dir_cookie *)malloc(sizeof(dir_cookie));
|
||||
if (cookie == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
cookie->current = volume->FirstEntry();
|
||||
cookie->state = ITERATION_STATE_BEGIN;
|
||||
|
||||
*_cookie = (void *)cookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_read_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie,
|
||||
struct dirent *dirent, size_t bufferSize, uint32 *_num)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
Inode *inode = (Inode *)_node;
|
||||
status_t status = 0;
|
||||
|
||||
TRACE(("cdda_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %ld, num = %p\n", _node, _cookie, dirent, bufferSize,_num));
|
||||
|
||||
if (_node != &volume->RootNode())
|
||||
return B_BAD_VALUE;
|
||||
|
||||
Locker _(volume->Lock());
|
||||
|
||||
dir_cookie *cookie = (dir_cookie *)_cookie;
|
||||
Inode *childNode = NULL;
|
||||
const char *name = NULL;
|
||||
Inode *nextChildNode = NULL;
|
||||
int nextState = cookie->state;
|
||||
|
||||
switch (cookie->state) {
|
||||
case ITERATION_STATE_DOT:
|
||||
childNode = inode;
|
||||
name = ".";
|
||||
nextChildNode = volume->FirstEntry();
|
||||
nextState = cookie->state + 1;
|
||||
break;
|
||||
case ITERATION_STATE_DOT_DOT:
|
||||
childNode = inode; // parent of the root node is the root node
|
||||
name = "..";
|
||||
nextChildNode = volume->FirstEntry();
|
||||
nextState = cookie->state + 1;
|
||||
break;
|
||||
default:
|
||||
childNode = cookie->current;
|
||||
if (childNode) {
|
||||
name = childNode->Name();
|
||||
nextChildNode = childNode->Next();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!childNode) {
|
||||
// we're at the end of the directory
|
||||
*_num = 0;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
dirent->d_dev = volume->ID();
|
||||
dirent->d_ino = inode->ID();
|
||||
dirent->d_reclen = strlen(name) + sizeof(struct dirent);
|
||||
|
||||
if (dirent->d_reclen > bufferSize)
|
||||
return ENOBUFS;
|
||||
|
||||
status = user_strlcpy(dirent->d_name, name, bufferSize);
|
||||
if (status < B_OK)
|
||||
return status;
|
||||
|
||||
cookie->current = nextChildNode;
|
||||
cookie->state = nextState;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_rewind_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
|
||||
{
|
||||
Volume *volume = (Volume *)_volume;
|
||||
|
||||
dir_cookie *cookie = (dir_cookie *)_cookie;
|
||||
cookie->current = volume->FirstEntry();
|
||||
cookie->state = ITERATION_STATE_BEGIN;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_close_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
|
||||
{
|
||||
TRACE(("cdda_close: entry vnode %p, cookie %p\n", _node, _cookie));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_free_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
|
||||
{
|
||||
dir_cookie *cookie = (dir_cookie *)_cookie;
|
||||
|
||||
TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _vnode, cookie));
|
||||
|
||||
free(cookie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
cdda_std_ops(int32 op, ...)
|
||||
{
|
||||
switch (op) {
|
||||
case B_MODULE_INIT:
|
||||
return B_OK;
|
||||
|
||||
case B_MODULE_UNINIT:
|
||||
return B_OK;
|
||||
|
||||
default:
|
||||
return B_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static file_system_module_info sCDDAFileSystem = {
|
||||
{
|
||||
"file_systems/cdda" B_CURRENT_FS_API_VERSION,
|
||||
0,
|
||||
cdda_std_ops,
|
||||
},
|
||||
|
||||
"CDDA File System",
|
||||
|
||||
NULL, // identify_partition()
|
||||
NULL, // scan_partition()
|
||||
NULL, // free_identify_partition_cookie()
|
||||
NULL, // free_partition_content_cookie()
|
||||
|
||||
&cdda_mount,
|
||||
&cdda_unmount,
|
||||
&cdda_read_fs_stat,
|
||||
&cdda_write_fs_stat,
|
||||
&cdda_sync,
|
||||
|
||||
&cdda_lookup,
|
||||
&cdda_get_vnode_name,
|
||||
|
||||
&cdda_get_vnode,
|
||||
&cdda_put_vnode,
|
||||
NULL, // fs_remove_vnode()
|
||||
|
||||
&cdda_can_page,
|
||||
&cdda_read_pages,
|
||||
&cdda_write_pages,
|
||||
|
||||
NULL, // get_file_map()
|
||||
|
||||
// common
|
||||
NULL, // fs_ioctl()
|
||||
NULL, // fs_set_flags()
|
||||
NULL, // fs_select()
|
||||
NULL, // fs_deselect()
|
||||
&cdda_fsync,
|
||||
|
||||
NULL, // fs_read_link()
|
||||
NULL, // fs_symlink()
|
||||
NULL, // fs_link()
|
||||
NULL, // fs_unlink()
|
||||
NULL, // fs_rename()
|
||||
|
||||
NULL, // fs_access()
|
||||
&cdda_read_stat,
|
||||
NULL, // fs_write_stat()
|
||||
|
||||
// file
|
||||
NULL, // fs_create()
|
||||
&cdda_open,
|
||||
&cdda_close,
|
||||
&cdda_free_cookie,
|
||||
&cdda_read,
|
||||
NULL, // fs_write()
|
||||
|
||||
// directory
|
||||
NULL, // fs_create_dir()
|
||||
NULL, // fs_remove_dir()
|
||||
&cdda_open_dir,
|
||||
&cdda_close_dir,
|
||||
&cdda_free_dir_cookie,
|
||||
&cdda_read_dir,
|
||||
&cdda_rewind_dir,
|
||||
|
||||
#if 0
|
||||
// attribute directory operations
|
||||
&cdda_open_attr_dir,
|
||||
&cdda_close_attr_dir,
|
||||
&cdda_free_attr_dir_cookie,
|
||||
&cdda_read_attr_dir,
|
||||
&cdda_rewind_attr_dir,
|
||||
|
||||
// attribute operations
|
||||
&cdda_create_attr,
|
||||
&cdda_open_attr,
|
||||
&cdda_close_attr,
|
||||
&cdda_free_attr_cookie,
|
||||
&cdda_read_attr,
|
||||
&cdda_write_attr,
|
||||
|
||||
&cdda_read_attr_stat,
|
||||
&cdda_write_attr_stat,
|
||||
&cdda_rename_attr,
|
||||
&cdda_remove_attr,
|
||||
#endif
|
||||
|
||||
// the other operations are not yet supported (indices, queries)
|
||||
NULL,
|
||||
};
|
||||
|
||||
module_info *modules[] = {
|
||||
(module_info *)&sCDDAFileSystem,
|
||||
NULL,
|
||||
};
|
Loading…
Reference in New Issue
Block a user