1 ////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2008-2017 by Alexander Galanin //
3 // al@galanin.nnov.ru //
4 // http://galanin.nnov.ru/~al //
5 // //
6 // This program is free software: you can redistribute it and/or modify //
7 // it under the terms of the GNU General Public License as published by //
8 // the Free Software Foundation, either version 3 of the License, or //
9 // (at your option) any later version. //
10 // //
11 // This program is distributed in the hope that it will be useful, //
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
14 // GNU General Public License for more details. //
15 // //
16 // You should have received a copy of the GNU General Public License //
17 // along with this program. If not, see <https://www.gnu.org/licenses/>.//
18 ////////////////////////////////////////////////////////////////////////////
19
20 #define __STDC_LIMIT_MACROS
21
22 #include <cerrno>
23 #include <climits>
24 #include <ctime>
25 #include <cstdlib>
26 #include <stdint.h>
27 #include <cstring>
28 #include <stdexcept>
29 #include <syslog.h>
30 #include <cassert>
31
32 #include "fileNode.h"
33 #include "extraField.h"
34
35 const zip_int64_t FileNode::ROOT_NODE_INDEX = -1;
36 const zip_int64_t FileNode::NEW_NODE_INDEX = -2;
37
FileNode(struct zip * zip,const char * fname,zip_int64_t _id)38 FileNode::FileNode(struct zip *zip, const char *fname, zip_int64_t _id) {
39 this->zip = zip;
40 metadataChanged = false;
41 full_name = fname;
42 id = _id;
43 m_uid = 0;
44 m_gid = 0;
45 }
46
createFile(struct zip * zip,const char * fname,uid_t owner,gid_t group,mode_t mode)47 FileNode *FileNode::createFile (struct zip *zip, const char *fname,
48 uid_t owner, gid_t group, mode_t mode) {
49 FileNode *n = new FileNode(zip, fname, NEW_NODE_INDEX);
50 if (n == NULL) {
51 return NULL;
52 }
53 n->state = NEW;
54 n->is_dir = false;
55 n->buffer = new BigBuffer();
56 if (!n->buffer) {
57 delete n;
58 return NULL;
59 }
60 n->has_cretime = true;
61 n->m_mtime = n->m_atime = n->m_ctime = n->cretime = time(NULL);
62
63 n->parse_name();
64 n->m_mode = mode;
65 n->m_uid = owner;
66 n->m_gid = group;
67
68 return n;
69 }
70
createSymlink(struct zip * zip,const char * fname)71 FileNode *FileNode::createSymlink(struct zip *zip, const char *fname) {
72 FileNode *n = new FileNode(zip, fname, NEW_NODE_INDEX);
73 if (n == NULL) {
74 return NULL;
75 }
76 n->state = NEW;
77 n->is_dir = false;
78 n->buffer = new BigBuffer();
79 if (!n->buffer) {
80 delete n;
81 return NULL;
82 }
83 n->has_cretime = true;
84 n->m_mtime = n->m_atime = n->m_ctime = n->cretime = time(NULL);
85
86 n->parse_name();
87 n->m_mode = S_IFLNK | 0777;
88
89 return n;
90 }
91
92 /**
93 * Create intermediate directory to build full tree
94 */
createIntermediateDir(struct zip * zip,const char * fname)95 FileNode *FileNode::createIntermediateDir(struct zip *zip,
96 const char *fname) {
97 FileNode *n = new FileNode(zip, fname, NEW_NODE_INDEX);
98 if (n == NULL) {
99 return NULL;
100 }
101 n->state = NEW_DIR;
102 n->is_dir = true;
103 n->has_cretime = true;
104 n->m_mtime = n->m_atime = n->m_ctime = n->cretime = time(NULL);
105 n->m_size = 0;
106 n->m_mode = S_IFDIR | 0775;
107
108 n->parse_name();
109
110 return n;
111 }
112
createDir(struct zip * zip,const char * fname,zip_int64_t id,uid_t owner,gid_t group,mode_t mode)113 FileNode *FileNode::createDir(struct zip *zip, const char *fname,
114 zip_int64_t id, uid_t owner, gid_t group, mode_t mode) {
115 FileNode *n = createNodeForZipEntry(zip, fname, id);
116 if (n == NULL) {
117 return NULL;
118 }
119 n->state = CLOSED;
120 n->has_cretime = true;
121 n->cretime = n->m_mtime;
122 // FUSE does not pass S_IFDIR bit here
123 n->m_mode = S_IFDIR | mode;
124 n->m_uid = owner;
125 n->m_gid = group;
126 n->is_dir = true;
127 return n;
128 }
129
createRootNode()130 FileNode *FileNode::createRootNode() {
131 FileNode *n = new FileNode(NULL, "", ROOT_NODE_INDEX);
132 if (n == NULL) {
133 return NULL;
134 }
135 n->is_dir = true;
136 n->state = NEW_DIR;
137 n->m_mtime = n->m_atime = n->m_ctime = n->cretime = time(NULL);
138 n->has_cretime = true;
139 n->m_size = 0;
140 n->name = n->full_name.c_str();
141 n->m_mode = S_IFDIR | 0775;
142 return n;
143 }
144
createNodeForZipEntry(struct zip * zip,const char * fname,zip_int64_t id)145 FileNode *FileNode::createNodeForZipEntry(struct zip *zip,
146 const char *fname, zip_int64_t id) {
147 FileNode *n = new FileNode(zip, fname, id);
148 if (n == NULL) {
149 return NULL;
150 }
151 n->is_dir = false;
152 n->open_count = 0;
153 n->state = CLOSED;
154
155 struct zip_stat stat;
156 zip_stat_index(zip, id, 0, &stat);
157 // check that all used fields are valid
158 zip_uint64_t needValid = ZIP_STAT_NAME | ZIP_STAT_INDEX |
159 ZIP_STAT_SIZE | ZIP_STAT_MTIME;
160 // required fields are always valid for existing items or newly added
161 // directories (see zip_stat_index.c from libzip)
162 assert((stat.valid & needValid) == needValid);
163 n->m_mtime = n->m_atime = n->m_ctime = stat.mtime;
164 n->has_cretime = false;
165 n->m_size = stat.size;
166
167 n->parse_name();
168
169 n->processExternalAttributes();
170 n->processExtraFields();
171 return n;
172 }
173
~FileNode()174 FileNode::~FileNode() {
175 if (state == OPENED || state == CHANGED || state == NEW) {
176 delete buffer;
177 }
178 }
179
180 /**
181 * Get short name of a file. If last char is '/' then node is a directory
182 */
parse_name()183 void FileNode::parse_name() {
184 assert(!full_name.empty());
185
186 const char *lsl = full_name.c_str();
187 while (*lsl++) {}
188 lsl--;
189 while (lsl > full_name.c_str() && *lsl != '/') {
190 lsl--;
191 }
192 // If the last symbol in file name is '/' then it is a directory
193 if (*lsl == '/' && *(lsl+1) == '\0') {
194 // It will produce two \0s at the end of file name. I think that
195 // it is not a problem
196 full_name[full_name.size() - 1] = 0;
197 this->is_dir = true;
198 while (lsl > full_name.c_str() && *lsl != '/') {
199 lsl--;
200 }
201 }
202 // Setting short name of node
203 if (*lsl == '/') {
204 lsl++;
205 }
206 this->name = lsl;
207 }
208
appendChild(FileNode * child)209 void FileNode::appendChild (FileNode *child) {
210 childs.push_back (child);
211 }
212
detachChild(FileNode * child)213 void FileNode::detachChild (FileNode *child) {
214 childs.remove (child);
215 }
216
rename(const char * new_name)217 void FileNode::rename(const char *new_name) {
218 full_name = new_name;
219 parse_name();
220 }
221
open()222 int FileNode::open() {
223 if (state == NEW) {
224 return 0;
225 }
226 if (state == OPENED) {
227 if (open_count == INT_MAX) {
228 return -EMFILE;
229 } else {
230 ++open_count;
231 }
232 }
233 if (state == CLOSED) {
234 open_count = 1;
235 try {
236 assert (zip != NULL);
237 buffer = new BigBuffer(zip, id, m_size);
238 state = OPENED;
239 }
240 catch (std::bad_alloc) {
241 return -ENOMEM;
242 }
243 catch (std::exception) {
244 return -EIO;
245 }
246 }
247 return 0;
248 }
249
read(char * buf,size_t sz,zip_uint64_t offset)250 int FileNode::read(char *buf, size_t sz, zip_uint64_t offset) {
251 m_atime = time(NULL);
252 return buffer->read(buf, sz, offset);
253 }
254
write(const char * buf,size_t sz,zip_uint64_t offset)255 int FileNode::write(const char *buf, size_t sz, zip_uint64_t offset) {
256 if (state == OPENED) {
257 state = CHANGED;
258 }
259 m_mtime = time(NULL);
260 metadataChanged = true;
261 return buffer->write(buf, sz, offset);
262 }
263
close()264 int FileNode::close() {
265 m_size = buffer->len;
266 if (state == OPENED && --open_count == 0) {
267 delete buffer;
268 state = CLOSED;
269 }
270 return 0;
271 }
272
save()273 int FileNode::save() {
274 assert (!is_dir);
275 // index is modified if state == NEW
276 assert (zip != NULL);
277 return buffer->saveToZip(m_mtime, zip, full_name.c_str(),
278 state == NEW, id);
279 }
280
saveMetadata() const281 int FileNode::saveMetadata() const {
282 assert(id >= 0);
283 int res = updateExtraFields();
284 if (res != 0)
285 return res;
286 return updateExternalAttributes();
287 }
288
truncate(zip_uint64_t offset)289 int FileNode::truncate(zip_uint64_t offset) {
290 if (state != CLOSED) {
291 if (state != NEW) {
292 state = CHANGED;
293 }
294 try {
295 buffer->truncate(offset);
296 return 0;
297 }
298 catch (const std::bad_alloc &) {
299 return EIO;
300 }
301 m_mtime = time(NULL);
302 metadataChanged = true;
303 } else {
304 return EBADF;
305 }
306 }
307
size() const308 zip_uint64_t FileNode::size() const {
309 if (state == NEW || state == OPENED || state == CHANGED) {
310 return buffer->len;
311 } else {
312 return m_size;
313 }
314 }
315
316 /**
317 * Get file mode from external attributes.
318 */
processExternalAttributes()319 void FileNode::processExternalAttributes () {
320 zip_uint8_t opsys;
321 zip_uint32_t attr;
322 assert(id >= 0);
323 assert (zip != NULL);
324 zip_file_get_external_attributes(zip, id, 0, &opsys, &attr);
325 switch (opsys) {
326 case ZIP_OPSYS_UNIX: {
327 m_mode = attr >> 16;
328 // force is_dir value
329 if (is_dir) {
330 m_mode = (m_mode & ~S_IFMT) | S_IFDIR;
331 } else {
332 m_mode = m_mode & ~S_IFDIR;
333 }
334 break;
335 }
336 case ZIP_OPSYS_DOS:
337 case ZIP_OPSYS_WINDOWS_NTFS:
338 case ZIP_OPSYS_MVS: {
339 /*
340 * Both WINDOWS_NTFS and OPSYS_MVS used here because of
341 * difference in constant assignment by PKWARE and Info-ZIP
342 */
343 m_mode = 0444;
344 // http://msdn.microsoft.com/en-us/library/windows/desktop/gg258117%28v=vs.85%29.aspx
345 // http://en.wikipedia.org/wiki/File_Allocation_Table#attributes
346 // FILE_ATTRIBUTE_READONLY
347 if ((attr & 1) == 0) {
348 m_mode |= 0220;
349 }
350 // directory
351 if (is_dir) {
352 m_mode |= S_IFDIR | 0111;
353 } else {
354 m_mode |= S_IFREG;
355 }
356
357 break;
358 }
359 default: {
360 if (is_dir) {
361 m_mode = S_IFDIR | 0775;
362 } else {
363 m_mode = S_IFREG | 0664;
364 }
365 }
366 }
367 }
368
369 /**
370 * Get timestamp information from extra fields.
371 * Get owner and group information.
372 */
processExtraFields()373 void FileNode::processExtraFields () {
374 zip_int16_t count;
375 // times from timestamp have precedence
376 bool mtimeFromTimestamp = false, atimeFromTimestamp = false;
377 // UIDs and GIDs from UNIX extra fields with bigger type IDs have
378 // precedence
379 int lastProcessedUnixField = 0;
380
381 assert (id >= 0);
382 assert (zip != NULL);
383 count = zip_file_extra_fields_count (zip, id, ZIP_FL_LOCAL);
384 for (zip_int16_t i = 0; i < count; ++i) {
385 bool has_mtime, has_atime, has_cretime;
386 time_t mt, at, cret;
387 zip_uint16_t type, len;
388 const zip_uint8_t *field = zip_file_extra_field_get (zip,
389 id, i, &type, &len, ZIP_FL_LOCAL);
390
391 switch (type) {
392 case FZ_EF_TIMESTAMP: {
393 if (ExtraField::parseExtTimeStamp (len, field, has_mtime, mt,
394 has_atime, at, has_cretime, cret)) {
395 if (has_mtime) {
396 m_mtime = mt;
397 mtimeFromTimestamp = true;
398 }
399 if (has_atime) {
400 m_atime = at;
401 atimeFromTimestamp = true;
402 }
403 if (has_cretime) {
404 cretime = cret;
405 this->has_cretime = true;
406 }
407 }
408 break;
409 }
410 case FZ_EF_PKWARE_UNIX:
411 case FZ_EF_INFOZIP_UNIX1:
412 case FZ_EF_INFOZIP_UNIX2:
413 case FZ_EF_INFOZIP_UNIXN: {
414 uid_t uid;
415 gid_t gid;
416 if (ExtraField::parseSimpleUnixField (type, len, field,
417 uid, gid, has_mtime, mt, has_atime, at)) {
418 if (type >= lastProcessedUnixField) {
419 m_uid = uid;
420 m_gid = gid;
421 lastProcessedUnixField = type;
422 }
423 if (has_mtime && !mtimeFromTimestamp) {
424 m_mtime = mt;
425 }
426 if (has_atime && !atimeFromTimestamp) {
427 m_atime = at;
428 }
429 }
430 break;
431 }
432 }
433 }
434 }
435
436 /**
437 * Save timestamp into extra fields
438 * @return 0 on success
439 */
updateExtraFields() const440 int FileNode::updateExtraFields () const {
441 static zip_flags_t locations[] = {ZIP_FL_CENTRAL, ZIP_FL_LOCAL};
442
443 assert (id >= 0);
444 assert (zip != NULL);
445 for (unsigned int loc = 0; loc < sizeof(locations) / sizeof(locations[0]); ++loc) {
446 // remove old extra fields
447 zip_int16_t count = zip_file_extra_fields_count (zip, id,
448 locations[loc]);
449 const zip_uint8_t *field;
450 for (zip_int16_t i = count; i >= 0; --i) {
451 zip_uint16_t type;
452 field = zip_file_extra_field_get (zip, id, i, &type,
453 NULL, locations[loc]);
454 // FZ_EF_PKWARE_UNIX not removed because can contain extra data
455 // that currently not handled by fuse-zip
456 if (field != NULL && (type == FZ_EF_TIMESTAMP ||
457 type == FZ_EF_INFOZIP_UNIX1 ||
458 type == FZ_EF_INFOZIP_UNIX2 ||
459 type == FZ_EF_INFOZIP_UNIXN)) {
460 zip_file_extra_field_delete (zip, id, i,
461 locations[loc]);
462 }
463 }
464 // add new extra fields
465 zip_uint16_t len;
466 int res;
467 // add timestamps
468 field = ExtraField::createExtTimeStamp (locations[loc], m_mtime,
469 m_atime, has_cretime, cretime, len);
470 res = zip_file_extra_field_set (zip, id, FZ_EF_TIMESTAMP,
471 ZIP_EXTRA_FIELD_NEW, field, len, locations[loc]);
472 if (res != 0) {
473 return res;
474 }
475 // add UNIX owner info
476 field = ExtraField::createInfoZipNewUnixField (m_uid, m_gid, len);
477 res = zip_file_extra_field_set (zip, id, FZ_EF_INFOZIP_UNIXN,
478 ZIP_EXTRA_FIELD_NEW, field, len, locations[loc]);
479 if (res != 0) {
480 return res;
481 }
482 }
483 return 0;
484 }
485
chmod(mode_t mode)486 void FileNode::chmod (mode_t mode) {
487 m_mode = (m_mode & S_IFMT) | mode;
488 m_ctime = time(NULL);
489 metadataChanged = true;
490 }
491
setUid(uid_t uid)492 void FileNode::setUid (uid_t uid) {
493 m_uid = uid;
494 metadataChanged = true;
495 }
496
setGid(gid_t gid)497 void FileNode::setGid (gid_t gid) {
498 m_gid = gid;
499 metadataChanged = true;
500 }
501
502 /**
503 * Save OS type and permissions into external attributes
504 * @return 0 on success or libzip error code (ZIP_ER_MEMORY or ZIP_ER_RDONLY)
505 */
updateExternalAttributes() const506 int FileNode::updateExternalAttributes() const {
507 assert(id >= 0);
508 assert (zip != NULL);
509 // save UNIX attributes in high word
510 mode_t mode = m_mode << 16;
511
512 // save DOS attributes in low byte
513 // http://msdn.microsoft.com/en-us/library/windows/desktop/gg258117%28v=vs.85%29.aspx
514 // http://en.wikipedia.org/wiki/File_Allocation_Table#attributes
515 if (is_dir) {
516 // FILE_ATTRIBUTE_DIRECTORY
517 mode |= 0x10;
518 }
519 if (name[0] == '.') {
520 // FILE_ATTRIBUTE_HIDDEN
521 mode |= 2;
522 }
523 if (!(mode & S_IWUSR)) {
524 // FILE_ATTRIBUTE_READONLY
525 mode |= 1;
526 }
527 return zip_file_set_external_attributes (zip, id, 0,
528 ZIP_OPSYS_UNIX, mode);
529 }
530
setTimes(time_t atime,time_t mtime)531 void FileNode::setTimes (time_t atime, time_t mtime) {
532 m_atime = atime;
533 m_mtime = mtime;
534 metadataChanged = true;
535 }
536
setCTime(time_t ctime)537 void FileNode::setCTime (time_t ctime) {
538 m_ctime = ctime;
539 metadataChanged = true;
540 }
541