I have a tar file I want to extract with libarchive to a specific directory. How can I make libarchive extract into to any directory I want? At the moment it always extracts into my program's working directory. I looked at this answer but all this does is change the location of an archive entry within the archive, i.e. it still extracts into my program's working directory just at a different sub-directory.
I resolved this issue next way:
(insert this code before calling 'archive_write_header' function)
const char* currentFile = archive_entry_pathname(archiveEntry);
const std::string fullOutputPath = destination + currentFile;
archive_entry_set_pathname(archiveEntry, fullOutputPath.c_str());
where destination is output path.
And it works.
From the libarchive discussion boards:
"It depends, of course, on the archive being extracted.
Typically, you would chdir() to the directory where you want the output to go, then use code similar to that in the Wiki Examples page:
A Complete Extractor Example
or in the untar.c sample program:
untar Example
Of course, if the tar file you're extracting has
interesting filenames (such as "c:\someotherdirectory"),
then you'll need to play with the filenames as you extract.
Note that the examples all use archive_read_next_header()
to get an entry object from the input archive describing
the next entry; you are then free to edit that entry description
in any way you wish -- in particular, you can change the
name, owner, or permissions -- before calling
archive_write_header() to recreate the entry on
disk.
The Examples page in the Wiki above is probably the
best place to start."
A longer C version of Alex's answer. To extract files to a temp_dir, use archive_entry_pathname() and archive_entry_set_pathname() to re-write each entry::
char* dest_file;
for (;;) {
r = archive_read_next_header(a, &entry);
if (r == ARCHIVE_EOF)
break;
if (r < ARCHIVE_OK)
fprintf(stderr, "%s\n", archive_error_string(a));
if (r < ARCHIVE_WARN)
return NULL;
asprintf(&dest_file, "%s/%s", temp_dir, archive_entry_pathname(entry));
archive_entry_set_pathname(entry, dest_file);
// printf(" writing %s\n", dest_file);
r = archive_write_header(ext, entry);
if (r < ARCHIVE_OK)
fprintf(stderr, "%s\n", archive_error_string(ext));
else if (archive_entry_size(entry) > 0) {
r = copy_data(a, ext);
if (r < ARCHIVE_OK)
fprintf(stderr, "%s\n", archive_error_string(ext));
if (r < ARCHIVE_WARN)
exit(1);
}
r = archive_write_finish_entry(ext);
if (r < ARCHIVE_OK)
fprintf(stderr, "%s\n", archive_error_string(ext));
if (r < ARCHIVE_WARN)
return NULL;
free(dest_file);
}
Related
For my C++ project I need a library to decompress various formats of archives. I found libarchive was the best solution for this, however when I started testing it I ran into a problem. I tried running an untar example from the library over a zip containing files named in Russian and it just refuses to work.
archive_read_support_compression_all(a);
archive_read_support_format_all(a);
archive_read_open_filename(a, filename, 10240)));
for (;;) {
r = archive_read_next_header(a, &entry);
if (r == ARCHIVE_EOF) {
break;
}
if (r != ARCHIVE_OK) {
if (r == ARCHIVE_WARN) {
warn("archive_read_next_header()", archive_error_string(a)); // This warning goes off
} else {
fail("archive_read_next_header()", archive_error_string(a), 1);
}
}
msg(archive_entry_pathname(entry)); // entry is null
}
}
When I run this over other archive formats containing the same files everything is fime but for zip it fails with
archive_read_next_header() failed: Pathname cannot be converted from UTF-8 to current locale.
What can I do to make this work? I'm generally fine with the names being gibberish at this point
The system I'm running is Ubuntu 20.04 if that's important
I'm trying to find out the solution to solve a problem;
In fact, i'm writing my own tool to make saves using libzip in C++ to compress the files.
Absolutly not finished but i wanted to make some tests, then i do and obtain a "funny" error from the log.
Here's my function:
void save(std::vector<std::string> filepath, std::string savepath){
int err;
savepath += time(NULL);
zip* saveArchive = zip_open(savepath.c_str(), ZIP_CREATE , &err);
if(err != ZIP_ER_OK) throw xif::sys_error("Error while opening the archive", zip_strerror(saveArchive));
for(int i = 0; i < filepath.size(); i++){
if(filepath[i].find("/") == std::string::npos){}
if(filepath[i].find(".cfg") == std::string::npos){
err = (int) zip_file_add(saveArchive, filepath[i].c_str(), NULL, NULL);
if(err == -1) throw xif::sys_error("Error while adding the files", zip_strerror(saveArchive));
}
}
if(zip_close(saveArchive) == -1) throw xif::sys_error("Error while closing the archive", zip_strerror(saveArchive));
}
I get a => Error : Error while opening the archive : No error
And, of course, i didn't have any .zip written.
If you could help me, thanks to you !
The documentation for zip_open says that it only sets *errorp if the open fails. Either test for saveArchive == nullptr or initialize err to
ZIP_ER_OK.
P.S. The search for '/' does nothing. Did you mean to put a continue in that block?
The other problematic line is:
savepath += time(NULL);
If that is the standard time function, that returns a time in seconds since the epoch. That will probably get truncated to a char, and then that char appended to the file name. That will cause strange characters to appear in the filename! I suggest using std::chrono to convert to text.
I use libzip to open zip files in my application and in order to ensure good behavior in case of corrupt zip files I manually corrupted a zip file (by removing a few random lines with a text editor) and try to load that file. However this hangs the entire app because zip_fread() never returns.
Is there a where to determine if a zip file is valid before loading it to avoid such situations?
Update
The behavior seems to depend on the version, so I probably only need to update. This is the code I use on Windows, Mac OS and Linux:
int err;
zip *z= zip_open(zipfile.c_str(), 0, &err);
if (!z)
{
if (err == ZIP_ER_NOZIP)
throw std::runtime_error("The file is not a Workbench document.");
else if (err == ZIP_ER_MEMORY)
throw grt::os_error("Cannot allocate enough memory to open document.");
else if (err == ZIP_ER_NOENT)
throw grt::os_error("File not found.");
int len= zip_error_to_str(NULL, 0, 0, err);
std::string msg;
if (len > 0)
{
char *buf= (char*)g_malloc(len+1);
zip_error_to_str(buf, len+1, 0, err);
msg= buf;
g_free(buf);
}
else
msg= "error opening zip archive";
zip_close(z);
throw std::runtime_error(strfmt(_("Cannot open document file: %s"), msg.c_str()));
}
On OS X this fragment does not return an error (I used the same file for all platforms). Instead the following zip_read() call just hangs. On the other platforms zip_read() immediately returns with a result < 0, so it's easy to catch the error there.
I need to know, how I can select the Last modified/created file in a given directory.
I currently have a directory named XML, and inside that there are many XML files. But I would like to select only the last modified file.
I use the following function to list all the items inside a folder. It writes all the files in a string vector, but you can change that.
bool ListContents (vector<string>& dest, string dir, string filter, bool recursively)
{
WIN32_FIND_DATAA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
DWORD dwError = 0;
// Prepare string
if (dir.back() != '\\') dir += "\\";
// Safety check
if (dir.length() >= MAX_PATH) {
Error("Cannot open folder %s: path too long", dir.c_str());
return false;
}
// First entry in directory
hFind = FindFirstFileA((dir + filter).c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
Error("Cannot open folder in folder %s: error accessing first entry.", dir.c_str());
return false;
}
// List files in directory
do {
// Ignore . and .. folders, they cause stack overflow
if (strcmp(ffd.cFileName, ".") == 0) continue;
if (strcmp(ffd.cFileName, "..") == 0) continue;
// Is directory?
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// Go inside recursively
if (recursively)
ListContents(dest, dir + ffd.cFileName, filter, recursively, content_type);
}
// Add file to our list
else dest.push_back(dir + ffd.cFileName);
} while (FindNextFileA(hFind, &ffd));
// Get last error
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES) {
Error("Error reading file list in folder %s.", dir.c_str());
return false;
}
return true;
}
(don't forget to include windows.h)
What you have to do is adapt it to find the newest file.
The ffd structure (WIN32_FIND_DATAA data type) contains ftCreationTime, ftLastAccessTime and ftLastWriteTime, you can use those to find the newest file.
These members are FILETIME structures, you can find the documentation here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
You can use FindFirstFile and FindNextFile, they deliver a struct describing the file like size as well as modified time.
Boost.Filesystem offers last_write_time. You can use this to sort the files in a directory. Boost.Filesystem and (Boost) in general can be a little bit intimidating for a C++ new-comer so you might want to check a solution for your OS first.
Anyone know of a C library that can create password protected zip files on windows? It appears that the option to password protect zip files with the built-in zip utility has been removed from windows 7, but I don't think this is an issue.
Can either zziplib or the 7-Zip SDK do this?
7-Zip SDK (LZMA SDK) supports password protected archive.
Related SO post:
https://stackoverflow.com/questions/221049/how-secure-is-7-zip
LZMA SDK:
http://www.7-zip.org/sdk.html
If you can use .NET, check out DotNetZip: http://dotnetzip.codeplex.com/
C++ .NET example to create password-protected ZIP:
http://cheeso.members.winisp.net/DotNetZipHelp/Code%20Examples/Cpp.htm
MINIZIP + zlib supports AES 256 encryption, and is very easy to use!
Code based on minizip unzip.c.
include stdio.h zip.h unzip.h
First, create a zip with a file inside with a password.
The zip file must be in the same directory as the executable.
Run the program from a prompt in the directory of the generated program. This example only extracts the first file!
/*-----------start-------------- */
/*Tries to open the zip in the current directory.*/
unzFile zfile = unzOpen("teste.zip");
if(zfile==NULL)
{
printf("Error!");
return;
}
printf("OK Zip teste.zip opened...\n");
int err = unzGoToFirstFile(zfile);/*go to first file in zip*/
if (err != UNZ_OK)
{
printf("error %d with zipfile in unzGoToFirstFile\n", err);
unzClose(zfile);/*close zip*/
}
/*At this point zfile points to the first file contained in the zip*/
char filename_inzip[256] = {0};/* The file name will be returned here */
unz_file_info file_info = {0};/*strcuture with info of the first file*/
err = unzGetCurrentFileInfo(zfile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
if (err != UNZ_OK)
{
printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
}
else
{
int len = 8192;/*size of chunk*/
char buffer[8192]={0};/*buffer used to save uncompressed data*/
printf("name of first file is :%s\n",filename_inzip);
printf("uncompressed_size = %d\n",file_info.uncompressed_size);
/*Use your password here, the same one used to create your zip */
err = unzOpenCurrentFilePassword(zfile, "yourpassword");
if (err != UNZ_OK)
printf("error %d with zipfile in unzOpenCurrentFilePassword\n", err);
else
printf("password ok\n");
FILE *fp = fopen(filename_inzip, "wb");/*file for data binary type*/
if (fp != NULL)
{
do
{/*read the current file returned by unzGoToFirstFile to buffer in chunks of the 8192*/
err = unzReadCurrentFile(zfile, &buffer, len );
if (err < 0)
{
printf("error %d with zipfile in unzReadCurrentFile\n", err);
break;
}
if (err == 0)
break;
/*Save the chunk read to the file*/
if (fwrite(&buffer, err, 1, fp) != 1)/*if error break*/
{
printf("error %d in writing extracted file\n", errno);
err = UNZ_ERRNO;
break;
}/*else continue*/
}
while (err > 0);
/*close file*/
fclose(fp);
}
}
unzClose(zfile);