fs: initial qnx6fs addition

Adds support for qnx6fs readonly support to the linux kernel.

* Mount option
  The option mmi_fs can be used to mount Harman Becker/Audi MMI 3G
  HDD qnx6fs filesystems.

* Documentation
  A high level filesystem stucture description can be found in the
  Documentation/filesystems directory. (qnx6.txt)

* Additional features
  - Active (stable) superblock selection
  - Superblock checksum check (enforced)
  - Supports mount of qnx6 filesystems with to host different endianess
  - Automatic endianess detection
  - Longfilename support (with non-enfocing crc check)
  - All blocksizes (512, 1024, 2048 and 4096 supported)

Signed-off-by: Kai Bankett <chaosman@ontika.net>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Kai Bankett 2012-02-17 05:59:20 +01:00 committed by Al Viro
parent 516cdb68e5
commit 5d026c7242
13 changed files with 1668 additions and 0 deletions

View File

@ -0,0 +1,174 @@
The QNX6 Filesystem
===================
The qnx6fs is used by newer QNX operating system versions. (e.g. Neutrino)
It got introduced in QNX 6.4.0 and is used default since 6.4.1.
Option
======
mmi_fs Mount filesystem as used for example by Audi MMI 3G system
Specification
=============
qnx6fs shares many properties with traditional Unix filesystems. It has the
concepts of blocks, inodes and directories.
On QNX it is possible to create little endian and big endian qnx6 filesystems.
This feature makes it possible to create and use a different endianness fs
for the target (QNX is used on quite a range of embedded systems) plattform
running on a different endianess.
The Linux driver handles endianness transparently. (LE and BE)
Blocks
------
The space in the device or file is split up into blocks. These are a fixed
size of 512, 1024, 2048 or 4096, which is decided when the filesystem is
created.
Blockpointers are 32bit, so the maximum space that can be adressed is
2^32 * 4096 bytes or 16TB
The superblocks
---------------
The superblock contains all global information about the filesystem.
Each qnx6fs got two superblocks, each one having a 64bit serial number.
That serial number is used to identify the "active" superblock.
In write mode with reach new snapshot (after each synchronous write), the
serial of the new master superblock is increased (old superblock serial + 1)
So basically the snapshot functionality is realized by an atomic final
update of the serial number. Before updating that serial, all modifications
are done by copying all modified blocks during that specific write request
(or period) and building up a new (stable) filesystem structure under the
inactive superblock.
Each superblock holds a set of root inodes for the different filesystem
parts. (Inode, Bitmap and Longfilenames)
Each of these root nodes holds information like total size of the stored
data and the adressing levels in that specific tree.
If the level value is 0, up to 16 direct blocks can be adressed by each
node.
Level 1 adds an additional indirect adressing level where each indirect
adressing block holds up to blocksize / 4 bytes pointers to data blocks.
Level 2 adds an additional indirect adressig block level (so, already up
to 16 * 256 * 256 = 1048576 blocks that can be adressed by such a tree)a
Unused block pointers are always set to ~0 - regardless of root node,
indirect adressing blocks or inodes.
Data leaves are always on the lowest level. So no data is stored on upper
tree levels.
The first Superblock is located at 0x2000. (0x2000 is the bootblock size)
The Audi MMI 3G first superblock directly starts at byte 0.
Second superblock position can either be calculated from the superblock
information (total number of filesystem blocks) or by taking the highest
device address, zeroing the last 3 bytes and then substracting 0x1000 from
that address.
0x1000 is the size reserved for each superblock - regardless of the
blocksize of the filesystem.
Inodes
------
Each object in the filesystem is represented by an inode. (index node)
The inode structure contains pointers to the filesystem blocks which contain
the data held in the object and all of the metadata about an object except
its longname. (filenames longer than 27 characters)
The metadata about an object includes the permissions, owner, group, flags,
size, number of blocks used, access time, change time and modification time.
Object mode field is POSIX format. (which makes things easier)
There are also pointers to the first 16 blocks, if the object data can be
adressed with 16 direct blocks.
For more than 16 blocks an indirect adressing in form of another tree is
used. (scheme is the same as the one used for the superblock root nodes)
The filesize is stored 64bit. Inode counting starts with 1. (whilst long
filename inodes start with 0)
Directories
-----------
A directory is a filesystem object and has an inode just like a file.
It is a specially formatted file containing records which associate each
name with an inode number.
'.' inode number points to the directory inode
'..' inode number points to the parent directory inode
Eeach filename record additionally got a filename length field.
One special case are long filenames or subdirectory names.
These got set a filename length field of 0xff in the corresponding directory
record plus the longfile inode number also stored in that record.
With that longfilename inode number, the longfilename tree can be walked
starting with the superblock longfilename root node pointers.
Special files
-------------
Symbolic links are also filesystem objects with inodes. They got a specific
bit in the inode mode field identifying them as symbolic link.
The directory entry file inode pointer points to the target file inode.
Hard links got an inode, a directory entry, but a specific mode bit set,
no block pointers and the directory file record pointing to the target file
inode.
Character and block special devices do not exist in QNX as those files
are handled by the QNX kernel/drivers and created in /dev independant of the
underlaying filesystem.
Long filenames
--------------
Long filenames are stored in a seperate adressing tree. The staring point
is the longfilename root node in the active superblock.
Each data block (tree leaves) holds one long filename. That filename is
limited to 510 bytes. The first two starting bytes are used as length field
for the actual filename.
If that structure shall fit for all allowed blocksizes, it is clear why there
is a limit of 510 bytes for the actual filename stored.
Bitmap
------
The qnx6fs filesystem allocation bitmap is stored in a tree under bitmap
root node in the superblock and each bit in the bitmap represents one
filesystem block.
The first block is block 0, which starts 0x1000 after superblock start.
So for a normal qnx6fs 0x3000 (bootblock + superblock) is the physical
address at which block 0 is located.
Bits at the end of the last bitmap block are set to 1, if the device is
smaller than addressing space in the bitmap.
Bitmap system area
------------------
The bitmap itself is devided into three parts.
First the system area, that is split into two halfs.
Then userspace.
The requirement for a static, fixed preallocated system area comes from how
qnx6fs deals with writes.
Each superblock got it's own half of the system area. So superblock #1
always uses blocks from the lower half whilst superblock #2 just writes to
blocks represented by the upper half bitmap system area bits.
Bitmap blocks, Inode blocks and indirect addressing blocks for those two
tree structures are treated as system blocks.
The rational behind that is that a write request can work on a new snapshot
(system area of the inactive - resp. lower serial numbered superblock) while
at the same time there is still a complete stable filesystem structer in the
other half of the system area.
When finished with writing (a sync write is completed, the maximum sync leap
time or a filesystem sync is requested), serial of the previously inactive
superblock atomically is increased and the fs switches over to that - then
stable declared - superblock.
For all data outside the system area, blocks are just copied while writing.

View File

@ -210,6 +210,7 @@ source "fs/minix/Kconfig"
source "fs/omfs/Kconfig"
source "fs/hpfs/Kconfig"
source "fs/qnx4/Kconfig"
source "fs/qnx6/Kconfig"
source "fs/romfs/Kconfig"
source "fs/pstore/Kconfig"
source "fs/sysv/Kconfig"

View File

@ -102,6 +102,7 @@ obj-$(CONFIG_UBIFS_FS) += ubifs/
obj-$(CONFIG_AFFS_FS) += affs/
obj-$(CONFIG_ROMFS_FS) += romfs/
obj-$(CONFIG_QNX4FS_FS) += qnx4/
obj-$(CONFIG_QNX6FS_FS) += qnx6/
obj-$(CONFIG_AUTOFS4_FS) += autofs4/
obj-$(CONFIG_ADFS_FS) += adfs/
obj-$(CONFIG_FUSE_FS) += fuse/

26
fs/qnx6/Kconfig Normal file
View File

@ -0,0 +1,26 @@
config QNX6FS_FS
tristate "QNX6 file system support (read only)"
depends on BLOCK && CRC32
help
This is the file system used by the real-time operating systems
QNX 6 (also called QNX RTP).
Further information is available at <http://www.qnx.com/>.
Say Y if you intend to mount QNX hard disks or floppies formatted
with a mkqnx6fs.
However, keep in mind that this currently is a readonly driver!
To compile this file system support as a module, choose M here: the
module will be called qnx6.
If you don't know whether you need it, then you don't need it:
answer N.
config QNX6FS_DEBUG
bool "QNX6 debugging information"
depends on QNX6FS_FS
help
Turns on extended debugging output.
If you are not a developer working on the QNX6FS, you probably don't
want this:
answer N.

7
fs/qnx6/Makefile Normal file
View File

@ -0,0 +1,7 @@
#
# Makefile for the linux qnx4-filesystem routines.
#
obj-$(CONFIG_QNX6FS_FS) += qnx6.o
qnx6-objs := inode.o dir.o namei.o super_mmi.o

8
fs/qnx6/README Normal file
View File

@ -0,0 +1,8 @@
This is a snapshot of the QNX6 filesystem for Linux.
Please send diffs and remarks to <chaosman@ontika.net> .
Credits :
Al Viro <viro@ZenIV.linux.org.uk> (endless patience with me & support ;))
Kai Bankett <chaosman@ontika.net> (Maintainer)

291
fs/qnx6/dir.c Normal file
View File

@ -0,0 +1,291 @@
/*
* QNX6 file system, Linux implementation.
*
* Version : 1.0.0
*
* History :
*
* 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
* 16-02-2012 pagemap extension by Al Viro
*
*/
#include "qnx6.h"
static unsigned qnx6_lfile_checksum(char *name, unsigned size)
{
unsigned crc = 0;
char *end = name + size;
while (name < end) {
crc = ((crc >> 1) + *(name++)) ^
((crc & 0x00000001) ? 0x80000000 : 0);
}
return crc;
}
static struct page *qnx6_get_page(struct inode *dir, unsigned long n)
{
struct address_space *mapping = dir->i_mapping;
struct page *page = read_mapping_page(mapping, n, NULL);
if (!IS_ERR(page))
kmap(page);
return page;
}
static inline unsigned long dir_pages(struct inode *inode)
{
return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
}
static unsigned last_entry(struct inode *inode, unsigned long page_nr)
{
unsigned long last_byte = inode->i_size;
last_byte -= page_nr << PAGE_CACHE_SHIFT;
if (last_byte > PAGE_CACHE_SIZE)
last_byte = PAGE_CACHE_SIZE;
return last_byte / QNX6_DIR_ENTRY_SIZE;
}
static struct qnx6_long_filename *qnx6_longname(struct super_block *sb,
struct qnx6_long_dir_entry *de,
struct page **p)
{
struct qnx6_sb_info *sbi = QNX6_SB(sb);
u32 s = fs32_to_cpu(sbi, de->de_long_inode); /* in block units */
u32 n = s >> (PAGE_CACHE_SHIFT - sb->s_blocksize_bits); /* in pages */
/* within page */
u32 offs = (s << sb->s_blocksize_bits) & ~PAGE_CACHE_MASK;
struct address_space *mapping = sbi->longfile->i_mapping;
struct page *page = read_mapping_page(mapping, n, NULL);
if (IS_ERR(page))
return ERR_CAST(page);
kmap(*p = page);
return (struct qnx6_long_filename *)(page_address(page) + offs);
}
static int qnx6_dir_longfilename(struct inode *inode,
struct qnx6_long_dir_entry *de,
void *dirent, loff_t pos,
unsigned de_inode, filldir_t filldir)
{
struct qnx6_long_filename *lf;
struct super_block *s = inode->i_sb;
struct qnx6_sb_info *sbi = QNX6_SB(s);
struct page *page;
int lf_size;
if (de->de_size != 0xff) {
/* error - long filename entries always have size 0xff
in direntry */
printk(KERN_ERR "qnx6: invalid direntry size (%i).\n",
de->de_size);
return 0;
}
lf = qnx6_longname(s, de, &page);
if (IS_ERR(lf)) {
printk(KERN_ERR "qnx6:Error reading longname\n");
return 0;
}
lf_size = fs16_to_cpu(sbi, lf->lf_size);
if (lf_size > QNX6_LONG_NAME_MAX) {
QNX6DEBUG((KERN_INFO "file %s\n", lf->lf_fname));
printk(KERN_ERR "qnx6:Filename too long (%i)\n", lf_size);
qnx6_put_page(page);
return 0;
}
/* calc & validate longfilename checksum
mmi 3g filesystem does not have that checksum */
if (!test_opt(s, MMI_FS) && fs32_to_cpu(sbi, de->de_checksum) !=
qnx6_lfile_checksum(lf->lf_fname, lf_size))
printk(KERN_INFO "qnx6: long filename checksum error.\n");
QNX6DEBUG((KERN_INFO "qnx6_readdir:%.*s inode:%u\n",
lf_size, lf->lf_fname, de_inode));
if (filldir(dirent, lf->lf_fname, lf_size, pos, de_inode,
DT_UNKNOWN) < 0) {
qnx6_put_page(page);
return 0;
}
qnx6_put_page(page);
/* success */
return 1;
}
static int qnx6_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct inode *inode = filp->f_path.dentry->d_inode;
struct super_block *s = inode->i_sb;
struct qnx6_sb_info *sbi = QNX6_SB(s);
loff_t pos = filp->f_pos & (QNX6_DIR_ENTRY_SIZE - 1);
unsigned long npages = dir_pages(inode);
unsigned long n = pos >> PAGE_CACHE_SHIFT;
unsigned start = (pos & ~PAGE_CACHE_MASK) / QNX6_DIR_ENTRY_SIZE;
bool done = false;
if (filp->f_pos >= inode->i_size)
return 0;
for ( ; !done && n < npages; n++, start = 0) {
struct page *page = qnx6_get_page(inode, n);
int limit = last_entry(inode, n);
struct qnx6_dir_entry *de;
int i = start;
if (IS_ERR(page)) {
printk(KERN_ERR "qnx6_readdir: read failed\n");
filp->f_pos = (n + 1) << PAGE_CACHE_SHIFT;
return PTR_ERR(page);
}
de = ((struct qnx6_dir_entry *)page_address(page)) + start;
for (; i < limit; i++, de++, pos += QNX6_DIR_ENTRY_SIZE) {
int size = de->de_size;
u32 no_inode = fs32_to_cpu(sbi, de->de_inode);
if (!no_inode || !size)
continue;
if (size > QNX6_SHORT_NAME_MAX) {
/* long filename detected
get the filename from long filename
structure / block */
if (!qnx6_dir_longfilename(inode,
(struct qnx6_long_dir_entry *)de,
dirent, pos, no_inode,
filldir)) {
done = true;
break;
}
} else {
QNX6DEBUG((KERN_INFO "qnx6_readdir:%.*s"
" inode:%u\n", size, de->de_fname,
no_inode));
if (filldir(dirent, de->de_fname, size,
pos, no_inode, DT_UNKNOWN)
< 0) {
done = true;
break;
}
}
}
qnx6_put_page(page);
}
filp->f_pos = pos;
return 0;
}
/*
* check if the long filename is correct.
*/
static unsigned qnx6_long_match(int len, const char *name,
struct qnx6_long_dir_entry *de, struct inode *dir)
{
struct super_block *s = dir->i_sb;
struct qnx6_sb_info *sbi = QNX6_SB(s);
struct page *page;
int thislen;
struct qnx6_long_filename *lf = qnx6_longname(s, de, &page);
if (IS_ERR(lf))
return 0;
thislen = fs16_to_cpu(sbi, lf->lf_size);
if (len != thislen) {
qnx6_put_page(page);
return 0;
}
if (memcmp(name, lf->lf_fname, len) == 0) {
qnx6_put_page(page);
return fs32_to_cpu(sbi, de->de_inode);
}
qnx6_put_page(page);
return 0;
}
/*
* check if the filename is correct.
*/
static unsigned qnx6_match(struct super_block *s, int len, const char *name,
struct qnx6_dir_entry *de)
{
struct qnx6_sb_info *sbi = QNX6_SB(s);
if (memcmp(name, de->de_fname, len) == 0)
return fs32_to_cpu(sbi, de->de_inode);
return 0;
}
unsigned qnx6_find_entry(int len, struct inode *dir, const char *name,
struct page **res_page)
{
struct super_block *s = dir->i_sb;
struct qnx6_inode_info *ei = QNX6_I(dir);
struct page *page = NULL;
unsigned long start, n;
unsigned long npages = dir_pages(dir);
unsigned ino;
struct qnx6_dir_entry *de;
struct qnx6_long_dir_entry *lde;
*res_page = NULL;
if (npages == 0)
return 0;
start = ei->i_dir_start_lookup;
if (start >= npages)
start = 0;
n = start;
do {
page = qnx6_get_page(dir, n);
if (!IS_ERR(page)) {
int limit = last_entry(dir, n);
int i;
de = (struct qnx6_dir_entry *)page_address(page);
for (i = 0; i < limit; i++, de++) {
if (len <= QNX6_SHORT_NAME_MAX) {
/* short filename */
if (len != de->de_size)
continue;
ino = qnx6_match(s, len, name, de);
if (ino)
goto found;
} else if (de->de_size == 0xff) {
/* deal with long filename */
lde = (struct qnx6_long_dir_entry *)de;
ino = qnx6_long_match(len,
name, lde, dir);
if (ino)
goto found;
} else
printk(KERN_ERR "qnx6: undefined "
"filename size in inode.\n");
}
qnx6_put_page(page);
}
if (++n >= npages)
n = 0;
} while (n != start);
return 0;
found:
*res_page = page;
ei->i_dir_start_lookup = n;
return ino;
}
const struct file_operations qnx6_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.readdir = qnx6_readdir,
.fsync = generic_file_fsync,
};
const struct inode_operations qnx6_dir_inode_operations = {
.lookup = qnx6_lookup,
};

698
fs/qnx6/inode.c Normal file
View File

@ -0,0 +1,698 @@
/*
* QNX6 file system, Linux implementation.
*
* Version : 1.0.0
*
* History :
*
* 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
* 16-02-2012 pagemap extension by Al Viro
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/highuid.h>
#include <linux/pagemap.h>
#include <linux/buffer_head.h>
#include <linux/writeback.h>
#include <linux/statfs.h>
#include <linux/parser.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/crc32.h>
#include <linux/mpage.h>
#include "qnx6.h"
static const struct super_operations qnx6_sops;
static void qnx6_put_super(struct super_block *sb);
static struct inode *qnx6_alloc_inode(struct super_block *sb);
static void qnx6_destroy_inode(struct inode *inode);
static int qnx6_remount(struct super_block *sb, int *flags, char *data);
static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf);
static int qnx6_show_options(struct seq_file *seq, struct dentry *root);
static const struct super_operations qnx6_sops = {
.alloc_inode = qnx6_alloc_inode,
.destroy_inode = qnx6_destroy_inode,
.put_super = qnx6_put_super,
.statfs = qnx6_statfs,
.remount_fs = qnx6_remount,
.show_options = qnx6_show_options,
};
static int qnx6_show_options(struct seq_file *seq, struct dentry *root)
{
struct super_block *sb = root->d_sb;
struct qnx6_sb_info *sbi = QNX6_SB(sb);
if (sbi->s_mount_opt & QNX6_MOUNT_MMI_FS)
seq_puts(seq, ",mmi_fs");
return 0;
}
static int qnx6_remount(struct super_block *sb, int *flags, char *data)
{
*flags |= MS_RDONLY;
return 0;
}
static unsigned qnx6_get_devblock(struct super_block *sb, __fs32 block)
{
struct qnx6_sb_info *sbi = QNX6_SB(sb);
return fs32_to_cpu(sbi, block) + sbi->s_blks_off;
}
static unsigned qnx6_block_map(struct inode *inode, unsigned iblock);
static int qnx6_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh, int create)
{
unsigned phys;
QNX6DEBUG((KERN_INFO "qnx6: qnx6_get_block inode=[%ld] iblock=[%ld]\n",
inode->i_ino, (unsigned long)iblock));
phys = qnx6_block_map(inode, iblock);
if (phys) {
/* logical block is before EOF */
map_bh(bh, inode->i_sb, phys);
}
return 0;
}
static int qnx6_check_blockptr(__fs32 ptr)
{
if (ptr == ~(__fs32)0) {
printk(KERN_ERR "qnx6: hit unused blockpointer.\n");
return 0;
}
return 1;
}
static int qnx6_readpage(struct file *file, struct page *page)
{
return mpage_readpage(page, qnx6_get_block);
}
static int qnx6_readpages(struct file *file, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages)
{
return mpage_readpages(mapping, pages, nr_pages, qnx6_get_block);
}
/*
* returns the block number for the no-th element in the tree
* inodebits requred as there are multiple inodes in one inode block
*/
static unsigned qnx6_block_map(struct inode *inode, unsigned no)
{
struct super_block *s = inode->i_sb;
struct qnx6_sb_info *sbi = QNX6_SB(s);
struct qnx6_inode_info *ei = QNX6_I(inode);
unsigned block = 0;
struct buffer_head *bh;
__fs32 ptr;
int levelptr;
int ptrbits = sbi->s_ptrbits;
int bitdelta;
u32 mask = (1 << ptrbits) - 1;
int depth = ei->di_filelevels;
int i;
bitdelta = ptrbits * depth;
levelptr = no >> bitdelta;
if (levelptr > QNX6_NO_DIRECT_POINTERS - 1) {
printk(KERN_ERR "qnx6:Requested file block number (%u) too big.",
no);
return 0;
}
block = qnx6_get_devblock(s, ei->di_block_ptr[levelptr]);
for (i = 0; i < depth; i++) {
bh = sb_bread(s, block);
if (!bh) {
printk(KERN_ERR "qnx6:Error reading block (%u)\n",
block);
return 0;
}
bitdelta -= ptrbits;
levelptr = (no >> bitdelta) & mask;
ptr = ((__fs32 *)bh->b_data)[levelptr];
if (!qnx6_check_blockptr(ptr))
return 0;
block = qnx6_get_devblock(s, ptr);
brelse(bh);
}
return block;
}
static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct super_block *sb = dentry->d_sb;
struct qnx6_sb_info *sbi = QNX6_SB(sb);
u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
buf->f_type = sb->s_magic;
buf->f_bsize = sb->s_blocksize;
buf->f_blocks = fs32_to_cpu(sbi, sbi->sb->sb_num_blocks);
buf->f_bfree = fs32_to_cpu(sbi, sbi->sb->sb_free_blocks);
buf->f_files = fs32_to_cpu(sbi, sbi->sb->sb_num_inodes);
buf->f_ffree = fs32_to_cpu(sbi, sbi->sb->sb_free_inodes);
buf->f_bavail = buf->f_bfree;
buf->f_namelen = QNX6_LONG_NAME_MAX;
buf->f_fsid.val[0] = (u32)id;
buf->f_fsid.val[1] = (u32)(id >> 32);
return 0;
}
/*
* Check the root directory of the filesystem to make sure
* it really _is_ a qnx6 filesystem, and to check the size
* of the directory entry.
*/
static const char *qnx6_checkroot(struct super_block *s)
{
static char match_root[2][3] = {".\0\0", "..\0"};
int i, error = 0;
struct qnx6_dir_entry *dir_entry;
struct inode *root = s->s_root->d_inode;
struct address_space *mapping = root->i_mapping;
struct page *page = read_mapping_page(mapping, 0, NULL);
if (IS_ERR(page))
return "error reading root directory";
kmap(page);
dir_entry = page_address(page);
for (i = 0; i < 2; i++) {
/* maximum 3 bytes - due to match_root limitation */
if (strncmp(dir_entry[i].de_fname, match_root[i], 3))
error = 1;
}
qnx6_put_page(page);
if (error)
return "error reading root directory.";
return NULL;
}
#ifdef CONFIG_QNX6FS_DEBUG
void qnx6_superblock_debug(struct qnx6_super_block *sb, struct super_block *s)
{
struct qnx6_sb_info *sbi = QNX6_SB(s);
QNX6DEBUG((KERN_INFO "magic: %08x\n",
fs32_to_cpu(sbi, sb->sb_magic)));
QNX6DEBUG((KERN_INFO "checksum: %08x\n",
fs32_to_cpu(sbi, sb->sb_checksum)));
QNX6DEBUG((KERN_INFO "serial: %llx\n",
fs64_to_cpu(sbi, sb->sb_serial)));
QNX6DEBUG((KERN_INFO "flags: %08x\n",
fs32_to_cpu(sbi, sb->sb_flags)));
QNX6DEBUG((KERN_INFO "blocksize: %08x\n",
fs32_to_cpu(sbi, sb->sb_blocksize)));
QNX6DEBUG((KERN_INFO "num_inodes: %08x\n",
fs32_to_cpu(sbi, sb->sb_num_inodes)));
QNX6DEBUG((KERN_INFO "free_inodes: %08x\n",
fs32_to_cpu(sbi, sb->sb_free_inodes)));
QNX6DEBUG((KERN_INFO "num_blocks: %08x\n",
fs32_to_cpu(sbi, sb->sb_num_blocks)));
QNX6DEBUG((KERN_INFO "free_blocks: %08x\n",
fs32_to_cpu(sbi, sb->sb_free_blocks)));
QNX6DEBUG((KERN_INFO "inode_levels: %02x\n",
sb->Inode.levels));
}
#endif
enum {
Opt_mmifs,
Opt_err
};
static const match_table_t tokens = {
{Opt_mmifs, "mmi_fs"},
{Opt_err, NULL}
};
static int qnx6_parse_options(char *options, struct super_block *sb)
{
char *p;
struct qnx6_sb_info *sbi = QNX6_SB(sb);
substring_t args[MAX_OPT_ARGS];
if (!options)
return 1;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_mmifs:
set_opt(sbi->s_mount_opt, MMI_FS);
break;
default:
return 0;
}
}
return 1;
}
static struct buffer_head *qnx6_check_first_superblock(struct super_block *s,
int offset, int silent)
{
struct qnx6_sb_info *sbi = QNX6_SB(s);
struct buffer_head *bh;
struct qnx6_super_block *sb;
/* Check the superblock signatures
start with the first superblock */
bh = sb_bread(s, offset);
if (!bh) {
printk(KERN_ERR "qnx6: unable to read the first superblock\n");
return NULL;
}
sb = (struct qnx6_super_block *)bh->b_data;
if (fs32_to_cpu(sbi, sb->sb_magic) != QNX6_SUPER_MAGIC) {
sbi->s_bytesex = BYTESEX_BE;
if (fs32_to_cpu(sbi, sb->sb_magic) == QNX6_SUPER_MAGIC) {
/* we got a big endian fs */
QNX6DEBUG((KERN_INFO "qnx6: fs got different"
" endianess.\n"));
return bh;
} else
sbi->s_bytesex = BYTESEX_LE;
if (!silent) {
if (offset == 0) {
printk(KERN_ERR "qnx6: wrong signature (magic)"
" in superblock #1.\n");
} else {
printk(KERN_INFO "qnx6: wrong signature (magic)"
" at position (0x%lx) - will try"
" alternative position (0x0000).\n",
offset * s->s_blocksize);
}
}
brelse(bh);
return NULL;
}
return bh;
}
static struct inode *qnx6_private_inode(struct super_block *s,
struct qnx6_root_node *p);
static int qnx6_fill_super(struct super_block *s, void *data, int silent)
{
struct buffer_head *bh1 = NULL, *bh2 = NULL;
struct qnx6_super_block *sb1 = NULL, *sb2 = NULL;
struct qnx6_sb_info *sbi;
struct inode *root;
const char *errmsg;
struct qnx6_sb_info *qs;
int ret = -EINVAL;
u64 offset;
int bootblock_offset = QNX6_BOOTBLOCK_SIZE;
qs = kzalloc(sizeof(struct qnx6_sb_info), GFP_KERNEL);
if (!qs)
return -ENOMEM;
s->s_fs_info = qs;
/* Superblock always is 512 Byte long */
if (!sb_set_blocksize(s, QNX6_SUPERBLOCK_SIZE)) {
printk(KERN_ERR "qnx6: unable to set blocksize\n");
goto outnobh;
}
/* parse the mount-options */
if (!qnx6_parse_options((char *) data, s)) {
printk(KERN_ERR "qnx6: invalid mount options.\n");
goto outnobh;
}
if (test_opt(s, MMI_FS)) {
sb1 = qnx6_mmi_fill_super(s, silent);
if (sb1)
goto mmi_success;
else
goto outnobh;
}
sbi = QNX6_SB(s);
sbi->s_bytesex = BYTESEX_LE;
/* Check the superblock signatures
start with the first superblock */
bh1 = qnx6_check_first_superblock(s,
bootblock_offset / QNX6_SUPERBLOCK_SIZE, silent);
if (!bh1) {
/* try again without bootblock offset */
bh1 = qnx6_check_first_superblock(s, 0, silent);
if (!bh1) {
printk(KERN_ERR "qnx6: unable to read the first superblock\n");
goto outnobh;
}
/* seems that no bootblock at partition start */
bootblock_offset = 0;
}
sb1 = (struct qnx6_super_block *)bh1->b_data;
#ifdef CONFIG_QNX6FS_DEBUG
qnx6_superblock_debug(sb1, s);
#endif
/* checksum check - start at byte 8 and end at byte 512 */
if (fs32_to_cpu(sbi, sb1->sb_checksum) !=
crc32_be(0, (char *)(bh1->b_data + 8), 504)) {
printk(KERN_ERR "qnx6: superblock #1 checksum error\n");
goto out;
}
/* set new blocksize */
if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
printk(KERN_ERR "qnx6: unable to set blocksize\n");
goto out;
}
/* blocksize invalidates bh - pull it back in */
brelse(bh1);
bh1 = sb_bread(s, bootblock_offset >> s->s_blocksize_bits);
if (!bh1)
goto outnobh;
sb1 = (struct qnx6_super_block *)bh1->b_data;
/* calculate second superblock blocknumber */
offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) +
(bootblock_offset >> s->s_blocksize_bits) +
(QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits);
/* set bootblock offset */
sbi->s_blks_off = (bootblock_offset >> s->s_blocksize_bits) +
(QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits);
/* next the second superblock */
bh2 = sb_bread(s, offset);
if (!bh2) {
printk(KERN_ERR "qnx6: unable to read the second superblock\n");
goto out;
}
sb2 = (struct qnx6_super_block *)bh2->b_data;
if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) {
if (!silent)
printk(KERN_ERR "qnx6: wrong signature (magic)"
" in superblock #2.\n");
goto out;
}
/* checksum check - start at byte 8 and end at byte 512 */
if (fs32_to_cpu(sbi, sb2->sb_checksum) !=
crc32_be(0, (char *)(bh2->b_data + 8), 504)) {
printk(KERN_ERR "qnx6: superblock #2 checksum error\n");
goto out;
}
if (fs64_to_cpu(sbi, sb1->sb_serial) >=
fs64_to_cpu(sbi, sb2->sb_serial)) {
/* superblock #1 active */
sbi->sb_buf = bh1;
sbi->sb = (struct qnx6_super_block *)bh1->b_data;
brelse(bh2);
printk(KERN_INFO "qnx6: superblock #1 active\n");
} else {
/* superblock #2 active */
sbi->sb_buf = bh2;
sbi->sb = (struct qnx6_super_block *)bh2->b_data;
brelse(bh1);
printk(KERN_INFO "qnx6: superblock #2 active\n");
}
mmi_success:
/* sanity check - limit maximum indirect pointer levels */
if (sb1->Inode.levels > QNX6_PTR_MAX_LEVELS) {
printk(KERN_ERR "qnx6: too many inode levels (max %i, sb %i)\n",
QNX6_PTR_MAX_LEVELS, sb1->Inode.levels);
goto out;
}
if (sb1->Longfile.levels > QNX6_PTR_MAX_LEVELS) {
printk(KERN_ERR "qnx6: too many longfilename levels"
" (max %i, sb %i)\n",
QNX6_PTR_MAX_LEVELS, sb1->Longfile.levels);
goto out;
}
s->s_op = &qnx6_sops;
s->s_magic = QNX6_SUPER_MAGIC;
s->s_flags |= MS_RDONLY; /* Yup, read-only yet */
/* ease the later tree level calculations */
sbi = QNX6_SB(s);
sbi->s_ptrbits = ilog2(s->s_blocksize / 4);
sbi->inodes = qnx6_private_inode(s, &sb1->Inode);
if (!sbi->inodes)
goto out;
sbi->longfile = qnx6_private_inode(s, &sb1->Longfile);
if (!sbi->longfile)
goto out1;
/* prefetch root inode */
root = qnx6_iget(s, QNX6_ROOT_INO);
if (IS_ERR(root)) {
printk(KERN_ERR "qnx6: get inode failed\n");
ret = PTR_ERR(root);
goto out2;
}
ret = -ENOMEM;
s->s_root = d_make_root(root);
if (!s->s_root)
goto out2;
ret = -EINVAL;
errmsg = qnx6_checkroot(s);
if (errmsg != NULL) {
if (!silent)
printk(KERN_ERR "qnx6: %s\n", errmsg);
goto out3;
}
return 0;
out3:
dput(s->s_root);
s->s_root = NULL;
out2:
iput(sbi->longfile);
out1:
iput(sbi->inodes);
out:
if (bh1)
brelse(bh1);
if (bh2)
brelse(bh2);
outnobh:
kfree(qs);
s->s_fs_info = NULL;
return ret;
}
static void qnx6_put_super(struct super_block *sb)
{
struct qnx6_sb_info *qs = QNX6_SB(sb);
brelse(qs->sb_buf);
iput(qs->longfile);
iput(qs->inodes);
kfree(qs);
sb->s_fs_info = NULL;
return;
}
static sector_t qnx6_bmap(struct address_space *mapping, sector_t block)
{
return generic_block_bmap(mapping, block, qnx6_get_block);
}
static const struct address_space_operations qnx6_aops = {
.readpage = qnx6_readpage,
.readpages = qnx6_readpages,
.bmap = qnx6_bmap
};
static struct inode *qnx6_private_inode(struct super_block *s,
struct qnx6_root_node *p)
{
struct inode *inode = new_inode(s);
if (inode) {
struct qnx6_inode_info *ei = QNX6_I(inode);
struct qnx6_sb_info *sbi = QNX6_SB(s);
inode->i_size = fs64_to_cpu(sbi, p->size);
memcpy(ei->di_block_ptr, p->ptr, sizeof(p->ptr));
ei->di_filelevels = p->levels;
inode->i_mode = S_IFREG | S_IRUSR; /* probably wrong */
inode->i_mapping->a_ops = &qnx6_aops;
}
return inode;
}
struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
{
struct qnx6_sb_info *sbi = QNX6_SB(sb);
struct qnx6_inode_entry *raw_inode;
struct inode *inode;
struct qnx6_inode_info *ei;
struct address_space *mapping;
struct page *page;
u32 n, offs;
inode = iget_locked(sb, ino);
if (!inode)
return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW))
return inode;
ei = QNX6_I(inode);
inode->i_mode = 0;
if (ino == 0) {
printk(KERN_ERR "qnx6: bad inode number on dev %s: %u is "
"out of range\n",
sb->s_id, ino);
iget_failed(inode);
return ERR_PTR(-EIO);
}
n = (ino - 1) >> (PAGE_CACHE_SHIFT - QNX6_INODE_SIZE_BITS);
offs = (ino - 1) & (~PAGE_CACHE_MASK >> QNX6_INODE_SIZE_BITS);
mapping = sbi->inodes->i_mapping;
page = read_mapping_page(mapping, n, NULL);
if (IS_ERR(page)) {
printk(KERN_ERR "qnx6: major problem: unable to read inode from "
"dev %s\n", sb->s_id);
iget_failed(inode);
return ERR_CAST(page);
}
kmap(page);
raw_inode = ((struct qnx6_inode_entry *)page_address(page)) + offs;
inode->i_mode = fs16_to_cpu(sbi, raw_inode->di_mode);
inode->i_uid = (uid_t)fs32_to_cpu(sbi, raw_inode->di_uid);
inode->i_gid = (gid_t)fs32_to_cpu(sbi, raw_inode->di_gid);
inode->i_size = fs64_to_cpu(sbi, raw_inode->di_size);
inode->i_mtime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_mtime);
inode->i_mtime.tv_nsec = 0;
inode->i_atime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_atime);
inode->i_atime.tv_nsec = 0;
inode->i_ctime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_ctime);
inode->i_ctime.tv_nsec = 0;
/* calc blocks based on 512 byte blocksize */
inode->i_blocks = (inode->i_size + 511) >> 9;
memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
sizeof(raw_inode->di_block_ptr));
ei->di_filelevels = raw_inode->di_filelevels;
if (S_ISREG(inode->i_mode)) {
inode->i_fop = &generic_ro_fops;
inode->i_mapping->a_ops = &qnx6_aops;
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &qnx6_dir_inode_operations;
inode->i_fop = &qnx6_dir_operations;
inode->i_mapping->a_ops = &qnx6_aops;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &page_symlink_inode_operations;
inode->i_mapping->a_ops = &qnx6_aops;
} else
init_special_inode(inode, inode->i_mode, 0);
qnx6_put_page(page);
unlock_new_inode(inode);
return inode;
}
static struct kmem_cache *qnx6_inode_cachep;
static struct inode *qnx6_alloc_inode(struct super_block *sb)
{
struct qnx6_inode_info *ei;
ei = kmem_cache_alloc(qnx6_inode_cachep, GFP_KERNEL);
if (!ei)
return NULL;
return &ei->vfs_inode;
}
static void qnx6_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
INIT_LIST_HEAD(&inode->i_dentry);
kmem_cache_free(qnx6_inode_cachep, QNX6_I(inode));
}
static void qnx6_destroy_inode(struct inode *inode)
{
call_rcu(&inode->i_rcu, qnx6_i_callback);
}
static void init_once(void *foo)
{
struct qnx6_inode_info *ei = (struct qnx6_inode_info *) foo;
inode_init_once(&ei->vfs_inode);
}
static int init_inodecache(void)
{
qnx6_inode_cachep = kmem_cache_create("qnx6_inode_cache",
sizeof(struct qnx6_inode_info),
0, (SLAB_RECLAIM_ACCOUNT|
SLAB_MEM_SPREAD),
init_once);
if (!qnx6_inode_cachep)
return -ENOMEM;
return 0;
}
static void destroy_inodecache(void)
{
kmem_cache_destroy(qnx6_inode_cachep);
}
static struct dentry *qnx6_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_bdev(fs_type, flags, dev_name, data, qnx6_fill_super);
}
static struct file_system_type qnx6_fs_type = {
.owner = THIS_MODULE,
.name = "qnx6",
.mount = qnx6_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static int __init init_qnx6_fs(void)
{
int err;
err = init_inodecache();
if (err)
return err;
err = register_filesystem(&qnx6_fs_type);
if (err) {
destroy_inodecache();
return err;
}
printk(KERN_INFO "QNX6 filesystem 1.0.0 registered.\n");
return 0;
}
static void __exit exit_qnx6_fs(void)
{
unregister_filesystem(&qnx6_fs_type);
destroy_inodecache();
}
module_init(init_qnx6_fs)
module_exit(exit_qnx6_fs)
MODULE_LICENSE("GPL");

42
fs/qnx6/namei.c Normal file
View File

@ -0,0 +1,42 @@
/*
* QNX6 file system, Linux implementation.
*
* Version : 1.0.0
*
* History :
*
* 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
* 16-02-2012 pagemap extension by Al Viro
*
*/
#include "qnx6.h"
struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
unsigned ino;
struct page *page;
struct inode *foundinode = NULL;
const char *name = dentry->d_name.name;
int len = dentry->d_name.len;
if (len > QNX6_LONG_NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);
ino = qnx6_find_entry(len, dir, name, &page);
if (ino) {
foundinode = qnx6_iget(dir->i_sb, ino);
qnx6_put_page(page);
if (IS_ERR(foundinode)) {
QNX6DEBUG((KERN_ERR "qnx6: lookup->iget -> "
" error %ld\n", PTR_ERR(foundinode)));
return ERR_CAST(foundinode);
}
} else {
QNX6DEBUG((KERN_INFO "qnx6_lookup: not found %s\n", name));
return NULL;
}
d_add(dentry, foundinode);
return NULL;
}

135
fs/qnx6/qnx6.h Normal file
View File

@ -0,0 +1,135 @@
/*
* QNX6 file system, Linux implementation.
*
* Version : 1.0.0
*
* History :
*
* 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
* 16-02-2012 page map extension by Al Viro
*
*/
#include <linux/fs.h>
#include <linux/pagemap.h>
typedef __u16 __bitwise __fs16;
typedef __u32 __bitwise __fs32;
typedef __u64 __bitwise __fs64;
#include <linux/qnx6_fs.h>
#ifdef CONFIG_QNX6FS_DEBUG
#define QNX6DEBUG(X) printk X
#else
#define QNX6DEBUG(X) (void) 0
#endif
struct qnx6_sb_info {
struct buffer_head *sb_buf; /* superblock buffer */
struct qnx6_super_block *sb; /* our superblock */
int s_blks_off; /* blkoffset fs-startpoint */
int s_ptrbits; /* indirect pointer bitfield */
unsigned long s_mount_opt; /* all mount options */
int s_bytesex; /* holds endianess info */
struct inode * inodes;
struct inode * longfile;
};
struct qnx6_inode_info {
__fs32 di_block_ptr[QNX6_NO_DIRECT_POINTERS];
__u8 di_filelevels;
__u32 i_dir_start_lookup;
struct inode vfs_inode;
};
extern struct inode *qnx6_iget(struct super_block *sb, unsigned ino);
extern struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd);
#ifdef CONFIG_QNX6FS_DEBUG
extern void qnx6_superblock_debug(struct qnx6_super_block *,
struct super_block *);
#endif
extern const struct inode_operations qnx6_dir_inode_operations;
extern const struct file_operations qnx6_dir_operations;
static inline struct qnx6_sb_info *QNX6_SB(struct super_block *sb)
{
return sb->s_fs_info;
}
static inline struct qnx6_inode_info *QNX6_I(struct inode *inode)
{
return container_of(inode, struct qnx6_inode_info, vfs_inode);
}
#define clear_opt(o, opt) (o &= ~(QNX6_MOUNT_##opt))
#define set_opt(o, opt) (o |= (QNX6_MOUNT_##opt))
#define test_opt(sb, opt) (QNX6_SB(sb)->s_mount_opt & \
QNX6_MOUNT_##opt)
enum {
BYTESEX_LE,
BYTESEX_BE,
};
static inline __u64 fs64_to_cpu(struct qnx6_sb_info *sbi, __fs64 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return le64_to_cpu((__force __le64)n);
else
return be64_to_cpu((__force __be64)n);
}
static inline __fs64 cpu_to_fs64(struct qnx6_sb_info *sbi, __u64 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return (__force __fs64)cpu_to_le64(n);
else
return (__force __fs64)cpu_to_be64(n);
}
static inline __u32 fs32_to_cpu(struct qnx6_sb_info *sbi, __fs32 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return le32_to_cpu((__force __le32)n);
else
return be32_to_cpu((__force __be32)n);
}
static inline __fs32 cpu_to_fs32(struct qnx6_sb_info *sbi, __u32 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return (__force __fs32)cpu_to_le32(n);
else
return (__force __fs32)cpu_to_be32(n);
}
static inline __u16 fs16_to_cpu(struct qnx6_sb_info *sbi, __fs16 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return le16_to_cpu((__force __le16)n);
else
return be16_to_cpu((__force __be16)n);
}
static inline __fs16 cpu_to_fs16(struct qnx6_sb_info *sbi, __u16 n)
{
if (sbi->s_bytesex == BYTESEX_LE)
return (__force __fs16)cpu_to_le16(n);
else
return (__force __fs16)cpu_to_be16(n);
}
extern struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s,
int silent);
static inline void qnx6_put_page(struct page *page)
{
kunmap(page);
page_cache_release(page);
}
extern unsigned qnx6_find_entry(int len, struct inode *dir, const char *name,
struct page **res_page);

150
fs/qnx6/super_mmi.c Normal file
View File

@ -0,0 +1,150 @@
/*
* QNX6 file system, Linux implementation.
*
* Version : 1.0.0
*
* History :
*
* 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
*
*/
#include <linux/buffer_head.h>
#include <linux/slab.h>
#include <linux/crc32.h>
#include "qnx6.h"
static void qnx6_mmi_copy_sb(struct qnx6_super_block *qsb,
struct qnx6_mmi_super_block *sb)
{
qsb->sb_magic = sb->sb_magic;
qsb->sb_checksum = sb->sb_checksum;
qsb->sb_serial = sb->sb_serial;
qsb->sb_blocksize = sb->sb_blocksize;
qsb->sb_num_inodes = sb->sb_num_inodes;
qsb->sb_free_inodes = sb->sb_free_inodes;
qsb->sb_num_blocks = sb->sb_num_blocks;
qsb->sb_free_blocks = sb->sb_free_blocks;
/* the rest of the superblock is the same */
memcpy(&qsb->Inode, &sb->Inode, sizeof(sb->Inode));
memcpy(&qsb->Bitmap, &sb->Bitmap, sizeof(sb->Bitmap));
memcpy(&qsb->Longfile, &sb->Longfile, sizeof(sb->Longfile));
}
struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
{
struct buffer_head *bh1, *bh2 = NULL;
struct qnx6_mmi_super_block *sb1, *sb2;
struct qnx6_super_block *qsb = NULL;
struct qnx6_sb_info *sbi;
__u64 offset;
/* Check the superblock signatures
start with the first superblock */
bh1 = sb_bread(s, 0);
if (!bh1) {
printk(KERN_ERR "qnx6: Unable to read first mmi superblock\n");
return NULL;
}
sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
sbi = QNX6_SB(s);
if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
if (!silent) {
printk(KERN_ERR "qnx6: wrong signature (magic) in"
" superblock #1.\n");
goto out;
}
}
/* checksum check - start at byte 8 and end at byte 512 */
if (fs32_to_cpu(sbi, sb1->sb_checksum) !=
crc32_be(0, (char *)(bh1->b_data + 8), 504)) {
printk(KERN_ERR "qnx6: superblock #1 checksum error\n");
goto out;
}
/* calculate second superblock blocknumber */
offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
fs32_to_cpu(sbi, sb1->sb_blocksize);
/* set new blocksize */
if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
printk(KERN_ERR "qnx6: unable to set blocksize\n");
goto out;
}
/* blocksize invalidates bh - pull it back in */
brelse(bh1);
bh1 = sb_bread(s, 0);
if (!bh1)
goto out;
sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
/* read second superblock */
bh2 = sb_bread(s, offset);
if (!bh2) {
printk(KERN_ERR "qnx6: unable to read the second superblock\n");
goto out;
}
sb2 = (struct qnx6_mmi_super_block *)bh2->b_data;
if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) {
if (!silent)
printk(KERN_ERR "qnx6: wrong signature (magic) in"
" superblock #2.\n");
goto out;
}
/* checksum check - start at byte 8 and end at byte 512 */
if (fs32_to_cpu(sbi, sb2->sb_checksum)
!= crc32_be(0, (char *)(bh2->b_data + 8), 504)) {
printk(KERN_ERR "qnx6: superblock #1 checksum error\n");
goto out;
}
qsb = kmalloc(sizeof(*qsb), GFP_KERNEL);
if (!qsb) {
printk(KERN_ERR "qnx6: unable to allocate memory.\n");
goto out;
}
if (fs64_to_cpu(sbi, sb1->sb_serial) >
fs64_to_cpu(sbi, sb2->sb_serial)) {
/* superblock #1 active */
qnx6_mmi_copy_sb(qsb, sb1);
#ifdef CONFIG_QNX6FS_DEBUG
qnx6_superblock_debug(qsb, s);
#endif
memcpy(bh1->b_data, qsb, sizeof(struct qnx6_super_block));
sbi->sb_buf = bh1;
sbi->sb = (struct qnx6_super_block *)bh1->b_data;
brelse(bh2);
printk(KERN_INFO "qnx6: superblock #1 active\n");
} else {
/* superblock #2 active */
qnx6_mmi_copy_sb(qsb, sb2);
#ifdef CONFIG_QNX6FS_DEBUG
qnx6_superblock_debug(qsb, s);
#endif
memcpy(bh2->b_data, qsb, sizeof(struct qnx6_super_block));
sbi->sb_buf = bh2;
sbi->sb = (struct qnx6_super_block *)bh2->b_data;
brelse(bh1);
printk(KERN_INFO "qnx6: superblock #2 active\n");
}
kfree(qsb);
/* offset for mmi_fs is just SUPERBLOCK_AREA bytes */
sbi->s_blks_off = QNX6_SUPERBLOCK_AREA / s->s_blocksize;
/* success */
return sbi->sb;
out:
if (bh1 != NULL)
brelse(bh1);
if (bh2 != NULL)
brelse(bh2);
return NULL;
}

View File

@ -42,6 +42,7 @@
#define OPENPROM_SUPER_MAGIC 0x9fa1
#define PROC_SUPER_MAGIC 0x9fa0
#define QNX4_SUPER_MAGIC 0x002f /* qnx4 fs detection */
#define QNX6_SUPER_MAGIC 0x68191122 /* qnx6 fs detection */
#define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */
/* used by file system utilities that

134
include/linux/qnx6_fs.h Normal file
View File

@ -0,0 +1,134 @@
/*
* Name : qnx6_fs.h
* Author : Kai Bankett
* Function : qnx6 global filesystem definitions
* History : 17-01-2012 created
*/
#ifndef _LINUX_QNX6_FS_H
#define _LINUX_QNX6_FS_H
#include <linux/types.h>
#include <linux/magic.h>
#define QNX6_ROOT_INO 1
/* for di_status */
#define QNX6_FILE_DIRECTORY 0x01
#define QNX6_FILE_DELETED 0x02
#define QNX6_FILE_NORMAL 0x03
#define QNX6_SUPERBLOCK_SIZE 0x200 /* superblock always is 512 bytes */
#define QNX6_SUPERBLOCK_AREA 0x1000 /* area reserved for superblock */
#define QNX6_BOOTBLOCK_SIZE 0x2000 /* heading bootblock area */
#define QNX6_DIR_ENTRY_SIZE 0x20 /* dir entry size of 32 bytes */
#define QNX6_INODE_SIZE 0x80 /* each inode is 128 bytes */
#define QNX6_INODE_SIZE_BITS 7 /* inode entry size shift */
#define QNX6_NO_DIRECT_POINTERS 16 /* 16 blockptrs in sbl/inode */
#define QNX6_PTR_MAX_LEVELS 5 /* maximum indirect levels */
/* for filenames */
#define QNX6_SHORT_NAME_MAX 27
#define QNX6_LONG_NAME_MAX 510
/* list of mount options */
#define QNX6_MOUNT_MMI_FS 0x010000 /* mount as Audi MMI 3G fs */
/*
* This is the original qnx6 inode layout on disk.
* Each inode is 128 byte long.
*/
struct qnx6_inode_entry {
__fs64 di_size;
__fs32 di_uid;
__fs32 di_gid;
__fs32 di_ftime;
__fs32 di_mtime;
__fs32 di_atime;
__fs32 di_ctime;
__fs16 di_mode;
__fs16 di_ext_mode;
__fs32 di_block_ptr[QNX6_NO_DIRECT_POINTERS];
__u8 di_filelevels;
__u8 di_status;
__u8 di_unknown2[2];
__fs32 di_zero2[6];
};
/*
* Each directory entry is maximum 32 bytes long.
* If more characters or special characters required it is stored
* in the longfilenames structure.
*/
struct qnx6_dir_entry {
__fs32 de_inode;
__u8 de_size;
char de_fname[QNX6_SHORT_NAME_MAX];
};
/*
* Longfilename direntries have a different structure
*/
struct qnx6_long_dir_entry {
__fs32 de_inode;
__u8 de_size;
__u8 de_unknown[3];
__fs32 de_long_inode;
__fs32 de_checksum;
};
struct qnx6_long_filename {
__fs16 lf_size;
__u8 lf_fname[QNX6_LONG_NAME_MAX];
};
struct qnx6_root_node {
__fs64 size;
__fs32 ptr[QNX6_NO_DIRECT_POINTERS];
__u8 levels;
__u8 mode;
__u8 spare[6];
};
struct qnx6_super_block {
__fs32 sb_magic;
__fs32 sb_checksum;
__fs64 sb_serial;
__fs32 sb_ctime; /* time the fs was created */
__fs32 sb_atime; /* last access time */
__fs32 sb_flags;
__fs16 sb_version1; /* filesystem version information */
__fs16 sb_version2; /* filesystem version information */
__u8 sb_volumeid[16];
__fs32 sb_blocksize;
__fs32 sb_num_inodes;
__fs32 sb_free_inodes;
__fs32 sb_num_blocks;
__fs32 sb_free_blocks;
__fs32 sb_allocgroup;
struct qnx6_root_node Inode;
struct qnx6_root_node Bitmap;
struct qnx6_root_node Longfile;
struct qnx6_root_node Unknown;
};
/* Audi MMI 3G superblock layout is different to plain qnx6 */
struct qnx6_mmi_super_block {
__fs32 sb_magic;
__fs32 sb_checksum;
__fs64 sb_serial;
__u8 sb_spare0[12];
__u8 sb_id[12];
__fs32 sb_blocksize;
__fs32 sb_num_inodes;
__fs32 sb_free_inodes;
__fs32 sb_num_blocks;
__fs32 sb_free_blocks;
__u8 sb_spare1[4];
struct qnx6_root_node Inode;
struct qnx6_root_node Bitmap;
struct qnx6_root_node Longfile;
struct qnx6_root_node Unknown;
};
#endif