/*- * Copyright 2010. Ivan Voras * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. */ /* * FreeBSD install - a package for the installation and maintainance * of non-core utilities. * * This is the module for creating patches from binary packages. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "pkg_patch.h" #include "mkpatch.h" #include "hashjob.h" void perform_mkpatch(char *file_old, char *file_new, char *file_patch) { char fold[PATH_MAX], fnew[PATH_MAX], fpatch[PATH_MAX]; char dold[PATH_MAX], dnew[PATH_MAX], dpatch[PATH_MAX]; char tmp[PATH_MAX], tmp2[PATH_MAX]; const char *method; struct pkgxjob xold, xnew; struct filelist_head flold, flnew; struct filelist_head fldiff_old_new, fldiff_new_old, flintersect; struct filelist_head flchanged; struct filelist *fl; unsigned n_changed_files; FILE *fp; if (realpath(file_old, fold) == NULL) err(1, "Error resolving path: %s", file_old); if (realpath(file_new, fnew) == NULL) err(1, "Error resolving path: %s", file_new); if (realpath(file_patch, fpatch) == NULL) err(1, "Error resolving path: %s", file_patch); if (access(fold, F_OK) != 0) err(1, "File not found: %s", fold); if (access(fnew, F_OK) != 0) err(1, "File not found: %s", fnew); if (access(fold, R_OK) != 0) err(1, "Access error reading file: %s", fold); if (access(fnew, R_OK) != 0) err(1, "Access error reading file: %s", fnew); snprintf(dold, PATH_MAX, "%s/old", my_tmp); if (access(dold, F_OK) == 0) rm_rf(dold); /* Obliterate any existing temp directories */ if (mkdir(dold, 0700) != 0) err(1, "Cannot create directory: %s", dold); snprintf(dnew, PATH_MAX, "%s/new", my_tmp); if (access(dnew, F_OK) == 0) rm_rf(dnew); if (mkdir(dnew, 0700) != 0) err(1, "Cannot create directory: %s", dnew); snprintf(dpatch, PATH_MAX, "%s/patch", my_tmp); if (access(dpatch, F_OK) == 0) rm_rf(dpatch); if (mkdir(dpatch, 0700) != 0) err(1, "Cannot create directory: %s", dpatch); if (pkgxjob_start(&xold, dold, fold) != 0) err(1, "Cannot extract package %s to %s (start)", fold, dold); if (pkgxjob_start(&xnew, dnew, fnew) != 0) err(1, "Cannot extract package %s to %s (start)", dnew, fnew); if (pkgxjob_finish(&xold) != 0) err(1, "Cannot extract package %s to %s (finish)", fold, dold); if (pkgxjob_finish(&xnew) != 0) err(1, "Cannot extract package %s to %s (finish)", dnew, fnew); SLIST_INIT(&flold); filelist_gather(dold, &flold); SLIST_INIT(&flnew); filelist_gather(dnew, &flnew); if (Verbose) printf("Processing %d files in old package and %d in new.\n", filelist_count(&flold), filelist_count(&flnew)); SLIST_INIT(&fldiff_old_new); filelist_diff(&flold, &flnew, &fldiff_old_new); SLIST_INIT(&fldiff_new_old); filelist_diff(&flnew, &flold, &fldiff_new_old); SLIST_INIT(&flintersect); filelist_intersect(&flnew, &flold, &flintersect); if (Verbose) printf("Found %d files to add and %d files to delete.\n", filelist_count(&fldiff_new_old), filelist_count(&fldiff_old_new)); if (Verbose > 2) { SLIST_FOREACH(fl, &fldiff_new_old, linkage) printf("++ %s\n", fl->filename); SLIST_FOREACH(fl, &fldiff_old_new, linkage) printf("-- %s\n", fl->filename); if (Verbose > 3) SLIST_FOREACH(fl, &flintersect, linkage) printf("?? %s\n", fl->filename); } SLIST_INIT(&flchanged); SLIST_FOREACH(fl, &flintersect, linkage) { char fcold[PATH_MAX], fcnew[PATH_MAX]; struct hashjob hjold_md5, hjold_sha256, hjnew_md5, hjnew_sha256; struct filelist *fl2; /* TODO: Handle when a file is replaced by a directory and * vice-versa */ if (S_ISDIR(fl->st.st_mode)) continue; snprintf(fcold, PATH_MAX, "%s/%s", dold, fl->filename); assert(access(fcold, R_OK) == 0); snprintf(fcnew, PATH_MAX, "%s/%s", dnew, fl->filename); assert(access(fcnew, R_OK) == 0); /* * Detect changed files using the traditional FreeBSD method - * by comparing MD5 and SHA256 checksums. As a note to possible * improvements: SHA256 is almost 3x slower than MD5. */ hashjob_start(&hjold_md5, fcold, HASH_MD5); hashjob_start(&hjold_sha256, fcold, HASH_SHA256); hashjob_start(&hjnew_md5, fcnew, HASH_MD5); hashjob_start(&hjnew_sha256, fcnew, HASH_SHA256); hashjob_finish(&hjold_md5); hashjob_finish(&hjold_sha256); hashjob_finish(&hjnew_md5); hashjob_finish(&hjnew_sha256); assert(hjold_md5.hash_len == hjnew_md5.hash_len); assert(hjnew_sha256.hash_len == hjnew_sha256.hash_len); if (memcmp(hjold_md5.hash, hjnew_md5.hash, hjold_md5.hash_len) != 0 || memcmp(hjold_sha256.hash, hjnew_sha256.hash, hjold_sha256.hash_len) != 0) { /* Assume changed files */ if (Verbose > 3) printf("~~ %s\n", fl->filename); fl2 = malloc(sizeof(*fl2)); memcpy(fl2, fl, sizeof(*fl2)); SLIST_INSERT_HEAD(&flchanged, fl2, linkage); } } if (Verbose) printf("Found %d changed files.\n", filelist_count(&flchanged)); if (flag_bsdiff) method = "bsdiff"; else method = "cp"; snprintf(tmp, PATH_MAX, "%s/%s", dpatch, PKGPATCH_FNAME); fp = fopen(tmp, "w"); if (fp == NULL) err(1, "Cannot open file for writing: %s", tmp); fprintf(fp, "# FreeBSD package patch archive created on %s\n", time_ctime(-1)); fprintf(fp, "@version %d.%d\n", PKGPATCH_VERSION_MAJOR, PKGPATCH_VERSION_MINOR); parse_package_name(fold, tmp, tmp2, NULL); fprintf(fp, "@source %s-%s\n", tmp, tmp2); parse_package_name(fnew, tmp, tmp2, NULL); fprintf(fp, "@target %s-%s\n", tmp, tmp2); SLIST_FOREACH(fl, &fldiff_new_old, linkage) fprintf(fp, "@add %s\n", fl->filename); SLIST_FOREACH(fl, &fldiff_old_new, linkage) if (!S_ISDIR(fl->st.st_mode)) fprintf(fp, "@remove %s\n", fl->filename); SLIST_FOREACH(fl, &fldiff_old_new, linkage) if (S_ISDIR(fl->st.st_mode)) fprintf(fp, "@rmdir %s\n", fl->filename); n_changed_files = 0; SLIST_FOREACH(fl, &flchanged, linkage) if (fl->filename[0] != '+') { fprintf(fp, "@patch [method=%s] %s\n", S_ISLNK(fl->st.st_mode) ? "ln" : method, fl->filename); n_changed_files++; } if (fclose(fp) != 0) err(1, "Cannot close %s", PKGPATCH_FNAME); /* Include all metadata files from the new package. */ SLIST_FOREACH(fl, &flnew, linkage) { if (fl->filename[0] == '+') { snprintf(tmp, PATH_MAX, "%s/%s", dnew, fl->filename); snprintf(tmp2, PATH_MAX, "%s/%s", dpatch, fl->filename); if (copy_file_absolute(tmp, tmp2) != 0) err(1, "[1] Cannot copy file: %s to file: %s", tmp, tmp2); } } /* Simply copy the directory hierarchy of the new package. */ if (replicate_dirtree(dnew, dpatch) != 0) err(1, "replicate_dirtree(%s,%s) failed", dnew, dpatch); /* Copy files to add */ SLIST_FOREACH(fl, &fldiff_new_old, linkage) { snprintf(tmp, PATH_MAX, "%s/%s", dnew, fl->filename); snprintf(tmp2, PATH_MAX, "%s/%s", dpatch, fl->filename); if (copy_file_absolute(tmp, tmp2) != 0) err(1, "[2] Cannot copy file: %s to file: %s", tmp, tmp2); } /* Handle changed files */ if (!flag_bsdiff) { SLIST_FOREACH(fl, &flchanged, linkage) { if (fl->filename[0] == '+') continue; snprintf(tmp, PATH_MAX, "%s/%s", dnew, fl->filename); snprintf(tmp2, PATH_MAX, "%s/%s", dpatch, fl->filename); if (copy_file_absolute(tmp, tmp2) != 0) err(1, "[3] Cannot copy file: %s to file: %s", tmp, tmp2); } } else { /* * bsdiff is extremely CPU-intensive, but multiple invocations * can be started so it becomes embarrasingly parallel on SMP. * I've observed linear or better processing time improvments * with this simple trick. */ FILE **fpvect; int n = 0; fpvect = calloc(n_changed_files, sizeof(*fpvect)); if (fpvect == NULL) err(1, "calloc() failed"); /* Start jobs */ SLIST_FOREACH(fl, &flchanged, linkage) { if (fl->filename[0] == '+') continue; if (S_ISLNK(fl->st.st_mode)) { snprintf(tmp, PATH_MAX, "%s/%s", dnew, fl->filename); snprintf(tmp2, PATH_MAX, "%s/%s", dpatch, fl->filename); if (copy_file_absolute(tmp, tmp2) != 0) err(1, "[4] Cannot copy file: " "%s to file: %s", tmp, tmp2); fpvect[n++] = NULL; continue; } if (Verbose > 1) printf("bsdiff for %s\n", fl->filename); snprintf(tmp, PATH_MAX, "%s \"%s/%s\" \"%s/%s\" \"%s/%s.bsdiff\"", _PATH_BSDIFF, dold, fl->filename, dnew, fl->filename, dpatch, fl->filename); fpvect[n] = popen(tmp, "r+"); if (fpvect[n] == NULL) err(1, "Cannot popen bsdiff for %s", fl->filename); n++; } /* Collect jobs */ n = 0; SLIST_FOREACH(fl, &flchanged, linkage) { if (fl->filename[0] == '+') continue; if (fpvect[n] == NULL) { n++; continue; } if (pclose(fpvect[n]) < 0) err(1, "pclose() failed for bsdiff of %s", fl->filename); n++; snprintf(tmp, PATH_MAX, "%s/%s", dold, fl->filename); snprintf(tmp2, PATH_MAX, "%s/%s.bsdiff", dpatch, fl->filename); if (copy_file_attrs(tmp, NULL, tmp2) != 0) err(1, "copy_file_attrs(%s,%s) failed", tmp, tmp2); } free(fpvect); } /* Finally, create the patch archive and call it a day. */ chdir(dpatch); snprintf(tmp, PATH_MAX, "%s -c -j -f %s *", _PATH_TAR, fpatch); fp = popen(tmp, "r+"); if (fp == NULL) err(1, "Final tar execution failed for: %s", fpatch); rm_rf(dold); rm_rf(dnew); if (pclose(fp) != 0) err(1, "pclose() failed on final tar"); if (Verbose) printf("Created %s.\n", fpatch); filelist_free(&flold); filelist_free(&flnew); filelist_free(&fldiff_old_new); filelist_free(&fldiff_new_old); filelist_free(&flintersect); filelist_free(&flchanged); }