I'm using libzip in a c++ application for Linux that will need to be able to zip/unzip directories containing symbolic links. I want to add the link itself without following it. Reading out the link with readlink() and adding it to the zip archive results in a nonsense standard file when unzipped with unzip.
Solution does not need to be portable, it will only be used under Linux. The linux zip command has a --symlinks flags so the zip standard should support it. System calls are not really an option, the number of files is quite large and this makes the application extremely slow.
Is it possible to add symlinks with libzip, and how?
Thanks,
Sander
Based on documentation: no
According to its webpage, libzip is based on zlib. The zip program used in Linux, etc, is info-zip, which does not use zlib, but is self-contained (and contains features not in zlib).
Yes it's possible.
Below a function i use for zipping a list of files in c-code.
The files to zip are stored in a cJSON struct,no uid/gid set and files/directories relative to a directory "base" (as that is my appliction).
The Function returns 0 on success.
int list_zip_it(char * upload_zip_name,char * base, cJSON * filelist)
{
int result=0;
int error_n = 0;
struct zip *archive = zip_open(upload_zip_name, ZIP_TRUNCATE | ZIP_CREATE, &error_n);
if(!archive)
{
printf(stderr,"could not open or create archive\n");
return -1;
}
mode_t mode=0;
cJSON * item;
cJSON_ArrayForEach(item,filelist)
{
char * path=NULL;
path=item->valuestring;
// stat the item
struct stat sb;
if (stat(path, &sb) == 0 ) mode=sb.st_mode;
zip_uint32_t attr=0;
attr=((mode ) << 16L);
char rel_file[1024];
if (strncmp(path,CI_PROJECT_DIR,strlen(base))==0 )
{
snprintf(rel_file,1024,"%s",path+strlen(base)+1);
printf("archive filename: %s\n",rel_file);
}
else
{
fprintf(stderr,"filename outside base-derectory\n");
continue;
}
if (S_ISDIR(mode))
{
int index = (int)zip_add_dir(archive, rel_file);
if (index>0) zip_file_set_external_attributes(archive, index, 0, ZIP_OPSYS_UNIX, attr);
}
else if (S_ISLNK(mode)) // symlink
{
char link[1024];//=calloc(1, 1024);
memset(link, 0, 1024);
ssize_t size_link=readlink(path , link, 1023);
if (size_link > 0)
{
struct zip_source *source = zip_source_buffer(archive , link, ( zip_uint64_t)size_link,0);
if (source)
{
int index = (int)zip_add(archive, rel_file, source);
if (index>0) zip_file_set_external_attributes(archive, index, 0, ZIP_OPSYS_UNIX, attr);
}
else
{
printf(stderr,"failed to create source buffer: %s \n", zip_strerror(archive) );
zip_source_free(source);
}
}
else error("failed to read link: %s \n",path );
}
else if (S_ISREG(mode))
{
struct zip_source *source = zip_source_file(archive, path, 0, 0);
if(source == NULL)
{
error("failed to create source buffer: %s \n", zip_strerror(archive) );
result=1;
break;
}
// todo calculate filename relative to project_dir
int index = (int)zip_add(archive, rel_file, source);
if(index < 0 )
{
int zep,sep;
zip_error_get(archive, &zep, &sep);
if (zep== ZIP_ER_EXISTS )
{
fprintf(stderr,"failed to add file to archive: %s \n", zip_strerror(archive) );
zip_source_free(source);
}
else
{
fprintf(stderr,"failed to add file to archive: %s \n", zip_strerror(archive) );
zip_source_free(source);
result=1;
break;
}
}
else
{
zip_file_set_external_attributes(archive, index, 0, ZIP_OPSYS_UNIX, attr);
}
}
}
zip_close(archive);
return result;
}
Related
I've never used LibArchive before and am trying to use it to create a .tar archive of a directory, since it is installed on my remote compute instance.
LibArchive has an example of how to create an archive from a flat list of files (shown below). But I can't find any examples of how to actually create an archive from a directory path. During the "Extract" example from the same website, it appears everything is contained within the "archive_entry" struct but there doesn't appear to be a counterpart for Create.
void
write_archive(const char *outname, const char **filename)
{
struct archive *a;
struct archive_entry *entry;
struct stat st;
char buff[8192];
int len;
int fd;
a = archive_write_new();
archive_write_add_filter_gzip(a);
archive_write_set_format_pax_restricted(a); // Note 1
archive_write_open_filename(a, outname);
while (*filename) {
stat(*filename, &st);
entry = archive_entry_new(); // Note 2
archive_entry_set_pathname(entry, *filename);
archive_entry_set_size(entry, st.st_size); // Note 3
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
archive_write_header(a, entry);
fd = open(*filename, O_RDONLY);
len = read(fd, buff, sizeof(buff));
while ( len > 0 ) {
archive_write_data(a, buff, len);
len = read(fd, buff, sizeof(buff));
}
close(fd);
archive_entry_free(entry);
filename++;
}
archive_write_close(a); // Note 4
archive_write_free(a); // Note 5
}
The archive_read_disk_descend function from the libtar api allows you to traverse a directory. It should be called before archive_write_header(a, entry); To use this function prior to starting the for loop you need to call struct archive *disk = archive_read_disk_new() in order to create an archive representing your disk.
I'm trying to compress a folder into cpio.gz archive with following code. But its not compressing empty folders and symlinks.
void write_archive(string archivename, vector<string> files) {
struct archive *a;
struct archive_entry *entry;
struct stat st;
char buff[8192];
int len;
int fd;
a = archive_write_new();
archive_write_add_filter_gzip(a);
archive_write_set_format_cpio(a);
archive_write_open_filename(a, archivename.c_str());
for (string file : files) {
string filename = file;
stat(file.c_str(), &st);
entry = archive_entry_new();
archive_entry_set_pathname(entry, trim(filename));
archive_entry_set_size(entry, st.st_size);
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
archive_write_header(a, entry);
fd = open(file.c_str(), O_RDONLY);
len = read(fd, buff, sizeof(buff));
while ( len > 0 ) {
archive_write_data(a, buff, len);
len = read(fd, buff, sizeof(buff));
}
close(fd);
archive_entry_free(entry);
}
archive_write_close(a);
archive_write_free(a);
}
I'm using this code to repack extracted ramdisk of Android. Extracting files using libarchive is working fine. It extracted all files, folders and symlinks....
full code for compressing here
Anyway fixed it https://github.com/Devil7DK/SimpleShits/commit/2859df5855ae26c63240a0ea99bf5f015d810b66
Key things are
archive_entry_set_filetype(entry, AE_IFLNK); - to set the type of entity which we can determine with help of lstat
and
archive_entry_set_symlink(entry, link.c_str()); // - to set the path of link
References:
https://github.com/libarchive/libarchive/issues/1034
https://www.unix.com/man-page/freebsd/3/archive_entry_set_symlink/
https://groups.google.com/forum/#!topic/libarchive-discuss/iz5wCAW2GZ4
I'm trying to implement the "Open In Folder" functionality that you seen in firefox and download managers. This is the code that I've come up so far, and I decided to use nautilux program to open the file.
int File::openTempFile(std::string temp_file_dir)
{
std::string file_path = temp_file_dir + "/" ;
file_path = file_path + this->additional_info ;
// if there is already an temporary file then delete it//
if( temporary_file != "" )
{
// :TODO: open temporary file stack //
// so when the application dinit we could remove those //
// remove(temporary_file.c_str() );
}
/* write temporary file */
FILE* fp = fopen (file_path.c_str(), "w");
if( fp== NULL)
return FALSE;
fwrite( m_data, 1, m_size, fp);
fclose(fp);
// now open it using natulus //
char * parmList[] = {strdup("nautilus"),strdup(file_path.c_str() )} ;
int pid;
if(( pid= fork() ) == -1)
perror("fork failed");
if( pid ==0 ){
int a = execvp("nautilus" , parmList);
printf("exevp failed to load the temporary file");
}
temporary_file = file_path ;
return TRUE;
}
but the error is natulux open three windows and can't figure out where is my bug.
Any idea ?
This question already has answers here:
Unzip a zip file using zlib
(4 answers)
Closed 7 years ago.
Is there a simple example of how to unzip a .zip file and extract the files to a directory? I am currently using zlib, and while I understand that zlib does not directly deal with zip files, there seems to be several additional things in zlibs's "contrib" library. I noticed and read about "minizip", and after reading some documents and looking at some of the code, I do not see a simple example of how to unzip a .zip file and extract the files to a directory.
I would like to find a platform independent way of doing so, but if that is not possible then I need to find a way for windows and mac.
zlib handles the deflate compression/decompression algorithm, but there is more than that in a ZIP file.
You can try libzip. It is free, portable and easy to use.
UPDATE: Here I attach quick'n'dirty example of libzip, with all the error controls ommited:
#include <zip.h>
int main()
{
//Open the ZIP archive
int err = 0;
zip *z = zip_open("foo.zip", 0, &err);
//Search for the file of given name
const char *name = "file.txt";
struct zip_stat st;
zip_stat_init(&st);
zip_stat(z, name, 0, &st);
//Alloc memory for its uncompressed contents
char *contents = new char[st.size];
//Read the compressed file
zip_file *f = zip_fopen(z, name, 0);
zip_fread(f, contents, st.size);
zip_fclose(f);
//And close the archive
zip_close(z);
//Do something with the contents
//delete allocated memory
delete[] contents;
}
Minizip does have an example programs to demonstrate its usage - the files are called minizip.c and miniunz.c.
Update: I had a few minutes so I whipped up this quick, bare bones example for you. It's very smelly C, and I wouldn't use it without major improvements. Hopefully it's enough to get you going for now.
// uzip.c - Simple example of using the minizip API.
// Do not use this code as is! It is educational only, and probably
// riddled with errors and leaks!
#include <stdio.h>
#include <string.h>
#include "unzip.h"
#define dir_delimter '/'
#define MAX_FILENAME 512
#define READ_SIZE 8192
int main( int argc, char **argv )
{
if ( argc < 2 )
{
printf( "usage:\n%s {file to unzip}\n", argv[ 0 ] );
return -1;
}
// Open the zip file
unzFile *zipfile = unzOpen( argv[ 1 ] );
if ( zipfile == NULL )
{
printf( "%s: not found\n" );
return -1;
}
// Get info about the zip file
unz_global_info global_info;
if ( unzGetGlobalInfo( zipfile, &global_info ) != UNZ_OK )
{
printf( "could not read file global info\n" );
unzClose( zipfile );
return -1;
}
// Buffer to hold data read from the zip file.
char read_buffer[ READ_SIZE ];
// Loop to extract all files
uLong i;
for ( i = 0; i < global_info.number_entry; ++i )
{
// Get info about current file.
unz_file_info file_info;
char filename[ MAX_FILENAME ];
if ( unzGetCurrentFileInfo(
zipfile,
&file_info,
filename,
MAX_FILENAME,
NULL, 0, NULL, 0 ) != UNZ_OK )
{
printf( "could not read file info\n" );
unzClose( zipfile );
return -1;
}
// Check if this entry is a directory or file.
const size_t filename_length = strlen( filename );
if ( filename[ filename_length-1 ] == dir_delimter )
{
// Entry is a directory, so create it.
printf( "dir:%s\n", filename );
mkdir( filename );
}
else
{
// Entry is a file, so extract it.
printf( "file:%s\n", filename );
if ( unzOpenCurrentFile( zipfile ) != UNZ_OK )
{
printf( "could not open file\n" );
unzClose( zipfile );
return -1;
}
// Open a file to write out the data.
FILE *out = fopen( filename, "wb" );
if ( out == NULL )
{
printf( "could not open destination file\n" );
unzCloseCurrentFile( zipfile );
unzClose( zipfile );
return -1;
}
int error = UNZ_OK;
do
{
error = unzReadCurrentFile( zipfile, read_buffer, READ_SIZE );
if ( error < 0 )
{
printf( "error %d\n", error );
unzCloseCurrentFile( zipfile );
unzClose( zipfile );
return -1;
}
// Write data to file.
if ( error > 0 )
{
fwrite( read_buffer, error, 1, out ); // You should check return of fwrite...
}
} while ( error > 0 );
fclose( out );
}
unzCloseCurrentFile( zipfile );
// Go the the next entry listed in the zip file.
if ( ( i+1 ) < global_info.number_entry )
{
if ( unzGoToNextFile( zipfile ) != UNZ_OK )
{
printf( "cound not read next file\n" );
unzClose( zipfile );
return -1;
}
}
}
unzClose( zipfile );
return 0;
}
I built and tested it with MinGW/MSYS on Windows like this:
contrib/minizip/$ gcc -I../.. -o unzip uzip.c unzip.c ioapi.c ../../libz.a
contrib/minizip/$ ./unzip.exe /j/zlib-125.zip
How do I get the total number of files in a directory by using C++ standard library?
If you don't exclude the basically always available C standard library, you can use that one.
Because it's available everywhere anyways, unlike boost, it's a pretty usable option!
An example is given here.
And here:
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main (void)
{
DIR *dp;
int i = 0;
struct dirent *ep;
dp = opendir ("./");
if (dp != NULL)
{
while (ep = readdir (dp))
i++;
(void) closedir (dp);
}
else
perror ("Couldn't open the directory");
printf("There's %d files in the current directory.\n", i);
return 0;
}
And sure enough
> $ ls -a | wc -l
138
> $ ./count
There's 138 files in the current directory.
This isn't C++ at all, but it is available on most, if not all, operating systems, and will work in C++ regardless.
UPDATE: I'll correct my previous statement about this being part of the C standard library - it's not. But you can carry this concept to other operating systems, because they all have their ways of dealing with files without having to grab out additional libraries.
EDIT: : Added initialization of i
You can't. The closest you are going to be able to get is to use something like Boost.Filesystem
EDIT: It is possible with C++17 using the STL's filesystem library
As of C++17 it can be done with STL:
auto dirIter = std::filesystem::directory_iterator("directory_path");
int fileCount = std::count_if(
begin(dirIter),
end(dirIter),
[](auto& entry) { return entry.is_regular_file(); }
);
A simple for-loop works, too:
auto dirIter = std::filesystem::directory_iterator("directory_path");
int fileCount = 0;
for (auto& entry : dirIter)
{
if (entry.is_regular_file())
{
++fileCount;
}
}
See https://en.cppreference.com/w/cpp/filesystem/directory_iterator
An old question, but since it appears first on Google search, I thought to add my answer since I had a need for something like that.
int findNumberOfFilesInDirectory(std::string& path)
{
int counter = 0;
WIN32_FIND_DATA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
// Start iterating over the files in the path directory.
hFind = ::FindFirstFileA (path.c_str(), &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do // Managed to locate and create an handle to that folder.
{
counter++;
} while (::FindNextFile(hFind, &ffd) == TRUE);
::FindClose(hFind);
} else {
printf("Failed to find path: %s", path.c_str());
}
return counter;
}
If they are well named, sorted, and have the same extension, you could simply do count them with standard C++ library.
Assume the file names are like "img_0.jpg..img_10000.jpg..img_n.jpg",
Just check if they are in the folder or not.
int Trainer::fileCounter(string dir, string prefix, string extension)
{
int returnedCount = 0;
int possibleMax = 5000000; //some number you can expect.
for (int istarter = 0; istarter < possibleMax; istarter++){
string fileName = "";
fileName.append(dir);
fileName.append(prefix);
fileName.append(to_string(istarter));
fileName.append(extension);
bool status = FileExistenceCheck(fileName);
returnedCount = istarter;
if (!status)
break;
}
return returnedCount;
}
bool Trainer::FileExistenceCheck(const std::string& name) {
struct stat buffer;
return (stat(name.c_str(), &buffer) == 0);
}
You would need to use a native API or framework.