DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/zipfile.cpp
00001 
00002 #ifndef _GNU_SOURCE
00003 # define _GNU_SOURCE
00004 #endif
00005 
00006 #include <stdint.h>
00007 #include <stdlib.h>
00008 #include <string.h>
00009 #include <stdio.h>
00010 #include <unistd.h>
00011 #include <assert.h>
00012 #include <stdarg.h>
00013 #include <fcntl.h>
00014 #include <sys/types.h>
00015 #include <algorithm> // std::transform
00016 #ifdef WIN32
00017 # include <signal.h>
00018 # include <sys/stat.h>
00019 # include <process.h>
00020 #endif
00021 
00022 #include "dosbox.h"
00023 #include "pic.h"
00024 #include "timer.h"
00025 #include "setup.h"
00026 #include "bios.h"
00027 #include "support.h"
00028 #include "debug.h"
00029 #include "ide.h"
00030 #include "bitop.h"
00031 #include "ptrop.h"
00032 #include "mapper.h"
00033 #include "zipfile.h"
00034 
00035 #include "mapper.h"
00036 #include "vga.h"
00037 #include "keyboard.h"
00038 #include "cpu.h"
00039 #include "fpu.h"
00040 #include "cross.h"
00041 #include "keymap.h"
00042 
00043 bool ZIPFileEntry::rewind(void) {
00044     if (can_write) return false;
00045     return (seek_file(0) == 0);
00046 }
00047 
00048 off_t ZIPFileEntry::seek_file(off_t pos) {
00049     if (file == NULL || file_offset == (off_t)0 || can_write) return (off_t)(-1LL);
00050     if (pos < (off_t)0) pos = (off_t)0;
00051     if (pos > file_length) pos = file_length;
00052     pos = file->seek_file(pos + file_offset) - file_offset;
00053     if (pos < 0 || pos > file_length) return (off_t)(-1LL);
00054     position = pos;
00055     return pos;
00056 }
00057 
00058 int ZIPFileEntry::read(void *buffer,size_t count) {
00059     if (file == NULL || file_offset == (off_t)0) return -1;
00060     if (position >= file_length) return 0;
00061 
00062     size_t mread = file_length - position;
00063     if (mread > count) mread = count;
00064 
00065     if (mread > 0) {
00066         if (seek_file(position) != position) return -1;
00067         mread = file->read(buffer,mread);
00068         if (mread > 0) position += mread;
00069     }
00070 
00071     return mread;
00072 }
00073 
00074 int ZIPFileEntry::write(const void *buffer,size_t count) {
00075     if (file == NULL || file_offset == (off_t)0 || !can_write) return -1;
00076 
00077     /* write stream only, no seeking.
00078      * this code assumes the file pointer will not change anywhere else,
00079      * and always to the end */
00080     if (count > 0) {
00081         count = file->write(buffer,count);
00082         if (count > 0) {
00083             position += count;
00084             write_crc = zipcrc_update(write_crc, buffer, count);
00085             file_length = position;
00086         }
00087     }
00088 
00089     return count;
00090 }
00091 
00092 ZIPFile::ZIPFile() {
00093 }
00094 
00095 ZIPFile::~ZIPFile() {
00096     close();
00097 }
00098 
00099 void ZIPFile::close(void) {
00100     if (file_fd >= 0) {
00101         ::close(file_fd);
00102         file_fd = -1;
00103     }
00104 
00105     entries.clear();
00106 }
00107 
00108 ZIPFileEntry *ZIPFile::get_entry(const char *name) {
00109     if (file_fd < 0) return NULL;
00110 
00111     /* no reading while writing except what is being written */
00112     if (!current_entry.empty() && current_entry != name) return NULL;
00113 
00114     /* no empty names */
00115     if (*name == 0) return NULL;
00116 
00117     auto i = entries.find(name);
00118     if (i == entries.end()) return NULL;
00119 
00120     return &(i->second);
00121 }
00122 
00123 ZIPFileEntry *ZIPFile::new_entry(const char *name) {
00124     if (file_fd < 0 || !can_write || wrote_trailer) return NULL;
00125 
00126     /* cannot make new entries that exist already */
00127     auto i = entries.find(name);
00128     if (i != entries.end()) return NULL;
00129 
00130     /* no empty names */
00131     if (*name == 0) return NULL;
00132 
00133     /* close current entry, if open */
00134     close_current();
00135 
00136     /* begin new entry at end */
00137     current_entry = name;
00138     write_pos = end_of_file();
00139 
00140     ZIPFileEntry *ent = &entries[name];
00141     ent->name = name;
00142     ent->can_write = true;
00143     ent->file_header_offset = write_pos;
00144     write_pos += sizeof(ZIPLocalFileHeader) + ent->name.length();
00145     ent->write_crc = zipcrc_init();
00146     ent->file_offset = write_pos;
00147     ent->file = this;
00148 
00149     if (seek_file(ent->file_header_offset) != ent->file_header_offset) {
00150         close_current();
00151         return NULL;
00152     }
00153 
00154     ZIPLocalFileHeader hdr;
00155     memset(&hdr,0,sizeof(hdr));
00156     hdr.local_file_header_signature = htole32(0x04034b50);  /* PK\x03\x04 */
00157     hdr.version_needed_to_extract = htole16(20);            /* PKZIP 2.0 */
00158     hdr.general_purpose_bit_flag = htole16(0 << 1);
00159     hdr.compression_method = 0;                             /* store (no compression) */
00160     hdr.file_name_length = htole16((uint16_t)ent->name.length());
00161     if (write(&hdr,sizeof(hdr)) != sizeof(hdr)) {
00162         close_current();
00163         return NULL;
00164     }
00165     assert(ent->name.length() != 0);
00166     if ((size_t)write(ent->name.c_str(),ent->name.length()) != ent->name.length()) {
00167         close_current();
00168         return NULL;
00169     }
00170     if (seek_file(ent->file_offset) != ent->file_offset) {
00171         close_current();
00172         return NULL;
00173     }
00174 
00175     return ent;
00176 }
00177 
00178 off_t ZIPFile::end_of_file(void) {
00179     return lseek(file_fd,0,SEEK_END);
00180 }
00181 
00182 void ZIPFile::close_current(void) {
00183     if (!can_write) return;
00184 
00185     if (!current_entry.empty()) {
00186         ZIPFileEntry *ent = get_entry(current_entry.c_str());
00187         ZIPLocalFileHeader hdr;
00188 
00189         if (ent != NULL && ent->can_write) {
00190             ent->can_write = false;
00191 
00192             if (seek_file(ent->file_header_offset) == ent->file_header_offset && read(&hdr,sizeof(hdr)) == sizeof(hdr)) {
00193                 hdr.compressed_size = hdr.uncompressed_size = htole32(((uint32_t)ent->file_length));
00194                 hdr.crc_32 = htole32(zipcrc_finalize(ent->write_crc));
00195 
00196                 if (seek_file(ent->file_header_offset) == ent->file_header_offset && write(&hdr,sizeof(hdr)) == sizeof(hdr)) {
00197                     /* good */
00198                 }
00199             }
00200         }
00201     }
00202 
00203     current_entry.clear();
00204 }
00205 
00206 int ZIPFile::open(const char *path,int mode) {
00207     unsigned char tmp[512];
00208 
00209     close();
00210 
00211     if (path == NULL) return -1;
00212 
00213     if ((mode & 3) == O_WRONLY) {
00214         LOG_MSG("WARNING: ZIPFile attempt to open with O_WRONLY, which will not work");
00215         return -1;
00216     }
00217 
00218 #if defined(O_BINARY)
00219     mode |= O_BINARY;
00220 #endif
00221 
00222     file_fd = ::open(path,mode,0644);
00223     if (file_fd < 0) return -1;
00224     if (lseek(file_fd,0,SEEK_SET) != 0) {
00225         close();
00226         return -1;
00227     }
00228 
00229     entries.clear();
00230     current_entry.clear();
00231     wrote_trailer = false;
00232     write_pos = 0;
00233 
00234     /* WARNING: This assumes O_RDONLY, O_WRONLY, O_RDWR are defined as in Linux (0, 1, 2) in the low two bits */
00235     if ((mode & 3) == O_RDWR)
00236         can_write = true;
00237     else
00238         can_write = false;
00239 
00240     /* if we're supposed to READ the ZIP file, then start scanning now */
00241     if ((mode & 3) == O_RDONLY) {
00242         struct pkzip_central_directory_header_main chdr;
00243         struct pkzip_central_directory_header_end ehdr;
00244 
00245         off_t fsz = end_of_file();
00246 
00247         /* check for 'PK' at the start of the file.
00248          * This code only expects to handle the ZIP files it generated, not ZIP files in general. */
00249         if (fsz < 64 || seek_file(0) != 0 || read(tmp,4) != 4 || memcmp(tmp,"PK\x03\x04",4) != 0) {
00250             LOG_MSG("Not a PKZIP file");
00251             close();
00252             return -1;
00253         }
00254 
00255         /* then look for the central directory at the end.
00256          * this code DOES NOT SUPPORT the ZIP comment field, nor will this code generate one. */
00257         if (seek_file(fsz - (off_t)sizeof(ehdr)) != (fsz - (off_t)sizeof(ehdr)) || (size_t)read(&ehdr,sizeof(ehdr)) != sizeof(ehdr) || ehdr.sig != PKZIP_CENTRAL_DIRECTORY_END_SIG || ehdr.size_of_central_directory > 0x100000u/*absurd size*/ || ehdr.offset_of_central_directory_from_start_disk == 0 || (off_t)ehdr.offset_of_central_directory_from_start_disk >= fsz) {
00258             LOG_MSG("Cannot locate Central Directory");
00259             close();
00260             return -1;
00261         }
00262         if (seek_file(ehdr.offset_of_central_directory_from_start_disk) != ehdr.offset_of_central_directory_from_start_disk) {
00263             LOG_MSG("Cannot locate Central Directory #2");
00264             close();
00265             return -1;
00266         }
00267 
00268         /* read the central directory */
00269         {
00270             long remain = (long)ehdr.size_of_central_directory;
00271 
00272             while (remain >= (long)sizeof(struct pkzip_central_directory_header_main)) {
00273                 if (read(&chdr,sizeof(chdr)) != sizeof(chdr)) break;
00274                 remain -= sizeof(chdr);
00275 
00276                 if (chdr.sig != PKZIP_CENTRAL_DIRECTORY_HEADER_SIG) break;
00277                 if (chdr.filename_length >= sizeof(tmp)) break;
00278 
00279                 tmp[chdr.filename_length] = 0;
00280                 if (chdr.filename_length != 0) {
00281                     if (read(tmp,chdr.filename_length) != chdr.filename_length) break;
00282                     remain -= chdr.filename_length;
00283                 }
00284 
00285                 if (tmp[0] == 0) continue;
00286 
00287                 ZIPFileEntry *ent = &entries[(char*)tmp];
00288                 ent->can_write = false;
00289                 ent->file_length = htole32(chdr.uncompressed_size);
00290                 ent->file_header_offset = htole32(chdr.relative_offset_of_local_header);
00291                 ent->file_offset = ent->file_header_offset + sizeof(struct ZIPLocalFileHeader) + htole16(chdr.filename_length) + htole16(chdr.extra_field_length);
00292                 ent->position = 0;
00293                 ent->name = (char*)tmp;
00294                 ent->file = this;
00295             }
00296         }
00297     }
00298 
00299     return 0;
00300 }
00301 
00302 off_t ZIPFile::seek_file(off_t pos) {
00303     if (file_fd < 0) return (off_t)(-1LL);
00304     return ::lseek(file_fd,pos,SEEK_SET);
00305 }
00306 
00307 int ZIPFile::read(void *buffer,size_t count) {
00308     if (file_fd < 0) return -1;
00309     return ::read(file_fd,buffer,count);
00310 }
00311 
00312 int ZIPFile::write(const void *buffer,size_t count) {
00313     if (file_fd < 0) return -1;
00314     return ::write(file_fd,buffer,count);
00315 }
00316 
00317 void ZIPFile::writeZIPFooter(void) {
00318     struct pkzip_central_directory_header_main chdr;
00319     struct pkzip_central_directory_header_end ehdr;
00320     uint32_t cdircount = 0;
00321     uint32_t cdirbytes = 0;
00322     off_t cdirofs = 0;
00323 
00324     if (file_fd < 0 || wrote_trailer || !can_write) return;
00325 
00326     close_current();
00327     cdirofs = end_of_file();
00328 
00329     for (auto i=entries.begin();i!=entries.end();i++) {
00330         const ZIPFileEntry &ent = i->second;
00331 
00332         memset(&chdr,0,sizeof(chdr));
00333         chdr.sig = htole32(PKZIP_CENTRAL_DIRECTORY_HEADER_SIG);
00334         chdr.version_made_by = htole16((0 << 8) + 20);      /* PKZIP 2.0 */
00335         chdr.version_needed_to_extract = htole16(20);       /* PKZIP 2.0 or higher */
00336         chdr.general_purpose_bit_flag = htole16(0 << 1);    /* just lie and say that "normal" deflate was used */
00337         chdr.compression_method = 0;                        /* stored (no compression) */
00338         chdr.last_mod_file_time = 0;
00339         chdr.last_mod_file_date = 0;
00340         chdr.compressed_size = htole32(((uint32_t)ent.file_length));
00341         chdr.uncompressed_size = htole32(((uint32_t)ent.file_length));
00342         chdr.filename_length = htole16((uint16_t)ent.name.length());
00343         chdr.disk_number_start = htole16(1u);
00344         chdr.internal_file_attributes = 0;
00345         chdr.external_file_attributes = 0;
00346         chdr.relative_offset_of_local_header = htole32(ent.file_header_offset);
00347         chdr.crc32 = htole32(zipcrc_finalize(ent.write_crc));
00348 
00349         if (write(&chdr,sizeof(chdr)) != sizeof(chdr)) break;
00350         cdirbytes += sizeof(chdr);
00351         cdircount++;
00352 
00353         assert(ent.name.length() != 0);
00354         if ((size_t)write(ent.name.c_str(),ent.name.length()) != ent.name.length()) break;
00355         cdirbytes += ent.name.length();
00356     }
00357 
00358     memset(&ehdr,0,sizeof(ehdr));
00359     ehdr.sig = htole32(PKZIP_CENTRAL_DIRECTORY_END_SIG);
00360     ehdr.number_of_disk_with_start_of_central_directory = htole16(0);
00361     ehdr.number_of_this_disk = htole16(0);
00362     ehdr.total_number_of_entries_of_central_dir_on_this_disk = htole16(cdircount);
00363     ehdr.total_number_of_entries_of_central_dir = htole16(cdircount);
00364     ehdr.size_of_central_directory = htole32(cdirbytes);
00365     ehdr.offset_of_central_directory_from_start_disk = htole32(cdirofs);
00366     write(&ehdr,sizeof(ehdr));
00367 
00368     wrote_trailer = true;
00369     current_entry.clear();
00370 }
00371 
00372 // MOVE
00373 static std::string zip_nv_pair_empty;
00374 
00375 zip_nv_pair_map::zip_nv_pair_map() {
00376 }
00377 
00378 zip_nv_pair_map::zip_nv_pair_map(ZIPFileEntry &ent) {
00379     read_nv_pairs(ent);
00380 }
00381 
00382 std::string &zip_nv_pair_map::get(const char *name) {
00383     auto i = find(name);
00384     if (i != end()) return i->second;
00385     return zip_nv_pair_empty;
00386 }
00387 
00388 bool zip_nv_pair_map::get_bool(const char *name) {
00389     std::string &val = get(name);
00390     return (strtol(val.c_str(),NULL,0) > 0);
00391 }
00392 
00393 long zip_nv_pair_map::get_long(const char *name) {
00394     std::string &val = get(name);
00395     return strtol(val.c_str(),NULL,0);
00396 }
00397 
00398 unsigned long zip_nv_pair_map::get_ulong(const char *name) {
00399     std::string &val = get(name);
00400     return strtoul(val.c_str(),NULL,0);
00401 }
00402 
00403 void zip_nv_pair_map::process_line(char *line/*will modify, assume caller has put NUL at the end*/) {
00404     char *equ = strchr(line,'=');
00405     if (equ == NULL) return;
00406     *equ++ = 0; /* overwite '=' with NUL, split name vs value */
00407 
00408     /* no null names */
00409     if (*line == 0) return;
00410 
00411     (*this)[line] = equ;
00412 }
00413 
00414 void zip_nv_pair_map::read_nv_pairs(ZIPFileEntry &ent) {
00415     char tmp[1024],*r,*f;
00416     char line[1024],*w,*wf=line+sizeof(line)-1;
00417     char c;
00418     int l;
00419 
00420     clear();
00421     ent.rewind();
00422 
00423     w = line;
00424     while ((l=ent.read(tmp,sizeof(tmp))) > 0) {
00425         r = tmp;
00426         f = tmp + l;
00427 
00428         while (r < f) {
00429             c = *r++;
00430 
00431             if (c == '\n') {
00432                 assert(w <= wf);
00433                 *w = 0;
00434                 process_line(line);
00435                 w = line;
00436             }
00437             else if (c == '\r') {
00438                 /* ignore */
00439             }
00440             else if (w < wf) {
00441                 *w++ = c;
00442             }
00443         }
00444     }
00445 
00446     if (w != line) {
00447         assert(w <= wf);
00448         *w = 0;
00449         process_line(line);
00450         w = line;
00451     }
00452 }
00453 
00454 static char zip_nv_tmp[1024];
00455 
00456 void zip_nv_write(ZIPFileEntry &ent,const char *name,bool val) {
00457     size_t l;
00458 
00459     if ((l = ((size_t)snprintf(zip_nv_tmp,sizeof(zip_nv_tmp),"%s=%d\n",name,val?1:0))) >= (sizeof(zip_nv_tmp)-1u))
00460         E_Exit("zip_nv_write buffer overrun (result too long)");
00461 
00462     ent.write(zip_nv_tmp,l);
00463 }
00464 
00465 void zip_nv_write(ZIPFileEntry &ent,const char *name,long val) {
00466     size_t l;
00467 
00468     if ((l = ((size_t)snprintf(zip_nv_tmp,sizeof(zip_nv_tmp),"%s=%ld\n",name,val))) >= (sizeof(zip_nv_tmp)-1u))
00469         E_Exit("zip_nv_write buffer overrun (result too long)");
00470 
00471     ent.write(zip_nv_tmp,l);
00472 }
00473 
00474 void zip_nv_write_hex(ZIPFileEntry &ent,const char *name,unsigned long val) {
00475     size_t l;
00476 
00477     if ((l = ((size_t)snprintf(zip_nv_tmp,sizeof(zip_nv_tmp),"%s=0x%lx\n",name,val))) >= (sizeof(zip_nv_tmp)-1u))
00478         E_Exit("zip_nv_write buffer overrun (result too long)");
00479 
00480     ent.write(zip_nv_tmp,l);
00481 }
00482