Fluid: Possible fix for T79799
[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
52 /* keep last */
53 #include "BLI_strict_flags.h"
54
55 /* **************** support for memory-write, for undo buffers *************** */
56
57 /* not memfile itself */
58 void BLO_memfile_free(MemFile *memfile)
59 {
60   MemFileChunk *chunk;
61
62   while ((chunk = BLI_pophead(&memfile->chunks))) {
63     if (chunk->is_identical == false) {
64       MEM_freeN((void *)chunk->buf);
65     }
66     MEM_freeN(chunk);
67   }
68   memfile->size = 0;
69 }
70
71 /* to keep list of memfiles consistent, 'first' is always first in list */
72 /* result is that 'first' is being freed */
73 void BLO_memfile_merge(MemFile *first, MemFile *second)
74 {
75   /* We use this mapping to store the memory buffers from second memfile chunks which are not owned
76    * by it (i.e. shared with some previous memory steps). */
77   GHash *buffer_to_second_memchunk = BLI_ghash_new(
78       BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
79
80   /* First, detect all memchunks in second memfile that are not owned by it. */
81   for (MemFileChunk *sc = second->chunks.first; sc != NULL; sc = sc->next) {
82     if (sc->is_identical) {
83       BLI_ghash_insert(buffer_to_second_memchunk, (void *)sc->buf, sc);
84     }
85   }
86
87   /* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
88    * it is also used by the second memfile, transfer the ownership. */
89   for (MemFileChunk *fc = first->chunks.first; fc != NULL; fc = fc->next) {
90     if (!fc->is_identical) {
91       MemFileChunk *sc = BLI_ghash_lookup(buffer_to_second_memchunk, fc->buf);
92       if (sc != NULL) {
93         BLI_assert(sc->is_identical);
94         sc->is_identical = false;
95         fc->is_identical = true;
96       }
97       /* Note that if the second memfile does not use that chunk, we assume that the first one
98        * fully owns it without sharing it with any other memfile, and hence it should be freed with
99        * it. */
100     }
101   }
102
103   BLI_ghash_free(buffer_to_second_memchunk, NULL, NULL);
104
105   BLO_memfile_free(first);
106 }
107
108 /* Clear is_identical_future before adding next memfile. */
109 void BLO_memfile_clear_future(MemFile *memfile)
110 {
111   LISTBASE_FOREACH (MemFileChunk *, chunk, &memfile->chunks) {
112     chunk->is_identical_future = false;
113   }
114 }
115
116 void BLO_memfile_write_init(MemFileWriteData *mem_data,
117                             MemFile *written_memfile,
118                             MemFile *reference_memfile)
119 {
120   mem_data->written_memfile = written_memfile;
121   mem_data->reference_memfile = reference_memfile;
122   mem_data->reference_current_chunk = reference_memfile ? reference_memfile->chunks.first : NULL;
123
124   /* If we have a reference memfile, we generate a mapping between the session_uuid's of the
125    * IDs stored in that previous undo step, and its first matching memchunk. This will allow
126    * us to easily find the existing undo memory storage of IDs even when some re-ordering in
127    * current Main data-base broke the order matching with the memchunks from previous step.
128    */
129   if (reference_memfile != NULL) {
130     mem_data->id_session_uuid_mapping = BLI_ghash_new(
131         BLI_ghashutil_inthash_p_simple, BLI_ghashutil_intcmp, __func__);
132     uint current_session_uuid = MAIN_ID_SESSION_UUID_UNSET;
133     LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &reference_memfile->chunks) {
134       if (!ELEM(mem_chunk->id_session_uuid, MAIN_ID_SESSION_UUID_UNSET, current_session_uuid)) {
135         current_session_uuid = mem_chunk->id_session_uuid;
136         void **entry;
137         if (!BLI_ghash_ensure_p(mem_data->id_session_uuid_mapping,
138                                 POINTER_FROM_UINT(current_session_uuid),
139                                 &entry)) {
140           *entry = mem_chunk;
141         }
142         else {
143           BLI_assert(0);
144         }
145       }
146     }
147   }
148 }
149
150 void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
151 {
152   if (mem_data->id_session_uuid_mapping != NULL) {
153     BLI_ghash_free(mem_data->id_session_uuid_mapping, NULL, NULL);
154   }
155 }
156
157 void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
158 {
159   MemFile *memfile = mem_data->written_memfile;
160   MemFileChunk **compchunk_step = &mem_data->reference_current_chunk;
161
162   MemFileChunk *curchunk = MEM_mallocN(sizeof(MemFileChunk), "MemFileChunk");
163   curchunk->size = size;
164   curchunk->buf = NULL;
165   curchunk->is_identical = false;
166   /* This is unsafe in the sense that an app handler or other code that does not
167    * perform an undo push may make changes after the last undo push that
168    * will then not be undo. Though it's not entirely clear that is wrong behavior. */
169   curchunk->is_identical_future = true;
170   curchunk->id_session_uuid = mem_data->current_id_session_uuid;
171   BLI_addtail(&memfile->chunks, curchunk);
172
173   /* we compare compchunk with buf */
174   if (*compchunk_step != NULL) {
175     MemFileChunk *compchunk = *compchunk_step;
176     if (compchunk->size == curchunk->size) {
177       if (memcmp(compchunk->buf, buf, size) == 0) {
178         curchunk->buf = compchunk->buf;
179         curchunk->is_identical = true;
180         compchunk->is_identical_future = true;
181       }
182     }
183     *compchunk_step = compchunk->next;
184   }
185
186   /* not equal... */
187   if (curchunk->buf == NULL) {
188     char *buf_new = MEM_mallocN(size, "Chunk buffer");
189     memcpy(buf_new, buf, size);
190     curchunk->buf = buf_new;
191     memfile->size += size;
192   }
193 }
194
195 struct Main *BLO_memfile_main_get(struct MemFile *memfile,
196                                   struct Main *bmain,
197                                   struct Scene **r_scene)
198 {
199   struct Main *bmain_undo = NULL;
200   BlendFileData *bfd = BLO_read_from_memfile(bmain,
201                                              BKE_main_blendfile_path(bmain),
202                                              memfile,
203                                              &(const struct BlendFileReadParams){0},
204                                              NULL);
205
206   if (bfd) {
207     bmain_undo = bfd->main;
208     if (r_scene) {
209       *r_scene = bfd->curscene;
210     }
211
212     MEM_freeN(bfd);
213   }
214
215   return bmain_undo;
216 }
217
218 /**
219  * Saves .blend using undo buffer.
220  *
221  * \return success.
222  */
223 bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename)
224 {
225   MemFileChunk *chunk;
226   int file, oflags;
227
228   /* note: This is currently used for autosave and 'quit.blend',
229    * where _not_ following symlinks is OK,
230    * however if this is ever executed explicitly by the user,
231    * we may want to allow writing to symlinks.
232    */
233
234   oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC;
235 #ifdef O_NOFOLLOW
236   /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */
237   oflags |= O_NOFOLLOW;
238 #else
239   /* TODO(sergey): How to deal with symlinks on windows? */
240 #  ifndef _MSC_VER
241 #    warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103"
242 #  endif
243 #endif
244   file = BLI_open(filename, oflags, 0666);
245
246   if (file == -1) {
247     fprintf(stderr,
248             "Unable to save '%s': %s\n",
249             filename,
250             errno ? strerror(errno) : "Unknown error opening file");
251     return false;
252   }
253
254   for (chunk = memfile->chunks.first; chunk; chunk = chunk->next) {
255 #ifdef _WIN32
256     if ((size_t)write(file, chunk->buf, (uint)chunk->size) != chunk->size)
257 #else
258     if ((size_t)write(file, chunk->buf, chunk->size) != chunk->size)
259 #endif
260     {
261       break;
262     }
263   }
264
265   close(file);
266
267   if (chunk) {
268     fprintf(stderr,
269             "Unable to save '%s': %s\n",
270             filename,
271             errno ? strerror(errno) : "Unknown error writing file");
272     return false;
273   }
274   return true;
275 }