/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <dirent.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sha1.h> #include <unistd.h> #include <limits.h> #include <sys/stat.h> #include <netinet/in.h> #include <resolv.h> #include <cutils/dir_hash.h> /** * Copies, if it fits within max_output_string bytes, into output_string * a hash of the contents, size, permissions, uid, and gid of the file * specified by path, using the specified algorithm. Returns the length * of the output string, or a negative number if the buffer is too short. */ int get_file_hash(HashAlgorithm algorithm, const char *path, char *output_string, size_t max_output_string) { SHA1_CTX context; struct stat sb; unsigned char md[SHA1_DIGEST_LENGTH]; int used; size_t n; if (algorithm != SHA_1) { errno = EINVAL; return -1; } if (stat(path, &sb) != 0) { return -1; } if (S_ISLNK(sb.st_mode)) { char buf[PATH_MAX]; int len; len = readlink(path, buf, sizeof(buf)); if (len < 0) { return -1; } SHA1Init(&context); SHA1Update(&context, (unsigned char *) buf, len); SHA1Final(md, &context); } else if (S_ISREG(sb.st_mode)) { char buf[10000]; FILE *f = fopen(path, "rb"); int len; if (f == NULL) { return -1; } SHA1Init(&context); while ((len = fread(buf, 1, sizeof(buf), f)) > 0) { SHA1Update(&context, (unsigned char *) buf, len); } if (ferror(f)) { fclose(f); return -1; } fclose(f); SHA1Final(md, &context); } if (S_ISLNK(sb.st_mode) || S_ISREG(sb.st_mode)) { used = b64_ntop(md, SHA1_DIGEST_LENGTH, output_string, max_output_string); if (used < 0) { errno = ENOSPC; return -1; } n = snprintf(output_string + used, max_output_string - used, " %d 0%o %d %d", (int) sb.st_size, sb.st_mode, (int) sb.st_uid, (int) sb.st_gid); } else { n = snprintf(output_string, max_output_string, "- - 0%o %d %d", sb.st_mode, (int) sb.st_uid, (int) sb.st_gid); } if (n >= max_output_string - used) { errno = ENOSPC; return -(used + n); } return used + n; } struct list { char *name; struct list *next; }; static int cmp(const void *a, const void *b) { struct list *const *ra = a; struct list *const *rb = b; return strcmp((*ra)->name, (*rb)->name); } static int recurse(HashAlgorithm algorithm, const char *directory_path, struct list **out) { struct list *list = NULL; struct list *f; struct dirent *de; DIR *d = opendir(directory_path); if (d == NULL) { return -1; } while ((de = readdir(d)) != NULL) { if (strcmp(de->d_name, ".") == 0) { continue; } if (strcmp(de->d_name, "..") == 0) { continue; } char *name = malloc(strlen(de->d_name) + 1); struct list *node = malloc(sizeof(struct list)); if (name == NULL || node == NULL) { struct list *next; for (f = list; f != NULL; f = next) { next = f->next; free(f->name); free(f); } free(name); free(node); return -1; } strcpy(name, de->d_name); node->name = name; node->next = list; list = node; } closedir(d); for (f = list; f != NULL; f = f->next) { struct stat sb; char *name; char outstr[NAME_MAX + 100]; char *keep; struct list *res; name = malloc(strlen(f->name) + strlen(directory_path) + 2); if (name == NULL) { struct list *next; for (f = list; f != NULL; f = f->next) { next = f->next; free(f->name); free(f); } for (f = *out; f != NULL; f = f->next) { next = f->next; free(f->name); free(f); } *out = NULL; return -1; } sprintf(name, "%s/%s", directory_path, f->name); int len = get_file_hash(algorithm, name, outstr, sizeof(outstr)); if (len < 0) { // should not happen return -1; } keep = malloc(len + strlen(name) + 3); res = malloc(sizeof(struct list)); if (keep == NULL || res == NULL) { struct list *next; for (f = list; f != NULL; f = f->next) { next = f->next; free(f->name); free(f); } for (f = *out; f != NULL; f = f->next) { next = f->next; free(f->name); free(f); } *out = NULL; free(keep); free(res); return -1; } sprintf(keep, "%s %s\n", name, outstr); res->name = keep; res->next = *out; *out = res; if ((stat(name, &sb) == 0) && S_ISDIR(sb.st_mode)) { if (recurse(algorithm, name, out) < 0) { struct list *next; for (f = list; f != NULL; f = next) { next = f->next; free(f->name); free(f); } return -1; } } } struct list *next; for (f = list; f != NULL; f = next) { next = f->next; free(f->name); free(f); } } /** * Allocates a string containing the names and hashes of all files recursively * reached under the specified directory_path, using the specified algorithm. * The string is returned as *output_string; the return value is the length * of the string, or a negative number if there was a failure. */ int get_recursive_hash_manifest(HashAlgorithm algorithm, const char *directory_path, char **output_string) { struct list *out = NULL; struct list *r; struct list **list; int count = 0; int len = 0; int retlen = 0; int i; char *buf; if (recurse(algorithm, directory_path, &out) < 0) { return -1; } for (r = out; r != NULL; r = r->next) { count++; len += strlen(r->name); } list = malloc(count * sizeof(struct list *)); if (list == NULL) { struct list *next; for (r = out; r != NULL; r = next) { next = r->next; free(r->name); free(r); } return -1; } count = 0; for (r = out; r != NULL; r = r->next) { list[count++] = r; } qsort(list, count, sizeof(struct list *), cmp); buf = malloc(len + 1); if (buf == NULL) { struct list *next; for (r = out; r != NULL; r = next) { next = r->next; free(r->name); free(r); } free(list); return -1; } for (i = 0; i < count; i++) { int n = strlen(list[i]->name); strcpy(buf + retlen, list[i]->name); retlen += n; } free(list); struct list *next; for (r = out; r != NULL; r = next) { next = r->next; free(r->name); free(r); } *output_string = buf; return retlen; }