seafile/daemon/vc-utils.c
feiniks 9ab77f59d2
Fix free path twice (#2680)
Co-authored-by: heran yang <heran.yang@seafile.com>
2023-06-07 10:00:31 +08:00

704 lines
20 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#define DEBUG_FLAG SEAFILE_DEBUG_MERGE
#include "log.h"
#include "seafile-error.h"
#include <stdlib.h>
#include <sys/stat.h>
#include <glib/gstdio.h>
#include "seafile-session.h"
#include "utils.h"
#include "fs-mgr.h"
#include "vc-utils.h"
#include "vc-common.h"
#include "index/index.h"
static gint
compare_dirents (gconstpointer a, gconstpointer b)
{
const SeafDirent *denta = a, *dentb = b;
return strcmp (dentb->name, denta->name);
}
int
commit_trees_cb (const char *repo_id, int version,
const char *worktree,
struct cache_tree *it, struct cache_entry **cache,
int entries, const char *base, int baselen)
{
SeafDir *seaf_dir;
GList *dirents = NULL, *ptr;
int i;
for (i = 0; i < entries; i++) {
SeafDirent *seaf_dent;
char *name;
struct cache_entry *ce = cache[i];
struct cache_tree_sub *sub;
const char *path, *slash;
int pathlen, entlen;
const unsigned char *sha1;
char hex[41];
unsigned mode;
guint64 mtime;
gint64 size;
char *modifier;
if (ce->ce_flags & CE_REMOVE)
continue; /* entry being removed */
path = ce->name;
pathlen = ce_namelen(ce);
if (pathlen <= baselen || memcmp(base, path, baselen))
break; /* at the end of this level */
slash = strchr(path + baselen, '/');
if (slash) {
entlen = slash - (path + baselen);
sub = cache_tree_find_subtree(it, path + baselen, entlen, 0);
g_return_val_if_fail (sub != NULL, -1);
/* Skip cache entries in the sub level. */
i += sub->cache_tree->entry_count - 1;
sha1 = sub->cache_tree->sha1;
mtime = sub->cache_tree->mtime;
mode = S_IFDIR;
name = g_strndup(path + baselen, entlen);
rawdata_to_hex (sha1, hex, 20);
seaf_dent = seaf_dirent_new (dir_version_from_repo_version(version),
hex, mode, name, mtime, NULL, -1);
g_free(name);
dirents = g_list_prepend (dirents, seaf_dent);
} else {
sha1 = ce->sha1;
mode = ce->ce_mode;
mtime = ce->ce_mtime.sec;
size = ce->ce_size;
modifier = ce->modifier;
entlen = pathlen - baselen;
name = g_strndup(path + baselen, entlen);
rawdata_to_hex (sha1, hex, 20);
if (version > 0) {
seaf_dent =
seaf_dirent_new (dir_version_from_repo_version(version),
hex, mode, name, mtime, modifier, size);
} else {
seaf_dent = seaf_dirent_new (0, hex, mode, name, 0, NULL, -1);
}
g_free(name);
dirents = g_list_prepend (dirents, seaf_dent);
}
#if DEBUG
fprintf(stderr, "cache-tree update-one %o %.*s\n",
mode, entlen, path + baselen);
#endif
}
/* Sort dirents in descending order. */
dirents = g_list_sort (dirents, compare_dirents);
seaf_dir = seaf_dir_new (NULL, dirents, dir_version_from_repo_version(version));
hex_to_rawdata (seaf_dir->dir_id, it->sha1, 20);
/* Dir's mtime is the latest of all dir entires. */
guint64 dir_mtime = 0;
SeafDirent *dent;
for (ptr = dirents; ptr; ptr = ptr->next) {
dent = ptr->data;
if (dent->mtime > dir_mtime)
dir_mtime = dent->mtime;
}
it->mtime = dir_mtime;
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
repo_id, version,
seaf_dir->dir_id))
seaf_dir_save (seaf->fs_mgr, repo_id, version, seaf_dir);
#if DEBUG
for (p = dirents; p; p = p->next) {
SeafDirent *tmp = (SeafDirent *)p->data;
fprintf(stderr, "dump dirent name %s id %s\n", tmp->name, tmp->id);
}
#endif
seaf_dir_free (seaf_dir);
return 0;
}
int
update_index (struct index_state *istate, const char *index_path)
{
char index_shadow[SEAF_PATH_MAX];
int index_fd;
int ret = 0;
snprintf (index_shadow, SEAF_PATH_MAX, "%s.shadow", index_path);
index_fd = seaf_util_create (index_shadow, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
0666);
if (index_fd < 0) {
seaf_warning ("Failed to open shadow index: %s.\n", strerror(errno));
return -1;
}
if (write_index (istate, index_fd) < 0) {
seaf_warning ("Failed to write shadow index: %s.\n", strerror(errno));
close (index_fd);
return -1;
}
close (index_fd);
ret = seaf_util_rename (index_shadow, index_path);
if (ret < 0) {
seaf_warning ("Failed to update index errno=%d %s\n", errno, strerror(errno));
return -1;
}
return 0;
}
#ifndef WIN32
int
seaf_remove_empty_dir (const char *path)
{
SeafStat st;
GDir *dir;
const char *dname;
char *full_path;
GError *error = NULL;
if (seaf_stat (path, &st) < 0 || !S_ISDIR(st.st_mode))
return 0;
if (seaf_util_rmdir (path) < 0) {
dir = g_dir_open (path, 0, &error);
if (!dir) {
seaf_warning ("Failed to open dir %s: %s.\n", path, error->message);
return -1;
}
/* Remove all ignored hidden files. */
while ((dname = g_dir_read_name (dir)) != NULL) {
if (seaf_repo_manager_is_ignored_hidden_file(dname)) {
full_path = g_build_path ("/", path, dname, NULL);
if (seaf_util_unlink (full_path) < 0)
seaf_warning ("Failed to remove file %s: %s.\n",
full_path, strerror(errno));
g_free (full_path);
}
}
g_dir_close (dir);
if (seaf_util_rmdir (path) < 0) {
seaf_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
return -1;
}
}
return 0;
}
#else
static int
remove_hidden_file (wchar_t *parent, WIN32_FIND_DATAW *fdata,
void *user_data, gboolean *stop)
{
char *dname = NULL;
wchar_t *subpath_w = NULL;
char *subpath = NULL;
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(!dname || seaf_repo_manager_is_ignored_hidden_file(dname))) {
subpath_w = g_new0 (wchar_t, wcslen(parent) + wcslen(fdata->cFileName) + 2);
wcscpy (subpath_w, parent);
wcscat (subpath_w, L"\\");
wcscat (subpath_w, fdata->cFileName);
if (!DeleteFileW (subpath_w)) {
subpath = g_utf16_to_utf8 (subpath_w, -1, NULL, NULL, NULL);
seaf_warning ("Failed to remove file %s: %lu.\n", subpath, GetLastError());
g_free (subpath);
}
g_free (subpath_w);
}
g_free (dname);
return 0;
}
int
seaf_remove_empty_dir (const char *path)
{
wchar_t *path_w = NULL;
WIN32_FILE_ATTRIBUTE_DATA attrs;
int ret = 0;
path_w = win32_long_path (path);
if (!GetFileAttributesExW (path_w, GetFileExInfoStandard, &attrs)) {
goto out;
}
if (!(attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
goto out;
}
if (RemoveDirectoryW (path_w))
goto out;
if (GetLastError() == ERROR_DIR_NOT_EMPTY) {
traverse_directory_win32 (path_w, remove_hidden_file, NULL);
if (!RemoveDirectoryW (path_w)) {
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
ret = -1;
}
} else {
seaf_warning ("Failed to remove dir %s: %lu.\n", path, GetLastError());
ret = -1;
}
out:
g_free (path_w);
return ret;
}
#endif /* WIN32 */
static int
compute_file_id_with_cdc (const char *path, SeafStat *st,
SeafileCrypt *crypt, int repo_version,
uint32_t blk_avg_size, uint32_t blk_min_size, uint32_t blk_max_size,
unsigned char sha1[])
{
CDCFileDescriptor cdc;
memset (&cdc, 0, sizeof(cdc));
cdc.block_sz = blk_avg_size;
cdc.block_min_sz = blk_min_size;
cdc.block_max_sz = blk_max_size;
cdc.write_block = seafile_write_chunk;
if (filename_chunk_cdc (path, &cdc, crypt, FALSE) < 0) {
seaf_warning ("Failed to chunk file.\n");
return -1;
}
if (repo_version > 0)
seaf_fs_manager_calculate_seafile_id_json (repo_version, &cdc, sha1);
else
memcpy (sha1, cdc.file_sum, 20);
if (cdc.blk_sha1s)
free (cdc.blk_sha1s);
return 0;
}
int
compare_file_content (const char *path, SeafStat *st, const unsigned char *ce_sha1,
SeafileCrypt *crypt, int repo_version)
{
unsigned char sha1[20];
if (st->st_size == 0) {
memset (sha1, 0, 20);
return hashcmp (sha1, ce_sha1);
} else {
if (seaf->cdc_average_block_size == 0) {
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
CDC_AVERAGE_BLOCK_SIZE,
CDC_MIN_BLOCK_SIZE,
CDC_MAX_BLOCK_SIZE,
sha1) < 0) {
return -1;
}
} else {
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
seaf->cdc_average_block_size,
seaf->cdc_average_block_size >> 1,
seaf->cdc_average_block_size << 1,
sha1) < 0) {
return -1;
}
}
if (hashcmp (sha1, ce_sha1) == 0)
return 0;
/* Compare with old cdc block size. */
uint32_t block_size = calculate_chunk_size (st->st_size);
if (compute_file_id_with_cdc (path, st, crypt, repo_version,
block_size,
block_size >> 2,
block_size << 2,
sha1) < 0) {
return -1;
}
return hashcmp (sha1, ce_sha1);
}
}
char *
build_checkout_path (const char *worktree, const char *ce_name, int len)
{
int base_len = strlen(worktree);
int full_len;
int offset;
SeafStat st;
if (!len) {
seaf_warning ("entry name should not be empty.\n");
return NULL;
}
GString *path = g_string_new ("");
g_string_append_printf (path, "%s/", worktree);
/* first create all leading directories. */
full_len = base_len + len + 1;
offset = base_len + 1;
while (offset < full_len) {
do {
g_string_append_c (path, ce_name[offset-base_len-1]);
offset++;
} while (offset < full_len && ce_name[offset-base_len-1] != '/');
if (offset >= full_len)
break;
if (seaf_stat (path->str, &st) == 0 && S_ISDIR(st.st_mode))
continue;
if (seaf_util_mkdir (path->str, 0777) < 0) {
g_string_free (path, TRUE);
seaf_warning ("Failed to create directory %s.\n", path->str);
return NULL;
}
}
return g_string_free (path, FALSE);
}
int
delete_path (const char *worktree, const char *name,
unsigned int mode, gint64 old_mtime)
{
char path[SEAF_PATH_MAX];
SeafStat st;
int len = strlen(name);
if (!len) {
seaf_warning ("entry name should not be empty.\n");
return -1;
}
snprintf (path, SEAF_PATH_MAX, "%s/%s", worktree, name);
if (!S_ISDIR(mode)) {
/* file doesn't exist in work tree */
if (seaf_stat (path, &st) < 0 || !S_ISREG(st.st_mode)) {
return 0;
}
/* file has been changed. */
if (!is_eml_file (name) && (old_mtime != st.st_mtime)) {
seaf_warning ("File %s is changed. Skip removing the file.\n", path);
return -1;
}
/* first unlink the file. */
if (seaf_util_unlink (path) < 0) {
seaf_warning ("Failed to remove %s: %s.\n", path, strerror(errno));
return -1;
}
} else {
if (seaf_remove_empty_dir (path) < 0) {
seaf_warning ("Failed to remove dir %s: %s.\n", path, strerror(errno));
return -1;
}
}
/* then remove all empty directories upwards. */
/* offset = base_len + len; */
/* do { */
/* if (path[offset] == '/') { */
/* path[offset] = '\0'; */
/* int ret = seaf_remove_empty_dir (path); */
/* if (ret < 0) { */
/* break; */
/* } */
/* } */
/* } while (--offset > base_len); */
return 0;
}
#ifdef WIN32
static gboolean
check_file_locked (const wchar_t *path_w, gboolean locked_on_server)
{
HANDLE handle;
/* If the file is locked on server, its local access right has been set to
* read-only. So trying to test GENERIC_WRITE access will certainly return
* ACCESS_DENIED. In this case, we can only test for GENERIC_READ.
* MS Office seems to gain exclusive read/write access to the file. So even
* trying read access can return a SHARING_VIOLATION error.
*/
DWORD access_mode = locked_on_server ? GENERIC_READ : GENERIC_WRITE;
handle = CreateFileW (path_w,
access_mode,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle (handle);
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
return TRUE;
}
return FALSE;
}
gboolean
do_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)
{
char *real_path;
wchar_t *real_path_w;
gboolean ret;
real_path = g_build_path(PATH_SEPERATOR, worktree, path, NULL);
real_path_w = win32_long_path (real_path);
ret = check_file_locked (real_path_w, locked_on_server);
g_free (real_path);
g_free (real_path_w);
return ret;
}
static gboolean
check_dir_locked (const wchar_t *path_w)
{
HANDLE handle;
handle = CreateFileW (path_w,
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle (handle);
} else if (GetLastError() == ERROR_SHARING_VIOLATION) {
return TRUE;
}
return FALSE;
}
static gboolean
check_dir_locked_recursive (const wchar_t *path_w)
{
WIN32_FIND_DATAW fdata;
HANDLE handle;
wchar_t *pattern;
wchar_t *sub_path_w;
char *path;
int path_len_w;
DWORD error;
gboolean ret = FALSE;
if (check_dir_locked (path_w))
return TRUE;
path = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);
if (!path)
return FALSE;
path_len_w = wcslen(path_w);
pattern = g_new0 (wchar_t, (path_len_w + 3));
wcscpy (pattern, path_w);
wcscat (pattern, L"\\*");
handle = FindFirstFileW (pattern, &fdata);
if (handle == INVALID_HANDLE_VALUE) {
seaf_warning ("FindFirstFile failed %s: %lu.\n",
path, GetLastError());
goto out;
}
do {
if (wcscmp (fdata.cFileName, L".") == 0 ||
wcscmp (fdata.cFileName, L"..") == 0)
continue;
sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);
wcscpy (sub_path_w, path_w);
wcscat (sub_path_w, L"\\");
wcscat (sub_path_w, fdata.cFileName);
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (check_dir_locked_recursive (sub_path_w)) {
ret = TRUE;
g_free (sub_path_w);
goto out;
}
} else {
if (check_file_locked (sub_path_w, FALSE)) {
ret = TRUE;
g_free (sub_path_w);
goto out;
}
}
g_free (sub_path_w);
} while (FindNextFileW (handle, &fdata) != 0);
error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
seaf_warning ("FindNextFile failed %s: %lu.\n",
path, error);
}
out:
FindClose (handle);
g_free (path);
g_free (pattern);
return ret;
}
gboolean
do_check_dir_locked (const char *path, const char *worktree)
{
char *real_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
wchar_t *real_path_w = win32_long_path (real_path);
gboolean ret = check_dir_locked_recursive (real_path_w);
g_free (real_path);
g_free (real_path_w);
return ret;
}
gboolean
files_locked_on_windows (struct index_state *index, const char *worktree)
{
gboolean ret = FALSE;
int i, entries;
struct cache_entry *ce;
entries = index->cache_nr;
for (i = 0; i < entries; ++i) {
ce = index->cache[i];
if (ce_stage(ce)) {
int mask = 0;
mask |= 1 << (ce_stage(ce) - 1);
while (i < entries) {
struct cache_entry *nce = index->cache[i];
if (strcmp(ce->name, nce->name))
break;
mask |= 1 << (ce_stage(nce) - 1);
i++;
}
i--;
/* Check unmerged cases that can potentially
update or remove current files in the worktree.
*/
if (mask == 7 || /* both changed */
mask == 6 || /* both added */
mask == 3) /* others removed */
{
if (do_check_file_locked (ce->name, worktree, FALSE)) {
ret = TRUE;
break;
}
}
} else if (ce->ce_flags & CE_UPDATE ||
ce->ce_flags & CE_WT_REMOVE) {
if (do_check_file_locked (ce->name, worktree, FALSE)) {
ret = TRUE;
break;
}
}
}
return ret;
}
#endif /* WIN32 */
#ifdef __APPLE__
/* On Mac, we only check whether an office file is opened and locked.
* The detection is done by checking ~$* tmp file.
*/
gboolean
do_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)
{
#define OFFICE_FILE_PATTERN ".*\\.(docx|xlsx|pptx|doc|xls|ppt|vsdx)"
char *dir_name = NULL, *file_name = NULL;
char *fullpath = NULL;
char *tmp_name = NULL;
int ret = FALSE;
if (!g_regex_match_simple (OFFICE_FILE_PATTERN, path, 0, 0))
return FALSE;
dir_name = g_path_get_dirname (path);
if (strcmp (dir_name, ".") == 0) {
g_free (dir_name);
dir_name = g_strdup("");
}
file_name = g_path_get_basename (path);
tmp_name = g_strconcat ("~$", file_name, NULL);
fullpath = g_build_path ("/", worktree, dir_name, tmp_name, NULL);
if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {
ret = TRUE;
goto out;
}
/* Sometimes the first two characters are replaced by ~$. */
if (g_utf8_strlen(file_name, -1) < 2)
goto out;
char *ptr = g_utf8_find_next_char(g_utf8_find_next_char (file_name, NULL), NULL);
g_free (tmp_name);
g_free (fullpath);
tmp_name = g_strconcat ("~$", ptr, NULL);
fullpath = g_build_path ("/", worktree, dir_name, tmp_name, NULL);
if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {
ret = TRUE;
goto out;
}
out:
g_free (fullpath);
g_free (tmp_name);
g_free (dir_name);
g_free (file_name);
return ret;
}
#endif