2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org
.jpc
.diskimages
;
35 public class TreeDirectoryFile
extends TreeFile
37 //We use TreeMap instead of HashMap here because TreeMap is ordered and HashMap is not.
38 protected TreeMap
<String
,TreeFile
> entries
;
39 //Volume name. Very special.
40 protected String volumeName
;
41 //Timestamp for entries.
43 private String _timestamp
;
44 //Cached key position and key.
45 protected int cachedPosition
;
46 protected String cachedKey
;
48 private static byte convertCodepoint(int cpoint
) throws IOException
50 if(cpoint
== 33 || (cpoint
>= 35 && cpoint
<= 41) || cpoint
== 45 || cpoint
== 125 || cpoint
== 126)
51 return (byte)cpoint
; //Misc.
52 if(cpoint
>= 48 && cpoint
<= 57) //0-9
54 if(cpoint
>= 64 && cpoint
<= 90) //@A-Z
56 if(cpoint
>= 97 && cpoint
<= 122) //a-z
57 return (byte)(cpoint
- 32);
58 if(cpoint
>= 94 && cpoint
<= 123) //^_`a-z{
60 if(cpoint
>= 160 && cpoint
<= 255) //High range.
62 throw new IOException("Character " + (char)cpoint
+ " not allowed in filename.");
65 public static void writeEntryName(byte[] sector
, String name
, int offset
, boolean noExtension
) throws IOException
67 for(int i
= 0; i
< 11; i
++)
68 sector
[offset
+ i
] = 32; //Pad with spaces.
69 int split
= name
.indexOf(".");
71 if(split
>= 0 && (split
< 1 || split
> 8 || name
.length() > split
+ 4) || (split
< 0 && name
.length() > 8))
72 throw new IOException("Illegal file name " + name
+ ".");
73 } else if(name
.length() < 1 || name
.length() > 11)
74 throw new IOException("Illegal file name " + name
+ ".");
77 split
= name
.length(); //Dirty hack.
79 String mainName
= name
.substring(0, split
);
81 if(split
> 0 && split
< name
.length())
82 extName
= name
.substring(split
+ 1);
85 for(int i
= 0; i
< mainName
.length(); i
++) {
86 sector
[offset
+ i
] = convertCodepoint(mainName
.charAt(i
));
88 for(int i
= 0; i
< extName
.length(); i
++) {
89 sector
[offset
+ 8 + i
] = convertCodepoint(extName
.charAt(i
));
93 public TreeDirectoryFile(String self
, String timestamp
) throws IOException
96 entries
= new TreeMap
<String
,TreeFile
>();
100 if(timestamp
== null)
101 timestamp
= "19900101000000";
102 _timestamp
= timestamp
;
103 dosTime
= dosFormatTimeStamp(timestamp
);
106 public TreeDirectoryFile(String self
, String volume
, String timestamp
) throws IOException
108 this(self
, timestamp
);
112 public void setClusterZeroOffset(int offset
)
114 doSetClusterZeroOffset(offset
);
115 Map
.Entry
<String
,TreeFile
> entry
= entries
.firstEntry();
116 while(entry
!= null) {
117 entry
.getValue().setClusterZeroOffset(offset
);
118 entry
= entries
.higherEntry(entry
.getKey());
122 public void setClusterSize(int size
)
124 doSetClusterSize(size
);
125 Map
.Entry
<String
,TreeFile
> entry
= entries
.firstEntry();
126 while(entry
!= null) {
127 entry
.getValue().setClusterSize(size
);
128 entry
= entries
.higherEntry(entry
.getKey());
135 if(volumeName
!= null)
137 return 32 * entries
.size() + extra
;
140 public static int dosFormatTimeStamp(String stamp
) throws IOException
142 if(stamp
.length() != 14)
143 throw new IOException("Invalid timestamp " + stamp
+ ".");
145 long nstamp
= Long
.parseLong(stamp
, 10);
146 if(nstamp
< 19800101000000L || nstamp
> 21071231235959L)
147 throw new IOException("Invalid timestamp " + stamp
+ ".");
148 int year
= (int)(nstamp
/ 10000000000L) - 1980;
149 int month
= (int)(nstamp
% 10000000000L / 100000000L);
150 int day
= (int)(nstamp
% 100000000L / 1000000L);
151 int hour
= (int)(nstamp
% 1000000L / 10000L);
152 int minute
= (int)(nstamp
% 10000L / 100L);
153 int second
= (int)(nstamp
% 100L) / 2;
154 if(month
< 1 || month
> 12 || day
< 0 || day
> 31 || hour
> 23 || minute
> 59 || second
> 59)
155 throw new IOException("Invalid timestamp " + stamp
+ ".");
156 if(day
== 30 && (month
== 2 || month
== 4 || month
== 6 || month
== 9 || month
== 11))
157 throw new IOException("Invalid timestamp " + stamp
+ ".");
158 if(day
== 29 && month
== 2 && (year
% 4 != 0 || year
== 120))
159 throw new IOException("Invalid timestamp " + stamp
+ ".");
160 return year
* 33554432 + month
* 2097152 + day
* 65536 + hour
* 2048 + minute
* 32 + second
;
161 } catch(NumberFormatException e
) {
162 throw new IOException("Invalid timestamp " + stamp
+ ".");
166 public void readSector(int sector
, byte[] data
) throws IOException
168 int extraEntries
= 0;
169 if(volumeName
!= null)
171 if(sector
>= (entries
.size() + extraEntries
+ 15) / 16) { //16 entries per sector.
172 for(int i
= 0; i
< 512; i
++)
177 if(cachedKey
== null || cachedPosition
> 16 * sector
) {
179 cachedPosition
= extraEntries
;
181 cachedKey
= entries
.firstKey();
182 } catch(Exception e
) {
186 while(cachedPosition
< 16 * sector
) {
188 cachedKey
= entries
.higherKey(cachedKey
);
191 for(int i
= 0; i
< 16; i
++) {
192 //Write zeroes as entry (at least for intialization). Also write the time.
193 for(int j
= 0; j
< 32; j
++)
194 data
[32 * i
+ j
] = 0;
195 data
[32 * i
+ 22] = (byte)(dosTime
& 0xFF);
196 data
[32 * i
+ 23] = (byte)((dosTime
>> 8) & 0xFF);
197 data
[32 * i
+ 24] = (byte)((dosTime
>> 16) & 0xFF);
198 data
[32 * i
+ 25] = (byte)((dosTime
>> 24) & 0xFF);
200 if(volumeName
!= null && sector
== 0 && i
== 0) {
201 //The special volume file.
202 writeEntryName(data
, volumeName
, 32 * i
, true);
203 data
[32 * i
+ 11] = 8; //Volume file -A -R -H -S.
204 //Cluster 0 and size 0.
205 } else if(cachedKey
!= null) {
206 TreeFile file
= entries
.get(cachedKey
);
208 writeEntryName(data
, cachedKey
, 32 * i
, false);
209 //Varous other stuff.
210 if(file
instanceof TreeDirectoryFile
) {
211 data
[32 * i
+ 11] = 16; //Directory file -A -R -H -S.
213 data
[32 * i
+ 11] = 0; //Regular file -A -R -H -S.
215 int size
= file
.getSize();
216 int cluster
= file
.getStartCluster();
217 if(size
== 0) cluster
= 0; //Handle empty files.
218 data
[32 * i
+ 26] = (byte)(cluster
& 0xFF);
219 data
[32 * i
+ 27] = (byte)((cluster
>>> 8) & 0xFF);
220 data
[32 * i
+ 28] = (byte)(size
& 0xFF);
221 data
[32 * i
+ 29] = (byte)((size
>>> 8) & 0xFF);
222 data
[32 * i
+ 30] = (byte)((size
>>> 16) & 0xFF);
223 data
[32 * i
+ 31] = (byte)((size
>>> 24) & 0xFF);
226 cachedKey
= entries
.higherKey(cachedKey
);
231 public void readSectorEnd()
233 //No resources to free.
236 public int assignCluster(int base
)
238 setStartCluster(base
);
239 base
+= getSizeInClusters();
241 base
= 2; //Special case for root directory.
242 Map
.Entry
<String
,TreeFile
> entry
= entries
.firstEntry();
243 while(entry
!= null) {
244 base
= entry
.getValue().assignCluster(base
);
245 entry
= entries
.higherEntry(entry
.getKey());
250 public TreeFile
nextFile()
252 Map
.Entry
<String
,TreeFile
> entry
= entries
.firstEntry();
256 return parent
.nextFile(selfName
);
258 return entry
.getValue();
261 protected TreeFile
nextFile(String key
)
263 Map
.Entry
<String
, TreeFile
> entry
= entries
.higherEntry(key
);
267 return parent
.nextFile(selfName
);
269 return (TreeFile
)entry
.getValue();
272 public void addFile(TreeFile newFile
) throws IOException
274 entries
.put(newFile
.getSelfName(), newFile
);
275 newFile
.parentTo(this);
278 public TreeDirectoryFile
pathToDirectory(String name
, String walked
, String timestamp
) throws IOException
280 if(name
== null || "".equals(name
))
283 int split
= name
.indexOf('/');
290 walk
= name
.substring(0, split
);
291 remaining
= name
.substring(split
+ 1);
293 if(walked
!= null && walked
!= "")
294 walked
= walked
+ "/" + walk
;
299 if(!entries
.containsKey(walk
)) {
300 addFile(new TreeDirectoryFile(walk
, timestamp
));
303 Object out
= entries
.get(walk
);
305 throw new IOException("What? Didn't I create " + walked
+ "???");
306 if(!(out
instanceof TreeDirectoryFile
))
307 throw new IOException("Conflicting types for " + walked
+ ". Was regular file, now should be directory?");
308 TreeDirectoryFile out2
= (TreeDirectoryFile
)out
;
309 return out2
.pathToDirectory(remaining
, walked
, timestamp
);
312 public static void importTree(File directory
, String unixFileName
, TreeDirectoryFile rootDir
, String timestamp
)
315 TreeDirectoryFile dir
= rootDir
.pathToDirectory(unixFileName
, null, timestamp
);
317 if(!directory
.isDirectory())
318 throw new IOException("Okay, who passed non-directory " + directory
.getAbsolutePath() + " to importTree()?");
319 File
[] objects
= directory
.listFiles();
321 throw new IOException("Can't read directory " + directory
.getAbsolutePath() + ".");
322 for(int i
= 0; i
< objects
.length
; i
++) {
323 if(objects
[i
].isDirectory()) {
324 if(unixFileName
== null)
325 importTree(objects
[i
], objects
[i
].getName(), rootDir
, timestamp
);
327 importTree(objects
[i
], unixFileName
+ "/" + objects
[i
].getName(), rootDir
, timestamp
);
329 //It's a regular file.
330 dir
.addFile(new TreeRegularFile(objects
[i
].getName(), objects
[i
].getAbsolutePath()));
335 public static TreeDirectoryFile
importTree(String fsPath
, String volumeName
, String timestamp
) throws IOException
337 TreeDirectoryFile root
= new TreeDirectoryFile("", volumeName
, timestamp
);
338 TreeDirectoryFile
.importTree(new File(fsPath
), null, root
, timestamp
);
342 private String
nformatwidth(int number
, int width
)
344 String x
= (new Integer(number
)).toString();
345 while(x
.length() < width
)
350 public List
<String
> getComments(String prefix
, String stamp
)
352 List
<String
> l
= new ArrayList
<String
>();
357 String dirMD5
= "N/A ";
359 l
.add("Entry: " + stamp
+ " " + dirMD5
+ " " + nformatwidth(entries
.size(), 10) + " " + prefix
+ "/");
361 if(volumeName
!= null) {
362 l
.add("Vname: " + _timestamp
+ " " + dirMD5
+ " " + nformatwidth(0, 10) + " " + volumeName
);
365 Map
.Entry
<String
,TreeFile
> entry
= entries
.firstEntry();
366 while(entry
!= null) {
367 List
<String
> sublist
= entry
.getValue().getComments(prefix
+ "/" + entry
.getKey(), _timestamp
);
369 entry
= entries
.higherEntry(entry
.getKey());