Commit 5d026c7242201e7c9d0e12fcb2bcaffead9d59fd
Committed by
Al Viro
1 parent
516cdb68e5
Exists in
master
and in
20 other branches
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>
Showing 13 changed files with 1668 additions and 0 deletions Side-by-side Diff
Documentation/filesystems/qnx6.txt
1 | +The QNX6 Filesystem | |
2 | +=================== | |
3 | + | |
4 | +The qnx6fs is used by newer QNX operating system versions. (e.g. Neutrino) | |
5 | +It got introduced in QNX 6.4.0 and is used default since 6.4.1. | |
6 | + | |
7 | +Option | |
8 | +====== | |
9 | + | |
10 | +mmi_fs Mount filesystem as used for example by Audi MMI 3G system | |
11 | + | |
12 | +Specification | |
13 | +============= | |
14 | + | |
15 | +qnx6fs shares many properties with traditional Unix filesystems. It has the | |
16 | +concepts of blocks, inodes and directories. | |
17 | +On QNX it is possible to create little endian and big endian qnx6 filesystems. | |
18 | +This feature makes it possible to create and use a different endianness fs | |
19 | +for the target (QNX is used on quite a range of embedded systems) plattform | |
20 | +running on a different endianess. | |
21 | +The Linux driver handles endianness transparently. (LE and BE) | |
22 | + | |
23 | +Blocks | |
24 | +------ | |
25 | + | |
26 | +The space in the device or file is split up into blocks. These are a fixed | |
27 | +size of 512, 1024, 2048 or 4096, which is decided when the filesystem is | |
28 | +created. | |
29 | +Blockpointers are 32bit, so the maximum space that can be adressed is | |
30 | +2^32 * 4096 bytes or 16TB | |
31 | + | |
32 | +The superblocks | |
33 | +--------------- | |
34 | + | |
35 | +The superblock contains all global information about the filesystem. | |
36 | +Each qnx6fs got two superblocks, each one having a 64bit serial number. | |
37 | +That serial number is used to identify the "active" superblock. | |
38 | +In write mode with reach new snapshot (after each synchronous write), the | |
39 | +serial of the new master superblock is increased (old superblock serial + 1) | |
40 | + | |
41 | +So basically the snapshot functionality is realized by an atomic final | |
42 | +update of the serial number. Before updating that serial, all modifications | |
43 | +are done by copying all modified blocks during that specific write request | |
44 | +(or period) and building up a new (stable) filesystem structure under the | |
45 | +inactive superblock. | |
46 | + | |
47 | +Each superblock holds a set of root inodes for the different filesystem | |
48 | +parts. (Inode, Bitmap and Longfilenames) | |
49 | +Each of these root nodes holds information like total size of the stored | |
50 | +data and the adressing levels in that specific tree. | |
51 | +If the level value is 0, up to 16 direct blocks can be adressed by each | |
52 | +node. | |
53 | +Level 1 adds an additional indirect adressing level where each indirect | |
54 | +adressing block holds up to blocksize / 4 bytes pointers to data blocks. | |
55 | +Level 2 adds an additional indirect adressig block level (so, already up | |
56 | +to 16 * 256 * 256 = 1048576 blocks that can be adressed by such a tree)a | |
57 | + | |
58 | +Unused block pointers are always set to ~0 - regardless of root node, | |
59 | +indirect adressing blocks or inodes. | |
60 | +Data leaves are always on the lowest level. So no data is stored on upper | |
61 | +tree levels. | |
62 | + | |
63 | +The first Superblock is located at 0x2000. (0x2000 is the bootblock size) | |
64 | +The Audi MMI 3G first superblock directly starts at byte 0. | |
65 | +Second superblock position can either be calculated from the superblock | |
66 | +information (total number of filesystem blocks) or by taking the highest | |
67 | +device address, zeroing the last 3 bytes and then substracting 0x1000 from | |
68 | +that address. | |
69 | + | |
70 | +0x1000 is the size reserved for each superblock - regardless of the | |
71 | +blocksize of the filesystem. | |
72 | + | |
73 | +Inodes | |
74 | +------ | |
75 | + | |
76 | +Each object in the filesystem is represented by an inode. (index node) | |
77 | +The inode structure contains pointers to the filesystem blocks which contain | |
78 | +the data held in the object and all of the metadata about an object except | |
79 | +its longname. (filenames longer than 27 characters) | |
80 | +The metadata about an object includes the permissions, owner, group, flags, | |
81 | +size, number of blocks used, access time, change time and modification time. | |
82 | + | |
83 | +Object mode field is POSIX format. (which makes things easier) | |
84 | + | |
85 | +There are also pointers to the first 16 blocks, if the object data can be | |
86 | +adressed with 16 direct blocks. | |
87 | +For more than 16 blocks an indirect adressing in form of another tree is | |
88 | +used. (scheme is the same as the one used for the superblock root nodes) | |
89 | + | |
90 | +The filesize is stored 64bit. Inode counting starts with 1. (whilst long | |
91 | +filename inodes start with 0) | |
92 | + | |
93 | +Directories | |
94 | +----------- | |
95 | + | |
96 | +A directory is a filesystem object and has an inode just like a file. | |
97 | +It is a specially formatted file containing records which associate each | |
98 | +name with an inode number. | |
99 | +'.' inode number points to the directory inode | |
100 | +'..' inode number points to the parent directory inode | |
101 | +Eeach filename record additionally got a filename length field. | |
102 | + | |
103 | +One special case are long filenames or subdirectory names. | |
104 | +These got set a filename length field of 0xff in the corresponding directory | |
105 | +record plus the longfile inode number also stored in that record. | |
106 | +With that longfilename inode number, the longfilename tree can be walked | |
107 | +starting with the superblock longfilename root node pointers. | |
108 | + | |
109 | +Special files | |
110 | +------------- | |
111 | + | |
112 | +Symbolic links are also filesystem objects with inodes. They got a specific | |
113 | +bit in the inode mode field identifying them as symbolic link. | |
114 | +The directory entry file inode pointer points to the target file inode. | |
115 | + | |
116 | +Hard links got an inode, a directory entry, but a specific mode bit set, | |
117 | +no block pointers and the directory file record pointing to the target file | |
118 | +inode. | |
119 | + | |
120 | +Character and block special devices do not exist in QNX as those files | |
121 | +are handled by the QNX kernel/drivers and created in /dev independant of the | |
122 | +underlaying filesystem. | |
123 | + | |
124 | +Long filenames | |
125 | +-------------- | |
126 | + | |
127 | +Long filenames are stored in a seperate adressing tree. The staring point | |
128 | +is the longfilename root node in the active superblock. | |
129 | +Each data block (tree leaves) holds one long filename. That filename is | |
130 | +limited to 510 bytes. The first two starting bytes are used as length field | |
131 | +for the actual filename. | |
132 | +If that structure shall fit for all allowed blocksizes, it is clear why there | |
133 | +is a limit of 510 bytes for the actual filename stored. | |
134 | + | |
135 | +Bitmap | |
136 | +------ | |
137 | + | |
138 | +The qnx6fs filesystem allocation bitmap is stored in a tree under bitmap | |
139 | +root node in the superblock and each bit in the bitmap represents one | |
140 | +filesystem block. | |
141 | +The first block is block 0, which starts 0x1000 after superblock start. | |
142 | +So for a normal qnx6fs 0x3000 (bootblock + superblock) is the physical | |
143 | +address at which block 0 is located. | |
144 | + | |
145 | +Bits at the end of the last bitmap block are set to 1, if the device is | |
146 | +smaller than addressing space in the bitmap. | |
147 | + | |
148 | +Bitmap system area | |
149 | +------------------ | |
150 | + | |
151 | +The bitmap itself is devided into three parts. | |
152 | +First the system area, that is split into two halfs. | |
153 | +Then userspace. | |
154 | + | |
155 | +The requirement for a static, fixed preallocated system area comes from how | |
156 | +qnx6fs deals with writes. | |
157 | +Each superblock got it's own half of the system area. So superblock #1 | |
158 | +always uses blocks from the lower half whilst superblock #2 just writes to | |
159 | +blocks represented by the upper half bitmap system area bits. | |
160 | + | |
161 | +Bitmap blocks, Inode blocks and indirect addressing blocks for those two | |
162 | +tree structures are treated as system blocks. | |
163 | + | |
164 | +The rational behind that is that a write request can work on a new snapshot | |
165 | +(system area of the inactive - resp. lower serial numbered superblock) while | |
166 | +at the same time there is still a complete stable filesystem structer in the | |
167 | +other half of the system area. | |
168 | + | |
169 | +When finished with writing (a sync write is completed, the maximum sync leap | |
170 | +time or a filesystem sync is requested), serial of the previously inactive | |
171 | +superblock atomically is increased and the fs switches over to that - then | |
172 | +stable declared - superblock. | |
173 | + | |
174 | +For all data outside the system area, blocks are just copied while writing. |
fs/Kconfig
fs/Makefile
... | ... | @@ -102,6 +102,7 @@ |
102 | 102 | obj-$(CONFIG_AFFS_FS) += affs/ |
103 | 103 | obj-$(CONFIG_ROMFS_FS) += romfs/ |
104 | 104 | obj-$(CONFIG_QNX4FS_FS) += qnx4/ |
105 | +obj-$(CONFIG_QNX6FS_FS) += qnx6/ | |
105 | 106 | obj-$(CONFIG_AUTOFS4_FS) += autofs4/ |
106 | 107 | obj-$(CONFIG_ADFS_FS) += adfs/ |
107 | 108 | obj-$(CONFIG_FUSE_FS) += fuse/ |
fs/qnx6/Kconfig
1 | +config QNX6FS_FS | |
2 | + tristate "QNX6 file system support (read only)" | |
3 | + depends on BLOCK && CRC32 | |
4 | + help | |
5 | + This is the file system used by the real-time operating systems | |
6 | + QNX 6 (also called QNX RTP). | |
7 | + Further information is available at <http://www.qnx.com/>. | |
8 | + Say Y if you intend to mount QNX hard disks or floppies formatted | |
9 | + with a mkqnx6fs. | |
10 | + However, keep in mind that this currently is a readonly driver! | |
11 | + | |
12 | + To compile this file system support as a module, choose M here: the | |
13 | + module will be called qnx6. | |
14 | + | |
15 | + If you don't know whether you need it, then you don't need it: | |
16 | + answer N. | |
17 | + | |
18 | +config QNX6FS_DEBUG | |
19 | + bool "QNX6 debugging information" | |
20 | + depends on QNX6FS_FS | |
21 | + help | |
22 | + Turns on extended debugging output. | |
23 | + | |
24 | + If you are not a developer working on the QNX6FS, you probably don't | |
25 | + want this: | |
26 | + answer N. |
fs/qnx6/Makefile
fs/qnx6/README
fs/qnx6/dir.c
1 | +/* | |
2 | + * QNX6 file system, Linux implementation. | |
3 | + * | |
4 | + * Version : 1.0.0 | |
5 | + * | |
6 | + * History : | |
7 | + * | |
8 | + * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release. | |
9 | + * 16-02-2012 pagemap extension by Al Viro | |
10 | + * | |
11 | + */ | |
12 | + | |
13 | +#include "qnx6.h" | |
14 | + | |
15 | +static unsigned qnx6_lfile_checksum(char *name, unsigned size) | |
16 | +{ | |
17 | + unsigned crc = 0; | |
18 | + char *end = name + size; | |
19 | + while (name < end) { | |
20 | + crc = ((crc >> 1) + *(name++)) ^ | |
21 | + ((crc & 0x00000001) ? 0x80000000 : 0); | |
22 | + } | |
23 | + return crc; | |
24 | +} | |
25 | + | |
26 | +static struct page *qnx6_get_page(struct inode *dir, unsigned long n) | |
27 | +{ | |
28 | + struct address_space *mapping = dir->i_mapping; | |
29 | + struct page *page = read_mapping_page(mapping, n, NULL); | |
30 | + if (!IS_ERR(page)) | |
31 | + kmap(page); | |
32 | + return page; | |
33 | +} | |
34 | + | |
35 | +static inline unsigned long dir_pages(struct inode *inode) | |
36 | +{ | |
37 | + return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT; | |
38 | +} | |
39 | + | |
40 | +static unsigned last_entry(struct inode *inode, unsigned long page_nr) | |
41 | +{ | |
42 | + unsigned long last_byte = inode->i_size; | |
43 | + last_byte -= page_nr << PAGE_CACHE_SHIFT; | |
44 | + if (last_byte > PAGE_CACHE_SIZE) | |
45 | + last_byte = PAGE_CACHE_SIZE; | |
46 | + return last_byte / QNX6_DIR_ENTRY_SIZE; | |
47 | +} | |
48 | + | |
49 | +static struct qnx6_long_filename *qnx6_longname(struct super_block *sb, | |
50 | + struct qnx6_long_dir_entry *de, | |
51 | + struct page **p) | |
52 | +{ | |
53 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
54 | + u32 s = fs32_to_cpu(sbi, de->de_long_inode); /* in block units */ | |
55 | + u32 n = s >> (PAGE_CACHE_SHIFT - sb->s_blocksize_bits); /* in pages */ | |
56 | + /* within page */ | |
57 | + u32 offs = (s << sb->s_blocksize_bits) & ~PAGE_CACHE_MASK; | |
58 | + struct address_space *mapping = sbi->longfile->i_mapping; | |
59 | + struct page *page = read_mapping_page(mapping, n, NULL); | |
60 | + if (IS_ERR(page)) | |
61 | + return ERR_CAST(page); | |
62 | + kmap(*p = page); | |
63 | + return (struct qnx6_long_filename *)(page_address(page) + offs); | |
64 | +} | |
65 | + | |
66 | +static int qnx6_dir_longfilename(struct inode *inode, | |
67 | + struct qnx6_long_dir_entry *de, | |
68 | + void *dirent, loff_t pos, | |
69 | + unsigned de_inode, filldir_t filldir) | |
70 | +{ | |
71 | + struct qnx6_long_filename *lf; | |
72 | + struct super_block *s = inode->i_sb; | |
73 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
74 | + struct page *page; | |
75 | + int lf_size; | |
76 | + | |
77 | + if (de->de_size != 0xff) { | |
78 | + /* error - long filename entries always have size 0xff | |
79 | + in direntry */ | |
80 | + printk(KERN_ERR "qnx6: invalid direntry size (%i).\n", | |
81 | + de->de_size); | |
82 | + return 0; | |
83 | + } | |
84 | + lf = qnx6_longname(s, de, &page); | |
85 | + if (IS_ERR(lf)) { | |
86 | + printk(KERN_ERR "qnx6:Error reading longname\n"); | |
87 | + return 0; | |
88 | + } | |
89 | + | |
90 | + lf_size = fs16_to_cpu(sbi, lf->lf_size); | |
91 | + | |
92 | + if (lf_size > QNX6_LONG_NAME_MAX) { | |
93 | + QNX6DEBUG((KERN_INFO "file %s\n", lf->lf_fname)); | |
94 | + printk(KERN_ERR "qnx6:Filename too long (%i)\n", lf_size); | |
95 | + qnx6_put_page(page); | |
96 | + return 0; | |
97 | + } | |
98 | + | |
99 | + /* calc & validate longfilename checksum | |
100 | + mmi 3g filesystem does not have that checksum */ | |
101 | + if (!test_opt(s, MMI_FS) && fs32_to_cpu(sbi, de->de_checksum) != | |
102 | + qnx6_lfile_checksum(lf->lf_fname, lf_size)) | |
103 | + printk(KERN_INFO "qnx6: long filename checksum error.\n"); | |
104 | + | |
105 | + QNX6DEBUG((KERN_INFO "qnx6_readdir:%.*s inode:%u\n", | |
106 | + lf_size, lf->lf_fname, de_inode)); | |
107 | + if (filldir(dirent, lf->lf_fname, lf_size, pos, de_inode, | |
108 | + DT_UNKNOWN) < 0) { | |
109 | + qnx6_put_page(page); | |
110 | + return 0; | |
111 | + } | |
112 | + | |
113 | + qnx6_put_page(page); | |
114 | + /* success */ | |
115 | + return 1; | |
116 | +} | |
117 | + | |
118 | +static int qnx6_readdir(struct file *filp, void *dirent, filldir_t filldir) | |
119 | +{ | |
120 | + struct inode *inode = filp->f_path.dentry->d_inode; | |
121 | + struct super_block *s = inode->i_sb; | |
122 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
123 | + loff_t pos = filp->f_pos & (QNX6_DIR_ENTRY_SIZE - 1); | |
124 | + unsigned long npages = dir_pages(inode); | |
125 | + unsigned long n = pos >> PAGE_CACHE_SHIFT; | |
126 | + unsigned start = (pos & ~PAGE_CACHE_MASK) / QNX6_DIR_ENTRY_SIZE; | |
127 | + bool done = false; | |
128 | + | |
129 | + if (filp->f_pos >= inode->i_size) | |
130 | + return 0; | |
131 | + | |
132 | + for ( ; !done && n < npages; n++, start = 0) { | |
133 | + struct page *page = qnx6_get_page(inode, n); | |
134 | + int limit = last_entry(inode, n); | |
135 | + struct qnx6_dir_entry *de; | |
136 | + int i = start; | |
137 | + | |
138 | + if (IS_ERR(page)) { | |
139 | + printk(KERN_ERR "qnx6_readdir: read failed\n"); | |
140 | + filp->f_pos = (n + 1) << PAGE_CACHE_SHIFT; | |
141 | + return PTR_ERR(page); | |
142 | + } | |
143 | + de = ((struct qnx6_dir_entry *)page_address(page)) + start; | |
144 | + for (; i < limit; i++, de++, pos += QNX6_DIR_ENTRY_SIZE) { | |
145 | + int size = de->de_size; | |
146 | + u32 no_inode = fs32_to_cpu(sbi, de->de_inode); | |
147 | + | |
148 | + if (!no_inode || !size) | |
149 | + continue; | |
150 | + | |
151 | + if (size > QNX6_SHORT_NAME_MAX) { | |
152 | + /* long filename detected | |
153 | + get the filename from long filename | |
154 | + structure / block */ | |
155 | + if (!qnx6_dir_longfilename(inode, | |
156 | + (struct qnx6_long_dir_entry *)de, | |
157 | + dirent, pos, no_inode, | |
158 | + filldir)) { | |
159 | + done = true; | |
160 | + break; | |
161 | + } | |
162 | + } else { | |
163 | + QNX6DEBUG((KERN_INFO "qnx6_readdir:%.*s" | |
164 | + " inode:%u\n", size, de->de_fname, | |
165 | + no_inode)); | |
166 | + if (filldir(dirent, de->de_fname, size, | |
167 | + pos, no_inode, DT_UNKNOWN) | |
168 | + < 0) { | |
169 | + done = true; | |
170 | + break; | |
171 | + } | |
172 | + } | |
173 | + } | |
174 | + qnx6_put_page(page); | |
175 | + } | |
176 | + filp->f_pos = pos; | |
177 | + return 0; | |
178 | +} | |
179 | + | |
180 | +/* | |
181 | + * check if the long filename is correct. | |
182 | + */ | |
183 | +static unsigned qnx6_long_match(int len, const char *name, | |
184 | + struct qnx6_long_dir_entry *de, struct inode *dir) | |
185 | +{ | |
186 | + struct super_block *s = dir->i_sb; | |
187 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
188 | + struct page *page; | |
189 | + int thislen; | |
190 | + struct qnx6_long_filename *lf = qnx6_longname(s, de, &page); | |
191 | + | |
192 | + if (IS_ERR(lf)) | |
193 | + return 0; | |
194 | + | |
195 | + thislen = fs16_to_cpu(sbi, lf->lf_size); | |
196 | + if (len != thislen) { | |
197 | + qnx6_put_page(page); | |
198 | + return 0; | |
199 | + } | |
200 | + if (memcmp(name, lf->lf_fname, len) == 0) { | |
201 | + qnx6_put_page(page); | |
202 | + return fs32_to_cpu(sbi, de->de_inode); | |
203 | + } | |
204 | + qnx6_put_page(page); | |
205 | + return 0; | |
206 | +} | |
207 | + | |
208 | +/* | |
209 | + * check if the filename is correct. | |
210 | + */ | |
211 | +static unsigned qnx6_match(struct super_block *s, int len, const char *name, | |
212 | + struct qnx6_dir_entry *de) | |
213 | +{ | |
214 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
215 | + if (memcmp(name, de->de_fname, len) == 0) | |
216 | + return fs32_to_cpu(sbi, de->de_inode); | |
217 | + return 0; | |
218 | +} | |
219 | + | |
220 | + | |
221 | +unsigned qnx6_find_entry(int len, struct inode *dir, const char *name, | |
222 | + struct page **res_page) | |
223 | +{ | |
224 | + struct super_block *s = dir->i_sb; | |
225 | + struct qnx6_inode_info *ei = QNX6_I(dir); | |
226 | + struct page *page = NULL; | |
227 | + unsigned long start, n; | |
228 | + unsigned long npages = dir_pages(dir); | |
229 | + unsigned ino; | |
230 | + struct qnx6_dir_entry *de; | |
231 | + struct qnx6_long_dir_entry *lde; | |
232 | + | |
233 | + *res_page = NULL; | |
234 | + | |
235 | + if (npages == 0) | |
236 | + return 0; | |
237 | + start = ei->i_dir_start_lookup; | |
238 | + if (start >= npages) | |
239 | + start = 0; | |
240 | + n = start; | |
241 | + | |
242 | + do { | |
243 | + page = qnx6_get_page(dir, n); | |
244 | + if (!IS_ERR(page)) { | |
245 | + int limit = last_entry(dir, n); | |
246 | + int i; | |
247 | + | |
248 | + de = (struct qnx6_dir_entry *)page_address(page); | |
249 | + for (i = 0; i < limit; i++, de++) { | |
250 | + if (len <= QNX6_SHORT_NAME_MAX) { | |
251 | + /* short filename */ | |
252 | + if (len != de->de_size) | |
253 | + continue; | |
254 | + ino = qnx6_match(s, len, name, de); | |
255 | + if (ino) | |
256 | + goto found; | |
257 | + } else if (de->de_size == 0xff) { | |
258 | + /* deal with long filename */ | |
259 | + lde = (struct qnx6_long_dir_entry *)de; | |
260 | + ino = qnx6_long_match(len, | |
261 | + name, lde, dir); | |
262 | + if (ino) | |
263 | + goto found; | |
264 | + } else | |
265 | + printk(KERN_ERR "qnx6: undefined " | |
266 | + "filename size in inode.\n"); | |
267 | + } | |
268 | + qnx6_put_page(page); | |
269 | + } | |
270 | + | |
271 | + if (++n >= npages) | |
272 | + n = 0; | |
273 | + } while (n != start); | |
274 | + return 0; | |
275 | + | |
276 | +found: | |
277 | + *res_page = page; | |
278 | + ei->i_dir_start_lookup = n; | |
279 | + return ino; | |
280 | +} | |
281 | + | |
282 | +const struct file_operations qnx6_dir_operations = { | |
283 | + .llseek = generic_file_llseek, | |
284 | + .read = generic_read_dir, | |
285 | + .readdir = qnx6_readdir, | |
286 | + .fsync = generic_file_fsync, | |
287 | +}; | |
288 | + | |
289 | +const struct inode_operations qnx6_dir_inode_operations = { | |
290 | + .lookup = qnx6_lookup, | |
291 | +}; |
fs/qnx6/inode.c
1 | +/* | |
2 | + * QNX6 file system, Linux implementation. | |
3 | + * | |
4 | + * Version : 1.0.0 | |
5 | + * | |
6 | + * History : | |
7 | + * | |
8 | + * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release. | |
9 | + * 16-02-2012 pagemap extension by Al Viro | |
10 | + * | |
11 | + */ | |
12 | + | |
13 | +#include <linux/module.h> | |
14 | +#include <linux/init.h> | |
15 | +#include <linux/slab.h> | |
16 | +#include <linux/highuid.h> | |
17 | +#include <linux/pagemap.h> | |
18 | +#include <linux/buffer_head.h> | |
19 | +#include <linux/writeback.h> | |
20 | +#include <linux/statfs.h> | |
21 | +#include <linux/parser.h> | |
22 | +#include <linux/seq_file.h> | |
23 | +#include <linux/mount.h> | |
24 | +#include <linux/crc32.h> | |
25 | +#include <linux/mpage.h> | |
26 | +#include "qnx6.h" | |
27 | + | |
28 | +static const struct super_operations qnx6_sops; | |
29 | + | |
30 | +static void qnx6_put_super(struct super_block *sb); | |
31 | +static struct inode *qnx6_alloc_inode(struct super_block *sb); | |
32 | +static void qnx6_destroy_inode(struct inode *inode); | |
33 | +static int qnx6_remount(struct super_block *sb, int *flags, char *data); | |
34 | +static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf); | |
35 | +static int qnx6_show_options(struct seq_file *seq, struct dentry *root); | |
36 | + | |
37 | +static const struct super_operations qnx6_sops = { | |
38 | + .alloc_inode = qnx6_alloc_inode, | |
39 | + .destroy_inode = qnx6_destroy_inode, | |
40 | + .put_super = qnx6_put_super, | |
41 | + .statfs = qnx6_statfs, | |
42 | + .remount_fs = qnx6_remount, | |
43 | + .show_options = qnx6_show_options, | |
44 | +}; | |
45 | + | |
46 | +static int qnx6_show_options(struct seq_file *seq, struct dentry *root) | |
47 | +{ | |
48 | + struct super_block *sb = root->d_sb; | |
49 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
50 | + | |
51 | + if (sbi->s_mount_opt & QNX6_MOUNT_MMI_FS) | |
52 | + seq_puts(seq, ",mmi_fs"); | |
53 | + return 0; | |
54 | +} | |
55 | + | |
56 | +static int qnx6_remount(struct super_block *sb, int *flags, char *data) | |
57 | +{ | |
58 | + *flags |= MS_RDONLY; | |
59 | + return 0; | |
60 | +} | |
61 | + | |
62 | +static unsigned qnx6_get_devblock(struct super_block *sb, __fs32 block) | |
63 | +{ | |
64 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
65 | + return fs32_to_cpu(sbi, block) + sbi->s_blks_off; | |
66 | +} | |
67 | + | |
68 | +static unsigned qnx6_block_map(struct inode *inode, unsigned iblock); | |
69 | + | |
70 | +static int qnx6_get_block(struct inode *inode, sector_t iblock, | |
71 | + struct buffer_head *bh, int create) | |
72 | +{ | |
73 | + unsigned phys; | |
74 | + | |
75 | + QNX6DEBUG((KERN_INFO "qnx6: qnx6_get_block inode=[%ld] iblock=[%ld]\n", | |
76 | + inode->i_ino, (unsigned long)iblock)); | |
77 | + | |
78 | + phys = qnx6_block_map(inode, iblock); | |
79 | + if (phys) { | |
80 | + /* logical block is before EOF */ | |
81 | + map_bh(bh, inode->i_sb, phys); | |
82 | + } | |
83 | + return 0; | |
84 | +} | |
85 | + | |
86 | +static int qnx6_check_blockptr(__fs32 ptr) | |
87 | +{ | |
88 | + if (ptr == ~(__fs32)0) { | |
89 | + printk(KERN_ERR "qnx6: hit unused blockpointer.\n"); | |
90 | + return 0; | |
91 | + } | |
92 | + return 1; | |
93 | +} | |
94 | + | |
95 | +static int qnx6_readpage(struct file *file, struct page *page) | |
96 | +{ | |
97 | + return mpage_readpage(page, qnx6_get_block); | |
98 | +} | |
99 | + | |
100 | +static int qnx6_readpages(struct file *file, struct address_space *mapping, | |
101 | + struct list_head *pages, unsigned nr_pages) | |
102 | +{ | |
103 | + return mpage_readpages(mapping, pages, nr_pages, qnx6_get_block); | |
104 | +} | |
105 | + | |
106 | +/* | |
107 | + * returns the block number for the no-th element in the tree | |
108 | + * inodebits requred as there are multiple inodes in one inode block | |
109 | + */ | |
110 | +static unsigned qnx6_block_map(struct inode *inode, unsigned no) | |
111 | +{ | |
112 | + struct super_block *s = inode->i_sb; | |
113 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
114 | + struct qnx6_inode_info *ei = QNX6_I(inode); | |
115 | + unsigned block = 0; | |
116 | + struct buffer_head *bh; | |
117 | + __fs32 ptr; | |
118 | + int levelptr; | |
119 | + int ptrbits = sbi->s_ptrbits; | |
120 | + int bitdelta; | |
121 | + u32 mask = (1 << ptrbits) - 1; | |
122 | + int depth = ei->di_filelevels; | |
123 | + int i; | |
124 | + | |
125 | + bitdelta = ptrbits * depth; | |
126 | + levelptr = no >> bitdelta; | |
127 | + | |
128 | + if (levelptr > QNX6_NO_DIRECT_POINTERS - 1) { | |
129 | + printk(KERN_ERR "qnx6:Requested file block number (%u) too big.", | |
130 | + no); | |
131 | + return 0; | |
132 | + } | |
133 | + | |
134 | + block = qnx6_get_devblock(s, ei->di_block_ptr[levelptr]); | |
135 | + | |
136 | + for (i = 0; i < depth; i++) { | |
137 | + bh = sb_bread(s, block); | |
138 | + if (!bh) { | |
139 | + printk(KERN_ERR "qnx6:Error reading block (%u)\n", | |
140 | + block); | |
141 | + return 0; | |
142 | + } | |
143 | + bitdelta -= ptrbits; | |
144 | + levelptr = (no >> bitdelta) & mask; | |
145 | + ptr = ((__fs32 *)bh->b_data)[levelptr]; | |
146 | + | |
147 | + if (!qnx6_check_blockptr(ptr)) | |
148 | + return 0; | |
149 | + | |
150 | + block = qnx6_get_devblock(s, ptr); | |
151 | + brelse(bh); | |
152 | + } | |
153 | + return block; | |
154 | +} | |
155 | + | |
156 | +static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf) | |
157 | +{ | |
158 | + struct super_block *sb = dentry->d_sb; | |
159 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
160 | + u64 id = huge_encode_dev(sb->s_bdev->bd_dev); | |
161 | + | |
162 | + buf->f_type = sb->s_magic; | |
163 | + buf->f_bsize = sb->s_blocksize; | |
164 | + buf->f_blocks = fs32_to_cpu(sbi, sbi->sb->sb_num_blocks); | |
165 | + buf->f_bfree = fs32_to_cpu(sbi, sbi->sb->sb_free_blocks); | |
166 | + buf->f_files = fs32_to_cpu(sbi, sbi->sb->sb_num_inodes); | |
167 | + buf->f_ffree = fs32_to_cpu(sbi, sbi->sb->sb_free_inodes); | |
168 | + buf->f_bavail = buf->f_bfree; | |
169 | + buf->f_namelen = QNX6_LONG_NAME_MAX; | |
170 | + buf->f_fsid.val[0] = (u32)id; | |
171 | + buf->f_fsid.val[1] = (u32)(id >> 32); | |
172 | + | |
173 | + return 0; | |
174 | +} | |
175 | + | |
176 | +/* | |
177 | + * Check the root directory of the filesystem to make sure | |
178 | + * it really _is_ a qnx6 filesystem, and to check the size | |
179 | + * of the directory entry. | |
180 | + */ | |
181 | +static const char *qnx6_checkroot(struct super_block *s) | |
182 | +{ | |
183 | + static char match_root[2][3] = {".\0\0", "..\0"}; | |
184 | + int i, error = 0; | |
185 | + struct qnx6_dir_entry *dir_entry; | |
186 | + struct inode *root = s->s_root->d_inode; | |
187 | + struct address_space *mapping = root->i_mapping; | |
188 | + struct page *page = read_mapping_page(mapping, 0, NULL); | |
189 | + if (IS_ERR(page)) | |
190 | + return "error reading root directory"; | |
191 | + kmap(page); | |
192 | + dir_entry = page_address(page); | |
193 | + for (i = 0; i < 2; i++) { | |
194 | + /* maximum 3 bytes - due to match_root limitation */ | |
195 | + if (strncmp(dir_entry[i].de_fname, match_root[i], 3)) | |
196 | + error = 1; | |
197 | + } | |
198 | + qnx6_put_page(page); | |
199 | + if (error) | |
200 | + return "error reading root directory."; | |
201 | + return NULL; | |
202 | +} | |
203 | + | |
204 | +#ifdef CONFIG_QNX6FS_DEBUG | |
205 | +void qnx6_superblock_debug(struct qnx6_super_block *sb, struct super_block *s) | |
206 | +{ | |
207 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
208 | + | |
209 | + QNX6DEBUG((KERN_INFO "magic: %08x\n", | |
210 | + fs32_to_cpu(sbi, sb->sb_magic))); | |
211 | + QNX6DEBUG((KERN_INFO "checksum: %08x\n", | |
212 | + fs32_to_cpu(sbi, sb->sb_checksum))); | |
213 | + QNX6DEBUG((KERN_INFO "serial: %llx\n", | |
214 | + fs64_to_cpu(sbi, sb->sb_serial))); | |
215 | + QNX6DEBUG((KERN_INFO "flags: %08x\n", | |
216 | + fs32_to_cpu(sbi, sb->sb_flags))); | |
217 | + QNX6DEBUG((KERN_INFO "blocksize: %08x\n", | |
218 | + fs32_to_cpu(sbi, sb->sb_blocksize))); | |
219 | + QNX6DEBUG((KERN_INFO "num_inodes: %08x\n", | |
220 | + fs32_to_cpu(sbi, sb->sb_num_inodes))); | |
221 | + QNX6DEBUG((KERN_INFO "free_inodes: %08x\n", | |
222 | + fs32_to_cpu(sbi, sb->sb_free_inodes))); | |
223 | + QNX6DEBUG((KERN_INFO "num_blocks: %08x\n", | |
224 | + fs32_to_cpu(sbi, sb->sb_num_blocks))); | |
225 | + QNX6DEBUG((KERN_INFO "free_blocks: %08x\n", | |
226 | + fs32_to_cpu(sbi, sb->sb_free_blocks))); | |
227 | + QNX6DEBUG((KERN_INFO "inode_levels: %02x\n", | |
228 | + sb->Inode.levels)); | |
229 | +} | |
230 | +#endif | |
231 | + | |
232 | +enum { | |
233 | + Opt_mmifs, | |
234 | + Opt_err | |
235 | +}; | |
236 | + | |
237 | +static const match_table_t tokens = { | |
238 | + {Opt_mmifs, "mmi_fs"}, | |
239 | + {Opt_err, NULL} | |
240 | +}; | |
241 | + | |
242 | +static int qnx6_parse_options(char *options, struct super_block *sb) | |
243 | +{ | |
244 | + char *p; | |
245 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
246 | + substring_t args[MAX_OPT_ARGS]; | |
247 | + | |
248 | + if (!options) | |
249 | + return 1; | |
250 | + | |
251 | + while ((p = strsep(&options, ",")) != NULL) { | |
252 | + int token; | |
253 | + if (!*p) | |
254 | + continue; | |
255 | + | |
256 | + token = match_token(p, tokens, args); | |
257 | + switch (token) { | |
258 | + case Opt_mmifs: | |
259 | + set_opt(sbi->s_mount_opt, MMI_FS); | |
260 | + break; | |
261 | + default: | |
262 | + return 0; | |
263 | + } | |
264 | + } | |
265 | + return 1; | |
266 | +} | |
267 | + | |
268 | +static struct buffer_head *qnx6_check_first_superblock(struct super_block *s, | |
269 | + int offset, int silent) | |
270 | +{ | |
271 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
272 | + struct buffer_head *bh; | |
273 | + struct qnx6_super_block *sb; | |
274 | + | |
275 | + /* Check the superblock signatures | |
276 | + start with the first superblock */ | |
277 | + bh = sb_bread(s, offset); | |
278 | + if (!bh) { | |
279 | + printk(KERN_ERR "qnx6: unable to read the first superblock\n"); | |
280 | + return NULL; | |
281 | + } | |
282 | + sb = (struct qnx6_super_block *)bh->b_data; | |
283 | + if (fs32_to_cpu(sbi, sb->sb_magic) != QNX6_SUPER_MAGIC) { | |
284 | + sbi->s_bytesex = BYTESEX_BE; | |
285 | + if (fs32_to_cpu(sbi, sb->sb_magic) == QNX6_SUPER_MAGIC) { | |
286 | + /* we got a big endian fs */ | |
287 | + QNX6DEBUG((KERN_INFO "qnx6: fs got different" | |
288 | + " endianess.\n")); | |
289 | + return bh; | |
290 | + } else | |
291 | + sbi->s_bytesex = BYTESEX_LE; | |
292 | + if (!silent) { | |
293 | + if (offset == 0) { | |
294 | + printk(KERN_ERR "qnx6: wrong signature (magic)" | |
295 | + " in superblock #1.\n"); | |
296 | + } else { | |
297 | + printk(KERN_INFO "qnx6: wrong signature (magic)" | |
298 | + " at position (0x%lx) - will try" | |
299 | + " alternative position (0x0000).\n", | |
300 | + offset * s->s_blocksize); | |
301 | + } | |
302 | + } | |
303 | + brelse(bh); | |
304 | + return NULL; | |
305 | + } | |
306 | + return bh; | |
307 | +} | |
308 | + | |
309 | +static struct inode *qnx6_private_inode(struct super_block *s, | |
310 | + struct qnx6_root_node *p); | |
311 | + | |
312 | +static int qnx6_fill_super(struct super_block *s, void *data, int silent) | |
313 | +{ | |
314 | + struct buffer_head *bh1 = NULL, *bh2 = NULL; | |
315 | + struct qnx6_super_block *sb1 = NULL, *sb2 = NULL; | |
316 | + struct qnx6_sb_info *sbi; | |
317 | + struct inode *root; | |
318 | + const char *errmsg; | |
319 | + struct qnx6_sb_info *qs; | |
320 | + int ret = -EINVAL; | |
321 | + u64 offset; | |
322 | + int bootblock_offset = QNX6_BOOTBLOCK_SIZE; | |
323 | + | |
324 | + qs = kzalloc(sizeof(struct qnx6_sb_info), GFP_KERNEL); | |
325 | + if (!qs) | |
326 | + return -ENOMEM; | |
327 | + s->s_fs_info = qs; | |
328 | + | |
329 | + /* Superblock always is 512 Byte long */ | |
330 | + if (!sb_set_blocksize(s, QNX6_SUPERBLOCK_SIZE)) { | |
331 | + printk(KERN_ERR "qnx6: unable to set blocksize\n"); | |
332 | + goto outnobh; | |
333 | + } | |
334 | + | |
335 | + /* parse the mount-options */ | |
336 | + if (!qnx6_parse_options((char *) data, s)) { | |
337 | + printk(KERN_ERR "qnx6: invalid mount options.\n"); | |
338 | + goto outnobh; | |
339 | + } | |
340 | + if (test_opt(s, MMI_FS)) { | |
341 | + sb1 = qnx6_mmi_fill_super(s, silent); | |
342 | + if (sb1) | |
343 | + goto mmi_success; | |
344 | + else | |
345 | + goto outnobh; | |
346 | + } | |
347 | + sbi = QNX6_SB(s); | |
348 | + sbi->s_bytesex = BYTESEX_LE; | |
349 | + /* Check the superblock signatures | |
350 | + start with the first superblock */ | |
351 | + bh1 = qnx6_check_first_superblock(s, | |
352 | + bootblock_offset / QNX6_SUPERBLOCK_SIZE, silent); | |
353 | + if (!bh1) { | |
354 | + /* try again without bootblock offset */ | |
355 | + bh1 = qnx6_check_first_superblock(s, 0, silent); | |
356 | + if (!bh1) { | |
357 | + printk(KERN_ERR "qnx6: unable to read the first superblock\n"); | |
358 | + goto outnobh; | |
359 | + } | |
360 | + /* seems that no bootblock at partition start */ | |
361 | + bootblock_offset = 0; | |
362 | + } | |
363 | + sb1 = (struct qnx6_super_block *)bh1->b_data; | |
364 | + | |
365 | +#ifdef CONFIG_QNX6FS_DEBUG | |
366 | + qnx6_superblock_debug(sb1, s); | |
367 | +#endif | |
368 | + | |
369 | + /* checksum check - start at byte 8 and end at byte 512 */ | |
370 | + if (fs32_to_cpu(sbi, sb1->sb_checksum) != | |
371 | + crc32_be(0, (char *)(bh1->b_data + 8), 504)) { | |
372 | + printk(KERN_ERR "qnx6: superblock #1 checksum error\n"); | |
373 | + goto out; | |
374 | + } | |
375 | + | |
376 | + /* set new blocksize */ | |
377 | + if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) { | |
378 | + printk(KERN_ERR "qnx6: unable to set blocksize\n"); | |
379 | + goto out; | |
380 | + } | |
381 | + /* blocksize invalidates bh - pull it back in */ | |
382 | + brelse(bh1); | |
383 | + bh1 = sb_bread(s, bootblock_offset >> s->s_blocksize_bits); | |
384 | + if (!bh1) | |
385 | + goto outnobh; | |
386 | + sb1 = (struct qnx6_super_block *)bh1->b_data; | |
387 | + | |
388 | + /* calculate second superblock blocknumber */ | |
389 | + offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + | |
390 | + (bootblock_offset >> s->s_blocksize_bits) + | |
391 | + (QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits); | |
392 | + | |
393 | + /* set bootblock offset */ | |
394 | + sbi->s_blks_off = (bootblock_offset >> s->s_blocksize_bits) + | |
395 | + (QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits); | |
396 | + | |
397 | + /* next the second superblock */ | |
398 | + bh2 = sb_bread(s, offset); | |
399 | + if (!bh2) { | |
400 | + printk(KERN_ERR "qnx6: unable to read the second superblock\n"); | |
401 | + goto out; | |
402 | + } | |
403 | + sb2 = (struct qnx6_super_block *)bh2->b_data; | |
404 | + if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) { | |
405 | + if (!silent) | |
406 | + printk(KERN_ERR "qnx6: wrong signature (magic)" | |
407 | + " in superblock #2.\n"); | |
408 | + goto out; | |
409 | + } | |
410 | + | |
411 | + /* checksum check - start at byte 8 and end at byte 512 */ | |
412 | + if (fs32_to_cpu(sbi, sb2->sb_checksum) != | |
413 | + crc32_be(0, (char *)(bh2->b_data + 8), 504)) { | |
414 | + printk(KERN_ERR "qnx6: superblock #2 checksum error\n"); | |
415 | + goto out; | |
416 | + } | |
417 | + | |
418 | + if (fs64_to_cpu(sbi, sb1->sb_serial) >= | |
419 | + fs64_to_cpu(sbi, sb2->sb_serial)) { | |
420 | + /* superblock #1 active */ | |
421 | + sbi->sb_buf = bh1; | |
422 | + sbi->sb = (struct qnx6_super_block *)bh1->b_data; | |
423 | + brelse(bh2); | |
424 | + printk(KERN_INFO "qnx6: superblock #1 active\n"); | |
425 | + } else { | |
426 | + /* superblock #2 active */ | |
427 | + sbi->sb_buf = bh2; | |
428 | + sbi->sb = (struct qnx6_super_block *)bh2->b_data; | |
429 | + brelse(bh1); | |
430 | + printk(KERN_INFO "qnx6: superblock #2 active\n"); | |
431 | + } | |
432 | +mmi_success: | |
433 | + /* sanity check - limit maximum indirect pointer levels */ | |
434 | + if (sb1->Inode.levels > QNX6_PTR_MAX_LEVELS) { | |
435 | + printk(KERN_ERR "qnx6: too many inode levels (max %i, sb %i)\n", | |
436 | + QNX6_PTR_MAX_LEVELS, sb1->Inode.levels); | |
437 | + goto out; | |
438 | + } | |
439 | + if (sb1->Longfile.levels > QNX6_PTR_MAX_LEVELS) { | |
440 | + printk(KERN_ERR "qnx6: too many longfilename levels" | |
441 | + " (max %i, sb %i)\n", | |
442 | + QNX6_PTR_MAX_LEVELS, sb1->Longfile.levels); | |
443 | + goto out; | |
444 | + } | |
445 | + s->s_op = &qnx6_sops; | |
446 | + s->s_magic = QNX6_SUPER_MAGIC; | |
447 | + s->s_flags |= MS_RDONLY; /* Yup, read-only yet */ | |
448 | + | |
449 | + /* ease the later tree level calculations */ | |
450 | + sbi = QNX6_SB(s); | |
451 | + sbi->s_ptrbits = ilog2(s->s_blocksize / 4); | |
452 | + sbi->inodes = qnx6_private_inode(s, &sb1->Inode); | |
453 | + if (!sbi->inodes) | |
454 | + goto out; | |
455 | + sbi->longfile = qnx6_private_inode(s, &sb1->Longfile); | |
456 | + if (!sbi->longfile) | |
457 | + goto out1; | |
458 | + | |
459 | + /* prefetch root inode */ | |
460 | + root = qnx6_iget(s, QNX6_ROOT_INO); | |
461 | + if (IS_ERR(root)) { | |
462 | + printk(KERN_ERR "qnx6: get inode failed\n"); | |
463 | + ret = PTR_ERR(root); | |
464 | + goto out2; | |
465 | + } | |
466 | + | |
467 | + ret = -ENOMEM; | |
468 | + s->s_root = d_make_root(root); | |
469 | + if (!s->s_root) | |
470 | + goto out2; | |
471 | + | |
472 | + ret = -EINVAL; | |
473 | + errmsg = qnx6_checkroot(s); | |
474 | + if (errmsg != NULL) { | |
475 | + if (!silent) | |
476 | + printk(KERN_ERR "qnx6: %s\n", errmsg); | |
477 | + goto out3; | |
478 | + } | |
479 | + return 0; | |
480 | + | |
481 | +out3: | |
482 | + dput(s->s_root); | |
483 | + s->s_root = NULL; | |
484 | +out2: | |
485 | + iput(sbi->longfile); | |
486 | +out1: | |
487 | + iput(sbi->inodes); | |
488 | +out: | |
489 | + if (bh1) | |
490 | + brelse(bh1); | |
491 | + if (bh2) | |
492 | + brelse(bh2); | |
493 | +outnobh: | |
494 | + kfree(qs); | |
495 | + s->s_fs_info = NULL; | |
496 | + return ret; | |
497 | +} | |
498 | + | |
499 | +static void qnx6_put_super(struct super_block *sb) | |
500 | +{ | |
501 | + struct qnx6_sb_info *qs = QNX6_SB(sb); | |
502 | + brelse(qs->sb_buf); | |
503 | + iput(qs->longfile); | |
504 | + iput(qs->inodes); | |
505 | + kfree(qs); | |
506 | + sb->s_fs_info = NULL; | |
507 | + return; | |
508 | +} | |
509 | + | |
510 | +static sector_t qnx6_bmap(struct address_space *mapping, sector_t block) | |
511 | +{ | |
512 | + return generic_block_bmap(mapping, block, qnx6_get_block); | |
513 | +} | |
514 | +static const struct address_space_operations qnx6_aops = { | |
515 | + .readpage = qnx6_readpage, | |
516 | + .readpages = qnx6_readpages, | |
517 | + .bmap = qnx6_bmap | |
518 | +}; | |
519 | + | |
520 | +static struct inode *qnx6_private_inode(struct super_block *s, | |
521 | + struct qnx6_root_node *p) | |
522 | +{ | |
523 | + struct inode *inode = new_inode(s); | |
524 | + if (inode) { | |
525 | + struct qnx6_inode_info *ei = QNX6_I(inode); | |
526 | + struct qnx6_sb_info *sbi = QNX6_SB(s); | |
527 | + inode->i_size = fs64_to_cpu(sbi, p->size); | |
528 | + memcpy(ei->di_block_ptr, p->ptr, sizeof(p->ptr)); | |
529 | + ei->di_filelevels = p->levels; | |
530 | + inode->i_mode = S_IFREG | S_IRUSR; /* probably wrong */ | |
531 | + inode->i_mapping->a_ops = &qnx6_aops; | |
532 | + } | |
533 | + return inode; | |
534 | +} | |
535 | + | |
536 | +struct inode *qnx6_iget(struct super_block *sb, unsigned ino) | |
537 | +{ | |
538 | + struct qnx6_sb_info *sbi = QNX6_SB(sb); | |
539 | + struct qnx6_inode_entry *raw_inode; | |
540 | + struct inode *inode; | |
541 | + struct qnx6_inode_info *ei; | |
542 | + struct address_space *mapping; | |
543 | + struct page *page; | |
544 | + u32 n, offs; | |
545 | + | |
546 | + inode = iget_locked(sb, ino); | |
547 | + if (!inode) | |
548 | + return ERR_PTR(-ENOMEM); | |
549 | + if (!(inode->i_state & I_NEW)) | |
550 | + return inode; | |
551 | + | |
552 | + ei = QNX6_I(inode); | |
553 | + | |
554 | + inode->i_mode = 0; | |
555 | + | |
556 | + if (ino == 0) { | |
557 | + printk(KERN_ERR "qnx6: bad inode number on dev %s: %u is " | |
558 | + "out of range\n", | |
559 | + sb->s_id, ino); | |
560 | + iget_failed(inode); | |
561 | + return ERR_PTR(-EIO); | |
562 | + } | |
563 | + n = (ino - 1) >> (PAGE_CACHE_SHIFT - QNX6_INODE_SIZE_BITS); | |
564 | + offs = (ino - 1) & (~PAGE_CACHE_MASK >> QNX6_INODE_SIZE_BITS); | |
565 | + mapping = sbi->inodes->i_mapping; | |
566 | + page = read_mapping_page(mapping, n, NULL); | |
567 | + if (IS_ERR(page)) { | |
568 | + printk(KERN_ERR "qnx6: major problem: unable to read inode from " | |
569 | + "dev %s\n", sb->s_id); | |
570 | + iget_failed(inode); | |
571 | + return ERR_CAST(page); | |
572 | + } | |
573 | + kmap(page); | |
574 | + raw_inode = ((struct qnx6_inode_entry *)page_address(page)) + offs; | |
575 | + | |
576 | + inode->i_mode = fs16_to_cpu(sbi, raw_inode->di_mode); | |
577 | + inode->i_uid = (uid_t)fs32_to_cpu(sbi, raw_inode->di_uid); | |
578 | + inode->i_gid = (gid_t)fs32_to_cpu(sbi, raw_inode->di_gid); | |
579 | + inode->i_size = fs64_to_cpu(sbi, raw_inode->di_size); | |
580 | + inode->i_mtime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_mtime); | |
581 | + inode->i_mtime.tv_nsec = 0; | |
582 | + inode->i_atime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_atime); | |
583 | + inode->i_atime.tv_nsec = 0; | |
584 | + inode->i_ctime.tv_sec = fs32_to_cpu(sbi, raw_inode->di_ctime); | |
585 | + inode->i_ctime.tv_nsec = 0; | |
586 | + | |
587 | + /* calc blocks based on 512 byte blocksize */ | |
588 | + inode->i_blocks = (inode->i_size + 511) >> 9; | |
589 | + | |
590 | + memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr, | |
591 | + sizeof(raw_inode->di_block_ptr)); | |
592 | + ei->di_filelevels = raw_inode->di_filelevels; | |
593 | + | |
594 | + if (S_ISREG(inode->i_mode)) { | |
595 | + inode->i_fop = &generic_ro_fops; | |
596 | + inode->i_mapping->a_ops = &qnx6_aops; | |
597 | + } else if (S_ISDIR(inode->i_mode)) { | |
598 | + inode->i_op = &qnx6_dir_inode_operations; | |
599 | + inode->i_fop = &qnx6_dir_operations; | |
600 | + inode->i_mapping->a_ops = &qnx6_aops; | |
601 | + } else if (S_ISLNK(inode->i_mode)) { | |
602 | + inode->i_op = &page_symlink_inode_operations; | |
603 | + inode->i_mapping->a_ops = &qnx6_aops; | |
604 | + } else | |
605 | + init_special_inode(inode, inode->i_mode, 0); | |
606 | + qnx6_put_page(page); | |
607 | + unlock_new_inode(inode); | |
608 | + return inode; | |
609 | +} | |
610 | + | |
611 | +static struct kmem_cache *qnx6_inode_cachep; | |
612 | + | |
613 | +static struct inode *qnx6_alloc_inode(struct super_block *sb) | |
614 | +{ | |
615 | + struct qnx6_inode_info *ei; | |
616 | + ei = kmem_cache_alloc(qnx6_inode_cachep, GFP_KERNEL); | |
617 | + if (!ei) | |
618 | + return NULL; | |
619 | + return &ei->vfs_inode; | |
620 | +} | |
621 | + | |
622 | +static void qnx6_i_callback(struct rcu_head *head) | |
623 | +{ | |
624 | + struct inode *inode = container_of(head, struct inode, i_rcu); | |
625 | + INIT_LIST_HEAD(&inode->i_dentry); | |
626 | + kmem_cache_free(qnx6_inode_cachep, QNX6_I(inode)); | |
627 | +} | |
628 | + | |
629 | +static void qnx6_destroy_inode(struct inode *inode) | |
630 | +{ | |
631 | + call_rcu(&inode->i_rcu, qnx6_i_callback); | |
632 | +} | |
633 | + | |
634 | +static void init_once(void *foo) | |
635 | +{ | |
636 | + struct qnx6_inode_info *ei = (struct qnx6_inode_info *) foo; | |
637 | + | |
638 | + inode_init_once(&ei->vfs_inode); | |
639 | +} | |
640 | + | |
641 | +static int init_inodecache(void) | |
642 | +{ | |
643 | + qnx6_inode_cachep = kmem_cache_create("qnx6_inode_cache", | |
644 | + sizeof(struct qnx6_inode_info), | |
645 | + 0, (SLAB_RECLAIM_ACCOUNT| | |
646 | + SLAB_MEM_SPREAD), | |
647 | + init_once); | |
648 | + if (!qnx6_inode_cachep) | |
649 | + return -ENOMEM; | |
650 | + return 0; | |
651 | +} | |
652 | + | |
653 | +static void destroy_inodecache(void) | |
654 | +{ | |
655 | + kmem_cache_destroy(qnx6_inode_cachep); | |
656 | +} | |
657 | + | |
658 | +static struct dentry *qnx6_mount(struct file_system_type *fs_type, | |
659 | + int flags, const char *dev_name, void *data) | |
660 | +{ | |
661 | + return mount_bdev(fs_type, flags, dev_name, data, qnx6_fill_super); | |
662 | +} | |
663 | + | |
664 | +static struct file_system_type qnx6_fs_type = { | |
665 | + .owner = THIS_MODULE, | |
666 | + .name = "qnx6", | |
667 | + .mount = qnx6_mount, | |
668 | + .kill_sb = kill_block_super, | |
669 | + .fs_flags = FS_REQUIRES_DEV, | |
670 | +}; | |
671 | + | |
672 | +static int __init init_qnx6_fs(void) | |
673 | +{ | |
674 | + int err; | |
675 | + | |
676 | + err = init_inodecache(); | |
677 | + if (err) | |
678 | + return err; | |
679 | + | |
680 | + err = register_filesystem(&qnx6_fs_type); | |
681 | + if (err) { | |
682 | + destroy_inodecache(); | |
683 | + return err; | |
684 | + } | |
685 | + | |
686 | + printk(KERN_INFO "QNX6 filesystem 1.0.0 registered.\n"); | |
687 | + return 0; | |
688 | +} | |
689 | + | |
690 | +static void __exit exit_qnx6_fs(void) | |
691 | +{ | |
692 | + unregister_filesystem(&qnx6_fs_type); | |
693 | + destroy_inodecache(); | |
694 | +} | |
695 | + | |
696 | +module_init(init_qnx6_fs) | |
697 | +module_exit(exit_qnx6_fs) | |
698 | +MODULE_LICENSE("GPL"); |
fs/qnx6/namei.c
1 | +/* | |
2 | + * QNX6 file system, Linux implementation. | |
3 | + * | |
4 | + * Version : 1.0.0 | |
5 | + * | |
6 | + * History : | |
7 | + * | |
8 | + * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release. | |
9 | + * 16-02-2012 pagemap extension by Al Viro | |
10 | + * | |
11 | + */ | |
12 | + | |
13 | +#include "qnx6.h" | |
14 | + | |
15 | +struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry, | |
16 | + struct nameidata *nd) | |
17 | +{ | |
18 | + unsigned ino; | |
19 | + struct page *page; | |
20 | + struct inode *foundinode = NULL; | |
21 | + const char *name = dentry->d_name.name; | |
22 | + int len = dentry->d_name.len; | |
23 | + | |
24 | + if (len > QNX6_LONG_NAME_MAX) | |
25 | + return ERR_PTR(-ENAMETOOLONG); | |
26 | + | |
27 | + ino = qnx6_find_entry(len, dir, name, &page); | |
28 | + if (ino) { | |
29 | + foundinode = qnx6_iget(dir->i_sb, ino); | |
30 | + qnx6_put_page(page); | |
31 | + if (IS_ERR(foundinode)) { | |
32 | + QNX6DEBUG((KERN_ERR "qnx6: lookup->iget -> " | |
33 | + " error %ld\n", PTR_ERR(foundinode))); | |
34 | + return ERR_CAST(foundinode); | |
35 | + } | |
36 | + } else { | |
37 | + QNX6DEBUG((KERN_INFO "qnx6_lookup: not found %s\n", name)); | |
38 | + return NULL; | |
39 | + } | |
40 | + d_add(dentry, foundinode); | |
41 | + return NULL; | |
42 | +} |
fs/qnx6/qnx6.h
1 | +/* | |
2 | + * QNX6 file system, Linux implementation. | |
3 | + * | |
4 | + * Version : 1.0.0 | |
5 | + * | |
6 | + * History : | |
7 | + * | |
8 | + * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release. | |
9 | + * 16-02-2012 page map extension by Al Viro | |
10 | + * | |
11 | + */ | |
12 | + | |
13 | +#include <linux/fs.h> | |
14 | +#include <linux/pagemap.h> | |
15 | + | |
16 | +typedef __u16 __bitwise __fs16; | |
17 | +typedef __u32 __bitwise __fs32; | |
18 | +typedef __u64 __bitwise __fs64; | |
19 | + | |
20 | +#include <linux/qnx6_fs.h> | |
21 | + | |
22 | +#ifdef CONFIG_QNX6FS_DEBUG | |
23 | +#define QNX6DEBUG(X) printk X | |
24 | +#else | |
25 | +#define QNX6DEBUG(X) (void) 0 | |
26 | +#endif | |
27 | + | |
28 | +struct qnx6_sb_info { | |
29 | + struct buffer_head *sb_buf; /* superblock buffer */ | |
30 | + struct qnx6_super_block *sb; /* our superblock */ | |
31 | + int s_blks_off; /* blkoffset fs-startpoint */ | |
32 | + int s_ptrbits; /* indirect pointer bitfield */ | |
33 | + unsigned long s_mount_opt; /* all mount options */ | |
34 | + int s_bytesex; /* holds endianess info */ | |
35 | + struct inode * inodes; | |
36 | + struct inode * longfile; | |
37 | +}; | |
38 | + | |
39 | +struct qnx6_inode_info { | |
40 | + __fs32 di_block_ptr[QNX6_NO_DIRECT_POINTERS]; | |
41 | + __u8 di_filelevels; | |
42 | + __u32 i_dir_start_lookup; | |
43 | + struct inode vfs_inode; | |
44 | +}; | |
45 | + | |
46 | +extern struct inode *qnx6_iget(struct super_block *sb, unsigned ino); | |
47 | +extern struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry, | |
48 | + struct nameidata *nd); | |
49 | + | |
50 | +#ifdef CONFIG_QNX6FS_DEBUG | |
51 | +extern void qnx6_superblock_debug(struct qnx6_super_block *, | |
52 | + struct super_block *); | |
53 | +#endif | |
54 | + | |
55 | +extern const struct inode_operations qnx6_dir_inode_operations; | |
56 | +extern const struct file_operations qnx6_dir_operations; | |
57 | + | |
58 | +static inline struct qnx6_sb_info *QNX6_SB(struct super_block *sb) | |
59 | +{ | |
60 | + return sb->s_fs_info; | |
61 | +} | |
62 | + | |
63 | +static inline struct qnx6_inode_info *QNX6_I(struct inode *inode) | |
64 | +{ | |
65 | + return container_of(inode, struct qnx6_inode_info, vfs_inode); | |
66 | +} | |
67 | + | |
68 | +#define clear_opt(o, opt) (o &= ~(QNX6_MOUNT_##opt)) | |
69 | +#define set_opt(o, opt) (o |= (QNX6_MOUNT_##opt)) | |
70 | +#define test_opt(sb, opt) (QNX6_SB(sb)->s_mount_opt & \ | |
71 | + QNX6_MOUNT_##opt) | |
72 | +enum { | |
73 | + BYTESEX_LE, | |
74 | + BYTESEX_BE, | |
75 | +}; | |
76 | + | |
77 | +static inline __u64 fs64_to_cpu(struct qnx6_sb_info *sbi, __fs64 n) | |
78 | +{ | |
79 | + if (sbi->s_bytesex == BYTESEX_LE) | |
80 | + return le64_to_cpu((__force __le64)n); | |
81 | + else | |
82 | + return be64_to_cpu((__force __be64)n); | |
83 | +} | |
84 | + | |
85 | +static inline __fs64 cpu_to_fs64(struct qnx6_sb_info *sbi, __u64 n) | |
86 | +{ | |
87 | + if (sbi->s_bytesex == BYTESEX_LE) | |
88 | + return (__force __fs64)cpu_to_le64(n); | |
89 | + else | |
90 | + return (__force __fs64)cpu_to_be64(n); | |
91 | +} | |
92 | + | |
93 | +static inline __u32 fs32_to_cpu(struct qnx6_sb_info *sbi, __fs32 n) | |
94 | +{ | |
95 | + if (sbi->s_bytesex == BYTESEX_LE) | |
96 | + return le32_to_cpu((__force __le32)n); | |
97 | + else | |
98 | + return be32_to_cpu((__force __be32)n); | |
99 | +} | |
100 | + | |
101 | +static inline __fs32 cpu_to_fs32(struct qnx6_sb_info *sbi, __u32 n) | |
102 | +{ | |
103 | + if (sbi->s_bytesex == BYTESEX_LE) | |
104 | + return (__force __fs32)cpu_to_le32(n); | |
105 | + else | |
106 | + return (__force __fs32)cpu_to_be32(n); | |
107 | +} | |
108 | + | |
109 | +static inline __u16 fs16_to_cpu(struct qnx6_sb_info *sbi, __fs16 n) | |
110 | +{ | |
111 | + if (sbi->s_bytesex == BYTESEX_LE) | |
112 | + return le16_to_cpu((__force __le16)n); | |
113 | + else | |
114 | + return be16_to_cpu((__force __be16)n); | |
115 | +} | |
116 | + | |
117 | +static inline __fs16 cpu_to_fs16(struct qnx6_sb_info *sbi, __u16 n) | |
118 | +{ | |
119 | + if (sbi->s_bytesex == BYTESEX_LE) | |
120 | + return (__force __fs16)cpu_to_le16(n); | |
121 | + else | |
122 | + return (__force __fs16)cpu_to_be16(n); | |
123 | +} | |
124 | + | |
125 | +extern struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, | |
126 | + int silent); | |
127 | + | |
128 | +static inline void qnx6_put_page(struct page *page) | |
129 | +{ | |
130 | + kunmap(page); | |
131 | + page_cache_release(page); | |
132 | +} | |
133 | + | |
134 | +extern unsigned qnx6_find_entry(int len, struct inode *dir, const char *name, | |
135 | + struct page **res_page); |
fs/qnx6/super_mmi.c
1 | +/* | |
2 | + * QNX6 file system, Linux implementation. | |
3 | + * | |
4 | + * Version : 1.0.0 | |
5 | + * | |
6 | + * History : | |
7 | + * | |
8 | + * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release. | |
9 | + * | |
10 | + */ | |
11 | + | |
12 | +#include <linux/buffer_head.h> | |
13 | +#include <linux/slab.h> | |
14 | +#include <linux/crc32.h> | |
15 | +#include "qnx6.h" | |
16 | + | |
17 | +static void qnx6_mmi_copy_sb(struct qnx6_super_block *qsb, | |
18 | + struct qnx6_mmi_super_block *sb) | |
19 | +{ | |
20 | + qsb->sb_magic = sb->sb_magic; | |
21 | + qsb->sb_checksum = sb->sb_checksum; | |
22 | + qsb->sb_serial = sb->sb_serial; | |
23 | + qsb->sb_blocksize = sb->sb_blocksize; | |
24 | + qsb->sb_num_inodes = sb->sb_num_inodes; | |
25 | + qsb->sb_free_inodes = sb->sb_free_inodes; | |
26 | + qsb->sb_num_blocks = sb->sb_num_blocks; | |
27 | + qsb->sb_free_blocks = sb->sb_free_blocks; | |
28 | + | |
29 | + /* the rest of the superblock is the same */ | |
30 | + memcpy(&qsb->Inode, &sb->Inode, sizeof(sb->Inode)); | |
31 | + memcpy(&qsb->Bitmap, &sb->Bitmap, sizeof(sb->Bitmap)); | |
32 | + memcpy(&qsb->Longfile, &sb->Longfile, sizeof(sb->Longfile)); | |
33 | +} | |
34 | + | |
35 | +struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) | |
36 | +{ | |
37 | + struct buffer_head *bh1, *bh2 = NULL; | |
38 | + struct qnx6_mmi_super_block *sb1, *sb2; | |
39 | + struct qnx6_super_block *qsb = NULL; | |
40 | + struct qnx6_sb_info *sbi; | |
41 | + __u64 offset; | |
42 | + | |
43 | + /* Check the superblock signatures | |
44 | + start with the first superblock */ | |
45 | + bh1 = sb_bread(s, 0); | |
46 | + if (!bh1) { | |
47 | + printk(KERN_ERR "qnx6: Unable to read first mmi superblock\n"); | |
48 | + return NULL; | |
49 | + } | |
50 | + sb1 = (struct qnx6_mmi_super_block *)bh1->b_data; | |
51 | + sbi = QNX6_SB(s); | |
52 | + if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) { | |
53 | + if (!silent) { | |
54 | + printk(KERN_ERR "qnx6: wrong signature (magic) in" | |
55 | + " superblock #1.\n"); | |
56 | + goto out; | |
57 | + } | |
58 | + } | |
59 | + | |
60 | + /* checksum check - start at byte 8 and end at byte 512 */ | |
61 | + if (fs32_to_cpu(sbi, sb1->sb_checksum) != | |
62 | + crc32_be(0, (char *)(bh1->b_data + 8), 504)) { | |
63 | + printk(KERN_ERR "qnx6: superblock #1 checksum error\n"); | |
64 | + goto out; | |
65 | + } | |
66 | + | |
67 | + /* calculate second superblock blocknumber */ | |
68 | + offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / | |
69 | + fs32_to_cpu(sbi, sb1->sb_blocksize); | |
70 | + | |
71 | + /* set new blocksize */ | |
72 | + if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) { | |
73 | + printk(KERN_ERR "qnx6: unable to set blocksize\n"); | |
74 | + goto out; | |
75 | + } | |
76 | + /* blocksize invalidates bh - pull it back in */ | |
77 | + brelse(bh1); | |
78 | + bh1 = sb_bread(s, 0); | |
79 | + if (!bh1) | |
80 | + goto out; | |
81 | + sb1 = (struct qnx6_mmi_super_block *)bh1->b_data; | |
82 | + | |
83 | + /* read second superblock */ | |
84 | + bh2 = sb_bread(s, offset); | |
85 | + if (!bh2) { | |
86 | + printk(KERN_ERR "qnx6: unable to read the second superblock\n"); | |
87 | + goto out; | |
88 | + } | |
89 | + sb2 = (struct qnx6_mmi_super_block *)bh2->b_data; | |
90 | + if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) { | |
91 | + if (!silent) | |
92 | + printk(KERN_ERR "qnx6: wrong signature (magic) in" | |
93 | + " superblock #2.\n"); | |
94 | + goto out; | |
95 | + } | |
96 | + | |
97 | + /* checksum check - start at byte 8 and end at byte 512 */ | |
98 | + if (fs32_to_cpu(sbi, sb2->sb_checksum) | |
99 | + != crc32_be(0, (char *)(bh2->b_data + 8), 504)) { | |
100 | + printk(KERN_ERR "qnx6: superblock #1 checksum error\n"); | |
101 | + goto out; | |
102 | + } | |
103 | + | |
104 | + qsb = kmalloc(sizeof(*qsb), GFP_KERNEL); | |
105 | + if (!qsb) { | |
106 | + printk(KERN_ERR "qnx6: unable to allocate memory.\n"); | |
107 | + goto out; | |
108 | + } | |
109 | + | |
110 | + if (fs64_to_cpu(sbi, sb1->sb_serial) > | |
111 | + fs64_to_cpu(sbi, sb2->sb_serial)) { | |
112 | + /* superblock #1 active */ | |
113 | + qnx6_mmi_copy_sb(qsb, sb1); | |
114 | +#ifdef CONFIG_QNX6FS_DEBUG | |
115 | + qnx6_superblock_debug(qsb, s); | |
116 | +#endif | |
117 | + memcpy(bh1->b_data, qsb, sizeof(struct qnx6_super_block)); | |
118 | + | |
119 | + sbi->sb_buf = bh1; | |
120 | + sbi->sb = (struct qnx6_super_block *)bh1->b_data; | |
121 | + brelse(bh2); | |
122 | + printk(KERN_INFO "qnx6: superblock #1 active\n"); | |
123 | + } else { | |
124 | + /* superblock #2 active */ | |
125 | + qnx6_mmi_copy_sb(qsb, sb2); | |
126 | +#ifdef CONFIG_QNX6FS_DEBUG | |
127 | + qnx6_superblock_debug(qsb, s); | |
128 | +#endif | |
129 | + memcpy(bh2->b_data, qsb, sizeof(struct qnx6_super_block)); | |
130 | + | |
131 | + sbi->sb_buf = bh2; | |
132 | + sbi->sb = (struct qnx6_super_block *)bh2->b_data; | |
133 | + brelse(bh1); | |
134 | + printk(KERN_INFO "qnx6: superblock #2 active\n"); | |
135 | + } | |
136 | + kfree(qsb); | |
137 | + | |
138 | + /* offset for mmi_fs is just SUPERBLOCK_AREA bytes */ | |
139 | + sbi->s_blks_off = QNX6_SUPERBLOCK_AREA / s->s_blocksize; | |
140 | + | |
141 | + /* success */ | |
142 | + return sbi->sb; | |
143 | + | |
144 | +out: | |
145 | + if (bh1 != NULL) | |
146 | + brelse(bh1); | |
147 | + if (bh2 != NULL) | |
148 | + brelse(bh2); | |
149 | + return NULL; | |
150 | +} |
include/linux/magic.h
... | ... | @@ -42,6 +42,7 @@ |
42 | 42 | #define OPENPROM_SUPER_MAGIC 0x9fa1 |
43 | 43 | #define PROC_SUPER_MAGIC 0x9fa0 |
44 | 44 | #define QNX4_SUPER_MAGIC 0x002f /* qnx4 fs detection */ |
45 | +#define QNX6_SUPER_MAGIC 0x68191122 /* qnx6 fs detection */ | |
45 | 46 | |
46 | 47 | #define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ |
47 | 48 | /* used by file system utilities that |
include/linux/qnx6_fs.h
1 | +/* | |
2 | + * Name : qnx6_fs.h | |
3 | + * Author : Kai Bankett | |
4 | + * Function : qnx6 global filesystem definitions | |
5 | + * History : 17-01-2012 created | |
6 | + */ | |
7 | +#ifndef _LINUX_QNX6_FS_H | |
8 | +#define _LINUX_QNX6_FS_H | |
9 | + | |
10 | +#include <linux/types.h> | |
11 | +#include <linux/magic.h> | |
12 | + | |
13 | +#define QNX6_ROOT_INO 1 | |
14 | + | |
15 | +/* for di_status */ | |
16 | +#define QNX6_FILE_DIRECTORY 0x01 | |
17 | +#define QNX6_FILE_DELETED 0x02 | |
18 | +#define QNX6_FILE_NORMAL 0x03 | |
19 | + | |
20 | +#define QNX6_SUPERBLOCK_SIZE 0x200 /* superblock always is 512 bytes */ | |
21 | +#define QNX6_SUPERBLOCK_AREA 0x1000 /* area reserved for superblock */ | |
22 | +#define QNX6_BOOTBLOCK_SIZE 0x2000 /* heading bootblock area */ | |
23 | +#define QNX6_DIR_ENTRY_SIZE 0x20 /* dir entry size of 32 bytes */ | |
24 | +#define QNX6_INODE_SIZE 0x80 /* each inode is 128 bytes */ | |
25 | +#define QNX6_INODE_SIZE_BITS 7 /* inode entry size shift */ | |
26 | + | |
27 | +#define QNX6_NO_DIRECT_POINTERS 16 /* 16 blockptrs in sbl/inode */ | |
28 | +#define QNX6_PTR_MAX_LEVELS 5 /* maximum indirect levels */ | |
29 | + | |
30 | +/* for filenames */ | |
31 | +#define QNX6_SHORT_NAME_MAX 27 | |
32 | +#define QNX6_LONG_NAME_MAX 510 | |
33 | + | |
34 | +/* list of mount options */ | |
35 | +#define QNX6_MOUNT_MMI_FS 0x010000 /* mount as Audi MMI 3G fs */ | |
36 | + | |
37 | +/* | |
38 | + * This is the original qnx6 inode layout on disk. | |
39 | + * Each inode is 128 byte long. | |
40 | + */ | |
41 | +struct qnx6_inode_entry { | |
42 | + __fs64 di_size; | |
43 | + __fs32 di_uid; | |
44 | + __fs32 di_gid; | |
45 | + __fs32 di_ftime; | |
46 | + __fs32 di_mtime; | |
47 | + __fs32 di_atime; | |
48 | + __fs32 di_ctime; | |
49 | + __fs16 di_mode; | |
50 | + __fs16 di_ext_mode; | |
51 | + __fs32 di_block_ptr[QNX6_NO_DIRECT_POINTERS]; | |
52 | + __u8 di_filelevels; | |
53 | + __u8 di_status; | |
54 | + __u8 di_unknown2[2]; | |
55 | + __fs32 di_zero2[6]; | |
56 | +}; | |
57 | + | |
58 | +/* | |
59 | + * Each directory entry is maximum 32 bytes long. | |
60 | + * If more characters or special characters required it is stored | |
61 | + * in the longfilenames structure. | |
62 | + */ | |
63 | +struct qnx6_dir_entry { | |
64 | + __fs32 de_inode; | |
65 | + __u8 de_size; | |
66 | + char de_fname[QNX6_SHORT_NAME_MAX]; | |
67 | +}; | |
68 | + | |
69 | +/* | |
70 | + * Longfilename direntries have a different structure | |
71 | + */ | |
72 | +struct qnx6_long_dir_entry { | |
73 | + __fs32 de_inode; | |
74 | + __u8 de_size; | |
75 | + __u8 de_unknown[3]; | |
76 | + __fs32 de_long_inode; | |
77 | + __fs32 de_checksum; | |
78 | +}; | |
79 | + | |
80 | +struct qnx6_long_filename { | |
81 | + __fs16 lf_size; | |
82 | + __u8 lf_fname[QNX6_LONG_NAME_MAX]; | |
83 | +}; | |
84 | + | |
85 | +struct qnx6_root_node { | |
86 | + __fs64 size; | |
87 | + __fs32 ptr[QNX6_NO_DIRECT_POINTERS]; | |
88 | + __u8 levels; | |
89 | + __u8 mode; | |
90 | + __u8 spare[6]; | |
91 | +}; | |
92 | + | |
93 | +struct qnx6_super_block { | |
94 | + __fs32 sb_magic; | |
95 | + __fs32 sb_checksum; | |
96 | + __fs64 sb_serial; | |
97 | + __fs32 sb_ctime; /* time the fs was created */ | |
98 | + __fs32 sb_atime; /* last access time */ | |
99 | + __fs32 sb_flags; | |
100 | + __fs16 sb_version1; /* filesystem version information */ | |
101 | + __fs16 sb_version2; /* filesystem version information */ | |
102 | + __u8 sb_volumeid[16]; | |
103 | + __fs32 sb_blocksize; | |
104 | + __fs32 sb_num_inodes; | |
105 | + __fs32 sb_free_inodes; | |
106 | + __fs32 sb_num_blocks; | |
107 | + __fs32 sb_free_blocks; | |
108 | + __fs32 sb_allocgroup; | |
109 | + struct qnx6_root_node Inode; | |
110 | + struct qnx6_root_node Bitmap; | |
111 | + struct qnx6_root_node Longfile; | |
112 | + struct qnx6_root_node Unknown; | |
113 | +}; | |
114 | + | |
115 | +/* Audi MMI 3G superblock layout is different to plain qnx6 */ | |
116 | +struct qnx6_mmi_super_block { | |
117 | + __fs32 sb_magic; | |
118 | + __fs32 sb_checksum; | |
119 | + __fs64 sb_serial; | |
120 | + __u8 sb_spare0[12]; | |
121 | + __u8 sb_id[12]; | |
122 | + __fs32 sb_blocksize; | |
123 | + __fs32 sb_num_inodes; | |
124 | + __fs32 sb_free_inodes; | |
125 | + __fs32 sb_num_blocks; | |
126 | + __fs32 sb_free_blocks; | |
127 | + __u8 sb_spare1[4]; | |
128 | + struct qnx6_root_node Inode; | |
129 | + struct qnx6_root_node Bitmap; | |
130 | + struct qnx6_root_node Longfile; | |
131 | + struct qnx6_root_node Unknown; | |
132 | +}; | |
133 | + | |
134 | +#endif |