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