diff --git a/Makefile-hacktree.am b/Makefile-hacktree.am index 308f3c03..77bf5470 100644 --- a/Makefile-hacktree.am +++ b/Makefile-hacktree.am @@ -13,6 +13,8 @@ libhtutil_la_LIBADD = $(GIO_UNIX_LIBS) noinst_LTLIBRARIES += libhacktree.la libhacktree_la_SOURCES = src/libhacktree/hacktree.h \ + src/libhacktree/hacktree-core.c \ + src/libhacktree/hacktree-core.h \ src/libhacktree/hacktree-repo.c \ src/libhacktree/hacktree-repo.h \ src/libhacktree/hacktree-types.h \ @@ -26,6 +28,7 @@ hacktree_SOURCES = src/main.c \ src/ht-builtins.h \ src/ht-builtin-init.c \ src/ht-builtin-link-file.c \ + src/ht-builtin-fsck.c \ $(NULL) hacktree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS) hacktree_LDADD = libhtutil.la libhacktree.la $(GIO_UNIX_LIBS) diff --git a/src/ht-builtin-fsck.c b/src/ht-builtin-fsck.c new file mode 100644 index 00000000..61737c5f --- /dev/null +++ b/src/ht-builtin-fsck.c @@ -0,0 +1,114 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#include "config.h" + +#include "ht-builtins.h" +#include "hacktree.h" + +#include + +static char *repo_path; + +static GOptionEntry options[] = { + { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL }, + { NULL } +}; + +typedef struct { + guint n_objects; +} HtFsckData; + +static void +object_iter_callback (HacktreeRepo *repo, + const char *path, + GFileInfo *file_info, + gpointer user_data) +{ + HtFsckData *data = user_data; + guint32 nlinks; + struct stat stbuf; + GChecksum *checksum = NULL; + GError *error = NULL; + char *dirname; + char *checksum_prefix; + char *checksum_string; + + dirname = g_path_get_dirname (path); + checksum_prefix = g_path_get_basename (dirname); + + nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink"); + + if (nlinks < 2) + g_printerr ("note: floating object: %s\n", path); + + if (!hacktree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error)) + goto out; + + checksum_string = g_strconcat (checksum_prefix, g_file_info_get_name (file_info), NULL); + + if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0) + { + g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n", + path, g_checksum_get_string (checksum)); + } + + data->n_objects++; + + out: + if (checksum != NULL) + g_checksum_free (checksum); + g_free (dirname); + g_free (checksum_prefix); + if (error != NULL) + { + g_printerr ("%s\n", error->message); + g_clear_error (&error); + } +} + +gboolean +hacktree_builtin_fsck (int argc, const char **argv, const char *prefix, GError **error) +{ + HtFsckData data; + gboolean ret = FALSE; + HacktreeRepo *repo = NULL; + int i; + + if (repo_path == NULL) + repo_path = "."; + + data.n_objects = 0; + + repo = hacktree_repo_new (repo_path); + if (!hacktree_repo_check (repo, error)) + goto out; + + if (!hacktree_repo_iter_objects (repo, object_iter_callback, &data, error)) + goto out; + + g_printerr ("Total Objects: %u\n", data.n_objects); + + ret = TRUE; + out: + g_clear_object (&repo); + return ret; +} diff --git a/src/ht-builtins.h b/src/ht-builtins.h index fee97773..58e69144 100644 --- a/src/ht-builtins.h +++ b/src/ht-builtins.h @@ -38,6 +38,7 @@ typedef struct { gboolean hacktree_builtin_init (int argc, const char **argv, const char *prefix, GError **error); gboolean hacktree_builtin_link_file (int argc, const char **argv, const char *prefix, GError **error); +gboolean hacktree_builtin_fsck (int argc, const char **argv, const char *prefix, GError **error); G_END_DECLS diff --git a/src/libhacktree/hacktree-core.c b/src/libhacktree/hacktree-core.c new file mode 100644 index 00000000..ddda1b5a --- /dev/null +++ b/src/libhacktree/hacktree-core.c @@ -0,0 +1,203 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#include "config.h" + +#include "hacktree.h" +#include "htutil.h" + +char * +stat_to_string (struct stat *stbuf) +{ + return g_strdup_printf ("%d:%d:%d", stbuf->st_mode, stbuf->st_uid, stbuf->st_gid); +} + +static char * +canonicalize_xattrs (char *xattr_string, size_t len) +{ + char *p; + GSList *xattrs = NULL; + GSList *iter; + GString *result; + + result = g_string_new (0); + + p = xattr_string; + while (p < xattr_string+len) + { + xattrs = g_slist_prepend (xattrs, p); + p += strlen (p) + 1; + } + + xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp); + for (iter = xattrs; iter; iter = iter->next) + g_string_append (result, iter->data); + + g_slist_free (xattrs); + return g_string_free (result, FALSE); +} + +gboolean +hacktree_stat_and_checksum_file (int dir_fd, const char *path, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error) +{ + GChecksum *content_sha256 = NULL; + GChecksum *content_and_meta_sha256 = NULL; + char *stat_string = NULL; + ssize_t bytes_read; + char *xattrs = NULL; + char *xattrs_canonicalized = NULL; + int fd = -1; + DIR *temp_dir = NULL; + char *basename = NULL; + gboolean ret = FALSE; + char *symlink_target = NULL; + char *device_id = NULL; + + basename = g_path_get_basename (path); + + if (dir_fd == -1) + { + char *dirname = g_path_get_dirname (path); + temp_dir = opendir (dirname); + if (temp_dir == NULL) + { + ht_util_set_error_from_errno (error, errno); + g_free (dirname); + } + g_free (dirname); + dir_fd = dirfd (temp_dir); + } + + if (fstatat (dir_fd, basename, out_stbuf, AT_SYMLINK_NOFOLLOW) < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + + if (!S_ISLNK(out_stbuf->st_mode)) + { + fd = ht_util_open_file_read_at (dir_fd, basename, error); + if (fd < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + } + + stat_string = stat_to_string (out_stbuf); + + /* FIXME - Add llistxattrat */ + if (!S_ISLNK(out_stbuf->st_mode)) + bytes_read = flistxattr (fd, NULL, 0); + else + bytes_read = llistxattr (path, NULL, 0); + + if (bytes_read < 0) + { + if (errno != ENOTSUP) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + } + if (errno != ENOTSUP) + { + gboolean tmp; + xattrs = g_malloc (bytes_read); + /* FIXME - Add llistxattrat */ + if (!S_ISLNK(out_stbuf->st_mode)) + tmp = flistxattr (fd, xattrs, bytes_read); + else + tmp = llistxattr (path, xattrs, bytes_read); + + if (!tmp) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + + xattrs_canonicalized = canonicalize_xattrs (xattrs, bytes_read); + } + + content_sha256 = g_checksum_new (G_CHECKSUM_SHA256); + + if (S_ISREG(out_stbuf->st_mode)) + { + guint8 buf[8192]; + + while ((bytes_read = read (fd, buf, sizeof (buf))) > 0) + g_checksum_update (content_sha256, buf, bytes_read); + if (bytes_read < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (S_ISLNK(out_stbuf->st_mode)) + { + symlink_target = g_malloc (PATH_MAX); + + if (readlinkat (dir_fd, basename, symlink_target, PATH_MAX) < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + g_checksum_update (content_sha256, symlink_target, strlen (symlink_target)); + } + else if (S_ISCHR(out_stbuf->st_mode) || S_ISBLK(out_stbuf->st_mode)) + { + device_id = g_strdup_printf ("%d", out_stbuf->st_rdev); + g_checksum_update (content_sha256, device_id, strlen (device_id)); + } + else + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unsupported file '%s' (must be regular, symbolic link, or device)", + path); + goto out; + } + + content_and_meta_sha256 = g_checksum_copy (content_sha256); + + g_checksum_update (content_and_meta_sha256, stat_string, strlen (stat_string)); + g_checksum_update (content_and_meta_sha256, xattrs_canonicalized, strlen (stat_string)); + + *out_checksum = content_and_meta_sha256; + ret = TRUE; + out: + if (fd >= 0) + close (fd); + if (temp_dir != NULL) + closedir (temp_dir); + g_free (symlink_target); + g_free (basename); + g_free (stat_string); + g_free (xattrs); + g_free (xattrs_canonicalized); + if (content_sha256) + g_checksum_free (content_sha256); + + return ret; +} diff --git a/src/libhacktree/hacktree-core.h b/src/libhacktree/hacktree-core.h new file mode 100644 index 00000000..124a90a5 --- /dev/null +++ b/src/libhacktree/hacktree-core.h @@ -0,0 +1,36 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ +/* hacktree-repo.h */ + +#ifndef _HACKTREE_CORE +#define _HACKTREE_CORE + +#include + +G_BEGIN_DECLS + +gboolean hacktree_stat_and_checksum_file (int dirfd, const char *path, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error); + + +#endif /* _HACKTREE_REPO */ diff --git a/src/libhacktree/hacktree-repo.c b/src/libhacktree/hacktree-repo.c index bb0a9b80..a95f92d6 100644 --- a/src/libhacktree/hacktree-repo.c +++ b/src/libhacktree/hacktree-repo.c @@ -167,173 +167,6 @@ hacktree_repo_check (HacktreeRepo *self, GError **error) return TRUE; } -char * -stat_to_string (struct stat *stbuf) -{ - return g_strdup_printf ("%d:%d:%d:%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT, - stbuf->st_mode, - stbuf->st_uid, - stbuf->st_gid, - stbuf->st_atime, - stbuf->st_mtime, - stbuf->st_ctime); -} - -static char * -canonicalize_xattrs (char *xattr_string, size_t len) -{ - char *p; - GSList *xattrs = NULL; - GSList *iter; - GString *result; - - result = g_string_new (0); - - p = xattr_string; - while (p < xattr_string+len) - { - xattrs = g_slist_prepend (xattrs, p); - p += strlen (p) + 1; - } - - xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp); - for (iter = xattrs; iter; iter = iter->next) - g_string_append (result, iter->data); - - g_slist_free (xattrs); - return g_string_free (result, FALSE); -} - -static gboolean -stat_and_compute_checksum (int dirfd, const char *path, - GChecksum **out_checksum, - struct stat *out_stbuf, - GError **error) -{ - GChecksum *content_sha256 = NULL; - GChecksum *content_and_meta_sha256 = NULL; - char *stat_string = NULL; - ssize_t bytes_read; - char *xattrs = NULL; - char *xattrs_canonicalized = NULL; - int fd = -1; - char *basename = NULL; - gboolean ret = FALSE; - char *symlink_target = NULL; - char *device_id = NULL; - - basename = g_path_get_basename (path); - - if (fstatat (dirfd, basename, out_stbuf, AT_SYMLINK_NOFOLLOW) < 0) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - - if (!S_ISLNK(out_stbuf->st_mode)) - { - fd = ht_util_open_file_read_at (dirfd, basename, error); - if (fd < 0) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - } - - stat_string = stat_to_string (out_stbuf); - - /* FIXME - Add llistxattrat */ - if (!S_ISLNK(out_stbuf->st_mode)) - bytes_read = flistxattr (fd, NULL, 0); - else - bytes_read = llistxattr (path, NULL, 0); - - if (bytes_read < 0) - { - if (errno != ENOTSUP) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - } - if (errno != ENOTSUP) - { - gboolean tmp; - xattrs = g_malloc (bytes_read); - /* FIXME - Add llistxattrat */ - if (!S_ISLNK(out_stbuf->st_mode)) - tmp = flistxattr (fd, xattrs, bytes_read); - else - tmp = llistxattr (path, xattrs, bytes_read); - - if (!tmp) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - - xattrs_canonicalized = canonicalize_xattrs (xattrs, bytes_read); - } - - content_sha256 = g_checksum_new (G_CHECKSUM_SHA256); - - if (S_ISREG(out_stbuf->st_mode)) - { - guint8 buf[8192]; - - while ((bytes_read = read (fd, buf, sizeof (buf))) > 0) - g_checksum_update (content_sha256, buf, bytes_read); - if (bytes_read < 0) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - } - else if (S_ISLNK(out_stbuf->st_mode)) - { - symlink_target = g_malloc (PATH_MAX); - - if (readlinkat (dirfd, basename, symlink_target, PATH_MAX) < 0) - { - ht_util_set_error_from_errno (error, errno); - goto out; - } - g_checksum_update (content_sha256, symlink_target, strlen (symlink_target)); - } - else if (S_ISCHR(out_stbuf->st_mode) || S_ISBLK(out_stbuf->st_mode)) - { - device_id = g_strdup_printf ("%d", out_stbuf->st_rdev); - g_checksum_update (content_sha256, device_id, strlen (device_id)); - } - else - { - g_set_error (error, G_IO_ERROR, - G_IO_ERROR_FAILED, - "Unsupported file '%s' (must be regular, symbolic link, or device)", - path); - goto out; - } - - content_and_meta_sha256 = g_checksum_copy (content_sha256); - - g_checksum_update (content_and_meta_sha256, stat_string, strlen (stat_string)); - g_checksum_update (content_and_meta_sha256, xattrs_canonicalized, strlen (stat_string)); - - *out_checksum = content_and_meta_sha256; - ret = TRUE; - out: - if (fd >= 0) - close (fd); - g_free (symlink_target); - g_free (basename); - g_free (stat_string); - g_free (xattrs); - g_free (xattrs_canonicalized); - if (content_sha256) - g_checksum_free (content_sha256); - - return ret; -} static char * prepare_dir_for_checksum_get_object_path (HacktreeRepo *self, @@ -346,14 +179,14 @@ prepare_dir_for_checksum_get_object_path (HacktreeRepo *self, char *object_path = NULL; GError *temp_error = NULL; - checksum_prefix = g_strdup (g_checksum_get_string (checksum)); - checksum_prefix[2] = '\0'; + checksum_prefix = g_strndup (g_checksum_get_string (checksum), 2); + g_assert_cmpuint (strlen (checksum_prefix), ==, 2); checksum_dir = g_build_filename (priv->objects_path, checksum_prefix, NULL); if (!ht_util_ensure_directory (checksum_dir, FALSE, error)) goto out; - object_path = g_build_filename (checksum_dir, checksum_prefix + 3, NULL); + object_path = g_build_filename (checksum_dir, g_checksum_get_string (checksum) + 2, NULL); out: g_free (checksum_prefix); g_free (checksum_dir); @@ -387,7 +220,7 @@ link_one_file (HacktreeRepo *self, const char *path, GError **error) goto out; } - if (!stat_and_compute_checksum (dirfd (src_dir), path, &id, &stbuf, error)) + if (!hacktree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error)) goto out; dest_path = prepare_dir_for_checksum_get_object_path (self, id, error); if (!dest_path) @@ -434,3 +267,115 @@ hacktree_repo_link_file (HacktreeRepo *self, return link_one_file (self, path, error); } + +static gboolean +iter_object_dir (HacktreeRepo *repo, + GFile *dir, + HacktreeRepoObjectIter callback, + gpointer user_data, + GError **error) +{ + gboolean ret = FALSE; + GError *temp_error = NULL; + GFileEnumerator *enumerator = NULL; + GFileInfo *file_info = NULL; + char *dirpath = NULL; + + dirpath = g_file_get_path (dir); + + enumerator = g_file_enumerate_children (dir, "standard::*,unix::*", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error); + if (!enumerator) + goto out; + + while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL) + { + const char *name; + guint32 type; + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + /* 64 - 2 */ + if (strlen (name) == 62 && type != G_FILE_TYPE_DIRECTORY) + { + char *path = g_build_filename (dirpath, name, NULL); + callback (repo, path, file_info, user_data); + g_free (path); + } + + g_object_unref (file_info); + } + if (file_info == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + if (!g_file_enumerator_close (enumerator, NULL, error)) + goto out; + + ret = TRUE; + out: + g_free (dirpath); + return ret; +} + +gboolean +hacktree_repo_iter_objects (HacktreeRepo *self, + HacktreeRepoObjectIter callback, + gpointer user_data, + GError **error) +{ + HacktreeRepoPrivate *priv = GET_PRIVATE (self); + GFile *objectdir = NULL; + GFileEnumerator *enumerator = NULL; + gboolean ret = FALSE; + GFileInfo *file_info = NULL; + GError *temp_error = NULL; + + g_return_val_if_fail (priv->inited, FALSE); + + objectdir = g_file_new_for_path (priv->objects_path); + enumerator = g_file_enumerate_children (objectdir, "standard::*,unix::*", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error); + if (!enumerator) + goto out; + + while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL) + { + const char *name; + guint32 type; + + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY) + { + GFile *objdir = g_file_get_child (objectdir, name); + if (!iter_object_dir (self, objdir, callback, user_data, error)) + { + g_object_unref (objdir); + goto out; + } + g_object_unref (objdir); + } + g_object_unref (file_info); + } + if (file_info == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + if (!g_file_enumerator_close (enumerator, NULL, error)) + goto out; + + ret = TRUE; + out: + g_clear_object (&file_info); + g_clear_object (&enumerator); + g_clear_object (&objectdir); + return ret; +} diff --git a/src/libhacktree/hacktree-repo.h b/src/libhacktree/hacktree-repo.h index 715560e0..08992959 100644 --- a/src/libhacktree/hacktree-repo.h +++ b/src/libhacktree/hacktree-repo.h @@ -57,6 +57,14 @@ gboolean hacktree_repo_link_file (HacktreeRepo *repo, const char *path, GError **error); +typedef void (*HacktreeRepoObjectIter) (HacktreeRepo *repo, const char *path, + GFileInfo *fileinfo, gpointer user_data); + +gboolean hacktree_repo_iter_objects (HacktreeRepo *repo, + HacktreeRepoObjectIter callback, + gpointer user_data, + GError **error); + G_END_DECLS #endif /* _HACKTREE_REPO */ diff --git a/src/libhacktree/hacktree.h b/src/libhacktree/hacktree.h index 11f0a89a..0cf2f15d 100644 --- a/src/libhacktree/hacktree.h +++ b/src/libhacktree/hacktree.h @@ -21,6 +21,7 @@ #ifndef __HACKTREE_H__ +#include #include #include diff --git a/src/libhtutil/ht-unix-utils.c b/src/libhtutil/ht-unix-utils.c index f0aebb22..4637c7df 100644 --- a/src/libhtutil/ht-unix-utils.c +++ b/src/libhtutil/ht-unix-utils.c @@ -30,7 +30,6 @@ #include #include - void ht_util_set_error_from_errno (GError **error, gint saved_errno) diff --git a/src/main.c b/src/main.c index df4022ec..7ff1c24d 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ static HacktreeBuiltin builtins[] = { { "init", hacktree_builtin_init, 0 }, { "link-file", hacktree_builtin_link_file, 0 }, + { "fsck", hacktree_builtin_fsck, 0 }, { NULL } };