/* * Copyright (C) 2014 Johannes Schauer * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include #include "hashtbl.h" #include "../mfapi/mfconn.h" #include "../mfapi/file.h" #include "../mfapi/folder.h" #include "../mfapi/apicalls.h" /* * we build a hashtable using the first three characters of the file or folder * key. Since the folder key is encoded in base 36 (10 digits and 26 letters), * this means that the resulting hashtable has to have 36^3=46656 buckets. */ #define NUM_BUCKETS 46656 /* * we use this table to convert from a base36 char to an integer * we "waste" these 128 bytes of memory so that we don't need branching * instructions when decoding * we only need 128 bytes because the input is a *signed* char */ static unsigned char base36_decoding_table[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, /* 0x70 */ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0 }; /* * a macro to convert a char* of the key into a hash of its first three * characters */ #define HASH_OF_KEY(key) base36_decoding_table[(int)(key)[0]]*36*36+\ base36_decoding_table[(int)(key)[1]]*36+\ base36_decoding_table[(int)(key)[2]] struct h_entry { /* * keys are either 13 (folders) or 15 (files) long since the structure * members are most likely 8-byte aligned anyways, it does not make sense * to differentiate between them */ char key[MFAPI_MAX_LEN_KEY + 1]; /* a filename is maximum 255 characters long */ char name[MFAPI_MAX_LEN_NAME + 1]; /* we do not add the parent here because it is not used anywhere */ /* char parent[20]; */ /* local revision */ uint64_t revision; /* creation time */ uint64_t ctime; /* key of the containing folder */ h_entry *parent; /******************** * only for folders * ********************/ /* number of children (files plus folders) */ uint64_t num_children; /* * Array of pointers to its children. Set to zero when storing on disk. * This member could also be an array of keys which would not require * lookups on updating but we expect more reads than writes so we * sacrifice slower updates for faster lookups */ h_entry **children; /****************** * only for files * ******************/ /* SHA256 is 256 bits = 32 bytes */ unsigned char hash[32]; /* * last access time to remove old locally cached files * atime is also never zero for files * a file that has never been accessed has an atime of 1 */ uint64_t atime; /* file size */ uint64_t fsize; }; /* * Each bucket is an array of pointers instead of an array of h_entry so that * the array can be changed without the memory location of the h_entry * changing because the children of each h_entry point to those locations * * This also allows us to implement each bucket as a sorted list in the * future. Queries could then be done using bisection (O(log(n))) instead of * having to traverse all elements in the bucket (O(n)). But with 46656 * buckets this should not make a performance difference often. */ struct folder_tree { uint64_t revision; uint64_t bucket_lens[NUM_BUCKETS]; h_entry **buckets[NUM_BUCKETS]; h_entry root; }; /* persistant storage file layout: * * byte 0: 0x4D -> ASCII M * byte 1: 0x46 -> ASCII F * byte 2: 0x53 -> ASCII S --> MFS == MediaFire Storage * byte 3: 0x00 -> version information * bytes 4-11 -> last seen device revision * bytes 12-19 -> number of h_entry objects (num_hts) * bytes 20... -> h_entry objects, each is 368 byte long * bytes xxx -> immediately following the num_hts objects are arrays of the * children of the preceding h_entry objects in the same order * as the h_entry objects. The children are identified by * uint64_t numbers indicating the index of the objects in the * preceding array of h_entry objects. The number of children * per h_entry object is given in its num_children attribute * * the children pointer member of the h_entry object is useless when stored, * should be set to zero and not used when reading the file */ folder_tree *folder_tree_create(void) { folder_tree *tree; tree = (folder_tree *) calloc(1, sizeof(folder_tree)); return tree; } static void folder_tree_free_entries(folder_tree * tree) { uint64_t i, j; for (i = 0; i < NUM_BUCKETS; i++) { for (j = 0; j < tree->bucket_lens[i]; j++) { free(tree->buckets[i][j]->children); free(tree->buckets[i][j]); } free(tree->buckets[i]); tree->buckets[i] = NULL; tree->bucket_lens[i] = 0; } free(tree->root.children); tree->root.children = NULL; tree->root.num_children = 0; } void folder_tree_destroy(folder_tree * tree) { folder_tree_free_entries(tree); free(tree); } /* * given a folderkey, lookup the h_entry of it in the tree * * if key is NULL, then a pointer to the root is returned * * if no matching h_entry is found, NULL is returned */ static h_entry *folder_tree_lookup_key(folder_tree * tree, const char *key) { int bucket_id; uint64_t i; if (key == NULL || key[0] == '\0') { return &(tree->root); } /* retrieve the right bucket for this key */ bucket_id = HASH_OF_KEY(key); for (i = 0; i < tree->bucket_lens[bucket_id]; i++) { if (strcmp(tree->buckets[bucket_id][i]->key, key) == 0) { return tree->buckets[bucket_id][i]; } } fprintf(stderr, "cannot find h_entry for key %s\n", key); return NULL; } /* * given a path, return the h_entry of the last component * * the path must start with a slash */ static h_entry *folder_tree_lookup_path(folder_tree * tree, const char *path) { char *tmp_path; char *new_path; char *slash_pos; h_entry *curr_dir; h_entry *result; uint64_t i; bool success; if (path[0] != '/') { fprintf(stderr, "Path must start with a slash\n"); return NULL; } curr_dir = &(tree->root); // if the root is requested, return directly if (strcmp(path, "/") == 0) { return curr_dir; } // strip off the leading slash new_path = strdup(path + 1); tmp_path = new_path; result = NULL; for (;;) { // path with a trailing slash, so the remainder is of zero length if (tmp_path[0] == '\0') { // return curr_dir result = curr_dir; break; } slash_pos = index(tmp_path, '/'); if (slash_pos == NULL) { // no slash found in the remaining path: // find entry in current directory and return it for (i = 0; i < curr_dir->num_children; i++) { if (strcmp(curr_dir->children[i]->name, tmp_path) == 0) { // return this directory result = curr_dir->children[i]; break; } } // no matter whether the last part was found or not, iteration // stops here break; } *slash_pos = '\0'; // a slash was found, so recurse into the directory of that name or // abort if the name matches a file success = false; for (i = 0; i < curr_dir->num_children; i++) { if (strcmp(curr_dir->children[i]->name, tmp_path) == 0) { // test if a file matched if (curr_dir->children[i]->atime != 0) { fprintf(stderr, "A file can only be at the end of a path\n"); break; } // a directory matched, break out of this loop and recurse // deeper in the next iteration curr_dir = curr_dir->children[i]; success = true; break; } } // either a file was part of a path or a folder of matching name was // not found, so we break out of this loop too if (!success) { break; } // point tmp_path to the character after the last found slash tmp_path = slash_pos + 1; } free(new_path); return result; } int folder_tree_getattr(folder_tree * tree, const char *path, struct stat *stbuf) { h_entry *entry; entry = folder_tree_lookup_path(tree, path); if (entry == NULL) { return -ENOENT; } stbuf->st_uid = geteuid(); stbuf->st_gid = getegid(); stbuf->st_ctime = entry->ctime; stbuf->st_mtime = entry->ctime; if (entry->atime == 0) { /* folder */ stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = entry->num_children + 2; stbuf->st_atime = entry->ctime; stbuf->st_size = 1024; } else { /* file */ stbuf->st_mode = S_IFREG | 0666; stbuf->st_nlink = 1; stbuf->st_atime = entry->atime; stbuf->st_size = entry->fsize; } return 0; } int folder_tree_readdir(folder_tree * tree, const char *path, void *buf, fuse_fill_dir_t filldir) { h_entry *entry; uint64_t i; entry = folder_tree_lookup_path(tree, path); /* either directory not found or found entry is not a directory */ if (entry == NULL || entry->atime != 0) { return -ENOENT; } filldir(buf, ".", NULL, 0); filldir(buf, "..", NULL, 0); for (i = 0; i < entry->num_children; i++) { filldir(buf, entry->children[i]->name, NULL, 0); } return 0; } static bool folder_tree_is_root(h_entry * entry) { if (entry == NULL) { fprintf(stderr, "entry is NULL\n"); return false; } return (entry->name[0] == '\0' || (strncmp(entry->name, "myfiles", sizeof(entry->name)) == 0)) && entry->key[0] == '\0'; } /* * given a key and the new parent, this function makes sure to allocate new * memory if necessary and adjust the children arrays of the former and new * parent to accommodate for the change */ static h_entry *folder_tree_allocate_entry(folder_tree * tree, const char *key, h_entry * new_parent) { h_entry *entry; int bucket_id; h_entry *old_parent; uint64_t i; bool found; if (tree == NULL) { fprintf(stderr, "tree cannot be NULL\n"); return NULL; } if (new_parent == NULL) { fprintf(stderr, "new parent cannot be NULL\n"); return NULL; } entry = folder_tree_lookup_key(tree, key); if (entry == NULL) { fprintf(stderr, "key is NULL but this is fine, we just create it now\n"); /* entry was not found, so append it to the end of the bucket */ entry = (h_entry *) calloc(1, sizeof(h_entry)); bucket_id = HASH_OF_KEY(key); tree->bucket_lens[bucket_id]++; tree->buckets[bucket_id] = realloc(tree->buckets[bucket_id], sizeof(h_entry *) * tree->bucket_lens[bucket_id]); if (tree->buckets[bucket_id] == NULL) { fprintf(stderr, "realloc failed\n"); return NULL; } tree->buckets[bucket_id][tree->bucket_lens[bucket_id] - 1] = entry; /* since this entry is new, add it to the children of its parent * * since the key of this file or folder did not exist in the * hashtable, we do not have to check whether the parent already has * it as a child but can just append to its list of children */ new_parent->num_children++; new_parent->children = (h_entry **) realloc(new_parent->children, new_parent->num_children * sizeof(h_entry *)); if (new_parent->children == NULL) { fprintf(stderr, "realloc failed\n"); return NULL; } new_parent->children[new_parent->num_children - 1] = entry; return entry; } /* Entry was found so check if the old parent is different from the * new parent. If yes, we need to adjust the children of the old and new * parent. */ old_parent = entry->parent; /* parent stays the same - nothing to do */ if (old_parent == new_parent) return entry; /* check whether entry does not have a parent (this is the case for the * root node) */ if (old_parent != NULL) { /* remove the file or folder from the old parent */ for (i = 0; i < old_parent->num_children; i++) { if (old_parent->children[i] == entry) { /* move the entries on the right one place to the left */ memmove(old_parent->children[i], old_parent->children[i + 1], sizeof(h_entry *) * (old_parent->num_children - i - 1)); old_parent->num_children--; /* change the children size */ if (old_parent->num_children) { free(old_parent->children); old_parent->children = NULL; } else { old_parent->children = (h_entry **) realloc(old_parent->children, old_parent->num_children * sizeof(h_entry *)); if (old_parent->children == NULL) { fprintf(stderr, "realloc failed\n"); return NULL; } } } } } else { /* sanity check: if the parent was NULL then this entry must be the * root */ if (!folder_tree_is_root(entry)) { fprintf(stderr, "the parent was NULL so this node should be root but is not\n"); fprintf(stderr, "name: %s, key: %s\n", entry->name, entry->key); return NULL; } } /* and add it to the new */ /* since the entry already existed, it can be that the new parent * already contains the child */ found = false; for (i = 0; i < new_parent->num_children; i++) { if (new_parent->children[i] == entry) { found = true; break; } } if (!found) { new_parent->num_children++; new_parent->children = (h_entry **) realloc(new_parent->children, new_parent->num_children * sizeof(h_entry *)); if (new_parent->children == 0) { fprintf(stderr, "realloc failed\n"); return NULL; } new_parent->children[new_parent->num_children - 1] = entry; } return entry; } /* * When adding an existing key, the old key is overwritten. * Return the inserted or updated key */ static h_entry *folder_tree_add_file(folder_tree * tree, mffile * file, h_entry * parent) { h_entry *entry; const char *key; if (tree == NULL) { fprintf(stderr, "tree cannot be NULL\n"); return NULL; } if (file == NULL) { fprintf(stderr, "file cannot be NULL\n"); return NULL; } if (parent == NULL) { fprintf(stderr, "parent cannot be NULL\n"); return NULL; } key = file_get_key(file); entry = folder_tree_allocate_entry(tree, key, parent); strncpy(entry->key, key, sizeof(entry->key)); strncpy(entry->name, file_get_name(file), sizeof(entry->name)); entry->parent = parent; entry->revision = file_get_revision(file); entry->ctime = file_get_created(file); entry->fsize = file_get_size(file); /* mark this h_entry as a file if its atime is not set yet */ if (entry->atime == 0) entry->atime = 1; return entry; } /* given an mffolder, add its information to a new h_entry, or update an * existing h_entry in the hashtable * * returns the added or updated h_entry */ static h_entry *folder_tree_add_folder(folder_tree * tree, mffolder * folder, h_entry * parent) { h_entry *entry; const char *key; const char *name; if (tree == NULL) { fprintf(stderr, "tree cannot be NULL\n"); return NULL; } if (folder == NULL) { fprintf(stderr, "folder cannot be NULL\n"); return NULL; } if (parent == NULL) { fprintf(stderr, "parent cannot be NULL\n"); return NULL; } key = folder_get_key(folder); entry = folder_tree_allocate_entry(tree, key, parent); /* can be NULL for root */ if (key != NULL) strncpy(entry->key, key, sizeof(entry->key)); /* can be NULL for root */ name = folder_get_name(folder); if (name != NULL) strncpy(entry->name, name, sizeof(entry->name)); entry->revision = folder_get_revision(folder); entry->ctime = folder_get_created(folder); entry->parent = parent; return entry; } /* When trying to delete a non-existing key, nothing happens */ static void folder_tree_remove(folder_tree * tree, const char *key) { int bucket_id; int found; uint64_t i; if (key == NULL) { fprintf(stderr, "cannot remove root"); return; } bucket_id = HASH_OF_KEY(key); /* check if the key exists */ found = 0; for (i = 0; i < tree->bucket_lens[bucket_id]; i++) { if (strcmp(tree->buckets[bucket_id][i]->key, key) == 0) { found = 1; break; } } /* if found, use the last value of i to adjust the bucket */ if (found) { /* remove its possible children */ free(tree->buckets[bucket_id][i]->children); /* remove entry */ free(tree->buckets[bucket_id][i]); /* move the items on the right one place to the left */ memmove(tree->buckets[bucket_id][i], tree->buckets[bucket_id][i + 1], sizeof(h_entry *) * (tree->bucket_lens[bucket_id] - i - 1)); /* change bucket size */ tree->bucket_lens[bucket_id]--; if (tree->bucket_lens[bucket_id] == 0) { free(tree->buckets[bucket_id]); tree->buckets[bucket_id] = NULL; } else { tree->buckets[bucket_id] = realloc(tree->buckets[bucket_id], sizeof(h_entry) * tree->bucket_lens[bucket_id]); if (tree->buckets[bucket_id] == NULL) { fprintf(stderr, "realloc failed\n"); return; } } } } /* * check if a h_entry is the parent of another * * this checks only pointer equivalence and does not compare the key for * better performance * * thus, this function relies on the fact that only one h_entry per key exists * * This function does not use the parent member of the child. If you want to * rely on that, then use it directly. */ static bool folder_tree_is_parent_of(h_entry * parent, h_entry * child) { uint64_t i; bool found; found = false; for (i = 0; i < parent->num_children; i++) { if (parent->children[i] == child) { found = true; break; } } return found; } /* * given a h_entry, this function gets the remote content of that folder and * fills its children * * it then recurses for each child that is a directory and does the same in a * full remote directory walk */ static int folder_tree_rebuild_helper(folder_tree * tree, mfconn * conn, h_entry * curr_entry, bool recurse) { int retval; mffolder **folder_result; mffile **file_result; h_entry *entry; int i; /* * free the old children array of this folder * we don't free the children it references because they might be * referenced by someone else */ free(curr_entry->children); curr_entry->children = NULL; curr_entry->num_children = 0; /* first folders */ folder_result = NULL; retval = mfconn_api_folder_get_content(conn, 0, curr_entry->key, &folder_result, NULL); mfconn_update_secret_key(conn); if (retval != 0) { fprintf(stderr, "folder/get_content failed\n"); if (folder_result != NULL) { for (i = 0; folder_result[i] != NULL; i++) { free(folder_result[i]); } free(folder_result); } return -1; } for (i = 0; folder_result[i] != NULL; i++) { if (folder_get_key(folder_result[i]) == NULL) { fprintf(stderr, "folder_get_key returned NULL\n"); folder_free(folder_result[i]); } entry = folder_tree_add_folder(tree, folder_result[i], curr_entry); /* recurse */ if (recurse) folder_tree_rebuild_helper(tree, conn, entry, true); folder_free(folder_result[i]); } free(folder_result); /* then files */ file_result = NULL; retval = mfconn_api_folder_get_content(conn, 1, curr_entry->key, NULL, &file_result); mfconn_update_secret_key(conn); if (retval != 0) { fprintf(stderr, "folder/get_content failed\n"); if (file_result != NULL) { for (i = 0; file_result[i] != NULL; i++) { file_free(file_result[i]); } free(file_result); } return -1; } for (i = 0; file_result[i] != NULL; i++) { if (file_get_key(file_result[i]) == NULL) { fprintf(stderr, "file_get_key returned NULL\n"); file_free(file_result[i]); } entry = folder_tree_add_file(tree, file_result[i], curr_entry); file_free(file_result[i]); } free(file_result); return 0; } /* * update the fields of a file */ static int folder_tree_update_file_info(folder_tree * tree, mfconn * conn, char *key) { mffile *file; int retval; h_entry *parent; file = file_alloc(); retval = mfconn_api_file_get_info(conn, file, key); mfconn_update_secret_key(conn); if (retval != 0) { fprintf(stderr, "api call unsuccessful\n"); /* TODO: check reason. It might be that the remote object does not * exist anymore in which case it has to be removed locally */ file_free(file); return -1; } parent = folder_tree_lookup_key(tree, file_get_parent(file)); if (parent == NULL) { fprintf(stderr, "file_tree_lookup_key failed\n"); return -1; } folder_tree_add_file(tree, file, parent); file_free(file); return 0; } /* * update the fields of a folder without checking its children * * we identify the folder to update by its key instead of its h_entry because * this function is to fill the h_entry in the first place */ static int folder_tree_update_folder_info(folder_tree * tree, mfconn * conn, char *key) { mffolder *folder; int retval; h_entry *parent; if (key != NULL && strcmp(key, "trash")) { fprintf(stderr, "cannot get folder info of trash\n"); return -1; } folder = folder_alloc(); retval = mfconn_api_folder_get_info(conn, folder, key); mfconn_update_secret_key(conn); if (retval != 0) { fprintf(stderr, "api call unsuccessful\n"); /* TODO: check reason. It might be that the remote object does not * exist anymore in which case it has to be removed locally */ folder_free(folder); return -1; } parent = folder_tree_lookup_key(tree, folder_get_parent(folder)); if (parent == NULL) { fprintf(stderr, "folder_tree_lookup_key failed\n"); return -1; } folder_tree_add_folder(tree, folder, parent); folder_free(folder); return 0; } /* * ask the remote if there are changes after the locally stored revision * * if yes, integrate those changes */ void folder_tree_update(folder_tree * tree, mfconn * conn) { uint64_t revision_remote; uint64_t i; struct mfconn_device_change *changes; int retval; mfconn_api_device_get_status(conn, &revision_remote); mfconn_update_secret_key(conn); if (tree->revision == revision_remote) { fprintf(stderr, "Request to update but nothing to do\n"); return; } /* * root never shows up in device_get_changes but since we rely on the * parent information of files and folders, we do not manually retrieve * its content */ /* * changes have to be applied in the right order but the result of * mfconn_api_device_get_changes is already sorted by revision */ changes = NULL; retval = mfconn_api_device_get_changes(conn, tree->revision, &changes); mfconn_update_secret_key(conn); if (retval != 0) { fprintf(stderr, "device/get_changes() failed\n"); return; } /* * TODO: before calling remote functions here, check if the revision of * the local object is lower than the one of the reported change * * TODO: only use the latest revision of the same file-/folderkey */ for (i = 0; changes[i].change != MFCONN_DEVICE_CHANGE_END; i++) { switch (changes[i].change) { case MFCONN_DEVICE_CHANGE_DELETED_FOLDER: case MFCONN_DEVICE_CHANGE_DELETED_FILE: folder_tree_remove(tree, changes[i].key); break; case MFCONN_DEVICE_CHANGE_UPDATED_FOLDER: /* ignore updates of the folder key "trash" or folders with * the parent folder key "trash" */ if (strcmp(changes[i].key, "trash") == 0) continue; if (strcmp(changes[i].parent, "trash") == 0) continue; /* if a folder has been updated then its name or location * might have changed */ folder_tree_update_folder_info(tree, conn, changes[i].key); /* its content might also have changed but we use the changes * to files to update it */ break; case MFCONN_DEVICE_CHANGE_UPDATED_FILE: /* ignore files updated in trash */ if (strcmp(changes[i].parent, "trash") == 0) continue; /* if a file changed, update its info */ folder_tree_update_file_info(tree, conn, changes[i].key); break; case MFCONN_DEVICE_CHANGE_END: break; } } /* the new revision of the tree is the revision of the terminating change * */ tree->revision = changes[i].revision; /* * it can happen that another change happened remotely while we were * trying to integrate the changes reported by the last device/get_changes * results. In that case, the file and folder information we retrieve will * have a revision greater than the local device revision we store. This * can also lead to lost changes. But this will only be temporary as the * situation should be rectified once the next device/get_changes is done. * * Just remember that due to this it can happen that the revision of the * local tree is less than the highest revision of a h_entry it stores. */ /* now fix up any possible errors */ /* clean the resulting folder_tree of any dangling objects */ fprintf(stderr, "tree before cleaning:\n"); folder_tree_debug(tree, NULL, 0); folder_tree_housekeep(tree, conn); fprintf(stderr, "tree after cleaning:\n"); folder_tree_debug(tree, NULL, 0); } /* * rebuild the folder_tree by a walk of the remote filesystem * * is called to initialize the folder_tree on first use * * might also be called when local and remote version get out of sync */ int folder_tree_rebuild(folder_tree * tree, mfconn * conn) { uint64_t revision_before; int ret; /* free local folder_tree */ folder_tree_free_entries(tree); /* get remote device revision before walking the tree */ ret = mfconn_api_device_get_status(conn, &revision_before); mfconn_update_secret_key(conn); if (ret != 0) { fprintf(stderr, "device/get_status call unsuccessful\n"); return -1; } tree->revision = revision_before; /* walk the remote tree to build the folder_tree */ /* populate the root */ ret = folder_tree_update_folder_info(tree, conn, NULL); if (ret != 0) { fprintf(stderr, "folder_tree_update_folder_info unsuccessful\n"); return -1; } folder_tree_rebuild_helper(tree, conn, &(tree->root), true); /* * call device/get_changes to get possible remote changes while we walked * the tree. */ folder_tree_update(tree, conn); return 0; } /* * clean up files and folders that are never referenced * * first find all folders that have children that do not reference their * parent. If a discrepancy is found, ask the remote for the true list of * children of that folder. * * then find all files and folders that have a parent that does not reference * them. If a discrepancy is found, ask the remote for the true parent of that * file. */ void folder_tree_housekeep(folder_tree * tree, mfconn * conn) { uint64_t i, j, k; bool found; /* * find objects with children who claim to have a different parent */ /* first check the root as a special case */ found = false; for (k = 0; k < tree->root.num_children; k++) { /* only compare pointers and not keys. This relies on keys * being unique */ if (tree->root.children[k]->parent != &(tree->root)) { fprintf(stderr, "root claims that %s is its child but %s doesn't think so\n", tree->root.children[k]->key, tree->root.children[k]->key); found = true; break; } } if (found) { folder_tree_rebuild_helper(tree, conn, &(tree->root), false); } /* then check the hashtable */ for (i = 0; i < NUM_BUCKETS; i++) { for (j = 0; j < tree->bucket_lens[i]; j++) { found = false; for (k = 0; k < tree->buckets[i][j]->num_children; k++) { /* only compare pointers and not keys. This relies on keys * being unique */ if (tree->buckets[i][j]->children[k]->parent != tree->buckets[i][j]) { fprintf(stderr, "%s claims that %s is its child but %s doesn't think so\n", tree->buckets[i][j]->key, tree->buckets[i][j]->children[k]->key, tree->buckets[i][j]->children[k]->key); found = true; break; } } if (found) { /* an entry was found that claims to have a different parent, * so ask the remote to retrieve the real list of children */ folder_tree_rebuild_helper(tree, conn, tree->buckets[i][j], false); } } } /* find objects whose parents do not match their actual parents */ for (i = 0; i < NUM_BUCKETS; i++) { for (j = 0; j < tree->bucket_lens[i]; j++) { if (!folder_tree_is_parent_of (tree->buckets[i][j]->parent, tree->buckets[i][j])) { fprintf(stderr, "%s claims that %s is its parent but it is not\n", tree->buckets[i][j]->key, tree->buckets[i][j]->parent->key); if (tree->buckets[i][j]->atime == 0) { /* folder */ folder_tree_update_folder_info(tree, conn, tree->buckets[i][j]->key); } else { /* file */ folder_tree_update_file_info(tree, conn, tree->buckets[i][j]->key); } } } } /* TODO: remove unreferenced cached files */ /* * for file in cache directory: * e = folder_tree_lookup_key(file.key) * if e == None: * delete(file) */ } void folder_tree_debug(folder_tree * tree, h_entry * ent, int depth) { uint64_t i; if (ent == NULL) { ent = &(tree->root); } for (i = 0; i < ent->num_children; i++) { if (ent->children[i]->atime == 0) { /* folder */ fprintf(stderr, "%*s d:%s k:%s p:%s\n", depth + 1, " ", ent->children[i]->name, ent->children[i]->key, ent->children[i]->parent->key); folder_tree_debug(tree, ent->children[i], depth + 1); } else { /* file */ fprintf(stderr, "%*s f:%s k:%s p:%s\n", depth + 1, " ", ent->children[i]->name, ent->children[i]->key, ent->children[i]->parent->key); } } }