Fix failing view layer tests after recent changes to naming convention
[blender.git] / source / blender / blenloader / intern / undofile.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2004 Blender Foundation
17  * All rights reserved.
18  * .blend file reading entry point
19  */
20
21 /** \file
22  * \ingroup blenloader
23  */
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 /* open/close */
33 #ifndef _WIN32
34 #  include <unistd.h>
35 #else
36 #  include <io.h>
37 #endif
38
39 #include "MEM_guardedalloc.h"
40
41 #include "DNA_listBase.h"
42
43 #include "BLI_blenlib.h"
44 #include "BLI_ghash.h"
45
46 #include "BLO_readfile.h"
47 #include "BLO_undofile.h"
48
49 #include "BKE_lib_id.h"
50 #include "BKE_main.h"
51 #include "BKE_undo_system.h"
52
53 /* keep last */
54 #include "BLI_strict_flags.h"
55
56 /* **************** support for memory-write, for undo buffers *************** */
57
58 /* not memfile itself */
59 void BLO_memfile_free(MemFile *memfile)
60 {
61   MemFileChunk *chunk;
62
63   while ((chunk = BLI_pophead(&memfile->chunks))) {
64     if (chunk->is_identical == false) {
65       MEM_freeN((void *)chunk->buf);
66     }
67     MEM_freeN(chunk);
68   }
69   memfile->size = 0;
70 }
71
72 /* to keep list of memfiles consistent, 'first' is always first in list */
73 /* result is that 'first' is being freed */
74 void BLO_memfile_merge(MemFile *first, MemFile *second)
75 {
76   /* We use this mapping to store the memory buffers from second memfile chunks which are not owned
77    * by it (i.e. shared with some previous memory steps). */
78   GHash *buffer_to_second_memchunk = BLI_ghash_new(
79       BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
80
81   /* First, detect all memchunks in second memfile that are not owned by it. */
82   for (MemFileChunk *sc = second->chunks.first; sc != NULL; sc = sc->next) {
83     if (sc->is_identical) {
84       BLI_ghash_insert(buffer_to_second_memchunk, (void *)sc->buf, sc);
85     }
86   }
87
88   /* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
89    * it is also used by the second memfile, transfer the ownership. */
90   for (MemFileChunk *fc = first->chunks.first; fc != NULL; fc = fc->next) {
91     if (!fc->is_identical) {
92       MemFileChunk *sc = BLI_ghash_lookup(buffer_to_second_memchunk, fc->buf);
93       if (sc != NULL) {
94         BLI_assert(sc->is_identical);
95         sc->is_identical = false;
96         fc->is_identical = true;
97       }
98       /* Note that if the second memfile does not use that chunk, we assume that the first one
99        * fully owns it without sharing it with any other memfile, and hence it should be freed with
100        * it. */
101     }
102   }
103
104   BLI_ghash_free(buffer_to_second_memchunk, NULL, NULL);
105
106   BLO_memfile_free(first);
107 }
108
109 /* Clear is_identical_future before adding next memfile. */
110 void BLO_memfile_clear_future(MemFile *memfile)
111 {
112   LISTBASE_FOREACH (MemFileChunk *, chunk, &memfile->chunks) {
113     chunk->is_identical_future = false;
114   }
115 }
116
117 void BLO_memfile_write_init(MemFileWriteData *mem_data,
118                             MemFile *written_memfile,
119                             MemFile *reference_memfile)
120 {
121   mem_data->written_memfile = written_memfile;
122   mem_data->reference_memfile = reference_memfile;
123   mem_data->reference_current_chunk = reference_memfile ? reference_memfile->chunks.first : NULL;
124
125   /* If we have a reference memfile, we generate a mapping between the session_uuid's of the
126    * IDs stored in that previous undo step, and its first matching memchunk. This will allow
127    * us to easily find the existing undo memory storage of IDs even when some re-ordering in
128    * current Main data-base broke the order matching with the memchunks from previous step.
129    */
130   if (reference_memfile != NULL) {
131     mem_data->id_session_uuid_mapping = BLI_ghash_new(
132         BLI_ghashutil_inthash_p_simple, BLI_ghashutil_intcmp, __func__);
133     uint current_session_uuid = MAIN_ID_SESSION_UUID_UNSET;
134     LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &reference_memfile->chunks) {
135       if (!ELEM(mem_chunk->id_session_uuid, MAIN_ID_SESSION_UUID_UNSET, current_session_uuid)) {
136         current_session_uuid = mem_chunk->id_session_uuid;
137         void **entry;
138         if (!BLI_ghash_ensure_p(mem_data->id_session_uuid_mapping,
139                                 POINTER_FROM_UINT(current_session_uuid),
140                                 &entry)) {
141           *entry = mem_chunk;
142         }
143         else {
144           BLI_assert(0);
145         }
146       }
147     }
148   }
149 }
150
151 void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
152 {
153   if (mem_data->id_session_uuid_mapping != NULL) {
154     BLI_ghash_free(mem_data->id_session_uuid_mapping, NULL, NULL);
155   }
156 }
157
158 void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
159 {
160   MemFile *memfile = mem_data->written_memfile;
161   MemFileChunk **compchunk_step = &mem_data->reference_current_chunk;
162
163   MemFileChunk *curchunk = MEM_mallocN(sizeof(MemFileChunk), "MemFileChunk");
164   curchunk->size = size;
165   curchunk->buf = NULL;
166   curchunk->is_identical = false;
167   /* This is unsafe in the sense that an app handler or other code that does not
168    * perform an undo push may make changes after the last undo push that
169    * will then not be undo. Though it's not entirely clear that is wrong behavior. */
170   curchunk->is_identical_future = true;
171   curchunk->id_session_uuid = mem_data->current_id_session_uuid;
172   BLI_addtail(&memfile->chunks, curchunk);
173
174   /* we compare compchunk with buf */
175   if (*compchunk_step != NULL) {
176     MemFileChunk *compchunk = *compchunk_step;
177     if (compchunk->size == curchunk->size) {
178       if (memcmp(compchunk->buf, buf, size) == 0) {
179         curchunk->buf = compchunk->buf;
180         curchunk->is_identical = true;
181         compchunk->is_identical_future = true;
182       }
183     }
184     *compchunk_step = compchunk->next;
185   }
186
187   /* not equal... */
188   if (curchunk->buf == NULL) {
189     char *buf_new = MEM_mallocN(size, "Chunk buffer");
190     memcpy(buf_new, buf, size);
191     curchunk->buf = buf_new;
192     memfile->size += size;
193   }
194 }
195
196 struct Main *BLO_memfile_main_get(struct MemFile *memfile,
197                                   struct Main *bmain,
198                                   struct Scene **r_scene)
199 {
200   struct Main *bmain_undo = NULL;
201   BlendFileData *bfd = BLO_read_from_memfile(bmain,
202                                              BKE_main_blendfile_path(bmain),
203                                              memfile,
204                                              &(const struct BlendFileReadParams){0},
205                                              NULL);
206
207   if (bfd) {
208     bmain_undo = bfd->main;
209     if (r_scene) {
210       *r_scene = bfd->curscene;
211     }
212
213     MEM_freeN(bfd);
214   }
215
216   return bmain_undo;
217 }
218
219 /**
220  * Saves .blend using undo buffer.
221  *
222  * \return success.
223  */
224 bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename)
225 {
226   MemFileChunk *chunk;
227   int file, oflags;
228
229   /* NOTE: This is currently used for autosave and 'quit.blend',
230    * where _not_ following symlinks is OK,
231    * however if this is ever executed explicitly by the user,
232    * we may want to allow writing to symlinks.
233    */
234
235   oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC;
236 #ifdef O_NOFOLLOW
237   /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */
238   oflags |= O_NOFOLLOW;
239 #else
240   /* TODO(sergey): How to deal with symlinks on windows? */
241 #  ifndef _MSC_VER
242 #    warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103"
243 #  endif
244 #endif
245   file = BLI_open(filename, oflags, 0666);
246
247   if (file == -1) {
248     fprintf(stderr,
249             "Unable to save '%s': %s\n",
250             filename,
251             errno ? strerror(errno) : "Unknown error opening file");
252     return false;
253   }
254
255   for (chunk = memfile->chunks.first; chunk; chunk = chunk->next) {
256 #ifdef _WIN32
257     if ((size_t)write(file, chunk->buf, (uint)chunk->size) != chunk->size)
258 #else
259     if ((size_t)write(file, chunk->buf, chunk->size) != chunk->size)
260 #endif
261     {
262       break;
263     }
264   }
265
266   close(file);
267
268   if (chunk) {
269     fprintf(stderr,
270             "Unable to save '%s': %s\n",
271             filename,
272             errno ? strerror(errno) : "Unknown error writing file");
273     return false;
274   }
275   return true;
276 }
277
278 static ssize_t undo_read(FileReader *reader, void *buffer, size_t size)
279 {
280   UndoReader *undo = (UndoReader *)reader;
281
282   static size_t seek = SIZE_MAX; /* The current position. */
283   static size_t offset = 0;      /* Size of previous chunks. */
284   static MemFileChunk *chunk = NULL;
285   size_t chunkoffset, readsize, totread;
286
287   undo->memchunk_identical = true;
288
289   if (size == 0) {
290     return 0;
291   }
292
293   if (seek != (size_t)undo->reader.offset) {
294     chunk = undo->memfile->chunks.first;
295     seek = 0;
296
297     while (chunk) {
298       if (seek + chunk->size > (size_t)undo->reader.offset) {
299         break;
300       }
301       seek += chunk->size;
302       chunk = chunk->next;
303     }
304     offset = seek;
305     seek = (size_t)undo->reader.offset;
306   }
307
308   if (chunk) {
309     totread = 0;
310
311     do {
312       /* First check if it's on the end if current chunk. */
313       if (seek - offset == chunk->size) {
314         offset += chunk->size;
315         chunk = chunk->next;
316       }
317
318       /* Debug, should never happen. */
319       if (chunk == NULL) {
320         printf("illegal read, chunk zero\n");
321         return 0;
322       }
323
324       chunkoffset = seek - offset;
325       readsize = size - totread;
326
327       /* Data can be spread over multiple chunks, so clamp size
328        * to within this chunk, and then it will read further in
329        * the next chunk. */
330       if (chunkoffset + readsize > chunk->size) {
331         readsize = chunk->size - chunkoffset;
332       }
333
334       memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
335       totread += readsize;
336       undo->reader.offset += (off64_t)readsize;
337       seek += readsize;
338
339       /* `is_identical` of current chunk represents whether it changed compared to previous undo
340        * step. this is fine in redo case, but not in undo case, where we need an extra flag
341        * defined when saving the next (future) step after the one we want to restore, as we are
342        * supposed to 'come from' that future undo step, and not the one before current one. */
343       undo->memchunk_identical &= undo->undo_direction == STEP_REDO ? chunk->is_identical :
344                                                                       chunk->is_identical_future;
345     } while (totread < size);
346
347     return (ssize_t)totread;
348   }
349
350   return 0;
351 }
352
353 static void undo_close(FileReader *reader)
354 {
355   MEM_freeN(reader);
356 }
357
358 FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
359 {
360   UndoReader *undo = MEM_callocN(sizeof(UndoReader), __func__);
361
362   undo->memfile = memfile;
363   undo->undo_direction = undo_direction;
364
365   undo->reader.read = undo_read;
366   undo->reader.seek = NULL;
367   undo->reader.close = undo_close;
368
369   return (FileReader *)undo;
370 }