manageiq-smartstate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (305) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +4 -0
  4. data/.rspec_ci +4 -0
  5. data/.travis.yml +15 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE.txt +202 -0
  8. data/README.md +45 -0
  9. data/Rakefile +23 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/MiqContainerGroup/MiqContainerGroup.rb +31 -0
  13. data/lib/MiqVm/MiqLocalVm.rb +50 -0
  14. data/lib/MiqVm/MiqRhevmVm.rb +179 -0
  15. data/lib/MiqVm/MiqVm.rb +355 -0
  16. data/lib/MiqVm/miq_azure_vm.rb +96 -0
  17. data/lib/MiqVm/miq_scvmm_vm.rb +38 -0
  18. data/lib/MiqVm/test/camcorder_fleece_test.rb +60 -0
  19. data/lib/MiqVm/test/localVm.rb +45 -0
  20. data/lib/MiqVm/test/partitionAlignmentCheck.rb +76 -0
  21. data/lib/MiqVm/test/remoteVm.rb +65 -0
  22. data/lib/MiqVm/test/rhevmNfsTest.rb +62 -0
  23. data/lib/MiqVm/test/rhevmNfsTest2.rb +66 -0
  24. data/lib/MiqVm/test/rhevmTest.rb +70 -0
  25. data/lib/OpenStackExtract/MiqOpenStackVm/MiqOpenStackCommon.rb +107 -0
  26. data/lib/OpenStackExtract/MiqOpenStackVm/MiqOpenStackImage.rb +67 -0
  27. data/lib/OpenStackExtract/MiqOpenStackVm/MiqOpenStackInstance.rb +182 -0
  28. data/lib/Scvmm/miq_hyperv_disk.rb +273 -0
  29. data/lib/Scvmm/miq_scvmm_parse_powershell.rb +75 -0
  30. data/lib/Scvmm/miq_scvmm_vm_ssa_info.rb +135 -0
  31. data/lib/Scvmm/test/miq_hyperv_disk_test.rb +33 -0
  32. data/lib/Scvmm/test/miq_scvmm_vm_ssa_info_test.rb +41 -0
  33. data/lib/VmLocalDiskAccess/test/localCfg.rb +97 -0
  34. data/lib/VolumeManager/LVM/logical_volume.rb +75 -0
  35. data/lib/VolumeManager/LVM/lv_segment.rb +43 -0
  36. data/lib/VolumeManager/LVM/lvm2disk.rb +158 -0
  37. data/lib/VolumeManager/LVM/parser.rb +138 -0
  38. data/lib/VolumeManager/LVM/physical_volume.rb +19 -0
  39. data/lib/VolumeManager/LVM/scanner.rb +156 -0
  40. data/lib/VolumeManager/LVM/thin/btree.rb +83 -0
  41. data/lib/VolumeManager/LVM/thin/constants.rb +86 -0
  42. data/lib/VolumeManager/LVM/thin/data_map.rb +44 -0
  43. data/lib/VolumeManager/LVM/thin/mapping_tree.rb +19 -0
  44. data/lib/VolumeManager/LVM/thin/space_maps.rb +58 -0
  45. data/lib/VolumeManager/LVM/thin/superblock.rb +136 -0
  46. data/lib/VolumeManager/LVM/thin.rb +6 -0
  47. data/lib/VolumeManager/LVM/volume_group.rb +97 -0
  48. data/lib/VolumeManager/LVM.rb +8 -0
  49. data/lib/VolumeManager/MiqLdm.rb +546 -0
  50. data/lib/VolumeManager/MiqLvm.rb +17 -0
  51. data/lib/VolumeManager/MiqNativeVolumeManager.rb +150 -0
  52. data/lib/VolumeManager/MiqVolumeManager.rb +277 -0
  53. data/lib/VolumeManager/VolMgrPlatformSupport.rb +18 -0
  54. data/lib/VolumeManager/VolMgrPlatformSupportLinux.rb +77 -0
  55. data/lib/VolumeManager/VolMgrPlatformSupportWin.rb +17 -0
  56. data/lib/VolumeManager/test/blockDevTest.rb +40 -0
  57. data/lib/VolumeManager/test/ldm.rb +97 -0
  58. data/lib/blackbox/VmBlackBox.rb +103 -0
  59. data/lib/blackbox/xmlStorage.rb +180 -0
  60. data/lib/db/MiqBdb/MiqBdb.rb +309 -0
  61. data/lib/db/MiqBdb/MiqBdbBtree.rb +219 -0
  62. data/lib/db/MiqBdb/MiqBdbHash.rb +199 -0
  63. data/lib/db/MiqBdb/MiqBdbPage.rb +159 -0
  64. data/lib/db/MiqBdb/MiqBdbUtil.rb +18 -0
  65. data/lib/db/MiqSqlite/MiqSqlite3.rb +330 -0
  66. data/lib/db/MiqSqlite/MiqSqlite3Cell.rb +167 -0
  67. data/lib/db/MiqSqlite/MiqSqlite3Page.rb +151 -0
  68. data/lib/db/MiqSqlite/MiqSqlite3Table.rb +124 -0
  69. data/lib/db/MiqSqlite/MiqSqlite3Util.rb +32 -0
  70. data/lib/disk/DiskProbe.rb +68 -0
  71. data/lib/disk/MiqDisk.rb +317 -0
  72. data/lib/disk/camcorder_test.rb +90 -0
  73. data/lib/disk/dos_mbr.img +0 -0
  74. data/lib/disk/modules/AzureBlobDisk.rb +101 -0
  75. data/lib/disk/modules/LocalDevMod.rb +47 -0
  76. data/lib/disk/modules/LocalDevProbe.rb +6 -0
  77. data/lib/disk/modules/MSCommon.rb +352 -0
  78. data/lib/disk/modules/MSVSDiffDisk.rb +91 -0
  79. data/lib/disk/modules/MSVSDiskProbe.rb +61 -0
  80. data/lib/disk/modules/MSVSDynamicDisk.rb +42 -0
  81. data/lib/disk/modules/MSVSFixedDisk.rb +45 -0
  82. data/lib/disk/modules/MiqLargeFile.rb +63 -0
  83. data/lib/disk/modules/MiqLargeFileWin32.rb +107 -0
  84. data/lib/disk/modules/QcowDisk.rb +692 -0
  85. data/lib/disk/modules/QcowDiskProbe.rb +34 -0
  86. data/lib/disk/modules/RawBlockIO.rb +116 -0
  87. data/lib/disk/modules/RawDisk.rb +45 -0
  88. data/lib/disk/modules/RawDiskProbe.rb +7 -0
  89. data/lib/disk/modules/RhevmDescriptor.rb +167 -0
  90. data/lib/disk/modules/RhevmDiskProbe.rb +52 -0
  91. data/lib/disk/modules/VMWareCowdDisk.rb +207 -0
  92. data/lib/disk/modules/VMWareDescriptor.rb +214 -0
  93. data/lib/disk/modules/VMWareDiskProbe.rb +74 -0
  94. data/lib/disk/modules/VMWareSparseDisk.rb +189 -0
  95. data/lib/disk/modules/VhdxDisk.rb +625 -0
  96. data/lib/disk/modules/VhdxDiskProbe.rb +46 -0
  97. data/lib/disk/modules/VixDiskMod.rb +54 -0
  98. data/lib/disk/modules/VixDiskProbe.rb +6 -0
  99. data/lib/disk/modules/miq_disk_cache.rb +135 -0
  100. data/lib/disk/modules/miq_dummy_disk.rb +41 -0
  101. data/lib/disk/modules/vhdx_bat_entry.rb +10 -0
  102. data/lib/disk/test.rb +66 -0
  103. data/lib/fs/MetakitFS/MetakitFS.rb +530 -0
  104. data/lib/fs/MetakitFS/test/Makefile +14 -0
  105. data/lib/fs/MetakitFS/test/MkCollectFiles.rb +165 -0
  106. data/lib/fs/MetakitFS/test/MkSelectFiles.rb +30 -0
  107. data/lib/fs/MetakitFS/test/collect_files.yaml +70 -0
  108. data/lib/fs/MetakitFS/test/init.rb +3 -0
  109. data/lib/fs/MetakitFS/test/mk2vmdk.rb +64 -0
  110. data/lib/fs/MetakitFS/test/mk4test.c +92 -0
  111. data/lib/fs/MetakitFS/test/mkFsTest.rb +113 -0
  112. data/lib/fs/MetakitFS/test/proto.rb +97 -0
  113. data/lib/fs/MiqFS/FsProbe.rb +39 -0
  114. data/lib/fs/MiqFS/MiqFS.rb +515 -0
  115. data/lib/fs/MiqFS/modules/AUFSProbe.rb +26 -0
  116. data/lib/fs/MiqFS/modules/Ext3.rb +305 -0
  117. data/lib/fs/MiqFS/modules/Ext3Probe.rb +25 -0
  118. data/lib/fs/MiqFS/modules/Ext4.rb +304 -0
  119. data/lib/fs/MiqFS/modules/Ext4Probe.rb +25 -0
  120. data/lib/fs/MiqFS/modules/Fat32.rb +318 -0
  121. data/lib/fs/MiqFS/modules/Fat32Probe.rb +30 -0
  122. data/lib/fs/MiqFS/modules/HFSProbe.rb +18 -0
  123. data/lib/fs/MiqFS/modules/Iso9660.rb +293 -0
  124. data/lib/fs/MiqFS/modules/Iso9660Probe.rb +18 -0
  125. data/lib/fs/MiqFS/modules/LocalFS.rb +105 -0
  126. data/lib/fs/MiqFS/modules/NTFS.rb +287 -0
  127. data/lib/fs/MiqFS/modules/NTFSProbe.rb +21 -0
  128. data/lib/fs/MiqFS/modules/NativeFS.rb +155 -0
  129. data/lib/fs/MiqFS/modules/ReFSProbe.rb +17 -0
  130. data/lib/fs/MiqFS/modules/RealFS.rb +79 -0
  131. data/lib/fs/MiqFS/modules/RealFSProbe.rb +6 -0
  132. data/lib/fs/MiqFS/modules/Reiser4Probe.rb +18 -0
  133. data/lib/fs/MiqFS/modules/ReiserFS.rb +315 -0
  134. data/lib/fs/MiqFS/modules/ReiserFSProbe.rb +42 -0
  135. data/lib/fs/MiqFS/modules/UnionFSProbe.rb +18 -0
  136. data/lib/fs/MiqFS/modules/WebDAV.rb +127 -0
  137. data/lib/fs/MiqFS/modules/WebDAVFile.rb +68 -0
  138. data/lib/fs/MiqFS/modules/XFS.rb +300 -0
  139. data/lib/fs/MiqFS/modules/XFSProbe.rb +26 -0
  140. data/lib/fs/MiqFS/modules/ZFSProbe.rb +18 -0
  141. data/lib/fs/MiqFS/test.rb +59 -0
  142. data/lib/fs/MiqFsUtil.rb +383 -0
  143. data/lib/fs/MiqMountManager.rb +209 -0
  144. data/lib/fs/MiqNativeMountManager.rb +101 -0
  145. data/lib/fs/MountManagerProbe.rb +29 -0
  146. data/lib/fs/ReiserFS/block.rb +209 -0
  147. data/lib/fs/ReiserFS/directory.rb +136 -0
  148. data/lib/fs/ReiserFS/directory_entry.rb +140 -0
  149. data/lib/fs/ReiserFS/file_data.rb +111 -0
  150. data/lib/fs/ReiserFS/superblock.rb +140 -0
  151. data/lib/fs/ReiserFS/utils.rb +95 -0
  152. data/lib/fs/VimDatastoreFS/VimDatastoreFS.rb +192 -0
  153. data/lib/fs/ext3/alloc_bitmap.rb +38 -0
  154. data/lib/fs/ext3/block_pointers_path.rb +130 -0
  155. data/lib/fs/ext3/directory.rb +51 -0
  156. data/lib/fs/ext3/directory_entry.rb +67 -0
  157. data/lib/fs/ext3/ex_attrib_header.rb +14 -0
  158. data/lib/fs/ext3/ex_attrib_name.rb +23 -0
  159. data/lib/fs/ext3/file_data.rb +130 -0
  160. data/lib/fs/ext3/group_descriptor_entry.rb +65 -0
  161. data/lib/fs/ext3/group_descriptor_table.rb +54 -0
  162. data/lib/fs/ext3/hash_tree_entry.rb +18 -0
  163. data/lib/fs/ext3/hash_tree_header.rb +15 -0
  164. data/lib/fs/ext3/inode.rb +228 -0
  165. data/lib/fs/ext3/posix_acl_entry.rb +29 -0
  166. data/lib/fs/ext3/posix_acl_header.rb +11 -0
  167. data/lib/fs/ext3/superblock.rb +406 -0
  168. data/lib/fs/ext3/test/tc_Ext3BlockPointersPath.rb +74 -0
  169. data/lib/fs/ext4/alloc_bitmap.rb +38 -0
  170. data/lib/fs/ext4/directory.rb +87 -0
  171. data/lib/fs/ext4/directory_entry.rb +77 -0
  172. data/lib/fs/ext4/ex_attrib_header.rb +14 -0
  173. data/lib/fs/ext4/ex_attrib_name.rb +23 -0
  174. data/lib/fs/ext4/extent.rb +35 -0
  175. data/lib/fs/ext4/extent_header.rb +40 -0
  176. data/lib/fs/ext4/extent_index.rb +33 -0
  177. data/lib/fs/ext4/group_descriptor_entry.rb +69 -0
  178. data/lib/fs/ext4/group_descriptor_table.rb +54 -0
  179. data/lib/fs/ext4/hash_tree_entry.rb +58 -0
  180. data/lib/fs/ext4/hash_tree_header.rb +35 -0
  181. data/lib/fs/ext4/inode.rb +465 -0
  182. data/lib/fs/ext4/posix_acl_entry.rb +29 -0
  183. data/lib/fs/ext4/posix_acl_header.rb +11 -0
  184. data/lib/fs/ext4/superblock.rb +412 -0
  185. data/lib/fs/fat32/boot_sect.rb +379 -0
  186. data/lib/fs/fat32/directory.rb +222 -0
  187. data/lib/fs/fat32/directory_entry.rb +540 -0
  188. data/lib/fs/fat32/file_data.rb +128 -0
  189. data/lib/fs/iso9660/boot_sector.rb +170 -0
  190. data/lib/fs/iso9660/directory.rb +90 -0
  191. data/lib/fs/iso9660/directory_entry.rb +147 -0
  192. data/lib/fs/iso9660/file_data.rb +78 -0
  193. data/lib/fs/iso9660/rock_ridge.rb +329 -0
  194. data/lib/fs/iso9660/util.rb +57 -0
  195. data/lib/fs/modules/LinuxMount.rb +300 -0
  196. data/lib/fs/modules/LinuxMountProbe.rb +29 -0
  197. data/lib/fs/modules/WinMount.rb +97 -0
  198. data/lib/fs/modules/WinMountProbe.rb +24 -0
  199. data/lib/fs/ntfs/attrib_attribute_list.rb +131 -0
  200. data/lib/fs/ntfs/attrib_bitmap.rb +26 -0
  201. data/lib/fs/ntfs/attrib_data.rb +74 -0
  202. data/lib/fs/ntfs/attrib_file_name.rb +110 -0
  203. data/lib/fs/ntfs/attrib_header.rb +194 -0
  204. data/lib/fs/ntfs/attrib_index_allocation.rb +19 -0
  205. data/lib/fs/ntfs/attrib_index_root.rb +247 -0
  206. data/lib/fs/ntfs/attrib_object_id.rb +40 -0
  207. data/lib/fs/ntfs/attrib_standard_information.rb +107 -0
  208. data/lib/fs/ntfs/attrib_type.rb +49 -0
  209. data/lib/fs/ntfs/attrib_volume_information.rb +53 -0
  210. data/lib/fs/ntfs/attrib_volume_name.rb +31 -0
  211. data/lib/fs/ntfs/boot_sect.rb +253 -0
  212. data/lib/fs/ntfs/data_run.rb +358 -0
  213. data/lib/fs/ntfs/directory_index_node.rb +114 -0
  214. data/lib/fs/ntfs/index_node_header.rb +69 -0
  215. data/lib/fs/ntfs/index_record_header.rb +85 -0
  216. data/lib/fs/ntfs/mft_entry.rb +288 -0
  217. data/lib/fs/ntfs/utils.rb +43 -0
  218. data/lib/fs/test/camcorder_fs_test.rb +108 -0
  219. data/lib/fs/test/collect_files_direct.yaml +22 -0
  220. data/lib/fs/test/collect_files_in.yaml +24 -0
  221. data/lib/fs/test/collect_files_in_nc.yaml +22 -0
  222. data/lib/fs/test/collect_files_out.yaml +6 -0
  223. data/lib/fs/test/collect_files_rm.yaml +6 -0
  224. data/lib/fs/test/copyTest.rb +126 -0
  225. data/lib/fs/test/fsTest.rb +87 -0
  226. data/lib/fs/test/updateTest.rb +184 -0
  227. data/lib/fs/xfs/allocation_group.rb +160 -0
  228. data/lib/fs/xfs/bmap_btree_block.rb +125 -0
  229. data/lib/fs/xfs/bmap_btree_record.rb +80 -0
  230. data/lib/fs/xfs/bmap_btree_root_node.rb +72 -0
  231. data/lib/fs/xfs/directory.rb +133 -0
  232. data/lib/fs/xfs/directory2_data_header.rb +27 -0
  233. data/lib/fs/xfs/directory3_data_header.rb +34 -0
  234. data/lib/fs/xfs/directory_block_tail.rb +22 -0
  235. data/lib/fs/xfs/directory_data_header.rb +46 -0
  236. data/lib/fs/xfs/directory_entry.rb +106 -0
  237. data/lib/fs/xfs/inode.rb +532 -0
  238. data/lib/fs/xfs/inode_map.rb +100 -0
  239. data/lib/fs/xfs/short_form_directory_entry.rb +91 -0
  240. data/lib/fs/xfs/short_form_header.rb +44 -0
  241. data/lib/fs/xfs/superblock.rb +556 -0
  242. data/lib/lib/tasks/azure.rake +52 -0
  243. data/lib/manageiq/smartstate/version.rb +5 -0
  244. data/lib/manageiq/smartstate.rb +7 -0
  245. data/lib/manageiq-smartstate.rb +1 -0
  246. data/lib/metadata/MIQExtract/MIQExtract.rb +297 -0
  247. data/lib/metadata/MIQExtract/test/extractTest.rb +41 -0
  248. data/lib/metadata/MIQExtract/test/full_extract_test.rb +68 -0
  249. data/lib/metadata/ScanProfile/HostScanItem.rb +4 -0
  250. data/lib/metadata/ScanProfile/HostScanProfile.rb +4 -0
  251. data/lib/metadata/ScanProfile/HostScanProfiles.rb +41 -0
  252. data/lib/metadata/ScanProfile/ScanItemBase.rb +63 -0
  253. data/lib/metadata/ScanProfile/ScanProfileBase.rb +51 -0
  254. data/lib/metadata/ScanProfile/ScanProfilesBase.rb +60 -0
  255. data/lib/metadata/ScanProfile/VmScanItem.rb +4 -0
  256. data/lib/metadata/ScanProfile/VmScanProfile.rb +4 -0
  257. data/lib/metadata/ScanProfile/VmScanProfiles.rb +38 -0
  258. data/lib/metadata/ScanProfile/modules/HostScanItemFile.rb +51 -0
  259. data/lib/metadata/ScanProfile/modules/HostScanItemNteventlog.rb +84 -0
  260. data/lib/metadata/ScanProfile/modules/VmScanItemFile.rb +39 -0
  261. data/lib/metadata/ScanProfile/modules/VmScanItemNteventlog.rb +34 -0
  262. data/lib/metadata/ScanProfile/modules/VmScanItemRegistry.rb +64 -0
  263. data/lib/metadata/VMMount/VMMount.rb +81 -0
  264. data/lib/metadata/VMMount/VMPlatformMount.rb +18 -0
  265. data/lib/metadata/VMMount/VMPlatformMountLinux.rb +75 -0
  266. data/lib/metadata/VMMount/VMPlatformMountWin.rb +13 -0
  267. data/lib/metadata/VmConfig/GetNativeCfg.rb +45 -0
  268. data/lib/metadata/VmConfig/VmConfig.rb +947 -0
  269. data/lib/metadata/VmConfig/cfgConfig.rb +45 -0
  270. data/lib/metadata/VmConfig/ovfConfig.rb +99 -0
  271. data/lib/metadata/VmConfig/test/GetVMwareCfgTest.rb +40 -0
  272. data/lib/metadata/VmConfig/vmcConfig.rb +116 -0
  273. data/lib/metadata/VmConfig/vmtxConfig.rb +4 -0
  274. data/lib/metadata/VmConfig/vmxConfig.rb +162 -0
  275. data/lib/metadata/VmConfig/xmlConfig.rb +79 -0
  276. data/lib/metadata/VmConfig/xmlMsHyperVConfig.rb +41 -0
  277. data/lib/metadata/linux/InitProcHash.rb +632 -0
  278. data/lib/metadata/linux/LinuxInitProcs.rb +142 -0
  279. data/lib/metadata/linux/LinuxOSInfo.rb +237 -0
  280. data/lib/metadata/linux/LinuxPackages.rb +209 -0
  281. data/lib/metadata/linux/LinuxSystemd.rb +130 -0
  282. data/lib/metadata/linux/LinuxUsers.rb +289 -0
  283. data/lib/metadata/linux/LinuxUtils.rb +197 -0
  284. data/lib/metadata/linux/MiqConaryPackages.rb +41 -0
  285. data/lib/metadata/linux/MiqRpmPackages.rb +160 -0
  286. data/lib/metadata/linux/test/Name +0 -0
  287. data/lib/metadata/linux/test/Packages +0 -0
  288. data/lib/metadata/linux/test/rpoTest.rb +5 -0
  289. data/lib/metadata/linux/test/tc_LinuxUtils.rb +4157 -0
  290. data/lib/metadata/util/event_log_filter.rb +61 -0
  291. data/lib/metadata/util/md5deep.rb +280 -0
  292. data/lib/metadata/util/win32/Win32Accounts.rb +764 -0
  293. data/lib/metadata/util/win32/Win32EventLog.rb +743 -0
  294. data/lib/metadata/util/win32/Win32Services.rb +86 -0
  295. data/lib/metadata/util/win32/Win32Software.rb +326 -0
  296. data/lib/metadata/util/win32/Win32System.rb +333 -0
  297. data/lib/metadata/util/win32/boot_info_win.rb +59 -0
  298. data/lib/metadata/util/win32/fleece_hives.rb +220 -0
  299. data/lib/metadata/util/win32/ms-registry.rb +650 -0
  300. data/lib/metadata/util/win32/peheader.rb +868 -0
  301. data/lib/metadata/util/win32/remote-registry.rb +142 -0
  302. data/lib/metadata/util/win32/system_path_win.rb +103 -0
  303. data/lib/metadata/util/win32/versioninfo.rb +17 -0
  304. data/manageiq-smartstate.gemspec +35 -0
  305. metadata +486 -0
@@ -0,0 +1,540 @@
1
+ # encoding: US-ASCII
2
+
3
+ require 'stringio'
4
+
5
+ require 'binary_struct'
6
+ require 'util/miq-unicode'
7
+
8
+ # ////////////////////////////////////////////////////////////////////////////
9
+ # // Data definitions.
10
+
11
+ # TODO: reserved1 is the infamous magic number. Somehow it works to preserve
12
+ # case on Windows XP. Nobody seems to know how. Here it is always set to 0
13
+ # (which yields uppercase names on XP).
14
+
15
+ module Fat32
16
+
17
+ DIR_ENT_SFN = BinaryStruct.new([
18
+ 'a11', 'name', # If name[0] = 0, unallocated; if name[0] = 0xe5, deleted. DOES NOT INCLUDE DOT.
19
+ 'C', 'attributes', # See FA_ below. If 0x0f then LFN entry.
20
+ 'C', 'reserved1', # Reserved.
21
+ 'C', 'ctime_tos', # Created time, tenths of second.
22
+ 'S', 'ctime_hms', # Created time, hours, minutes & seconds.
23
+ 'S', 'ctime_day', # Created day.
24
+ 'S', 'atime_day', # Accessed day.
25
+ 'S', 'first_clus_hi', # Hi 16-bits of first cluster address.
26
+ 'S', 'mtime_hms', # Modified time, hours, minutes & seconds.
27
+ 'S', 'mtime_day', # Modified day.
28
+ 'S', 'first_clus_lo', # Lo 16-bits of first cluster address.
29
+ 'L', 'file_size', # Size of file (0 for directories).
30
+ ])
31
+
32
+ DIR_ENT_LFN = BinaryStruct.new([
33
+ 'C', 'seq_num', # Sequence number, bit 6 marks end, 0xe5 if deleted.
34
+ 'a10', 'name', # UNICODE chars 1-5 of name.
35
+ 'C', 'attributes', # Always 0x0f.
36
+ 'C', 'reserved1', # Reserved.
37
+ 'C', 'checksum', # Checksum of SFN entry, all LFN entries must match.
38
+ 'a12', 'name2', # UNICODE chars 6-11 of name.
39
+ 'S', 'reserved2', # Reserved.
40
+ 'a4', 'name3' # UNICODE chars 12-13 of name.
41
+ ])
42
+
43
+ CHARS_PER_LFN = 13
44
+ LFN_NAME_MAXLEN = 260
45
+ DIR_ENT_SIZE = 32
46
+ ATTRIB_OFFSET = 11
47
+
48
+ # ////////////////////////////////////////////////////////////////////////////
49
+ # // Class.
50
+
51
+ class DirectoryEntry
52
+
53
+ # From the UTF-8 perspective.
54
+ # LFN name components: entry hash name, char offset, length.
55
+ LFN_NAME_COMPONENTS = [
56
+ ['name', 0, 5],
57
+ ['name2', 5, 6],
58
+ ['name3', 11, 2]
59
+ ]
60
+ # Name component second sub access names.
61
+ LFN_NC_HASHNAME = 0
62
+ LFN_NC_OFFSET = 1
63
+ LFN_NC_LENGTH = 2
64
+
65
+ # SFN failure cases.
66
+ SFN_NAME_LENGTH = 1
67
+ SFN_EXT_LENGTH = 2
68
+ SFN_NAME_NULL = 3
69
+ SFN_NAME_DEVICE = 4
70
+ SFN_ILLEGAL_CHARS = 5
71
+
72
+ # LFN failure cases.
73
+ LFN_NAME_LENGTH = 1
74
+ LFN_NAME_DEVICE = 2
75
+ LFN_ILLEGAL_CHARS = 3
76
+
77
+ # FileAttributes
78
+ FA_READONLY = 0x01
79
+ FA_HIDDEN = 0x02
80
+ FA_SYSTEM = 0x04
81
+ FA_LABEL = 0x08
82
+ FA_DIRECTORY = 0x10
83
+ FA_ARCHIVE = 0x20
84
+ FA_LFN = 0x0f
85
+
86
+ # DOS time masks.
87
+ MSK_DAY = 0x001f # Range: 1 - 31
88
+ MSK_MONTH = 0x01e0 # Right shift 5, Range: 1 - 12
89
+ MSK_YEAR = 0xfe00 # Right shift 9, Range: 127 (add 1980 for year).
90
+ MSK_SEC = 0x001f # Range: 0 - 29 WARNING: 2 second granularity on this.
91
+ MSK_MIN = 0x07e0 # Right shift 5, Range: 0 - 59
92
+ MSK_HOUR = 0xf800 # Right shift 11, Range: 0 - 23
93
+
94
+ # AllocationFlags
95
+ AF_NOT_ALLOCATED = 0x00
96
+ AF_DELETED = 0xe5
97
+ AF_LFN_LAST = 0x40
98
+
99
+ # Members.
100
+ attr_reader :unused, :name, :dirty
101
+ attr_accessor :parentCluster, :parentOffset
102
+ # NOTE: Directory is responsible for setting parent.
103
+ # These describe the cluster & offset of the START of the directory entry.
104
+
105
+ # Initialization
106
+ def initialize(buf = nil)
107
+ # Create for write.
108
+ if buf == nil
109
+ self.create
110
+ return
111
+ end
112
+
113
+ # Handle possibly multiple LFN records.
114
+ data = StringIO.new(buf); @lfn_ents = []
115
+ checksum = 0; @name = ""
116
+ loop do
117
+ buf = data.read(DIR_ENT_SIZE)
118
+ if buf == nil
119
+ @unused = ""
120
+ return
121
+ end
122
+
123
+ # If attribute contains 0x0f then LFN entry.
124
+ isLfn = buf[ATTRIB_OFFSET] == FA_LFN
125
+ @dir_ent = isLfn ? DIR_ENT_LFN.decode(buf) : DIR_ENT_SFN.decode(buf)
126
+ break if !isLfn
127
+
128
+ # Ignore this entry if deleted or not allocated.
129
+ af = @dir_ent['seq_num']
130
+ if af == AF_DELETED || af == AF_NOT_ALLOCATED
131
+ @name = @dir_ent['seq_num']
132
+ @unused = data.read()
133
+ return
134
+ end
135
+
136
+ # Set checksum or make sure it's the same
137
+ checksum = @dir_ent['checksum'] if checksum == 0
138
+ raise "Directory entry LFN checksum mismatch." if @dir_ent['checksum'] != checksum
139
+
140
+ # Track LFN entry, gather names & prepend to name.
141
+ @lfn_ents << @dir_ent
142
+ @name = getLongNameFromEntry(@dir_ent) + @name
143
+ end #LFN loop
144
+
145
+ # Push the rest of the data back.
146
+ @unused = data.read()
147
+
148
+ # If this is the last record of an LFN chain, check the checksum.
149
+ if checksum != 0
150
+ csum = calcChecksum
151
+ if csum != checksum
152
+ puts "Directory entry SFN checksum does not match LFN entries:"
153
+ puts "Got 0x#{'%02x' % csum}, should be 0x#{'%02x' % checksum}."
154
+ puts "Non LFN OS corruption?"
155
+ puts dump
156
+ raise "Checksum error"
157
+ end
158
+ end
159
+
160
+ # Populate name if not LFN.
161
+ if @name == "" && !@dir_ent['name'].empty?
162
+ @name = @dir_ent['name'][0, 8].strip
163
+ ext = @dir_ent['name'][8, 3].strip
164
+ @name += "." + ext unless ext.empty?
165
+ end
166
+ end
167
+
168
+ # ////////////////////////////////////////////////////////////////////////////
169
+ # // Class helpers & accessors.
170
+
171
+ # Return this entry as a raw string.
172
+ def raw
173
+ out = ""
174
+ @lfn_ents.each {|ent| out += BinaryStruct.encode(ent, DIR_ENT_LFN)} if @lfn_ents
175
+ out += BinaryStruct.encode(@dir_ent, DIR_ENT_SFN)
176
+ end
177
+
178
+ # Number of dir ent structures (both sfn and lfn).
179
+ def numEnts
180
+ num = 1
181
+ num += @lfn_ents.size if @lfn_ents
182
+ return num
183
+ end
184
+
185
+ # Return normalized 8.3 name.
186
+ def shortName
187
+ name = @dir_ent['name'][0, 8].strip
188
+ ext = @dir_ent['name'][8, 3].strip
189
+ name += "." + ext if ext != ""
190
+ return name
191
+ end
192
+
193
+ # Construct & return long name from lfn entries.
194
+ def longName
195
+ return nil if @lfn_ents == nil
196
+ name = ""
197
+ @lfn_ents.reverse.each {|ent| name += getLongNameFromEntry(ent)}
198
+ return name
199
+ end
200
+
201
+ # WRITE: change filename.
202
+ def name=(filename)
203
+ @dirty = true
204
+ # dot and double dot are special cases (no processing please).
205
+ if filename != "." and filename != ".."
206
+ if filename.size > 12 || (not filename.include?(".") && filename.size > 8)
207
+ mkLongName(filename)
208
+ @name = self.longName
209
+ else
210
+ @dir_ent['name'] = mkSfn(filename)
211
+ @name = self.shortName
212
+ end
213
+ else
214
+ @dir_ent['name']= filename.ljust(11)
215
+ @name = filename
216
+ end
217
+ end
218
+
219
+ # WRITE: change magic number.
220
+ def magic=(magic)
221
+ @dirty = true
222
+ @dir_ent['reserved1'] = magic
223
+ end
224
+
225
+ def magic
226
+ return @dir_ent['reserved1']
227
+ end
228
+
229
+ # WRITE: change attribs.
230
+ def setAttribute(attrib, set = true)
231
+ @dirty = true
232
+ if set
233
+ @dir_ent['attributes'] |= attrib
234
+ else
235
+ @dir_ent['attributes'] &= (~attrib)
236
+ end
237
+ end
238
+
239
+ # WRITE: change length.
240
+ def length=(len)
241
+ @dirty = true
242
+ @dir_ent['file_size'] = len
243
+ end
244
+
245
+ # WRITE: change first cluster.
246
+ def firstCluster=(first_clus)
247
+ @dirty = true
248
+ @dir_ent['first_clus_hi'] = (first_clus >> 16)
249
+ @dir_ent['first_clus_lo'] = (first_clus & 0xffff)
250
+ end
251
+
252
+ # WRITE: change access time.
253
+ def aTime=(tim)
254
+ @dirty = true
255
+ time, day = rubyToDosTime(tim)
256
+ @dir_ent['atime_day'] = day
257
+ end
258
+
259
+ # To support root dir times (all zero).
260
+ def zeroTime
261
+ @dirty = true
262
+ @dir_ent['atime_day'] = 0
263
+ @dir_ent['ctime_tos'] = 0; @dir_ent['ctime_hms'] = 0; @dir_ent['ctime_day'] = 0
264
+ @dir_ent['mtime_hms'] = 0; @dir_ent['mtime_day'] = 0
265
+ end
266
+
267
+ # WRITE: change modified (written) time.
268
+ def mTime=(tim)
269
+ @dirty = true
270
+ @dir_ent['mtime_hms'], @dir_ent['mtime_day'] = rubyToDosTime(tim)
271
+ end
272
+
273
+ # WRITE: write or rewrite directory entry.
274
+ def writeEntry(bs)
275
+ return if not @dirty
276
+ cluster = @parentCluster; offset = @parentOffset
277
+ buf = bs.getCluster(cluster)
278
+ if @lfn_ents
279
+ @lfn_ents.each {|ent|
280
+ buf[offset...(offset + DIR_ENT_SIZE)] = BinaryStruct.encode(ent, DIR_ENT_LFN)
281
+ offset += DIR_ENT_SIZE
282
+ if offset >= bs.bytesPerCluster
283
+ bs.putCluster(cluster, buf)
284
+ cluster, buf = bs.getNextCluster(cluster)
285
+ offset = 0
286
+ end
287
+ }
288
+ end
289
+ buf[offset...(offset + DIR_ENT_SIZE)] = BinaryStruct.encode(@dir_ent, DIR_ENT_SFN)
290
+ bs.putCluster(cluster, buf)
291
+ @dirty = false
292
+ end
293
+
294
+ # WRITE: delete file.
295
+ def delete(bs)
296
+ # Deallocate data chain.
297
+ bs.wipeChain(self.firstCluster) if self.firstCluster != 0
298
+ # Deallocate dir entry.
299
+ if @lfn_ents then @lfn_ents.each {|ent| ent['seq_num'] = AF_DELETED} end
300
+ @dir_ent['name'][0] = AF_DELETED
301
+ @dirty = true
302
+ self.writeEntry(bs)
303
+ end
304
+
305
+ def close(bs)
306
+ writeEntry(bs) if @dirty
307
+ end
308
+
309
+ def attributes
310
+ return @dir_ent['attributes']
311
+ end
312
+
313
+ def length
314
+ return @dir_ent['file_size']
315
+ end
316
+
317
+ def firstCluster
318
+ return (@dir_ent['first_clus_hi'] << 16) + @dir_ent['first_clus_lo']
319
+ end
320
+
321
+ def isDir?
322
+ return true if @dir_ent['attributes'] & FA_DIRECTORY == FA_DIRECTORY
323
+ return false
324
+ end
325
+
326
+ def mTime
327
+ return dosToRubyTime(@dir_ent['mtime_day'], @dir_ent['mtime_hms'])
328
+ end
329
+
330
+ def aTime
331
+ return dosToRubyTime(@dir_ent['atime_day'], 0)
332
+ end
333
+
334
+ def cTime
335
+ return dosToRubyTime(@dir_ent['ctime_day'], @dir_ent['ctime_hms'])
336
+ end
337
+
338
+ # ////////////////////////////////////////////////////////////////////////////
339
+ # // Utility functions.
340
+
341
+ def getLongNameFromEntry(ent)
342
+ pre_name = ""; hashNames = %w(name name2 name3)
343
+ hashNames.each {|name|
344
+ n = ent["#{name}"]
345
+
346
+ # Regexp.new options used below:
347
+ # nil (default options: not case insensitive, extended, multiline, etc.)
348
+ # 'n' - No encoding on the regexp
349
+ regex = Regexp.new('\377', nil, 'n')
350
+ pre_name += n.gsub(regex, "").UnicodeToUtf8.gsub(/\000/, "")
351
+ }
352
+ return pre_name
353
+ end
354
+
355
+ def incShortName
356
+ @dirty = true
357
+ num = @dir_ent['name'][7].to_i
358
+ num += 1
359
+ raise "More than 9 files with name: #{@dir_ent['name'][0, 6]}" if num > 57
360
+ @dir_ent['name'][7] = num
361
+ csum = calcChecksum()
362
+ if @lfn_ents
363
+ @lfn_ents.each {|ent| ent['checksum'] = csum}
364
+ end
365
+ end
366
+
367
+ def create
368
+ @dirty = true
369
+ @dir_ent = Hash.new
370
+ @dir_ent['name'] = "FILENAMEEXT"
371
+ @name = self.shortName
372
+ @dir_ent['attributes'] = FA_ARCHIVE
373
+ @dir_ent['ctime_tos'] = 0
374
+ @dir_ent['ctime_hms'], @dir_ent['ctime_day'] = rubyToDosTime(Time.now)
375
+ @dir_ent['atime_day'] = @dir_ent['ctime_day']
376
+ @dir_ent['mtime_hms'], @dir_ent['mtime_day'] = @dir_ent['ctime_hms'], @dir_ent['ctime_day']
377
+ # Must fill all members or BinaryStruct.encode fails.
378
+ self.magic = 0x00; self.length = 0; self.firstCluster = 0 #magic used to be 0x18
379
+ end
380
+
381
+ def mkLongName(name)
382
+ @lfn_ents = mkLfn(name)
383
+ @dir_ent['name'] = mkSfn(name)
384
+ # Change magic number to 0.
385
+ @dir_ent['reserved1'] = 0
386
+ # Do checksums in lfn entries.
387
+ csum = calcChecksum()
388
+ @lfn_ents.each {|ent| ent['checksum'] = csum}
389
+ end
390
+
391
+ def mkLfn(name)
392
+ name = mkLegalLfn(name)
393
+ lfn_ents = []
394
+ # Get number of LFN entries necessary to encode name.
395
+ ents, leftover = name.length.divmod(CHARS_PER_LFN)
396
+ if leftover > 0
397
+ ents += 1
398
+ name += "\000"
399
+ end
400
+ # Split out & convert name components.
401
+ 1.upto(ents) {|ent_num|
402
+ ent = {}; ent['attributes'] = FA_LFN; ent['seq_num'] = ent_num
403
+ ent['reserved1'] = 0; ent['reserved2'] = 0;
404
+ LFN_NAME_COMPONENTS.each {|comp|
405
+ chStart = (ent_num - 1) * CHARS_PER_LFN + comp[LFN_NC_OFFSET]
406
+ if chStart > name.length
407
+ ent["#{comp[LFN_NC_HASHNAME]}"] = "\377".b * (comp[LFN_NC_LENGTH] * 2)
408
+ else
409
+ ptName = name[chStart, comp[LFN_NC_LENGTH]]
410
+ ptName.Utf8ToUnicode!
411
+ if ptName.length < comp[LFN_NC_LENGTH] * 2
412
+ ptName += "\377".b * (comp[LFN_NC_LENGTH] * 2 - ptName.length)
413
+ end
414
+ ent["#{comp[LFN_NC_HASHNAME]}"] = ptName
415
+ end
416
+ }
417
+ lfn_ents << ent
418
+ }
419
+ lfn_ents.reverse!
420
+ lfn_ents[0]['seq_num'] |= AF_LFN_LAST
421
+ return lfn_ents
422
+ end
423
+
424
+ def mkSfn(name)
425
+ return mkLegalSfn(name)
426
+ end
427
+
428
+ def isIllegalSfn(name)
429
+ # Check: name length, extension length, NULL file name,
430
+ # device names as file names & illegal chars.
431
+ return SFN_NAME_LENGTH if name.length > 12
432
+ extpos = name.reverse.index(".")
433
+ return SFN_EXT_LENGTH if extpos > 3
434
+ return SFN_NAME_NULL if extpos == 0
435
+ fn = name[0...extpos].downcase
436
+ return SFN_NAME_DEVICE if checkForDeviceNames(fn)
437
+ return SFN_ILLEGAL_CHARS if name.index(/[;+=\[\]',\"*\\<>\/?\:|]/) != nil
438
+ return false
439
+ end
440
+
441
+ def checkForDeviceName(fn)
442
+ %w[aux com1 com2 com3 com4 lpt lpt1 lpt2 lpt3 lpt4 mailslot nul pipe prn].each {|bad|
443
+ return true if fn == bad
444
+ }
445
+ return false
446
+ end
447
+
448
+ def mkLegalSfn(name)
449
+ name = name.upcase; name = name.delete(" ")
450
+ name = name + "." if not name.include?(".")
451
+ extpos = name.reverse.index(".")
452
+ if extpos == 0 then ext = "" else ext = name[-extpos, 3] end
453
+ fn = name[0, (name.length - extpos - 1)]
454
+ fn = fn[0, 6] + "~1" if fn.length > 8
455
+ return (fn.ljust(8) + ext.ljust(3)).gsub(/[;+=\[\]',\"*\\<>\/?\:|]/, "_")
456
+ end
457
+
458
+ def isIllegalLfn(name)
459
+ return LFN_NAME_LENGTH if name.length > LFN_NAME_MAXLEN
460
+ return LFN_ILLEGAL_CHARS if name.index(/\/\\:><?/) != nil
461
+ return false
462
+ end
463
+
464
+ def mkLegalLfn(name)
465
+ name = name[0...LFN_NAME_MAXLEN] if name.length > LFN_NAME_MAXLEN
466
+ return name.gsub(/\/\\:><?/, "_")
467
+ end
468
+
469
+ def calcChecksum
470
+ name = @dir_ent['name']; csum = 0
471
+ 0.upto(10) {|i|
472
+ csum = ((csum & 1 == 1 ? 0x80 : 0) + (csum >> 1) + name[i]) & 0xff
473
+ }
474
+ return csum
475
+ end
476
+
477
+ def dosToRubyTime(dos_day, dos_time)
478
+ # Extract d,m,y,s,m,h & range check.
479
+ day = dos_day & MSK_DAY; day = 1 if day == 0
480
+ month = (dos_day & MSK_MONTH) >> 5; month = 1 if month == 0
481
+ month = month.modulo(12) if month > 12
482
+ year = ((dos_day & MSK_YEAR) >> 9) + 1980 #DOS year epoc is 1980.
483
+ # Extract seconds, range check & expand granularity.
484
+ sec = (dos_time & MSK_SEC); sec = sec.modulo(29) if sec > 29; sec *= 2
485
+ min = (dos_time & MSK_MIN) >> 5; min = min.modulo(59) if min > 59
486
+ hour = (dos_time & MSK_HOUR) >> 11; hour = hour.modulo(23) if hour > 23
487
+ # Make a Ruby time.
488
+ return Time.mktime(year, month, day, hour, min, sec)
489
+ end
490
+
491
+ def rubyToDosTime(tim)
492
+ # Time
493
+ sec = tim.sec; sec -= 1 if sec == 60 #correction for possible leap second.
494
+ sec = (sec / 2).to_i #dos granularity is 2sec.
495
+ min = tim.min; hour = tim.hour
496
+ dos_time = (hour << 11) + (min << 5) + sec
497
+ # Day
498
+ day = tim.day; month = tim.month
499
+ # NOTE: This fails after 2107.
500
+ year = tim.year - 1980 #DOS year epoc is 1980.
501
+ dos_day = (year << 9) + (month << 5) + day
502
+ return dos_time, dos_day
503
+ end
504
+
505
+ # Dump object.
506
+ def dump
507
+ out = "\#<#{self.class}:0x#{'%08x' % self.object_id}>\n"
508
+ if @lfn_ents
509
+ out += "LFN Entries:\n"
510
+ @lfn_ents.each {|ent|
511
+ out += "Sequence num : 0x#{'%02x' % ent['seq_num']}\n"
512
+ n = ent['name']; n.UnicodeToUtf8! unless n == nil
513
+ out += "Name1 : '#{n}'\n"
514
+ out += "Attributes : 0x#{'%02x' % ent['attributes']}\n"
515
+ out += "Reserved1 : 0x#{'%02x' % ent['reserved1']}\n"
516
+ out += "Checksum : 0x#{'%02x' % ent['checksum']}\n"
517
+ n = ent['name2']; n.UnicodeToUtf8! unless n == nil
518
+ out += "Name2 : '#{n}'\n"
519
+ out += "Reserved2 : 0x#{'%04x' % ent['reserved2']}\n"
520
+ n = ent['name3']; n.UnicodeToUtf8! unless n == nil
521
+ out += "Name3 : '#{n}'\n\n"
522
+ }
523
+ end
524
+ out += "SFN Entry:\n"
525
+ out += "Name : #{@dir_ent['name']}\n"
526
+ out += "Attributes : 0x#{'%02x' % @dir_ent['attributes']}\n"
527
+ out += "Reserved1 : 0x#{'%02x' % @dir_ent['reserved1']}\n"
528
+ out += "CTime, tenths: 0x#{'%02x' % @dir_ent['ctime_tos']}\n"
529
+ out += "CTime, hms : 0x#{'%04x' % @dir_ent['ctime_hms']}\n"
530
+ out += "CTime, day : 0x#{'%04x' % @dir_ent['ctime_day']} (#{cTime})\n"
531
+ out += "ATime, day : 0x#{'%04x' % @dir_ent['atime_day']} (#{aTime})\n"
532
+ out += "First clus hi: 0x#{'%04x' % @dir_ent['first_clus_hi']}\n"
533
+ out += "MTime, hms : 0x#{'%04x' % @dir_ent['mtime_hms']}\n"
534
+ out += "MTime, day : 0x#{'%04x' % @dir_ent['mtime_day']} (#{mTime})\n"
535
+ out += "First clus lo: 0x#{'%04x' % @dir_ent['first_clus_lo']}\n"
536
+ out += "File size : 0x#{'%08x' % @dir_ent['file_size']}\n"
537
+ end
538
+
539
+ end
540
+ end # module Fat32
@@ -0,0 +1,128 @@
1
+ require 'fs/fat32/boot_sect'
2
+ require 'memory_buffer'
3
+
4
+ module Fat32
5
+ class FileData
6
+ attr_reader :pos
7
+
8
+ # Initialization
9
+ def initialize(dirEntry, bootSector)
10
+ raise "Nil directory entry" if dirEntry.nil?
11
+ raise "Nil boot sector" if bootSector.nil?
12
+
13
+ @bs = bootSector
14
+ @de = dirEntry
15
+ rewind
16
+ end
17
+
18
+ def rewind
19
+ @pos = 0
20
+ end
21
+
22
+ def firstCluster
23
+ return -1 if !@clusterMap || @clusterMap.size == 0
24
+ @clusterMap[0]
25
+ end
26
+
27
+ def seek(offset, method = IO::SEEK_SET)
28
+ @pos = case method
29
+ when IO::SEEK_SET then offset
30
+ when IO::SEEK_CUR then @pos + offset
31
+ when IO::SEEK_END then @de.length - offset
32
+ end
33
+ @pos = 0 if @pos < 0
34
+ @pos = @de.length if @pos > @de.length
35
+ @pos
36
+ end
37
+
38
+ def read(bytes = @de.length)
39
+ return nil if @pos >= @de.length
40
+ @clusterMap = @bs.mkClusterMap(@de.firstCluster) unless @clusterMap
41
+
42
+ # Get start & end locs.
43
+ bpc = @bs.bytesPerCluster
44
+ startCluster, startOffset = @pos.divmod(bpc)
45
+ endCluster, endOffset = (@pos + (bytes - 1)).divmod(bpc)
46
+
47
+ # Single cluster.
48
+ if startCluster == endCluster
49
+ @pos += (endOffset - startOffset)
50
+ return getCluster(startCluster)[startOffset..endOffset]
51
+ end
52
+
53
+ # Span clusters.
54
+ out = MemoryBuffer.create(bytes)
55
+ totalLen = 0
56
+ (startCluster..endCluster).each do |clus|
57
+ offset = 0; len = bpc
58
+ if clus == startCluster
59
+ offset = startOffset
60
+ len -= offset
61
+ end
62
+ len -= (bpc - (endOffset + 1)) if clus == endCluster
63
+ out[totalLen, len] = getCluster(clus)[offset, len]
64
+ totalLen += len
65
+ @pos += len
66
+ end
67
+ out[0..totalLen]
68
+ end
69
+
70
+ def write(buf, bytes = buf.length)
71
+ @clusterMap = @bs.mkClusterMap(@de.firstCluster) unless @clusterMap
72
+
73
+ # Get start & end locs.
74
+ bytesWritten = 0; bpc = @bs.bytesPerCluster
75
+ startCluster, startOffset = @pos.divmod(bpc)
76
+ endCluster, endOffset = (@pos + (bytes - 1)).divmod(bpc)
77
+
78
+ # For each cluster, read, deposit & write.
79
+ (startCluster..endCluster).each do |clus|
80
+ offset = 0; len = bpc
81
+ if clus == startCluster
82
+ offset = startOffset
83
+ len -= offset
84
+ end
85
+ len -= (bpc - (endOffset + 1)) if clus == endCluster
86
+ current = getCluster(clus)
87
+ current[startOffset, len] = buf[bytesWritten, len]
88
+ putCluster(clus, current)
89
+ @pos += len; bytesWritten += len
90
+ end
91
+ @de.length = @pos if @pos > @de.length
92
+ @de.firstCluster = @clusterMap[0] if @de.firstCluster == 0
93
+ bytesWritten
94
+ end
95
+
96
+ def getCluster(vcn)
97
+ lcn = getLCN(vcn)
98
+ # puts "vcn=#{vcn}, lcn=#{lcn}" if $track_pos
99
+ return MemoryBuffer.create(@bs.bytesPerCluster) if lcn == -1
100
+ raise "LCN is nill" if lcn.nil?
101
+ @bs.getCluster(lcn)
102
+ end
103
+
104
+ def putCluster(vcn, buf)
105
+ lcn = getLCN(vcn)
106
+ if lcn == -1
107
+ lcn = @bs.allocClusters(@clusterMap.size == 0 ? 0 : @clusterMap[@clusterMap.size - 1])
108
+ @clusterMap << lcn
109
+ end
110
+ @bs.writeClusters(lcn, buf)
111
+ end
112
+
113
+ def close
114
+ @de.close(@bs)
115
+ end
116
+
117
+ def getLCN(relClus)
118
+ return -1 if relClus >= @clusterMap.size || @clusterMap.size == 0
119
+ lcn = @clusterMap[relClus]
120
+ if lcn.nil?
121
+ # puts "LCN is nil for VCN #{relClus}; Map size is #{@clusterMap.size}, cluster map follows:"
122
+ # puts @clusterMap.inspect
123
+ raise "Bad cluster map."
124
+ end
125
+ lcn
126
+ end
127
+ end
128
+ end # module Fat32