| /* fsys_xfs.c - an implementation for the SGI XFS file system */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2001,2002,2004 Free Software Foundation, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #ifdef FSYS_XFS |
| |
| #include "shared.h" |
| #include "filesys.h" |
| #include "xfs.h" |
| |
| #define MAX_LINK_COUNT 8 |
| |
| typedef struct xad { |
| xfs_fileoff_t offset; |
| xfs_fsblock_t start; |
| xfs_filblks_t len; |
| } xad_t; |
| |
| struct xfs_info { |
| int bsize; |
| int dirbsize; |
| int isize; |
| unsigned int agblocks; |
| int bdlog; |
| int blklog; |
| int inopblog; |
| int agblklog; |
| int agnolog; |
| unsigned int nextents; |
| xfs_daddr_t next; |
| xfs_daddr_t daddr; |
| xfs_dablk_t forw; |
| xfs_dablk_t dablk; |
| xfs_bmbt_rec_32_t *xt; |
| xfs_bmbt_ptr_t ptr0; |
| int btnode_ptr0_off; |
| int i8param; |
| int dirpos; |
| int dirmax; |
| int blkoff; |
| int fpos; |
| xfs_ino_t rootino; |
| }; |
| |
| static struct xfs_info xfs; |
| |
| #define dirbuf ((char *)FSYS_BUF) |
| #define filebuf ((char *)FSYS_BUF + 4096) |
| #define inode ((xfs_dinode_t *)((char *)FSYS_BUF + 8192)) |
| #define icore (inode->di_core) |
| |
| #define mask32lo(n) (((xfs_uint32_t)1 << (n)) - 1) |
| |
| #define XFS_INO_MASK(k) ((xfs_uint32_t)((1ULL << (k)) - 1)) |
| #define XFS_INO_OFFSET_BITS xfs.inopblog |
| #define XFS_INO_AGBNO_BITS xfs.agblklog |
| #define XFS_INO_AGINO_BITS (xfs.agblklog + xfs.inopblog) |
| #define XFS_INO_AGNO_BITS xfs.agnolog |
| #define NAME_BUF ((char *)FSYS_BUF + 12288) |
| |
| static inline xfs_agblock_t |
| agino2agbno (xfs_agino_t agino) |
| { |
| return agino >> XFS_INO_OFFSET_BITS; |
| } |
| |
| static inline xfs_agnumber_t |
| ino2agno (xfs_ino_t ino) |
| { |
| return ino >> XFS_INO_AGINO_BITS; |
| } |
| |
| static inline xfs_agino_t |
| ino2agino (xfs_ino_t ino) |
| { |
| return ino & XFS_INO_MASK(XFS_INO_AGINO_BITS); |
| } |
| |
| static inline int |
| ino2offset (xfs_ino_t ino) |
| { |
| return ino & XFS_INO_MASK(XFS_INO_OFFSET_BITS); |
| } |
| |
| static inline /*__const__*/ xfs_uint16_t |
| le16 (xfs_uint16_t x) |
| { |
| __asm__("xchgb %b0,%h0" \ |
| : "=q" (x) \ |
| : "0" (x)); |
| return x; |
| } |
| |
| static inline /*__const__*/ xfs_uint32_t |
| le32 (xfs_uint32_t x) |
| { |
| #if 0 |
| /* 386 doesn't have bswap. */ |
| __asm__("bswap %0" : "=r" (x) : "0" (x)); |
| #else |
| /* This is slower but this works on all x86 architectures. */ |
| __asm__("xchgb %b0, %h0" \ |
| "\n\troll $16, %0" \ |
| "\n\txchgb %b0, %h0" \ |
| : "=q" (x) : "0" (x)); |
| #endif |
| return x; |
| } |
| |
| static inline /*__const__*/ xfs_uint64_t |
| le64 (xfs_uint64_t x) |
| { |
| xfs_uint32_t h = x >> 32; |
| xfs_uint32_t l = x & ((1ULL<<32)-1); |
| return (((xfs_uint64_t)le32(l)) << 32) | ((xfs_uint64_t)(le32(h))); |
| } |
| |
| |
| static xfs_fsblock_t |
| xt_start (xfs_bmbt_rec_32_t *r) |
| { |
| return (((xfs_fsblock_t)(le32 (r->l1) & mask32lo(9))) << 43) | |
| (((xfs_fsblock_t)le32 (r->l2)) << 11) | |
| (((xfs_fsblock_t)le32 (r->l3)) >> 21); |
| } |
| |
| static xfs_fileoff_t |
| xt_offset (xfs_bmbt_rec_32_t *r) |
| { |
| return (((xfs_fileoff_t)le32 (r->l0) & |
| mask32lo(31)) << 23) | |
| (((xfs_fileoff_t)le32 (r->l1)) >> 9); |
| } |
| |
| static xfs_filblks_t |
| xt_len (xfs_bmbt_rec_32_t *r) |
| { |
| return le32(r->l3) & mask32lo(21); |
| } |
| |
| static inline int |
| xfs_highbit32(xfs_uint32_t v) |
| { |
| int i; |
| |
| if (--v) { |
| for (i = 0; i < 31; i++, v >>= 1) { |
| if (v == 0) |
| return i; |
| } |
| } |
| return 0; |
| } |
| |
| static int |
| isinxt (xfs_fileoff_t key, xfs_fileoff_t offset, xfs_filblks_t len) |
| { |
| return (key >= offset) ? (key < offset + len ? 1 : 0) : 0; |
| } |
| |
| static xfs_daddr_t |
| agb2daddr (xfs_agnumber_t agno, xfs_agblock_t agbno) |
| { |
| return ((xfs_fsblock_t)agno*xfs.agblocks + agbno) << xfs.bdlog; |
| } |
| |
| static xfs_daddr_t |
| fsb2daddr (xfs_fsblock_t fsbno) |
| { |
| return agb2daddr ((xfs_agnumber_t)(fsbno >> xfs.agblklog), |
| (xfs_agblock_t)(fsbno & mask32lo(xfs.agblklog))); |
| } |
| |
| #undef offsetof |
| #define offsetof(t,m) ((int)&(((t *)0)->m)) |
| |
| static inline int |
| btroot_maxrecs (void) |
| { |
| int tmp = icore.di_forkoff ? (icore.di_forkoff << 3) : xfs.isize; |
| |
| return (tmp - sizeof(xfs_bmdr_block_t) - offsetof(xfs_dinode_t, di_u)) / |
| (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t)); |
| } |
| |
| static int |
| di_read (xfs_ino_t ino) |
| { |
| xfs_agino_t agino; |
| xfs_agnumber_t agno; |
| xfs_agblock_t agbno; |
| xfs_daddr_t daddr; |
| int offset; |
| |
| agno = ino2agno (ino); |
| agino = ino2agino (ino); |
| agbno = agino2agbno (agino); |
| offset = ino2offset (ino); |
| daddr = agb2daddr (agno, agbno); |
| |
| devread (daddr, offset*xfs.isize, xfs.isize, (unsigned long long)(unsigned int)(char *)inode, 0xedde0d90); |
| |
| xfs.ptr0 = *(xfs_bmbt_ptr_t *) |
| (inode->di_u.di_c + sizeof(xfs_bmdr_block_t) |
| + btroot_maxrecs ()*sizeof(xfs_bmbt_key_t)); |
| |
| return 1; |
| } |
| |
| static void |
| init_extents (void) |
| { |
| xfs_bmbt_ptr_t ptr0; |
| xfs_btree_lblock_t h; |
| |
| switch (icore.di_format) { |
| case XFS_DINODE_FMT_EXTENTS: |
| xfs.xt = inode->di_u.di_bmx; |
| xfs.nextents = le32 (icore.di_nextents); |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| ptr0 = xfs.ptr0; |
| for (;;) { |
| xfs.daddr = fsb2daddr (le64(ptr0)); |
| devread (xfs.daddr, 0, |
| sizeof(xfs_btree_lblock_t), (unsigned long long)(unsigned int)(char *)&h, 0xedde0d90); |
| if (!h.bb_level) { |
| xfs.nextents = le16(h.bb_numrecs); |
| xfs.next = fsb2daddr (le64(h.bb_rightsib)); |
| xfs.fpos = sizeof(xfs_btree_block_t); |
| return; |
| } |
| devread (xfs.daddr, xfs.btnode_ptr0_off, |
| sizeof(xfs_bmbt_ptr_t), (unsigned long long)(unsigned int)(char *)&ptr0, 0xedde0d90); |
| } |
| } |
| } |
| |
| static xad_t * |
| next_extent (void) |
| { |
| static xad_t xad; |
| |
| switch (icore.di_format) { |
| case XFS_DINODE_FMT_EXTENTS: |
| if (xfs.nextents == 0) |
| return NULL; |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| if (xfs.nextents == 0) { |
| xfs_btree_lblock_t h; |
| if (xfs.next == 0) |
| return NULL; |
| xfs.daddr = xfs.next; |
| devread (xfs.daddr, 0, sizeof(xfs_btree_lblock_t), (unsigned long long)(unsigned int)(char *)&h, 0xedde0d90); |
| xfs.nextents = le16(h.bb_numrecs); |
| xfs.next = fsb2daddr (le64(h.bb_rightsib)); |
| xfs.fpos = sizeof(xfs_btree_block_t); |
| } |
| /* Yeah, I know that's slow, but I really don't care */ |
| devread (xfs.daddr, xfs.fpos, sizeof(xfs_bmbt_rec_t), (unsigned long long)(unsigned int)filebuf, 0xedde0d90); |
| xfs.xt = (xfs_bmbt_rec_32_t *)filebuf; |
| xfs.fpos += sizeof(xfs_bmbt_rec_32_t); |
| } |
| xad.offset = xt_offset (xfs.xt); |
| xad.start = xt_start (xfs.xt); |
| xad.len = xt_len (xfs.xt); |
| ++xfs.xt; |
| --xfs.nextents; |
| |
| return &xad; |
| } |
| |
| /* |
| * Name lies - the function reads only first 100 bytes |
| */ |
| static void |
| xfs_dabread (void) |
| { |
| xad_t *xad; |
| xfs_fileoff_t offset;; |
| |
| init_extents (); |
| while ((xad = next_extent ())) { |
| offset = xad->offset; |
| if (isinxt (xfs.dablk, offset, xad->len)) { |
| devread (fsb2daddr (xad->start + xfs.dablk - offset), |
| 0, 100, (unsigned long long)(unsigned int)dirbuf, 0xedde0d90); |
| break; |
| } |
| } |
| } |
| |
| static inline xfs_ino_t |
| sf_ino (char *sfe, int namelen) |
| { |
| void *p = sfe + namelen + 3; |
| |
| return (xfs.i8param == 0) |
| ? le64(*(xfs_ino_t *)p) : le32(*(xfs_uint32_t *)p); |
| } |
| |
| static inline xfs_ino_t |
| sf_parent_ino (void) |
| { |
| return (xfs.i8param == 0) |
| ? le64(*(xfs_ino_t *)(&inode->di_u.di_dir2sf.hdr.parent)) |
| : le32(*(xfs_uint32_t *)(&inode->di_u.di_dir2sf.hdr.parent)); |
| } |
| |
| static inline int |
| roundup8 (int n) |
| { |
| return ((n+7)&~7); |
| } |
| |
| static char * |
| next_dentry (xfs_ino_t *ino) |
| { |
| int namelen = 1; |
| int toread; |
| static char usual[2][3] = {".", ".."}; |
| static xfs_dir2_sf_entry_t *sfe; |
| char *name = usual[0]; |
| int j, k; |
| char ch1; |
| //#ifdef GRUB_UTIL |
| // char tmp_name[512]; |
| //#else |
| char *tmp_name = NAME_BUF; /* MAXNAMLEN is 255, so 512 byte buffer is needed. */ |
| //#endif |
| |
| if (xfs.dirpos >= xfs.dirmax) { |
| if (xfs.forw == 0) |
| return NULL; |
| xfs.dablk = xfs.forw; |
| xfs_dabread (); |
| #define h ((xfs_dir2_leaf_hdr_t *)dirbuf) |
| xfs.dirmax = le16 (h->count) - le16 (h->stale); |
| xfs.forw = le32 (h->info.forw); |
| #undef h |
| xfs.dirpos = 0; |
| } |
| |
| switch (icore.di_format) { |
| case XFS_DINODE_FMT_LOCAL: |
| switch (xfs.dirpos) { |
| case -2: |
| *ino = 0; |
| break; |
| case -1: |
| *ino = sf_parent_ino (); |
| ++name; |
| ++namelen; |
| sfe = (xfs_dir2_sf_entry_t *) |
| (inode->di_u.di_c |
| + sizeof(xfs_dir2_sf_hdr_t) |
| - xfs.i8param); |
| break; |
| default: |
| namelen = sfe->namelen; |
| *ino = sf_ino ((char *)sfe, namelen); |
| name = (char *)(sfe->name); |
| sfe = (xfs_dir2_sf_entry_t *) |
| ((char *)sfe + namelen + 11 - xfs.i8param); |
| } |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| case XFS_DINODE_FMT_EXTENTS: |
| #define dau ((xfs_dir2_data_union_t *)dirbuf) |
| for (;;) { |
| if (xfs.blkoff >= xfs.dirbsize) { |
| xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); |
| filepos &= ~(xfs.dirbsize - 1); |
| filepos |= xfs.blkoff; |
| } |
| xfs_read ((unsigned long long)(unsigned int)dirbuf, 4, 0xedde0d90); |
| xfs.blkoff += 4; |
| if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { |
| toread = roundup8 (le16(dau->unused.length)) - 4; |
| xfs.blkoff += toread; |
| filepos += toread; |
| continue; |
| } |
| break; |
| } |
| xfs_read ((unsigned long long)(unsigned int)(char *)dirbuf + 4, 5, 0xedde0d90); |
| *ino = le64 (dau->entry.inumber); |
| namelen = dau->entry.namelen; |
| #undef dau |
| toread = roundup8 (namelen + 11) - 9; |
| xfs_read ((unsigned long long)(unsigned int)dirbuf, toread, 0xedde0d90); |
| name = (char *)dirbuf; |
| xfs.blkoff += toread + 5; |
| } |
| ++xfs.dirpos; |
| name[namelen] = 0; |
| |
| /* copy name to tmp_name, and quote the spaces with a '\\' */ |
| for (j = 0, k = 0; j < namelen; j++) |
| { |
| if (! (ch1 = name[j])) |
| break; |
| if (ch1 == ' ') |
| tmp_name[k++] = '\\'; |
| tmp_name[k++] = ch1; |
| } |
| tmp_name[k] = 0; |
| return tmp_name; |
| } |
| |
| static char * |
| first_dentry (xfs_ino_t *ino) |
| { |
| xfs.forw = 0; |
| switch (icore.di_format) { |
| case XFS_DINODE_FMT_LOCAL: |
| xfs.dirmax = inode->di_u.di_dir2sf.hdr.count; |
| xfs.i8param = inode->di_u.di_dir2sf.hdr.i8count ? 0 : 4; |
| xfs.dirpos = -2; |
| break; |
| case XFS_DINODE_FMT_EXTENTS: |
| case XFS_DINODE_FMT_BTREE: |
| filepos = 0; |
| xfs_read ((unsigned long long)(unsigned int)dirbuf, sizeof(xfs_dir2_data_hdr_t), 0xedde0d90); |
| if (((xfs_dir2_data_hdr_t *)dirbuf)->magic == le32(XFS_DIR2_BLOCK_MAGIC)) { |
| #define tail ((xfs_dir2_block_tail_t *)dirbuf) |
| filepos = xfs.dirbsize - sizeof(*tail); |
| xfs_read ((unsigned long long)(unsigned int)dirbuf, sizeof(*tail), 0xedde0d90); |
| xfs.dirmax = le32 (tail->count) - le32 (tail->stale); |
| #undef tail |
| } else { |
| xfs.dablk = (1ULL << 35) >> xfs.blklog; |
| #define h ((xfs_dir2_leaf_hdr_t *)dirbuf) |
| #define n ((xfs_da_intnode_t *)dirbuf) |
| for (;;) { |
| xfs_dabread (); |
| if ((n->hdr.info.magic == le16(XFS_DIR2_LEAFN_MAGIC)) |
| || (n->hdr.info.magic == le16(XFS_DIR2_LEAF1_MAGIC))) { |
| xfs.dirmax = le16 (h->count) - le16 (h->stale); |
| xfs.forw = le32 (h->info.forw); |
| break; |
| } |
| xfs.dablk = le32 (n->btree[0].before); |
| } |
| #undef n |
| #undef h |
| } |
| xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); |
| filepos = xfs.blkoff; |
| xfs.dirpos = 0; |
| } |
| return next_dentry (ino); |
| } |
| |
| int |
| xfs_mount (void) |
| { |
| #ifdef GRUB_UTIL |
| xfs_sb_t super1; /* struct size 188 */ |
| xfs_sb_t *super = &super1; |
| #else |
| xfs_sb_t *super = (xfs_sb_t *)0x600; |
| #endif |
| |
| if (!devread (0, 0, sizeof(xfs_sb_t), (unsigned long long)(unsigned int)(char *)super, 0xedde0d90) |
| || (le32(super->sb_magicnum) != XFS_SB_MAGIC) |
| || ((le16(super->sb_versionnum) |
| & XFS_SB_VERSION_NUMBITS) != XFS_SB_VERSION_4) ) { |
| return 0; |
| } |
| |
| xfs.bsize = le32 (super->sb_blocksize); |
| xfs.blklog = super->sb_blocklog; |
| xfs.bdlog = xfs.blklog - SECTOR_BITS; |
| xfs.rootino = le64 (super->sb_rootino); |
| xfs.isize = le16 (super->sb_inodesize); |
| xfs.agblocks = le32 (super->sb_agblocks); |
| xfs.dirbsize = xfs.bsize << super->sb_dirblklog; |
| |
| xfs.inopblog = super->sb_inopblog; |
| xfs.agblklog = super->sb_agblklog; |
| xfs.agnolog = xfs_highbit32 (le32(super->sb_agcount)); |
| |
| xfs.btnode_ptr0_off = |
| ((xfs.bsize - sizeof(xfs_btree_block_t)) / |
| (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t))) |
| * sizeof(xfs_bmbt_key_t) + sizeof(xfs_btree_block_t); |
| |
| return 1; |
| } |
| |
| unsigned long long |
| xfs_read (unsigned long long buf, unsigned long long len, unsigned long write) |
| { |
| xad_t *xad; |
| xfs_fileoff_t endofprev, endofcur, offset; |
| xfs_filblks_t xadlen; |
| unsigned long toread, startpos, endpos; |
| |
| if (icore.di_format == XFS_DINODE_FMT_LOCAL) { |
| if (buf) |
| grub_memmove64 (buf, (unsigned long long)(unsigned int)(inode->di_u.di_c + filepos), len); |
| filepos += len; |
| return len; |
| } |
| |
| startpos = filepos; |
| endpos = filepos + len; |
| endofprev = (xfs_fileoff_t)-1; |
| init_extents (); |
| while (len > 0 && (xad = next_extent ())) |
| { |
| offset = xad->offset; |
| xadlen = xad->len; |
| if (isinxt (filepos >> xfs.blklog, offset, xadlen)) { |
| endofcur = (offset + xadlen) << xfs.blklog; |
| toread = (endofcur >= endpos) |
| ? len : (endofcur - filepos); |
| |
| disk_read_func = disk_read_hook; |
| devread (fsb2daddr (xad->start), |
| filepos - (offset << xfs.blklog), toread, buf, write); |
| disk_read_func = NULL; |
| |
| if (buf) |
| buf += toread; |
| len -= toread; /* len always >= 0 */ |
| filepos += toread; |
| } else if (offset > endofprev) { |
| if (write == 0x900ddeed) |
| { |
| grub_printf ("Fatal: Cannot write NULL blocks to file!\n"); |
| return !(errnum = ERR_WRITE); |
| } |
| toread = ((offset << xfs.blklog) >= endpos) |
| ? len : ((offset - endofprev) << xfs.blklog); |
| filepos += toread; |
| #if 0 |
| { |
| unsigned long tmp_pos = toread; |
| for (; tmp_pos; tmp_pos--) { |
| if (buf) |
| *buf++ = 0; |
| } |
| } |
| #else |
| if (buf) |
| { |
| grub_memset64 (buf, 0, toread); |
| buf += toread; |
| } |
| #endif |
| if (len < toread) |
| break; |
| len -= toread; |
| continue; |
| } |
| endofprev = offset + xadlen; |
| } |
| |
| return filepos - startpos; |
| } |
| |
| int |
| xfs_dir (char *dirname) |
| { |
| xfs_ino_t ino, parent_ino, new_ino; |
| xfs_fsize_t di_size; |
| unsigned long di_mode; |
| int cmp; |
| unsigned long n, link_count; |
| char *rest, *name, ch; |
| |
| #ifdef GRUB_UTIL |
| char linkbuf[xfs.bsize]; |
| #else |
| char *linkbuf = (char *)(FSYS_BUF - xfs.bsize); |
| #endif |
| |
| parent_ino = ino = xfs.rootino; |
| link_count = 0; |
| for (;;) { |
| di_read (ino); |
| di_size = le64 (icore.di_size); |
| di_mode = le16 (icore.di_mode); |
| |
| if ((di_mode & IFMT) == IFLNK) { |
| if (++link_count > MAX_LINK_COUNT) { |
| errnum = ERR_SYMLINK_LOOP; |
| return 0; |
| } |
| if (di_size < xfs.bsize - 1) { |
| filepos = 0; |
| filemax = di_size; |
| n = xfs_read ((unsigned long long)(unsigned int)linkbuf, filemax, 0xedde0d90); |
| } else { |
| errnum = ERR_FILELENGTH; |
| return 0; |
| } |
| |
| ino = (linkbuf[0] == '/') ? xfs.rootino : parent_ino; |
| while (n < (xfs.bsize - 1) && (linkbuf[n++] = *dirname++)); |
| linkbuf[n] = 0; |
| dirname = linkbuf; |
| continue; |
| } |
| |
| if (!*dirname || isspace (*dirname)) { |
| if ((di_mode & IFMT) != IFREG) { |
| errnum = ERR_BAD_FILETYPE; |
| return 0; |
| } |
| filepos = 0; |
| filemax = di_size; |
| return 1; |
| } |
| |
| if ((di_mode & IFMT) != IFDIR) { |
| errnum = ERR_BAD_FILETYPE; |
| return 0; |
| } |
| |
| for (; *dirname == '/'; dirname++); |
| |
| //for (rest = dirname; (ch = *rest) && !isspace (ch) && ch != '/'; rest++); |
| for (rest = dirname; (ch = *rest) && !isspace (ch) && ch != '/'; rest++) |
| { |
| if (ch == '\\') |
| { |
| rest++; |
| if (! (ch = *rest)) |
| break; |
| } |
| } |
| *rest = 0; |
| |
| name = first_dentry (&new_ino); |
| for (;;) { |
| cmp = (!*dirname) ? -1 : substring (dirname, name, 0); |
| #ifndef STAGE1_5 |
| if (print_possibilities && ch != '/' && cmp <= 0) { |
| if (print_possibilities > 0) |
| print_possibilities = -print_possibilities; |
| print_a_completion (name); |
| } else |
| #endif |
| if (cmp == 0) { |
| parent_ino = ino; |
| if (new_ino) |
| ino = new_ino; |
| *(dirname = rest) = ch; |
| break; |
| } |
| name = next_dentry (&new_ino); |
| if (name == NULL) { |
| if (print_possibilities < 0) |
| return 1; |
| |
| errnum = ERR_FILE_NOT_FOUND; |
| *rest = ch; |
| return 0; |
| } |
| } |
| } |
| } |
| |
| #endif /* FSYS_XFS */ |