relative to cab_C_02. This one adds most of the remaining infrastructure ... and still does not implement actual decompression. Probably, it's best not to apply this until C_04, which will hopefully finish all of the FDI API's except the undocumented Truncate. License: LGPL (sorry) ChangeLog: * dlls/cabinet: fdi.c: Greg Turner <gmturner007@ameritech.net> - most of FDICopy is now implemented, although the actual decompression is not. - "can" -> "do" - a novella about a bug - fix some memory leaks -- diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/dlls/cabinet/fdi.c ./dlls/cabinet/fdi.c --- ../wine/dlls/cabinet/fdi.c 2003-06-09 02:32:40.000000000 -0500 +++ ./dlls/cabinet/fdi.c 2003-06-09 02:28:48.000000000 -0500 @@ -34,6 +34,7 @@ #include "winbase.h" #include "winerror.h" #include "stdio.h" +#include "msvcrt/fcntl.h" /* _O_.* */ #include "fdi.h" #include "cabinet.h" @@ -42,6 +43,62 @@ WINE_DEFAULT_DEBUG_CHANNEL(cabinet); +struct fdi_cab { + struct fdi_cab *next; /* for making a list of cabinets */ + LPCSTR filename; /* input name of cabinet */ + int *fh; /* open file handle or NULL */ + cab_off_t filelen; /* length of cabinet file */ + struct fdi_cab *prevcab, *nextcab; /* multipart cabinet chains */ + char *prevname, *nextname; /* and their filenames */ + char *previnfo, *nextinfo; /* and their visible names */ + struct fdi_folder *folders; /* first folder in this cabinet */ + struct fdi_file *files; /* first file in this cabinet */ + cab_UBYTE block_resv; /* reserved space in datablocks */ + cab_UBYTE flags; /* header flags */ +}; + +struct fdi_file { + struct fdi_file *next; /* next file in sequence */ + struct fdi_folder *folder; /* folder that contains this file */ + LPCSTR filename; /* output name of file */ + int fh; /* open file handle or NULL */ + cab_ULONG length; /* uncompressed length of file */ + cab_ULONG offset; /* uncompressed offset in folder */ + cab_UWORD index; /* magic index number of folder */ + cab_UWORD time, date, attribs; /* MS-DOS time/date/attributes */ +}; + +struct fdi_folder { + struct fdi_folder *next; + struct fdi_cab *cab[CAB_SPLITMAX]; /* cabinet(s) this folder spans */ + cab_off_t offset[CAB_SPLITMAX]; /* offset to data blocks (32 bit) */ + cab_UWORD comp_type; /* compression format/window size */ + cab_ULONG comp_size; /* compressed size of folder */ + cab_UBYTE num_splits; /* number of split blocks + 1 */ + cab_UWORD num_blocks; /* total number of blocks */ + struct fdi_file *contfile; /* the first split file */ +}; + +/* + * ugh, well, this ended up being pretty damn silly... + * now that I've conceeded to build equivalent structures to struct cab.*, + * I should have just used those, or, better yet, unified the two... sue me. + * (Note to Microsoft: That's a joke. Please /don't/ actually sue me! -gmt). + * Nevertheless, I've come this far, it works, so I'm not gonna change it + * for now. + */ + +/* + * this structure fills the gaps between what is available in a PFDICABINETINFO + * vs what is needed by FDICopy. Memory allocated for these becomes the responsibility + * of the caller to free. Yes, I am aware that this is totally, utterly inelegant. + */ +typedef struct { + char *prevname, *previnfo; + char *nextname, *nextinfo; + int folder_resv, header_resv; +} MORE_ISCAB_INFO, *PMORE_ISCAB_INFO; + /*********************************************************************** * FDICreate (CABINET.20) */ @@ -66,7 +123,7 @@ /* PONDERME: Certainly, we cannot tolerate a missing pfnalloc, as we call it just below. pfnfree is tested as well, for symmetry. As for the rest, should we test these too? In a vacuum, I would say yes... but does Windows care? If not, then, I guess, - neither can we.... */ + neither do we.... */ if ((!pfnalloc) || (!pfnfree)) { perf->erfOper = FDIERROR_NONE; perf->erfType = ERROR_BAD_ARGUMENTS; @@ -187,20 +244,27 @@ * * process the cabinet header in the style of FDIIsCabinet, but * without the sanity checks (and bug) + * + * if pmii is non-null, some info not expressed in FDICABINETINFO struct + * will be stored there... responsibility to free the enclosed stuff is + * delegated to the caller in this case. */ BOOL FDI_read_entries( - HFDI hfdi, - INT_PTR hf, - PFDICABINETINFO pfdici) + HFDI hfdi, + INT_PTR hf, + PFDICABINETINFO pfdici, + PMORE_ISCAB_INFO pmii) { int num_folders, num_files, header_resv, folder_resv = 0; LONG base_offset, cabsize; USHORT setid, cabidx, flags; cab_UBYTE buf[64], block_resv; - char *prevname, *previnfo, *nextname, *nextinfo; + char *prevname = NULL, *previnfo = NULL, *nextname = NULL, *nextinfo = NULL; TRACE("(hfdi == ^%p, hf == %d, pfdici == ^%p)\n", hfdi, hf, pfdici); + if (pmii) ZeroMemory(pmii, sizeof(MORE_ISCAB_INFO)); + /* get basic offset & size info */ base_offset = FDI_getoffset(hfdi, hf); @@ -222,7 +286,7 @@ } /* read in the CFHEADER */ - if (!PFDI_READ(hfdi, hf, buf, cfhead_SIZEOF)) { + if (PFDI_READ(hfdi, hf, buf, cfhead_SIZEOF) != cfhead_SIZEOF) { PFDI_INT(hfdi)->perf->erfOper = FDIERROR_NOT_A_CABINET; PFDI_INT(hfdi)->perf->erfType = 0; PFDI_INT(hfdi)->perf->fError = TRUE; @@ -281,8 +345,8 @@ /* read the reserved-sizes part of header, if present */ if (flags & cfheadRESERVE_PRESENT) { - if (!PFDI_READ(hfdi, hf, buf, cfheadext_SIZEOF)) { - WARN("bunk reserve-sizes?\n"); + if (PFDI_READ(hfdi, hf, buf, cfheadext_SIZEOF) != cfheadext_SIZEOF) { + ERR("bunk reserve-sizes?\n"); PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CORRUPT_CABINET; PFDI_INT(hfdi)->perf->erfType = 0; /* ? */ PFDI_INT(hfdi)->perf->fError = TRUE; @@ -290,7 +354,9 @@ } header_resv = EndGetI16(buf+cfheadext_HeaderReserved); + if (pmii) pmii->header_resv = header_resv; folder_resv = buf[cfheadext_FolderReserved]; + if (pmii) pmii->folder_resv = folder_resv; block_resv = buf[cfheadext_DataReserved]; if (header_resv > 60000) { @@ -314,19 +380,43 @@ PFDI_INT(hfdi)->perf->erfType = 0; /* ? */ PFDI_INT(hfdi)->perf->fError = TRUE; return FALSE; - } + } else + if (pmii) + pmii->prevname = prevname; + else + PFDI_FREE(hfdi, prevname); previnfo = FDI_read_string(hfdi, hf, cabsize); + if (previnfo) { + if (pmii) + pmii->previnfo = previnfo; + else + PFDI_FREE(hfdi, previnfo); + } } if (flags & cfheadNEXT_CABINET) { nextname = FDI_read_string(hfdi, hf, cabsize); if (!nextname) { + if ((flags & cfheadPREV_CABINET) && pmii) { + if (pmii->prevname) PFDI_FREE(hfdi, prevname); + if (pmii->previnfo) PFDI_FREE(hfdi, previnfo); + } PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CORRUPT_CABINET; PFDI_INT(hfdi)->perf->erfType = 0; /* ? */ PFDI_INT(hfdi)->perf->fError = TRUE; return FALSE; - } + } else + if (pmii) + pmii->nextname = nextname; + else + PFDI_FREE(hfdi, nextname); nextinfo = FDI_read_string(hfdi, hf, cabsize); + if (nextinfo) { + if (pmii) + pmii->nextinfo = nextinfo; + else + PFDI_FREE(hfdi, nextinfo); + } } /* we could process the whole cabinet searching for problems; @@ -377,7 +467,7 @@ SetLastError(ERROR_BAD_ARGUMENTS); return FALSE; } - rv = FDI_read_entries(hfdi, hf, pfdici); + rv = FDI_read_entries(hfdi, hf, pfdici, NULL); if (rv) pfdici->hasnext = FALSE; /* yuck. duplicate apparent cabinet.dll bug */ @@ -396,9 +486,23 @@ PFNFDINOTIFY pfnfdin, PFNFDIDECRYPT pfnfdid, void *pvUser) -{ - FIXME("(hfdi == ^%p, pszCabinet == ^%p, pszCabPath == ^%p, flags == %0d, \ - pfnfdin == ^%p, pfnfdid == ^%p, pvUser == ^%p): stub\n", +{ + FDICABINETINFO fdici; + FDINOTIFICATION fdin; + MORE_ISCAB_INFO mii; + int hf, i, idx; + char fullpath[MAX_PATH]; + size_t pathlen, filenamelen; + char emptystring = '\0'; + cab_UBYTE buf[64]; + BOOL initialcab = TRUE; + struct fdi_folder *fol = NULL, *linkfol = NULL, *firstfol = NULL; + struct fdi_file *file = NULL, *linkfile = NULL, *firstfile = NULL; + struct fdi_cab _cab; + struct fdi_cab *cab = &_cab; + + TRACE("(hfdi == ^%p, pszCabinet == ^%p, pszCabPath == ^%p, flags == %0d, \ + pfnfdin == ^%p, pfnfdid == ^%p, pvUser == ^%p)\n", hfdi, pszCabinet, pszCabPath, flags, pfnfdin, pfnfdid, pvUser); if (!REALLY_IS_FDI(hfdi)) { @@ -406,8 +510,223 @@ return FALSE; } - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; + while (TRUE) { /* this loop executes one per. cabinet */ + pathlen = (pszCabPath) ? strlen(pszCabPath) : 0; + filenamelen = (pszCabinet) ? strlen(pszCabinet) : 0; + + /* slight overestimation here to save CPU cycles in the developer's brain */ + if ((pathlen + filenamelen + 3) > MAX_PATH) { + ERR("MAX_PATH exceeded.\n"); + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CABINET_NOT_FOUND; + PFDI_INT(hfdi)->perf->erfType = ERROR_FILE_NOT_FOUND; + PFDI_INT(hfdi)->perf->fError = TRUE; + SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + + /* paste the path and filename together */ + idx = 0; + if (pathlen) { + for (i = 0; i < pathlen; i++) fullpath[idx++] = pszCabPath[i]; + if (fullpath[idx - 1] != '\\') fullpath[idx++] = '\\'; + } + if (filenamelen) for (i = 0; i < filenamelen; i++) fullpath[idx++] = pszCabinet[i]; + fullpath[idx] = '\0'; + + TRACE("full cab path/file name: %s\n", debugstr_a(fullpath)); + + /* get a handle to the cabfile */ + hf = PFDI_OPEN(hfdi, fullpath, _O_BINARY | _O_RDONLY | _O_SEQUENTIAL, 0); + if (hf == -1) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CABINET_NOT_FOUND; + PFDI_INT(hfdi)->perf->erfType = ERROR_FILE_NOT_FOUND; + PFDI_INT(hfdi)->perf->fError = TRUE; + SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + + /* check if it's really a cabfile. Note that this doesn't implement the bug */ + if (!FDI_read_entries(hfdi, hf, &fdici, &mii)) { + ERR("FDIIsCabinet failed.\n"); + PFDI_CLOSE(hfdi, hf); + return FALSE; + } + + /* cabinet notification */ + ZeroMemory(&fdin, sizeof(FDINOTIFICATION)); + fdin.setID = fdici.setID; + fdin.iCabinet = fdici.iCabinet; + fdin.pv = pvUser; + fdin.psz1 = (mii.nextname) ? mii.nextname : &emptystring; + fdin.psz2 = (mii.nextinfo) ? mii.nextinfo : &emptystring; + fdin.psz3 = pszCabPath; + + if (((*pfnfdin)(fdintCABINET_INFO, &fdin))) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_USER_ABORT; + PFDI_INT(hfdi)->perf->erfType = 0; + PFDI_INT(hfdi)->perf->fError = TRUE; + goto bail_and_fail; + } + + /* read folders */ + for (i = 0; i < fdici.cFolders; i++) { + if (PFDI_READ(hfdi, hf, buf, cffold_SIZEOF) != cffold_SIZEOF) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CORRUPT_CABINET; + PFDI_INT(hfdi)->perf->erfType = 0; + PFDI_INT(hfdi)->perf->fError = TRUE; + goto bail_and_fail; + } + + if (mii.folder_resv > 0) + PFDI_SEEK(hfdi, hf, mii.folder_resv, SEEK_CUR); + + fol = (struct fdi_folder *) PFDI_ALLOC(hfdi, sizeof(struct fdi_folder)); + if (!fol) { + ERR("out of memory!\n"); + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_ALLOC_FAIL; + PFDI_INT(hfdi)->perf->erfType = ERROR_NOT_ENOUGH_MEMORY; + PFDI_INT(hfdi)->perf->fError = TRUE; + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + goto bail_and_fail; + } + ZeroMemory(fol, sizeof(struct fdi_folder)); + if (!firstfol) firstfol = fol; + + fol->cab[0] = cab; + fol->offset[0] = (cab_off_t) EndGetI32(buf+cffold_DataOffset); + fol->num_blocks = EndGetI16(buf+cffold_NumBlocks); + fol->comp_type = EndGetI16(buf+cffold_CompType); + + if (!linkfol) + cab->folders = fol; + else + linkfol->next = fol; + + linkfol = fol; + } + + /* read files */ + for (i = 0; i < fdici.cFiles; i++) { + if (PFDI_READ(hfdi, hf, buf, cffile_SIZEOF) != cffile_SIZEOF) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CORRUPT_CABINET; + PFDI_INT(hfdi)->perf->erfType = 0; + PFDI_INT(hfdi)->perf->fError = TRUE; + goto bail_and_fail; + } + + file = (struct fdi_file *) PFDI_ALLOC(hfdi, sizeof(struct fdi_file)); + if (!file) { + ERR("out of memory!\n"); + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_ALLOC_FAIL; + PFDI_INT(hfdi)->perf->erfType = ERROR_NOT_ENOUGH_MEMORY; + PFDI_INT(hfdi)->perf->fError = TRUE; + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + goto bail_and_fail; + } + ZeroMemory(file, sizeof(struct fdi_file)); + if (!firstfile) firstfile = file; + + file->length = EndGetI32(buf+cffile_UncompressedSize); + file->offset = EndGetI32(buf+cffile_FolderOffset); + file->index = EndGetI16(buf+cffile_FolderIndex); + file->time = EndGetI16(buf+cffile_Time); + file->date = EndGetI16(buf+cffile_Date); + file->attribs = EndGetI16(buf+cffile_Attribs); + file->filename = FDI_read_string(hfdi, hf, fdici.cbCabinet); + + if (!file->filename) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_CORRUPT_CABINET; + PFDI_INT(hfdi)->perf->erfType = 0; + PFDI_INT(hfdi)->perf->fError = TRUE; + goto bail_and_fail; + } + + if (!linkfile) + cab->files = file; + else + linkfile->next = file; + + linkfile = file; + } + + /* partial file notification (do it just once for the first cabinet) */ + if (initialcab && (firstfile->attribs & cffileCONTINUED_FROM_PREV) && (fdici.iCabinet != 0)) { + /* OK, more MS bugs to simulate here, I think. I don't have a huge spanning + * cabinet to test this theory on ATM, but here's the deal. The SDK says that we + * are supposed to notify the user of the filename and "disk name" (info) of + * the cabinet where the spanning file /started/. That would certainly be convenient + * for the consumer, who could decide to abort everything and try to start over with + * that cabinet so as not to create a front-truncated output file. Note that this + * task would be a horrible bitch from the implementor's (wine's) perspective: the + * information is associated nowhere with the file header and is not to be found in + * the cabinet header. So we would have to open the previous cabinet, and check + * if it contains a single spanning file that's continued from yet another prior cabinet, + * and so-on, until we find the beginning. Note that cabextract.c has code to do exactly + * this. Luckily, MS clearly didn't implement this logic, so we don't have to either. + * Watching the callbacks (and debugmsg +file) clearly shows that they don't open + * the preceeding cabinet -- and therefore, I deduce, there is NO WAY they could + * have implemented what's in the spec. Instead, they are obviously just returning + * the previous cabinet and it's info from the header of this cabinet. So we shall + * do the same. Of course, I could be missing something... + */ + ZeroMemory(&fdin, sizeof(FDINOTIFICATION)); + fdin.pv = pvUser; + fdin.psz1 = (char *)firstfile->filename; + fdin.psz2 = (mii.prevname) ? mii.prevname : &emptystring; + fdin.psz3 = (mii.previnfo) ? mii.previnfo : &emptystring; + + if (((*pfnfdin)(fdintPARTIAL_FILE, &fdin))) { + PFDI_INT(hfdi)->perf->erfOper = FDIERROR_USER_ABORT; + PFDI_INT(hfdi)->perf->erfType = 0; + PFDI_INT(hfdi)->perf->fError = TRUE; + goto bail_and_fail; + } + + } + + + while (firstfol) { + fol = firstfol; + firstfol = firstfol->next; + PFDI_FREE(hfdi, fol); + } + while (firstfile) { + file = firstfile; + firstfile = firstfile->next; + PFDI_FREE(hfdi, file); + } + + /* free the storage remembered by mii */ + if (mii.nextname) PFDI_FREE(hfdi, mii.nextname); + if (mii.nextinfo) PFDI_FREE(hfdi, mii.nextinfo); + if (mii.prevname) PFDI_FREE(hfdi, mii.prevname); + if (mii.previnfo) PFDI_FREE(hfdi, mii.previnfo); + + PFDI_CLOSE(hfdi, hf); + /* TODO: if (:?) */ return TRUE; /* else { ...; initialcab=FALSE; continue; } */ + + bail_and_fail: /* here we free ram before error returns */ + + while (firstfol) { + fol = firstfol; + firstfol = firstfol->next; + PFDI_FREE(hfdi, fol); + } + while (firstfile) { + file = firstfile; + firstfile = firstfile->next; + PFDI_FREE(hfdi, file); + } + + /* free the storage remembered by mii */ + if (mii.nextname) PFDI_FREE(hfdi, mii.nextname); + if (mii.nextinfo) PFDI_FREE(hfdi, mii.nextinfo); + if (mii.prevname) PFDI_FREE(hfdi, mii.prevname); + if (mii.previnfo) PFDI_FREE(hfdi, mii.previnfo); + + PFDI_CLOSE(hfdi, hf); + return FALSE; + } } /*********************************************************************** -- "The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man." -- George Bernard Shaw gmt