fshelp.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /* fshelp.c -- Filesystem helper functions */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2004,2005,2006,2007,2008 Free Software Foundation, Inc.
  5. *
  6. * GRUB is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * GRUB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <grub/err.h>
  20. #include <grub/mm.h>
  21. #include <grub/misc.h>
  22. #include <grub/disk.h>
  23. #include <grub/fshelp.h>
  24. /* Lookup the node PATH. The node ROOTNODE describes the root of the
  25. directory tree. The node found is returned in FOUNDNODE, which is
  26. either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
  27. iterate over all directory entries in the current node.
  28. READ_SYMLINK is used to read the symlink if a node is a symlink.
  29. EXPECTTYPE is the type node that is expected by the called, an
  30. error is generated if the node is not of the expected type. Make
  31. sure you use the NESTED_FUNC_ATTR macro for HOOK, this is required
  32. because GCC has a nasty bug when using regparm=3. */
  33. grub_err_t
  34. grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
  35. grub_fshelp_node_t *foundnode,
  36. int (*iterate_dir) (grub_fshelp_node_t dir,
  37. int NESTED_FUNC_ATTR (*hook)
  38. (const char *filename,
  39. enum grub_fshelp_filetype filetype,
  40. grub_fshelp_node_t node)),
  41. char *(*read_symlink) (grub_fshelp_node_t node),
  42. enum grub_fshelp_filetype expecttype)
  43. {
  44. grub_err_t err;
  45. enum grub_fshelp_filetype foundtype = GRUB_FSHELP_DIR;
  46. int symlinknest = 0;
  47. auto grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
  48. grub_fshelp_node_t currroot,
  49. grub_fshelp_node_t *currfound);
  50. grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
  51. grub_fshelp_node_t currroot,
  52. grub_fshelp_node_t *currfound)
  53. {
  54. char fpath[grub_strlen (currpath) + 1];
  55. char *name = fpath;
  56. char *next;
  57. // unsigned int pos = 0;
  58. enum grub_fshelp_filetype type = GRUB_FSHELP_DIR;
  59. grub_fshelp_node_t currnode = currroot;
  60. grub_fshelp_node_t oldnode = currroot;
  61. auto int NESTED_FUNC_ATTR iterate (const char *filename,
  62. enum grub_fshelp_filetype filetype,
  63. grub_fshelp_node_t node);
  64. auto void free_node (grub_fshelp_node_t node);
  65. void free_node (grub_fshelp_node_t node)
  66. {
  67. if (node != rootnode && node != currroot)
  68. grub_free (node);
  69. }
  70. int NESTED_FUNC_ATTR iterate (const char *filename,
  71. enum grub_fshelp_filetype filetype,
  72. grub_fshelp_node_t node)
  73. {
  74. if (filetype == GRUB_FSHELP_UNKNOWN ||
  75. (grub_strcmp (name, filename) &&
  76. (! (filetype & GRUB_FSHELP_CASE_INSENSITIVE) ||
  77. grub_strncasecmp (name, filename, GRUB_LONG_MAX))))
  78. {
  79. grub_free (node);
  80. return 0;
  81. }
  82. /* The node is found, stop iterating over the nodes. */
  83. type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
  84. oldnode = currnode;
  85. currnode = node;
  86. return 1;
  87. }
  88. grub_strncpy (fpath, currpath, grub_strlen (currpath) + 1);
  89. /* Remove all leading slashes. */
  90. while (*name == '/')
  91. name++;
  92. if (! *name)
  93. {
  94. *currfound = currnode;
  95. return 0;
  96. }
  97. for (;;)
  98. {
  99. int found;
  100. /* Extract the actual part from the pathname. */
  101. next = grub_strchr (name, '/');
  102. if (next)
  103. {
  104. /* Remove all leading slashes. */
  105. while (*next == '/')
  106. *(next++) = '\0';
  107. }
  108. /* At this point it is expected that the current node is a
  109. directory, check if this is true. */
  110. if (type != GRUB_FSHELP_DIR)
  111. {
  112. free_node (currnode);
  113. return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
  114. }
  115. /* Iterate over the directory. */
  116. found = iterate_dir (currnode, iterate);
  117. if (! found)
  118. {
  119. if (grub_errno)
  120. return grub_errno;
  121. break;
  122. }
  123. /* Read in the symlink and follow it. */
  124. if (type == GRUB_FSHELP_SYMLINK)
  125. {
  126. char *symlink;
  127. /* Test if the symlink does not loop. */
  128. if (++symlinknest == 8)
  129. {
  130. free_node (currnode);
  131. free_node (oldnode);
  132. return grub_error (GRUB_ERR_SYMLINK_LOOP,
  133. "too deep nesting of symlinks");
  134. }
  135. symlink = read_symlink (currnode);
  136. free_node (currnode);
  137. if (!symlink)
  138. {
  139. free_node (oldnode);
  140. return grub_errno;
  141. }
  142. /* The symlink is an absolute path, go back to the root inode. */
  143. if (symlink[0] == '/')
  144. {
  145. free_node (oldnode);
  146. oldnode = rootnode;
  147. }
  148. /* Lookup the node the symlink points to. */
  149. find_file (symlink, oldnode, &currnode);
  150. type = foundtype;
  151. grub_free (symlink);
  152. if (grub_errno)
  153. {
  154. free_node (oldnode);
  155. return grub_errno;
  156. }
  157. }
  158. free_node (oldnode);
  159. /* Found the node! */
  160. if (! next || *next == '\0')
  161. {
  162. *currfound = currnode;
  163. foundtype = type;
  164. return 0;
  165. }
  166. name = next;
  167. }
  168. return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
  169. }
  170. if (!path || path[0] != '/')
  171. {
  172. grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
  173. return grub_errno;
  174. }
  175. err = find_file (path, rootnode, foundnode);
  176. if (err)
  177. return err;
  178. /* Check if the node that was found was of the expected type. */
  179. if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype)
  180. return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
  181. else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
  182. return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
  183. return 0;
  184. }
  185. /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
  186. beginning with the block POS. READ_HOOK should be set before
  187. reading a block from the file. GET_BLOCK is used to translate file
  188. blocks to disk blocks. The file is FILESIZE bytes big and the
  189. blocks have a size of LOG2BLOCKSIZE (in log2). */
  190. grub_ssize_t
  191. grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node,
  192. void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
  193. unsigned offset,
  194. unsigned length),
  195. grub_off_t pos, grub_size_t len, char *buf,
  196. grub_disk_addr_t (*get_block) (grub_fshelp_node_t node,
  197. grub_disk_addr_t block),
  198. grub_off_t filesize, int log2blocksize)
  199. {
  200. grub_disk_addr_t i, blockcnt;
  201. int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);
  202. /* Adjust LEN so it we can't read past the end of the file. */
  203. if (pos + len > filesize)
  204. len = filesize - pos;
  205. blockcnt = ((len + pos) + blocksize - 1) >> (log2blocksize + GRUB_DISK_SECTOR_BITS);
  206. for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++)
  207. {
  208. grub_disk_addr_t blknr;
  209. int blockoff = pos & (blocksize - 1);
  210. int blockend = blocksize;
  211. int skipfirst = 0;
  212. blknr = get_block (node, i);
  213. if (grub_errno)
  214. return -1;
  215. blknr = blknr << log2blocksize;
  216. /* Last block. */
  217. if (i == blockcnt - 1)
  218. {
  219. blockend = (len + pos) & (blocksize - 1);
  220. /* The last portion is exactly blocksize. */
  221. if (! blockend)
  222. blockend = blocksize;
  223. }
  224. /* First block. */
  225. if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS)))
  226. {
  227. skipfirst = blockoff;
  228. blockend -= skipfirst;
  229. }
  230. /* If the block number is 0 this block is not stored on disk but
  231. is zero filled instead. */
  232. if (blknr)
  233. {
  234. disk->read_hook = read_hook;
  235. grub_disk_read (disk, blknr, skipfirst,
  236. blockend, buf);
  237. disk->read_hook = 0;
  238. if (grub_errno)
  239. return -1;
  240. }
  241. else
  242. grub_memset (buf, 0, blockend);
  243. buf += blocksize - skipfirst;
  244. }
  245. return len;
  246. }
  247. unsigned int
  248. grub_fshelp_log2blksize (unsigned int blksize, unsigned int *pow)
  249. {
  250. int mod;
  251. *pow = 0;
  252. while (blksize > 1)
  253. {
  254. mod = blksize - ((blksize >> 1) << 1);
  255. blksize >>= 1;
  256. /* Check if it really is a power of two. */
  257. if (mod)
  258. return grub_error (GRUB_ERR_BAD_NUMBER,
  259. "the blocksize is not a power of two");
  260. (*pow)++;
  261. }
  262. return GRUB_ERR_NONE;
  263. }