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