/* * Back-end main module * See "diff.h" for the details of data structure. * This module should be independent from GUI frontend. * * Copyright INOUE Seiichiro , licensed under the GPL. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(HAVE_STRING_H) #include #elif defined(HAVE_STRINGS_H) #include #endif #include #include #include #include #include #if defined(HAVE_ERRNO_H) #include #else defined(HAVE_SYS_ERRNO_H) #include #endif #include "diff.h" /* Private function declarations */ static void diffdir_delete(DiffDir *diffdir); static DiffFiles* dfiles_new(const char *fname1, const char *fname2, const char *fname3); static void dfiles_delete(DiffFiles *dfiles); static void init_fileinfo(FileInfo *fi, char const *fname); static void term_fileinfo(FileInfo *fi); static int get_nlines(const char *ptr, int lenb); /** * diffdir_new: * Allocate DiffDir structure, and return its pointer. * Input: * const char *fname1; Regular file or directory name. * const char *fname2; Regular file or directory name. * const char *fname3; Regular file or directory name. If non-NULL, use diff3(1). * const char *args; Argument string to diff(1) or diff3(1). * Output: * Return value: DiffDir*; **/ DiffDir* diffdir_new(const char *fname1, const char *fname2, const char *fname3, const char *args) { DiffDir *diffdir; diffdir = g_new(DiffDir, 1); diffdir->ref_count = 0; diffdir->dfiles_list = NULL; diffdir_add_dfiles(diffdir, fname1, fname2, fname3); if (fname3 && fname3[0]) { diffdir->is_diff3 = TRUE; run_diff3(diffdir, fname1, fname2, fname3, args, NULL); } else { diffdir->is_diff3 = FALSE; run_diff(diffdir, fname1, fname2, args, NULL); } return diffdir; } void diffdir_ref(DiffDir *diffdir) { g_return_if_fail(diffdir != NULL); diffdir->ref_count++; } void diffdir_unref(DiffDir *diffdir) { g_return_if_fail(diffdir != NULL); g_return_if_fail(diffdir->ref_count > 0); diffdir->ref_count--; if (diffdir->ref_count == 0) diffdir_delete(diffdir); } /** * diffdir_delete: * Finalize DiffDir structure, and free its memory. **/ static void diffdir_delete(DiffDir *diffdir) { GSList *list; #ifdef DEBUG g_print("diffdir_delete\n"); #endif for (list = diffdir->dfiles_list; list; list = list->next) { dfiles_unref(list->data); } g_slist_free(diffdir->dfiles_list); g_free(diffdir); } /** * diffdir_add_dfiles: * Allocate DiffFiles (call dfiles_new()), * and add it to GSList *dfiles_list. * This never moves the first node, which is a special node. * On the other hand, the order of other nodes doesn't matter. * Input: * DiffDir *diffdir; * const char *fname1; * const char *fname2; * const char *fname3; Could be NULL. * Output: * DiffDir *diffdir; GSList *dfiles_list is updated. * Return value: DiffFiles*; Added DiffFiles' pointer. **/ DiffFiles* diffdir_add_dfiles(DiffDir *diffdir, const char *fname1, const char *fname2, const char *fname3) { DiffFiles *dfiles; dfiles = dfiles_new(fname1, fname2, fname3); dfiles_ref(dfiles);/* diffdir owns it */ /* append is not so efficient, but don't care */ diffdir->dfiles_list = g_slist_append(diffdir->dfiles_list, dfiles); return dfiles; } /** * diffdir_remove_dfiles: * Remove specified DiffFiles from singly linked list in DiffDir. * Input: * DiffDir *diffdir; * DiffFiles *dfiles; * Output: * DiffDir *diffdir; GSList *dfiles_list is updated. * DiffFiles *dfiles; Freed. **/ void diffdir_remove_dfiles(DiffDir *diffdir, DiffFiles *dfiles) { dfiles_unref(dfiles); diffdir->dfiles_list = g_slist_remove(diffdir->dfiles_list, dfiles); } /** * dfiles_new: * Allocate DiffFiles structure, initialize and return its pointer. * Input: * const char *fname1; First file name. Directory or regular file. * const char *fname2; Second file name. Directory or regular file. * const char *fname3; Third file name. Directory or regular file. This could be NULL. * Output: * Return value: DiffFile*; Allocated DiffFiles structure; **/ static DiffFiles* dfiles_new(const char *fname1, const char *fname2, const char *fname3) { DiffFiles *dfiles; dfiles = g_new(DiffFiles, 1); dfiles->ref_count = 0; dfiles->binary = FALSE; init_fileinfo(&dfiles->fileinfo[FIRST_FILE], fname1); init_fileinfo(&dfiles->fileinfo[SECOND_FILE], fname2); init_fileinfo(&dfiles->fileinfo[THIRD_FILE], fname3); if (fname3 && fname3[0]) dfiles->is_diff3 = TRUE; else dfiles->is_diff3 = FALSE; dfiles->dlines_list = NULL; return dfiles; } void dfiles_ref(DiffFiles *dfiles) { g_return_if_fail(dfiles != NULL); dfiles->ref_count++; } void dfiles_unref(DiffFiles *dfiles) { g_return_if_fail(dfiles != NULL); g_return_if_fail(dfiles->ref_count > 0); dfiles->ref_count--; if (dfiles->ref_count == 0) dfiles_delete(dfiles); } /** * dfiles_delete: * Finalize DiffFiles structure, and free its memory. **/ static void dfiles_delete(DiffFiles *dfiles) { GList *list; #ifdef DEBUG g_print("dfiles_delete\n"); #endif term_fileinfo(&dfiles->fileinfo[FIRST_FILE]); term_fileinfo(&dfiles->fileinfo[SECOND_FILE]); term_fileinfo(&dfiles->fileinfo[THIRD_FILE]); for (list = dfiles->dlines_list; list; list = list->next) { g_free(list->data); } g_list_free(dfiles->dlines_list); g_free(dfiles); } /** * dfiles_get_fileinfo: * Return the pointer to FileInfo(member variable of DiffFiles). * In general, mmap() is delayed. * Only when @f_mmap is TRUE, mmap() is executed. * (If the caller needs an actual buffer content or its number of lines, * it should set TRUE for @f_mmap.) * Input: * DiffFiles *dfiles; * WhichFile n; * gboolean f_mmap; if TRUE do mmap its buffer. * Output: * DiffFiles *dfiles; @buf and @nlines can be updated by mmap(). * Return value; pointer to FileInfo. **/ const FileInfo* dfiles_get_fileinfo(DiffFiles *dfiles, WhichFile n, gboolean f_mmap) { FileInfo *fi = &dfiles->fileinfo[n]; if (f_mmap == TRUE && fi->f_dir == FALSE && fi->buf == NULL && fi->lenb > 0) { int fd; g_assert(fi->fname[0] != '\0');/* should be checked by fi->lenb > 0 */ if ((fd = open(fi->fname, O_RDONLY)) == -1) { g_warning("dfiles_get_fileinfo:open %s %d", fi->fname, errno); return fi; } /*XXX: MAP_NOEXTEND is dependent on platform */ /*fi->buf = mmap(0, fi->lenb, PROT_READ, MAP_PRIVATE|MAP_NOEXTEND, fd, 0);*/ #ifdef DEBUG g_print("do mmap\n"); #endif fi->buf = mmap(0, fi->lenb, PROT_READ, MAP_PRIVATE, fd, 0); if (fi->buf == (caddr_t)-1) { g_warning("dfiles_get_fileinfo:mmap %s %d", fi->fname, errno); fi->buf = NULL; return fi; } fi->nlines = get_nlines(fi->buf, fi->lenb); close(fd); } return fi; } /** * dfiles_add_dlines: * Allocate DiffLines, and add it to @dlines_list of DiffFiles structure. * Input: * DiffFiles *dfiles; * DiffType dtype; * const int *begin, int *end; Each is line number. * Output: * DiffFiles* dfiles; GList *dlines_list is updated. **/ void dfiles_add_dlines(DiffFiles* dfiles, DiffType dtype, const int *begin, const int *end) { DiffLines *dlines; int n; dlines = g_new(DiffLines, 1); dlines->difftype = dtype; for (n = 0; n < MAX_NUM_COMPARE_FILES; n++) { dlines->between[n].begin = begin[n]; dlines->between[n].end = end[n]; } /* Not so efficient, but don't care */ dfiles->dlines_list = g_list_append(dfiles->dlines_list, dlines); } /** Internal functions **/ /** * init_fileinfo: * Initialize FileInfo structure. * But doesn't do mmap() here. * mmap() is delayed for dfiles_get_fileinfo(). * @fname might be NULL or "", but I always use "" for non-existing file, * because fi->fname may be passed to some str functions such as strcpy(3). **/ static void init_fileinfo(FileInfo *fi, const char *fname) { int fd; struct stat sb; /* default values */ fi->nlines = 0; fi->buf = NULL; fi->lenb = 0; fi->f_dir = FALSE; if (fname && fname[0] != '\0') { fi->fname = g_strdup(fname); if ((fd = open(fname, O_RDONLY)) == -1) { g_warning("init_fileinfo:open %s %d", fname, errno); return; } if (fstat(fd, &sb) == -1) { g_warning("init_fileinfo:fstat %s %d", fname, errno); close(fd); return; } if (sb.st_mode & S_IFREG) { fi->lenb = sb.st_size; } else if (sb.st_mode & S_IFDIR) { fi->f_dir = TRUE; } else { /* not regular-file, nor directory. */ g_warning("init_fileinfo:special file"); } fi->mtime = sb.st_mtime; fi->ctime = sb.st_ctime; close(fd); } else { fi->fname = ""; } } /** * term_fileinfo: * Terminate FileInfo structure. (not free its own memory.) **/ static void term_fileinfo(FileInfo *fi) { if (fi->fname[0]) { g_free(fi->fname); } if (fi->buf) { if (munmap(fi->buf, fi->lenb) == -1) g_warning("munmap in term_fileinfo %d", errno); } } /** * get_nlines: * Return the number of lines of the buffer. * Counts the last line, although it isn't ended with '\n' character. * Input: * const char *ptr; Buffer. Not null-terminated. * int lenb; Buffer length(Bytes). * Output: * Return value; The number of lines of the buffer. **/ static int get_nlines(const char *ptr, int lenb) { int nl = 0; const char *pt2; while ((pt2 = memchr(ptr, '\n', lenb))) { nl++; lenb -= (pt2 + 1 - ptr); ptr = pt2 + 1; } if (ptr[lenb - 1] != '\n') nl++; return nl; }