Blender  V3.3
undofile.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2004 Blender Foundation. All rights reserved. */
3 
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <math.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 /* open/close */
16 #ifndef _WIN32
17 # include <unistd.h>
18 #else
19 # include <io.h>
20 #endif
21 
22 #include "MEM_guardedalloc.h"
23 
24 #include "DNA_listBase.h"
25 
26 #include "BLI_blenlib.h"
27 #include "BLI_ghash.h"
28 
29 #include "BLO_readfile.h"
30 #include "BLO_undofile.h"
31 
32 #include "BKE_lib_id.h"
33 #include "BKE_main.h"
34 #include "BKE_undo_system.h"
35 
36 /* keep last */
37 #include "BLI_strict_flags.h"
38 
39 /* **************** support for memory-write, for undo buffers *************** */
40 
41 void BLO_memfile_free(MemFile *memfile)
42 {
43  MemFileChunk *chunk;
44 
45  while ((chunk = BLI_pophead(&memfile->chunks))) {
46  if (chunk->is_identical == false) {
47  MEM_freeN((void *)chunk->buf);
48  }
49  MEM_freeN(chunk);
50  }
51  memfile->size = 0;
52 }
53 
54 void BLO_memfile_merge(MemFile *first, MemFile *second)
55 {
56  /* We use this mapping to store the memory buffers from second memfile chunks which are not owned
57  * by it (i.e. shared with some previous memory steps). */
58  GHash *buffer_to_second_memchunk = BLI_ghash_new(
60 
61  /* First, detect all memchunks in second memfile that are not owned by it. */
62  for (MemFileChunk *sc = second->chunks.first; sc != NULL; sc = sc->next) {
63  if (sc->is_identical) {
64  BLI_ghash_insert(buffer_to_second_memchunk, (void *)sc->buf, sc);
65  }
66  }
67 
68  /* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
69  * it is also used by the second memfile, transfer the ownership. */
70  for (MemFileChunk *fc = first->chunks.first; fc != NULL; fc = fc->next) {
71  if (!fc->is_identical) {
72  MemFileChunk *sc = BLI_ghash_lookup(buffer_to_second_memchunk, fc->buf);
73  if (sc != NULL) {
75  sc->is_identical = false;
76  fc->is_identical = true;
77  }
78  /* Note that if the second memfile does not use that chunk, we assume that the first one
79  * fully owns it without sharing it with any other memfile, and hence it should be freed with
80  * it. */
81  }
82  }
83 
84  BLI_ghash_free(buffer_to_second_memchunk, NULL, NULL);
85 
86  BLO_memfile_free(first);
87 }
88 
90 {
91  LISTBASE_FOREACH (MemFileChunk *, chunk, &memfile->chunks) {
92  chunk->is_identical_future = false;
93  }
94 }
95 
97  MemFile *written_memfile,
98  MemFile *reference_memfile)
99 {
100  mem_data->written_memfile = written_memfile;
101  mem_data->reference_memfile = reference_memfile;
102  mem_data->reference_current_chunk = reference_memfile ? reference_memfile->chunks.first : NULL;
103 
104  /* If we have a reference memfile, we generate a mapping between the session_uuid's of the
105  * IDs stored in that previous undo step, and its first matching memchunk. This will allow
106  * us to easily find the existing undo memory storage of IDs even when some re-ordering in
107  * current Main data-base broke the order matching with the memchunks from previous step.
108  */
109  if (reference_memfile != NULL) {
112  uint current_session_uuid = MAIN_ID_SESSION_UUID_UNSET;
113  LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &reference_memfile->chunks) {
114  if (!ELEM(mem_chunk->id_session_uuid, MAIN_ID_SESSION_UUID_UNSET, current_session_uuid)) {
115  current_session_uuid = mem_chunk->id_session_uuid;
116  void **entry;
118  POINTER_FROM_UINT(current_session_uuid),
119  &entry)) {
120  *entry = mem_chunk;
121  }
122  else {
124  }
125  }
126  }
127  }
128 }
129 
131 {
132  if (mem_data->id_session_uuid_mapping != NULL) {
134  }
135 }
136 
137 void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
138 {
139  MemFile *memfile = mem_data->written_memfile;
140  MemFileChunk **compchunk_step = &mem_data->reference_current_chunk;
141 
142  MemFileChunk *curchunk = MEM_mallocN(sizeof(MemFileChunk), "MemFileChunk");
143  curchunk->size = size;
144  curchunk->buf = NULL;
145  curchunk->is_identical = false;
146  /* This is unsafe in the sense that an app handler or other code that does not
147  * perform an undo push may make changes after the last undo push that
148  * will then not be undo. Though it's not entirely clear that is wrong behavior. */
149  curchunk->is_identical_future = true;
150  curchunk->id_session_uuid = mem_data->current_id_session_uuid;
151  BLI_addtail(&memfile->chunks, curchunk);
152 
153  /* we compare compchunk with buf */
154  if (*compchunk_step != NULL) {
155  MemFileChunk *compchunk = *compchunk_step;
156  if (compchunk->size == curchunk->size) {
157  if (memcmp(compchunk->buf, buf, size) == 0) {
158  curchunk->buf = compchunk->buf;
159  curchunk->is_identical = true;
160  compchunk->is_identical_future = true;
161  }
162  }
163  *compchunk_step = compchunk->next;
164  }
165 
166  /* not equal... */
167  if (curchunk->buf == NULL) {
168  char *buf_new = MEM_mallocN(size, "Chunk buffer");
169  memcpy(buf_new, buf, size);
170  curchunk->buf = buf_new;
171  memfile->size += size;
172  }
173 }
174 
175 struct Main *BLO_memfile_main_get(struct MemFile *memfile,
176  struct Main *bmain,
177  struct Scene **r_scene)
178 {
179  struct Main *bmain_undo = NULL;
182  memfile,
183  &(const struct BlendFileReadParams){0},
184  NULL);
185 
186  if (bfd) {
187  bmain_undo = bfd->main;
188  if (r_scene) {
189  *r_scene = bfd->curscene;
190  }
191 
192  MEM_freeN(bfd);
193  }
194 
195  return bmain_undo;
196 }
197 
198 bool BLO_memfile_write_file(struct MemFile *memfile, const char *filepath)
199 {
200  MemFileChunk *chunk;
201  int file, oflags;
202 
203  /* NOTE: This is currently used for autosave and 'quit.blend',
204  * where _not_ following symlinks is OK,
205  * however if this is ever executed explicitly by the user,
206  * we may want to allow writing to symlinks.
207  */
208 
209  oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC;
210 #ifdef O_NOFOLLOW
211  /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */
212  oflags |= O_NOFOLLOW;
213 #else
214  /* TODO(sergey): How to deal with symlinks on windows? */
215 # ifndef _MSC_VER
216 # warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103"
217 # endif
218 #endif
219  file = BLI_open(filepath, oflags, 0666);
220 
221  if (file == -1) {
222  fprintf(stderr,
223  "Unable to save '%s': %s\n",
224  filepath,
225  errno ? strerror(errno) : "Unknown error opening file");
226  return false;
227  }
228 
229  for (chunk = memfile->chunks.first; chunk; chunk = chunk->next) {
230 #ifdef _WIN32
231  if ((size_t)write(file, chunk->buf, (uint)chunk->size) != chunk->size)
232 #else
233  if ((size_t)write(file, chunk->buf, chunk->size) != chunk->size)
234 #endif
235  {
236  break;
237  }
238  }
239 
240  close(file);
241 
242  if (chunk) {
243  fprintf(stderr,
244  "Unable to save '%s': %s\n",
245  filepath,
246  errno ? strerror(errno) : "Unknown error writing file");
247  return false;
248  }
249  return true;
250 }
251 
252 static ssize_t undo_read(FileReader *reader, void *buffer, size_t size)
253 {
254  UndoReader *undo = (UndoReader *)reader;
255 
256  static size_t seek = SIZE_MAX; /* The current position. */
257  static size_t offset = 0; /* Size of previous chunks. */
258  static MemFileChunk *chunk = NULL;
259  size_t chunkoffset, readsize, totread;
260 
261  undo->memchunk_identical = true;
262 
263  if (size == 0) {
264  return 0;
265  }
266 
267  if (seek != (size_t)undo->reader.offset) {
268  chunk = undo->memfile->chunks.first;
269  seek = 0;
270 
271  while (chunk) {
272  if (seek + chunk->size > (size_t)undo->reader.offset) {
273  break;
274  }
275  seek += chunk->size;
276  chunk = chunk->next;
277  }
278  offset = seek;
279  seek = (size_t)undo->reader.offset;
280  }
281 
282  if (chunk) {
283  totread = 0;
284 
285  do {
286  /* First check if it's on the end if current chunk. */
287  if (seek - offset == chunk->size) {
288  offset += chunk->size;
289  chunk = chunk->next;
290  }
291 
292  /* Debug, should never happen. */
293  if (chunk == NULL) {
294  printf("illegal read, chunk zero\n");
295  return 0;
296  }
297 
298  chunkoffset = seek - offset;
299  readsize = size - totread;
300 
301  /* Data can be spread over multiple chunks, so clamp size
302  * to within this chunk, and then it will read further in
303  * the next chunk. */
304  if (chunkoffset + readsize > chunk->size) {
305  readsize = chunk->size - chunkoffset;
306  }
307 
308  memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
309  totread += readsize;
310  undo->reader.offset += (off64_t)readsize;
311  seek += readsize;
312 
313  /* `is_identical` of current chunk represents whether it changed compared to previous undo
314  * step. this is fine in redo case, but not in undo case, where we need an extra flag
315  * defined when saving the next (future) step after the one we want to restore, as we are
316  * supposed to 'come from' that future undo step, and not the one before current one. */
317  undo->memchunk_identical &= undo->undo_direction == STEP_REDO ? chunk->is_identical :
318  chunk->is_identical_future;
319  } while (totread < size);
320 
321  return (ssize_t)totread;
322  }
323 
324  return 0;
325 }
326 
327 static void undo_close(FileReader *reader)
328 {
329  MEM_freeN(reader);
330 }
331 
332 FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
333 {
334  UndoReader *undo = MEM_callocN(sizeof(UndoReader), __func__);
335 
336  undo->memfile = memfile;
337  undo->undo_direction = undo_direction;
338 
339  undo->reader.read = undo_read;
340  undo->reader.seek = NULL;
341  undo->reader.close = undo_close;
342 
343  return (FileReader *)undo;
344 }
#define MAIN_ID_SESSION_UUID_UNSET
Definition: BKE_lib_id.h:82
const char * BKE_main_blendfile_path(const struct Main *bmain) ATTR_NONNULL()
@ STEP_REDO
#define BLI_assert_unreachable()
Definition: BLI_assert.h:93
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define O_BINARY
Definition: BLI_fileops.h:319
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: fileops.c:920
unsigned int BLI_ghashutil_ptrhash(const void *key)
GHash * BLI_ghash_new(GHashHashFP hashfp, GHashCmpFP cmpfp, const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.c:689
unsigned int BLI_ghashutil_inthash_p_simple(const void *ptr)
bool BLI_ghashutil_intcmp(const void *a, const void *b)
void * BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.c:734
bool BLI_ghashutil_ptrcmp(const void *a, const void *b)
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition: BLI_ghash.c:710
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition: BLI_ghash.c:863
bool BLI_ghash_ensure_p(GHash *gh, void *key, void ***r_val) ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.c:755
void * BLI_pophead(ListBase *listbase) ATTR_NONNULL(1)
Definition: listbase.c:221
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:336
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:80
Strict compiler flags for areas of code we want to ensure don't do conversions without us knowing abo...
unsigned int uint
Definition: BLI_sys_types.h:67
#define ELEM(...)
#define POINTER_FROM_UINT(i)
#define POINTER_OFFSET(v, ofs)
SSIZE_T ssize_t
Definition: BLI_winstuff.h:71
external readfile function prototypes.
BlendFileData * BLO_read_from_memfile(struct Main *oldmain, const char *filepath, struct MemFile *memfile, const struct BlendFileReadParams *params, struct ReportList *reports)
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
ATTR_WARN_UNUSED_RESULT const BMFlagLayer * oflags
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition: btDbvt.cpp:52
FILE * file
ccl_global float * buffer
ccl_gpu_kernel_postfix ccl_global float int int int int float bool int offset
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:31
void *(* MEM_mallocN)(size_t len, const char *str)
Definition: mallocn.c:33
#define SIZE_MAX
Definition: stdint.h:206
struct Scene * curscene
Definition: BLO_readfile.h:64
struct Main * main
Definition: BLO_readfile.h:56
FileReaderSeekFn seek
off64_t offset
FileReaderCloseFn close
FileReaderReadFn read
void * first
Definition: DNA_listBase.h:31
Definition: BKE_main.h:121
char filepath[1024]
Definition: BKE_main.h:124
bool is_identical_future
Definition: BLO_undofile.h:26
uint id_session_uuid
Definition: BLO_undofile.h:29
bool is_identical
Definition: BLO_undofile.h:22
const char * buf
Definition: BLO_undofile.h:18
MemFile * reference_memfile
Definition: BLO_undofile.h:39
MemFile * written_memfile
Definition: BLO_undofile.h:38
struct GHash * id_session_uuid_mapping
Definition: BLO_undofile.h:45
MemFileChunk * reference_current_chunk
Definition: BLO_undofile.h:42
uint current_id_session_uuid
Definition: BLO_undofile.h:41
ListBase chunks
Definition: BLO_undofile.h:33
size_t size
Definition: BLO_undofile.h:34
bool memchunk_identical
Definition: BLO_undofile.h:61
int undo_direction
Definition: BLO_undofile.h:59
MemFile * memfile
Definition: BLO_undofile.h:58
FileReader reader
Definition: BLO_undofile.h:56
static ssize_t undo_read(FileReader *reader, void *buffer, size_t size)
Definition: undofile.c:252
static void undo_close(FileReader *reader)
Definition: undofile.c:327
FileReader * BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
Definition: undofile.c:332
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
Definition: undofile.c:137
void BLO_memfile_write_init(MemFileWriteData *mem_data, MemFile *written_memfile, MemFile *reference_memfile)
Definition: undofile.c:96
bool BLO_memfile_write_file(struct MemFile *memfile, const char *filepath)
Definition: undofile.c:198
struct Main * BLO_memfile_main_get(struct MemFile *memfile, struct Main *bmain, struct Scene **r_scene)
Definition: undofile.c:175
void BLO_memfile_clear_future(MemFile *memfile)
Definition: undofile.c:89
void BLO_memfile_free(MemFile *memfile)
Definition: undofile.c:41
void BLO_memfile_merge(MemFile *first, MemFile *second)
Definition: undofile.c:54
void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
Definition: undofile.c:130