Lua: Don't lua_error() out of context with pending dtors
[lsnes.git] / src / library / filesystem.cpp
blobc9a65b1aff5a3d6a7fedf36817329944a7cf8342
1 #include "filesystem.hpp"
2 #include "minmax.hpp"
3 #include "serialization.hpp"
4 #include <cstring>
5 #include <stdexcept>
6 #include <iostream>
8 filesystem::filesystem(const std::string& file)
10 backing.open(file, std::ios_base::out | std::ios_base::app);
11 backing.close();
12 backing.open(file, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::ate);
13 if(!backing)
14 throw std::runtime_error("Can't open file '" + file + "'");
15 uint64_t backing_size = backing.tellp();
16 backing.seekp(0, std::ios_base::beg);
17 if(!backing)
18 throw std::runtime_error("Can't get file size.");
19 supercluster_count = (backing_size + SUPERCLUSTER_SIZE - 1) / SUPERCLUSTER_SIZE;
20 for(unsigned i = 0; i < supercluster_count; i++)
21 superclusters[i].load(backing, i);
22 if(supercluster_count == 0) {
23 allocate_cluster(); //Will allocate cluster 2 (main directory).
24 //Write superblock to cluster 1.
25 char superblock[CLUSTER_SIZE];
26 memset(superblock, 0, CLUSTER_SIZE);
27 uint32_t c = 2;
28 uint32_t p = 0;
29 uint32_t c2, p2;
30 write_data(c, p, superblock, CLUSTER_SIZE, c2, p2);
31 strcpy(superblock, "sefs-magic");
32 c = 1;
33 p = 0;
34 write_data(c, p, superblock, CLUSTER_SIZE, c2, p2);
35 } else {
36 //Read superblock from cluster 1.
37 char superblock[CLUSTER_SIZE];
38 uint32_t c = 1;
39 uint32_t p = 0;
40 read_data(c, p, superblock, CLUSTER_SIZE);
41 if(strcmp(superblock, "sefs-magic"))
42 throw std::runtime_error("Bad magic");
47 uint32_t filesystem::allocate_cluster()
49 for(unsigned i = 0; i < supercluster_count; i++) {
50 supercluster& c = superclusters[i];
51 if(c.free_clusters)
52 for(unsigned j = 0; j < CLUSTERS_PER_SUPER; j++)
53 if(!c.clusters[j]) {
54 c.clusters[j] = 1;
55 c.save(backing, i);
56 //Write zeroes over the cluster.
57 uint32_t cluster = i * CLUSTERS_PER_SUPER + j;
58 char buffer[CLUSTER_SIZE];
59 memset(buffer, 0, CLUSTER_SIZE);
60 backing.seekp(cluster * CLUSTER_SIZE, std::ios_base::beg);
61 backing.write(buffer, CLUSTER_SIZE);
62 if(!backing)
63 throw std::runtime_error("Can't zero out the new cluster");
64 return cluster;
67 //Create a new supercluster.
68 supercluster& c = superclusters[supercluster_count];
69 c.free_clusters = 65535;
70 c.clusters[0] = 0xFFFFFFFFU; //Reserved for cluster table.
71 for(unsigned i = 1; i < CLUSTERS_PER_SUPER; i++)
72 c.clusters[i] = 0; //Free.
73 if(!supercluster_count)
74 c.clusters[1] = 0xFFFFFFFFU; //Reserved for superblock.
75 unsigned j = supercluster_count ? 1 : 2;
76 if(!supercluster_count)
77 c.free_clusters--; //The superblock.
78 c.clusters[j] = 1; //End of chain.
79 c.save(backing, supercluster_count);
80 char blankbuf[2 * CLUSTER_SIZE];
81 memset(blankbuf, 0, 2 * CLUSTER_SIZE);
82 backing.clear();
83 backing.seekp(supercluster_count * SUPERCLUSTER_SIZE + CLUSTER_SIZE, std::ios_base::beg);
84 backing.write(blankbuf, supercluster_count ? CLUSTER_SIZE : 2 * CLUSTER_SIZE);
85 if(!backing)
86 throw std::runtime_error("Can't write new supercluster");
87 return (supercluster_count++) * CLUSTERS_PER_SUPER + j;
90 void filesystem::link_cluster(uint32_t cluster, uint32_t linkto)
92 if(cluster / CLUSTERS_PER_SUPER >= supercluster_count)
93 throw std::runtime_error("Bad cluster to link from");
94 if(linkto / CLUSTERS_PER_SUPER >= supercluster_count)
95 throw std::runtime_error("Bad cluster to link to");
96 if(superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER] != 1)
97 throw std::runtime_error("Only end of chain clusters can be linked");
98 superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER] = linkto;
99 superclusters[cluster / CLUSTERS_PER_SUPER].save(backing, cluster / CLUSTERS_PER_SUPER);
102 void filesystem::free_cluster_chain(uint32_t cluster)
104 if(cluster == 2)
105 throw std::runtime_error("Cluster 2 can't be freed");
106 if(cluster / CLUSTERS_PER_SUPER >= supercluster_count)
107 throw std::runtime_error("Bad cluster to free");
108 uint32_t oldnext = superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER];
109 if(oldnext == 0)
110 throw std::runtime_error("Attempted to free free cluster");
111 if(oldnext == 0xFFFFFFFFU)
112 throw std::runtime_error("Attempted to free system cluster");
113 superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER] = 0;
114 //If there is no next block, or the next block is in different supercluster, save the cluster table.
115 if(oldnext == 1 || oldnext / CLUSTERS_PER_SUPER != cluster / CLUSTERS_PER_SUPER)
116 superclusters[cluster / CLUSTERS_PER_SUPER].save(backing, cluster / CLUSTERS_PER_SUPER);
117 if(oldnext != 1)
118 free_cluster_chain(oldnext);
121 size_t filesystem::skip_data(uint32_t& cluster, uint32_t& ptr, uint32_t length)
123 size_t r = 0;
124 do {
125 if(cluster / CLUSTERS_PER_SUPER >= supercluster_count || (cluster % CLUSTERS_PER_SUPER) == 0)
126 throw std::runtime_error("Bad cluster to read");
127 if(!superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER])
128 throw std::runtime_error("Bad cluster to read");
129 //Read to end of cluster.
130 size_t maxread = min(length, max(static_cast<uint32_t>(CLUSTER_SIZE), ptr) - ptr);
131 if(maxread) {
132 length -= maxread;
133 ptr += maxread;
134 r += maxread;
136 if(ptr >= CLUSTER_SIZE) {
137 uint32_t n = superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster %
138 CLUSTERS_PER_SUPER];
139 if(n == 0)
140 throw std::runtime_error("Bad next cluster when reading");
141 else if(n == 1 || n == 0xFFFFFFFFU) {
142 //Cluster is right.
143 ptr = CLUSTER_SIZE;
144 return r;
145 } else {
146 cluster = n;
147 ptr = 0;
150 } while(length > 0);
151 return r;
154 size_t filesystem::read_data(uint32_t& cluster, uint32_t& ptr, void* data, uint32_t length)
156 char* _data = reinterpret_cast<char*>(data);
157 size_t r = 0;
158 do {
159 if(cluster / CLUSTERS_PER_SUPER >= supercluster_count || (cluster % CLUSTERS_PER_SUPER) == 0)
160 throw std::runtime_error("Bad cluster to read");
161 if(!superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER])
162 throw std::runtime_error("Bad cluster to read");
163 //Read to end of cluster.
164 size_t maxread = min(length, max(static_cast<uint32_t>(CLUSTER_SIZE), ptr) - ptr);
165 if(maxread) {
166 backing.clear();
167 backing.seekg(cluster * CLUSTER_SIZE + ptr, std::ios_base::beg);
168 backing.read(_data, maxread);
169 if(!backing)
170 throw std::runtime_error("Can't read data");
171 length -= maxread;
172 _data += maxread;
173 ptr += maxread;
174 r += maxread;
176 if(ptr >= CLUSTER_SIZE) {
177 uint32_t n = superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster %
178 CLUSTERS_PER_SUPER];
179 if(n == 0)
180 throw std::runtime_error("Bad next cluster when reading");
181 else if(n == 1 || n == 0xFFFFFFFFU) {
182 //Cluster is right.
183 ptr = CLUSTER_SIZE;
184 return r;
185 } else {
186 cluster = n;
187 ptr = 0;
190 } while(length > 0);
191 return r;
194 void filesystem::write_data(uint32_t& cluster, uint32_t& ptr, const void* data, uint32_t length,
195 uint32_t& real_cluster, uint32_t& real_ptr)
197 const char* _data = reinterpret_cast<const char*>(data);
198 size_t r = 0;
199 bool assigned = false;
200 do {
201 if(cluster / CLUSTERS_PER_SUPER >= supercluster_count || (cluster % CLUSTERS_PER_SUPER) == 0)
202 throw std::runtime_error("Bad cluster to write");
203 if(!superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster % CLUSTERS_PER_SUPER])
204 throw std::runtime_error("Bad cluster to write");
205 //Write to end of cluster.
206 size_t maxwrite = min(length, max(static_cast<uint32_t>(CLUSTER_SIZE), ptr) - ptr);
207 if(maxwrite) {
208 char buffer[CLUSTER_SIZE];
209 memset(buffer, 0, CLUSTER_SIZE);
210 if(!assigned) {
211 real_cluster = cluster;
212 real_ptr = ptr;
213 assigned = true;
215 backing.seekp(0, std::ios_base::end);
216 uint64_t backing_size = backing.tellp();
217 if(backing_size > cluster * CLUSTER_SIZE) {
218 backing.seekg(cluster * CLUSTER_SIZE, std::ios_base::beg);
219 backing.read(buffer, CLUSTER_SIZE);
221 memcpy(buffer + ptr, _data, maxwrite);
222 backing.seekp(cluster * CLUSTER_SIZE, std::ios_base::beg);
223 backing.write(buffer, CLUSTER_SIZE);
224 if(!backing)
225 throw std::runtime_error("Can't write data");
226 length -= maxwrite;
227 _data += maxwrite;
228 ptr += maxwrite;
229 r += maxwrite;
231 if(ptr >= CLUSTER_SIZE) {
232 uint32_t n = superclusters[cluster / CLUSTERS_PER_SUPER].clusters[cluster %
233 CLUSTERS_PER_SUPER];
234 if(n == 0)
235 throw std::runtime_error("Bad next cluster when writing");
236 else if(n == 0xFFFFFFFFU) {
237 if(length > 0)
238 throw std::runtime_error("Bad next cluster when writing");
239 return;
241 else if(n == 1) {
242 if(!length) {
243 ptr = CLUSTER_SIZE;
244 return;
246 n = allocate_cluster();
247 link_cluster(cluster, n);
248 cluster = n;
249 ptr = 0;
250 } else {
251 cluster = n;
252 ptr = 0;
255 } while(length > 0);
258 void filesystem::supercluster::load(std::fstream& s, uint32_t index)
260 uint64_t offset = SUPERCLUSTER_SIZE * index;
261 char buffer[CLUSTER_SIZE];
262 s.clear();
263 s.seekg(offset, std::ios_base::beg);
264 s.read(buffer, CLUSTER_SIZE);
265 if(!s)
266 throw std::runtime_error("Can't read cluster table");
267 free_clusters = 0;
268 for(unsigned i = 0; i < CLUSTERS_PER_SUPER; i++) {
269 if(!(clusters[i] = serialization::u32b(buffer + 4 * i)))
270 free_clusters++;
274 void filesystem::supercluster::save(std::fstream& s, uint32_t index)
276 uint64_t offset = SUPERCLUSTER_SIZE * index;
277 char buffer[CLUSTER_SIZE];
278 for(unsigned i = 0; i < CLUSTERS_PER_SUPER; i++)
279 serialization::u32b(buffer + 4 * i, clusters[i]);
280 s.clear();
281 s.seekp(offset, std::ios_base::beg);
282 s.write(buffer, CLUSTER_SIZE);
283 if(!s)
284 throw std::runtime_error("Can't write cluster table");
287 filesystem::ref& filesystem::ref::operator=(const filesystem::ref& r)
289 if(this == &r)
290 return *this;
291 //This is tricky, due to having to lock two objects.
292 threads::lock* mtodelete = NULL;
293 if(!refcnt) {
294 //We just have to grab a ref and copy.
295 threads::alock m(*r.mlock);
296 ++*(r.refcnt);
297 refcnt = r.refcnt;
298 mlock = r.mlock;
299 fs = r.fs;
300 } else {
301 //Two-object case.
302 threads::alock_multiple ms({r.mlock, mlock});
303 --*refcnt;
304 if(!*refcnt) {
305 delete fs;
306 delete refcnt;
307 mtodelete = mlock;
309 ++*(r.refcnt);
310 refcnt = r.refcnt;
311 mlock = r.mlock;
312 fs = r.fs;
314 if(mtodelete)
315 delete mtodelete;
316 return *this;