Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
emutools_snapshot.c
Go to the documentation of this file.
1 /* Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
2  Copyright (C)2016-2017,2019-2020 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
17 
18 #ifdef XEMU_SNAPSHOT_SUPPORT
19 
20 #include "xemu/emutools.h"
21 #include "xemu/emutools_files.h"
22 #include "xemu/emutools_snapshot.h"
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <stdlib.h>
31 
32 
33 static int snapfd = -1;
34 static const char *framework_ident = "github.com/lgblgblgb/xemu";
35 static const Uint8 block_framing_id[] = { 'X','e','m','u','S','n','a','p' };
36 static const struct xemu_snapshot_definition_st *snapdef = NULL;
37 char xemusnap_error_buffer[XEMUSNAP_ERROR_BUFFER_SIZE * 2];
38 char xemusnap_user_error_buffer[XEMUSNAP_ERROR_BUFFER_SIZE];
39 static char *emu_ident;
40 static int last_sub_block_size_written = -1;
41 
42 
43 void xemusnap_close ( void )
44 {
45  if (snapfd >= 0) {
46  close(snapfd);
47  snapfd = -1;
48  }
49 }
50 
51 
52 void xemusnap_init ( const struct xemu_snapshot_definition_st *def )
53 {
54  if (snapdef)
55  return;
56  snapdef = def;
57  emu_ident = xemu_malloc(strlen(XEMU_SNAPSHOT_SUPPORT) + strlen(framework_ident) + 8);
58  sprintf(emu_ident, "Ident:%s:%s", framework_ident, XEMU_SNAPSHOT_SUPPORT);
59  atexit(xemusnap_close);
60 }
61 
62 
63 int xemusnap_read_file ( void *buffer, size_t size )
64 {
65  ssize_t ret = xemu_safe_read(snapfd, buffer, size);
66  if (ret < 0)
67  return XSNAPERR_IO;
68  if (!ret)
69  return XSNAPERR_NODATA;
70  if (ret != size)
71  return XSNAPERR_TRUNCATED;
72  return 0;
73 }
74 
75 
76 int xemusnap_skip_file_bytes ( off_t size )
77 {
78  return (lseek(snapfd, size, SEEK_CUR) == OFF_T_ERROR) ? XSNAPERR_IO : 0;
79 }
80 
81 
82 int xemusnap_write_file ( const void *buffer, size_t size )
83 {
84  ssize_t ret = xemu_safe_write(snapfd, buffer, size);
85  if (ret < 0)
86  return XSNAPERR_IO;
87  if (ret == 0 || ret != size)
88  return XSNAPERR_NODATA;
89  return 0;
90 }
91 
92 
93 int xemusnap_read_block_header ( struct xemu_snapshot_block_st *block )
94 {
95  Uint8 buffer[256];
96  // Read fixed size portion of the block header
97  int ret = xemusnap_read_file(buffer, XEMUSNAP_FIXED_HEADER_SIZE);
98  if (ret)
99  return ret;
100  // Check
101  if (memcmp(buffer, block_framing_id, 8))
102  return XSNAPERR_FORMAT;
103  if (P_AS_BE32(buffer + 8) != XEMUSNAP_FRAMING_VERSION)
104  return XSNAPERR_FORMAT;
105  if (P_AS_BE32(buffer + 12))
106  return XSNAPERR_FORMAT;
107  block->block_version = P_AS_BE32(buffer + 16);
108  block->header_size = buffer[20];
109  block->idlen = block->header_size - XEMUSNAP_FIXED_HEADER_SIZE;
110  if (block->idlen < 1 || block->idlen > XEMUSNAP_MAX_IDENT_LENGTH)
111  return XSNAPERR_FORMAT;
112  // Read the block ident string part
113  ret = xemusnap_read_file(block->idstr, block->idlen);
114  if (ret)
115  return ret;
116  block->idstr[block->idlen] = 0;
117  return 0;
118 }
119 
120 
121 int xemusnap_write_block_header ( const char *ident, Uint32 version )
122 {
123  int len = strlen(ident);
124  Uint8 buffer[len + XEMUSNAP_FIXED_HEADER_SIZE];
125  memcpy(buffer, block_framing_id, 8);
126  U32_AS_BE(buffer + 8, XEMUSNAP_FRAMING_VERSION);
127  U32_AS_BE(buffer + 12, 0);
128  U32_AS_BE(buffer + 16, version);
129  buffer[20] = len + XEMUSNAP_FIXED_HEADER_SIZE;
130  memcpy(buffer + XEMUSNAP_FIXED_HEADER_SIZE, ident, len);
131  last_sub_block_size_written = -1;
132  return xemusnap_write_file(buffer, len + XEMUSNAP_FIXED_HEADER_SIZE);
133 }
134 
135 
136 int xemusnap_read_be32 ( Uint32 *result )
137 {
138  Uint8 buffer[4];
139  int ret = xemusnap_read_file(buffer, 4);
140  *result = P_AS_BE32(buffer);
141  return ret;
142 }
143 
144 
145 int xemusnap_skip_sub_blocks ( int num )
146 {
147  do {
148  Uint32 size;
149  int ret = xemusnap_read_be32(&size);
150  if (ret)
151  return ret;
152  if (!size)
153  break;
154  ret = xemusnap_skip_file_bytes(size);
155  if (ret)
156  return ret;
157  num--;
158  } while (num);
159  return 0;
160 }
161 
162 
163 int xemusnap_write_sub_block ( const Uint8 *buffer, Uint32 size )
164 {
165  int ret;
166  Uint8 sizbuf[4];
167  if (!size && !last_sub_block_size_written)
168  FATAL("Xemu internal error while saving snapshot: there can be no two zero length sub-blocks together, as one is already signals end of sub-blocks!");
169  last_sub_block_size_written = size;
170  U32_AS_BE(sizbuf, size);
171  ret = xemusnap_write_file(sizbuf, 4);
172  if (ret)
173  return ret;
174  if (size) {
175  ret = xemusnap_write_file(buffer, size);
176  if (ret)
177  return ret;
178  }
179  return 0;
180 }
181 
182 
183 #define RETURN_XSNAPERR(...) \
184  do { \
185  CHECK_SNPRINTF(snprintf(xemusnap_error_buffer, sizeof xemusnap_error_buffer, __VA_ARGS__), sizeof xemusnap_error_buffer); \
186  return 1; \
187  } while (0)
188 
189 
190 static int load_from_open_file ( void )
191 {
192  struct xemu_snapshot_block_st block;
193  const struct xemu_snapshot_definition_st *def;
194  int ret;
195  if (!snapdef)
196  RETURN_XSNAPERR("Xemu error: snapshot format is not defined!");
197  block.counter = 0;
198  block.sub_counter = -1;
199  for (;;) {
200  ret = xemusnap_read_block_header(&block);
201  if ((ret == XSNAPERR_NODATA) && block.counter)
202  goto end_of_load;
203  block.sub_counter = -1;
204  handle_error:
205  switch (ret) {
206  case XSNAPERR_CALLBACK:
207  RETURN_XSNAPERR("Error while loading snapshot block \"%s\" (%d/%d): %s", block.idstr, block.counter, block.sub_counter, xemusnap_user_error_buffer);
208  case XSNAPERR_NODATA:
209  case XSNAPERR_TRUNCATED:
210  RETURN_XSNAPERR("Truncated file, unexpected end of file (~ %d/%d)", block.counter, block.sub_counter);
211  case XSNAPERR_FORMAT:
212  RETURN_XSNAPERR("Snapshot format error, maybe not a snapshot file, or version mismatch");
213  case XSNAPERR_IO:
214  RETURN_XSNAPERR("File I/O error while reading snapshot: %s", strerror(errno));
215  case 0:
216  block.is_ident = !memcmp(block.idstr, emu_ident, 6);
217  def = snapdef;
218  if (block.is_ident) { // is it an ident block?
219  if (block.counter)
220  goto end_of_load; // ident block other than the first one also signals end of snapshot, regardless of the rest of ident string
221  // check the ident string if it's really our one
222  if (strcmp(block.idstr + 6, emu_ident + 6))
223  RETURN_XSNAPERR("Not our snapshot file, format is \"%s\", expected: \"%s\"", block.idstr + 6, emu_ident + 6);
224  } else {
225  if (!block.counter)
226  RETURN_XSNAPERR("Invalid snapshot file, first block must be the ident block");
227  for (;;) {
228  if (!def->idstr)
229  RETURN_XSNAPERR("Unknown block type: \"%s\"", block.idstr);
230  if (!strcmp(def->idstr, block.idstr))
231  break;
232  def++;
233  }
234  }
235  block.sub_counter = 0;
236  for (;;) {
237  ret = xemusnap_read_be32(&block.sub_size);
238  if (ret) goto handle_error;
239  if (!block.sub_size)
240  break;
241  if (block.is_ident) {
242  ret = xemusnap_skip_sub_blocks(0);
243  if (ret) goto handle_error;
244  } else {
245  // Block can be "save only", ie no LOAD callback
246  if (def->load) {
247  strcpy(xemusnap_user_error_buffer, "?");
248  ret = def->load(def, &block);
249  if (ret) goto handle_error;
250  } else {
251  ret = xemusnap_skip_sub_blocks(0);
252  if (ret) goto handle_error;
253  }
254  }
255  block.sub_counter++;
256  }
257  break;
258  default:
259  FATAL("Xemu snapshot load internal error: unknown xemusnap_read_block() answer: %d", ret);
260  }
261  block.counter++;
262  }
263 end_of_load:
264  // the last entry given in the snapshot def table can hold the "finalizer" callback, call that too.
265  // for this, find the end of the definition first
266  def = snapdef;
267  while (def->idstr)
268  def++;
269  if (def->load) { // and call it, if it's not NULL
270  strcpy(xemusnap_user_error_buffer, "?");
271  ret = def->load(def, NULL);
272  if (ret) goto handle_error;
273  }
274  return 0;
275 }
276 
277 
278 int xemusnap_load ( const char *filename )
279 {
280  char pathbuf[PATH_MAX];
281  snapfd = xemu_open_file(filename, O_RDONLY, NULL, pathbuf);
282  if (snapfd < 0)
283  RETURN_XSNAPERR("Cannot open file %s: %s", pathbuf, strerror(errno));
284  if (load_from_open_file()) {
285  xemusnap_close();
286  return 1;
287  }
288  xemusnap_close();
289  return 0;
290 }
291 
292 
293 static int save_to_open_file ( void )
294 {
295  const struct xemu_snapshot_definition_st *def = snapdef;
296  int ret;
297  if (!snapdef)
298  RETURN_XSNAPERR("Xemu error: snapshot format is not defined!");
299  // Ident block
300  ret = xemusnap_write_block_header(emu_ident, 0);
301  if (ret) goto handle_error;
302  ret = xemusnap_write_sub_block(NULL, 0);
303  if (ret) goto handle_error;
304  // Walk on blocks, use saver callback to save them all
305  while (def->idstr) {
306  if (def->save) {
307  // Block can be "load only", ie no SAVE callback
308  strcpy(xemusnap_user_error_buffer, "?");
309  ret = def->save(def);
310  if (ret) goto handle_error;
311  ret = xemusnap_write_sub_block(NULL, 0); // close block with writing a zero-sized sub-block
312  if (ret) goto handle_error;
313  }
314  def++;
315  }
316  // the last entry given in the snapshot def table can hold the "finalizer" callback, call that too.
317  if (def->save) {
318  strcpy(xemusnap_user_error_buffer, "?");
319  ret = def->save(def);
320  if (ret) goto handle_error;
321  }
322  return 0;
323 handle_error:
324  switch (ret) {
325  case XSNAPERR_NODATA:
326  case XSNAPERR_TRUNCATED:
327  RETURN_XSNAPERR("Cannot write file");
328  case XSNAPERR_FORMAT:
329  RETURN_XSNAPERR("Format violation");
330  case XSNAPERR_IO:
331  RETURN_XSNAPERR("File I/O error while writing snapshot: %s", strerror(errno));
332  case XSNAPERR_CALLBACK:
333  RETURN_XSNAPERR("Error while saving snapshot block \"%s\": %s", def->idstr, xemusnap_user_error_buffer);
334  default:
335  FATAL("Xemu snapshot save internal error: unknown error code: %d", ret);
336  }
337 }
338 
339 
340 int xemusnap_save ( const char *filename )
341 {
342  char pathbuf[PATH_MAX];
343  xemusnap_close();
344  snapfd = xemu_open_file(filename, O_WRONLY | O_CREAT | O_TRUNC, NULL, pathbuf);
345  if (snapfd < 0)
346  RETURN_XSNAPERR("Cannot create file %s: %s", pathbuf, strerror(errno));
347  if (save_to_open_file()) {
348  xemusnap_close();
349  unlink(filename);
350  return 1;
351  }
352  xemusnap_close();
353  return 0;
354 }
355 #endif
emutools.h
xemu_open_file
int xemu_open_file(const char *filename, int mode, int *mode2, char *filepath_back)
Definition: emutools_files.c:469
OFF_T_ERROR
#define OFF_T_ERROR
Definition: fat32.c:42
XEMU_SNAPSHOT_SUPPORT
#define XEMU_SNAPSHOT_SUPPORT
Definition: xemu-target.h:5
Uint32
uint32_t Uint32
Definition: fat32.c:49
Uint8
uint8_t Uint8
Definition: fat32.c:51
block
Uint32 block
Definition: fat32.c:156
emutools_files.h
xemu_malloc
void * xemu_malloc(size_t size)
Definition: emutools.c:226
xemu_safe_read
ssize_t xemu_safe_read(int fd, void *buffer, size_t length)
Definition: emutools_files.c:543
size
int size
Definition: inject.c:37
emutools_snapshot.h
xemu_safe_write
ssize_t xemu_safe_write(int fd, const void *buffer, size_t length)
Definition: emutools_files.c:563
FATAL
#define FATAL(...)
Definition: xep128.h:117