1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-06-29 00:16:53 +00:00
seafile-server/server/pack-dir.c
2018-05-13 23:48:36 -07:00

486 lines
14 KiB
C

#include "common.h"
#define DEBUG_FLAG SEAFILE_DEBUG_HTTP
#include "log.h"
#include <ccnet.h>
#include "seafile-object.h"
#include "seafile-crypt.h"
#include "seafile-error.h"
#include "utils.h"
#include "seafile-session.h"
#include "pack-dir.h"
#include <archive.h>
#include <archive_entry.h>
#include <iconv.h>
#ifdef WIN32
#define S_IFLNK 0120000 /* Symbolic link */
#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
#endif
typedef struct {
struct archive *a;
SeafileCrypt *crypt;
const char *top_dir_name;
gboolean is_windows;
time_t mtime;
char store_id[37];
int repo_version;
int tmp_fd;
char *tmp_zip_file;
} PackDirData;
static char *
do_iconv (char *fromcode, char *tocode, char *in)
{
iconv_t conv;
size_t inlen, outlen, len;
char out[1024];
char *pin = in;
char *pout = out;
conv = iconv_open (tocode, fromcode);
if (conv == (iconv_t)-1) {
return NULL;
}
inlen = strlen (in);
outlen = sizeof(out);
len = iconv (conv, &pin, &inlen, &pout, &outlen);
iconv_close (conv);
if (len == -1) {
return NULL;
}
outlen = sizeof(out) - outlen;
return g_strndup(out, outlen);
}
static int
add_file_to_archive (PackDirData *data,
const char *parent_dir,
SeafDirent *dent)
{
struct archive *a = data->a;
struct SeafileCrypt *crypt = data->crypt;
gboolean is_windows = data->is_windows;
const char *top_dir_name = data->top_dir_name;
struct archive_entry *entry = NULL;
Seafile *file = NULL;
char *pathname = NULL;
char buf[64 * 1024];
int len = 0;
int n = 0;
int idx = 0;
BlockHandle *handle = NULL;
BlockMetadata *bmd = NULL;
char *blk_id = NULL;
uint32_t remain = 0;
EVP_CIPHER_CTX *ctx;
gboolean enc_init = FALSE;
char *dec_out = NULL;
int dec_out_len = -1;
int ret = 0;
pathname = g_build_filename (top_dir_name, parent_dir, dent->name, NULL);
file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
data->store_id, data->repo_version,
dent->id);
if (!file) {
ret = -1;
goto out;
}
entry = archive_entry_new ();
/* File name fixup for WinRAR */
if (is_windows && seaf->http_server->windows_encoding) {
char *win_file_name = do_iconv ("UTF-8",
seaf->http_server->windows_encoding,
pathname);
if (!win_file_name) {
seaf_warning ("Failed to convert file name to %s\n",
seaf->http_server->windows_encoding);
ret = -1;
goto out;
}
archive_entry_copy_pathname (entry, win_file_name);
g_free (win_file_name);
} else {
archive_entry_set_pathname (entry, pathname);
}
/* FIXME: 0644 should be set when upload files in repo-mgr.c */
archive_entry_set_mode (entry, dent->mode | 0644);
archive_entry_set_size (entry, file->file_size);
archive_entry_set_mtime (entry, data->mtime, 0);
n = archive_write_header (a, entry);
if (n != ARCHIVE_OK) {
seaf_warning ("archive_write_header error: %s\n", archive_error_string(a));
ret = -1;
goto out;
}
/* Read data of this entry block by block */
while (idx < file->n_blocks) {
blk_id = file->blk_sha1s[idx];
handle = seaf_block_manager_open_block (seaf->block_mgr,
data->store_id,
data->repo_version,
blk_id, BLOCK_READ);
if (!handle) {
seaf_warning ("Failed to open block %s:%s\n", data->store_id, blk_id);
ret = -1;
goto out;
}
bmd = seaf_block_manager_stat_block_by_handle (seaf->block_mgr,
handle);
if (!bmd) {
seaf_warning ("Failed to stat block %s:%s\n", data->store_id, blk_id);
ret = -1;
goto out;
}
remain = bmd->size;
g_free (bmd);
if (crypt) {
if (seafile_decrypt_init (&ctx, crypt->version,
crypt->key, crypt->iv) < 0) {
seaf_warning ("Failed to init decrypt.\n");
ret = -1;
goto out;
}
enc_init = TRUE;
}
while (remain != 0) {
n = seaf_block_manager_read_block (seaf->block_mgr, handle,
buf, sizeof(buf));
if (n <= 0) {
seaf_warning ("failed to read block %s\n", blk_id);
ret = -1;
goto out;
}
remain -= n;
/* OK, We're read some data of this block */
if (crypt == NULL) {
/* not encrypted */
len = archive_write_data (a, buf, n);
if (len <= 0) {
seaf_warning ("archive_write_data error: %s\n", archive_error_string(a));
ret = -1;
goto out;
}
} else {
/* an encrypted block */
dec_out = g_new (char, n + 16);
if (!dec_out) {
seaf_warning ("Failed to alloc memory.\n");
ret = -1;
goto out;
}
int r = EVP_DecryptUpdate (ctx,
(unsigned char *)dec_out,
&dec_out_len,
(unsigned char *)buf,
n);
/* EVP_DecryptUpdate returns 1 on success, 0 on failure */
if (r != 1) {
seaf_warning ("Decrypt block %s failed.\n", blk_id);
ret = -1;
goto out;
}
if (dec_out_len > 0) {
len = archive_write_data (a, dec_out, dec_out_len);
if (len <= 0) {
seaf_warning ("archive_write_data error: %s\n", archive_error_string(a));
ret = -1;
goto out;
}
}
/* If it's the last piece of a block, call decrypt_final()
* to decrypt the possible partial block. */
if (remain == 0) {
r = EVP_DecryptFinal_ex (ctx,
(unsigned char *)dec_out,
&dec_out_len);
if (r != 1) {
seaf_warning ("Decrypt block %s failed.\n", blk_id);
ret = -1;
goto out;
}
if (dec_out_len != 0) {
len = archive_write_data (a, dec_out, dec_out_len);
if (len <= 0) {
seaf_warning ("archive_write_data error: %s\n", archive_error_string(a));
ret = -1;
goto out;
}
}
}
g_free (dec_out);
dec_out = NULL;
}
}
seaf_block_manager_close_block (seaf->block_mgr, handle);
seaf_block_manager_block_handle_free (seaf->block_mgr, handle);
handle = NULL;
/* turn to next block */
idx++;
}
out:
g_free (pathname);
if (entry)
archive_entry_free (entry);
if (file)
seafile_unref (file);
if (handle) {
seaf_block_manager_close_block (seaf->block_mgr, handle);
seaf_block_manager_block_handle_free(seaf->block_mgr, handle);
}
if (crypt != NULL && enc_init)
EVP_CIPHER_CTX_free (ctx);
g_free (dec_out);
return ret;
}
static int
archive_dir (PackDirData *data,
const char *root_id,
const char *dirpath,
Progress *progress)
{
SeafDir *dir = NULL;
SeafDirent *dent;
GList *ptr;
char *subpath = NULL;
int ret = 0;
dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
data->store_id, data->repo_version,
root_id);
if (!dir) {
seaf_warning ("failed to get dir %s:%s\n", data->store_id, root_id);
goto out;
}
if (!dir->entries) {
char *pathname = g_build_filename (data->top_dir_name, dirpath, NULL);
struct archive_entry *entry = archive_entry_new ();
gboolean is_windows = data->is_windows;
if (is_windows && seaf->http_server->windows_encoding) {
char *win_file_name = do_iconv ("UTF-8",
seaf->http_server->windows_encoding,
pathname);
if (!win_file_name) {
seaf_warning ("Failed to convert file name to %s\n",
seaf->http_server->windows_encoding);
ret = -1;
goto out;
}
archive_entry_copy_pathname (entry, win_file_name);
g_free (win_file_name);
} else {
archive_entry_set_pathname (entry, pathname);
}
archive_entry_set_filetype (entry, AE_IFDIR);
archive_entry_set_mtime (entry, data->mtime, 0);
archive_entry_set_perm (entry, 0755);
int n = archive_write_header (data->a, entry);
if (n != ARCHIVE_OK) {
seaf_warning ("archive_write_header error: %s\n", archive_error_string(data->a));
ret = -1;
}
archive_entry_free (entry);
g_free (pathname);
goto out;
}
for (ptr = dir->entries; ptr; ptr = ptr->next) {
if (progress->canceled) {
ret = -1;
goto out;
}
dent = ptr->data;
if (S_ISREG(dent->mode)) {
ret = add_file_to_archive (data, dirpath, dent);
if (ret == 0) {
g_atomic_int_inc (&progress->zipped);
}
} else if (S_ISLNK(dent->mode)) {
if (archive_version_number() >= 3000001) {
/* Symlink in zip arhive is not supported in earlier version
* of libarchive */
ret = add_file_to_archive (data, dirpath, dent);
}
} else if (S_ISDIR(dent->mode)) {
subpath = g_build_filename (dirpath, dent->name, NULL);
ret = archive_dir (data, dent->id, subpath, progress);
g_free (subpath);
}
if (ret < 0) {
goto out;
}
}
out:
if (dir)
seaf_dir_free (dir);
return ret;
}
static PackDirData *
pack_dir_data_new (const char *store_id,
int repo_version,
const char *dirname,
SeafileCrypt *crypt,
gboolean is_windows)
{
struct archive *a = NULL;
char *tmpfile_name = NULL ;
int fd = -1;
PackDirData *data = NULL;
tmpfile_name = g_strdup_printf ("%s/seafile-XXXXXX.zip",
seaf->http_server->http_temp_dir);
fd = g_mkstemp (tmpfile_name);
if (fd < 0) {
seaf_warning ("Failed to open temp file: %s.\n", strerror (errno));
g_free (tmpfile_name);
return NULL;
}
a = archive_write_new ();
archive_write_set_compression_none (a);
archive_write_set_format_zip (a);
archive_write_open_fd (a, fd);
data = g_new0 (PackDirData, 1);
data->crypt = crypt;
data->is_windows = is_windows;
data->a = a;
data->top_dir_name = dirname;
data->mtime = time(NULL);
memcpy (data->store_id, store_id, 36);
data->repo_version = repo_version;
data->tmp_fd = fd;
data->tmp_zip_file = tmpfile_name;
return data;
}
static int
archive_multi (PackDirData *data, GList *dirent_list,
Progress *progress)
{
GList *iter;
SeafDirent *dirent;
for (iter = dirent_list; iter; iter = iter->next) {
if (progress->canceled)
return -1;
dirent = iter->data;
if (S_ISREG(dirent->mode)) {
if (add_file_to_archive (data, "", dirent) < 0) {
seaf_warning ("Failed to archive file: %s.\n", dirent->name);
return -1;
}
g_atomic_int_inc (&progress->zipped);
} else if (S_ISDIR(dirent->mode)) {
if (archive_dir (data, dirent->id, dirent->name, progress) < 0) {
seaf_warning ("Failed to archive dir: %s.\n", dirent->name);
return -1;
}
}
}
return 0;
}
int
pack_files (const char *store_id,
int repo_version,
const char *dirname,
void *internal,
SeafileCrypt *crypt,
gboolean is_windows,
Progress *progress)
{
int ret = 0;
PackDirData *data = NULL;
data = pack_dir_data_new (store_id, repo_version, dirname,
crypt, is_windows);
if (!data) {
seaf_warning ("Failed to create pack dir data for %s.\n",
strcmp (dirname, "")==0 ? "multi files" : dirname);
return -1;
}
progress->zip_file_path = data->tmp_zip_file;
if (strcmp (dirname, "") != 0) {
// Pack dir
if (archive_dir (data, (char *)internal, "", progress) < 0) {
if (progress->canceled)
seaf_warning ("Zip task for dir %s in repo %.8s canceled.\n", dirname, store_id);
else
seaf_warning ("Failed to archive dir %s in repo %.8s.\n", dirname, store_id);
ret = -1;
}
} else {
// Pack multi
if (archive_multi (data, (GList *)internal, progress) < 0) {
if (progress->canceled)
seaf_warning ("Archiving multi files in repo %.8s canceled.\n", store_id);
else
seaf_warning ("Failed to archive multi files in repo %.8s.\n", store_id);
ret = -1;
}
}
if (archive_write_finish(data->a) < 0) {
seaf_warning ("Failed to archive write finish for %s in repo %.8s.\n",
strcmp (dirname, "")==0 ? "multi files" : dirname, store_id);
ret = -1;
}
close (data->tmp_fd);
free (data);
return ret;
}