+\f
+static void
+diff_dir (void)
+{
+ struct stat stat_data;
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISDIR (stat_data.st_mode))
+ report_difference (¤t_stat_info, _("File type differs"));
+ else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (¤t_stat_info, _("Mode differs"));
+}
+
+static void
+diff_file (void)
+{
+ char const *file_name = current_stat_info.file_name;
+ struct stat stat_data;
+
+ if (!get_stat_data (file_name, &stat_data))
+ skip_member ();
+ else if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (¤t_stat_info, _("File type differs"));
+ skip_member ();
+ }
+ else
+ {
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (¤t_stat_info, _("Mode differs"));
+
+ if (!sys_compare_uid (&stat_data, ¤t_stat_info.stat))
+ report_difference (¤t_stat_info, _("Uid differs"));
+ if (!sys_compare_gid (&stat_data, ¤t_stat_info.stat))
+ report_difference (¤t_stat_info, _("Gid differs"));
+
+ if (tar_timespec_cmp (get_stat_mtime (&stat_data),
+ current_stat_info.mtime))
+ report_difference (¤t_stat_info, _("Mod time differs"));
+ if (current_header->header.typeflag != GNUTYPE_SPARSE
+ && stat_data.st_size != current_stat_info.stat.st_size)
+ {
+ report_difference (¤t_stat_info, _("Size differs"));
+ skip_member ();
+ }
+ else
+ {
+ int atime_flag =
+ (atime_preserve_option == system_atime_preserve
+ ? O_NOATIME
+ : 0);
+
+ diff_handle = open (file_name, O_RDONLY | O_BINARY | atime_flag);
+
+ if (diff_handle < 0)
+ {
+ open_error (file_name);
+ skip_member ();
+ report_difference (¤t_stat_info, NULL);
+ }
+ else
+ {
+ int status;
+
+ if (current_stat_info.is_sparse)
+ sparse_diff_file (diff_handle, ¤t_stat_info);
+ else
+ read_and_process (¤t_stat_info, process_rawdata);
+
+ if (atime_preserve_option == replace_atime_preserve)
+ {
+ struct timespec ts[2];
+ ts[0] = get_stat_atime (&stat_data);
+ ts[1] = get_stat_mtime (&stat_data);
+ if (set_file_atime (diff_handle, file_name, ts) != 0)
+ utime_error (file_name);
+ }
+
+ status = close (diff_handle);
+ if (status != 0)
+ close_error (file_name);
+ }
+ }
+ }
+}
+
+static void
+diff_link (void)
+{
+ struct stat file_data;
+ struct stat link_data;
+
+ if (get_stat_data (current_stat_info.file_name, &file_data)
+ && get_stat_data (current_stat_info.link_name, &link_data)
+ && !sys_compare_links (&file_data, &link_data))
+ report_difference (¤t_stat_info,
+ _("Not linked to %s"),
+ quote (current_stat_info.link_name));
+}
+
+#ifdef HAVE_READLINK
+static void
+diff_symlink (void)
+{
+ size_t len = strlen (current_stat_info.link_name);
+ char *linkbuf = alloca (len + 1);
+
+ int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
+
+ if (status < 0)
+ {
+ if (errno == ENOENT)
+ readlink_warn (current_stat_info.file_name);
+ else
+ readlink_error (current_stat_info.file_name);
+ report_difference (¤t_stat_info, NULL);
+ }
+ else if (status != len
+ || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
+ report_difference (¤t_stat_info, _("Symlink differs"));
+}
+#endif
+
+static void
+diff_special (void)
+{
+ struct stat stat_data;
+
+ /* FIXME: deal with umask. */
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (current_header->header.typeflag == CHRTYPE
+ ? !S_ISCHR (stat_data.st_mode)
+ : current_header->header.typeflag == BLKTYPE
+ ? !S_ISBLK (stat_data.st_mode)
+ : /* current_header->header.typeflag == FIFOTYPE */
+ !S_ISFIFO (stat_data.st_mode))
+ {
+ report_difference (¤t_stat_info, _("File type differs"));
+ return;
+ }
+
+ if ((current_header->header.typeflag == CHRTYPE
+ || current_header->header.typeflag == BLKTYPE)
+ && current_stat_info.stat.st_rdev != stat_data.st_rdev)
+ {
+ report_difference (¤t_stat_info, _("Device number differs"));
+ return;
+ }
+
+ if ((current_stat_info.stat.st_mode & MODE_ALL) !=
+ (stat_data.st_mode & MODE_ALL))
+ report_difference (¤t_stat_info, _("Mode differs"));
+}
+
+static void
+diff_dumpdir (void)
+{
+ char *dumpdir_buffer;
+ dev_t dev = 0;
+ struct stat stat;
+
+ if (deref_stat (true, current_stat_info.file_name, &stat))
+ {
+ if (errno == ENOENT)
+ stat_warn (current_stat_info.file_name);
+ else
+ stat_error (current_stat_info.file_name);
+ }
+ else
+ dev = stat.st_dev;
+
+ dumpdir_buffer = get_directory_contents (current_stat_info.file_name, dev);
+
+ if (dumpdir_buffer)
+ {
+ dumpdir_cursor = dumpdir_buffer;
+ read_and_process (¤t_stat_info, process_dumpdir);
+ free (dumpdir_buffer);
+ }
+ else
+ read_and_process (¤t_stat_info, process_noop);
+}
+
+static void
+diff_multivol (void)
+{
+ struct stat stat_data;
+ int fd, status;
+ off_t offset;
+
+ if (current_stat_info.had_trailing_slash)
+ {
+ diff_dir ();
+ return;
+ }
+
+ if (!get_stat_data (current_stat_info.file_name, &stat_data))
+ return;
+
+ if (!S_ISREG (stat_data.st_mode))
+ {
+ report_difference (¤t_stat_info, _("File type differs"));
+ skip_member ();
+ return;
+ }
+
+ offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
+ if (stat_data.st_size != current_stat_info.stat.st_size + offset)
+ {
+ report_difference (¤t_stat_info, _("Size differs"));
+ skip_member ();
+ return;
+ }
+
+ fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
+
+ if (fd < 0)
+ {
+ open_error (current_stat_info.file_name);
+ report_difference (¤t_stat_info, NULL);
+ skip_member ();
+ return;
+ }
+
+ if (lseek (fd, offset, SEEK_SET) < 0)
+ {
+ seek_error_details (current_stat_info.file_name, offset);
+ report_difference (¤t_stat_info, NULL);
+ return;
+ }
+
+ read_and_process (¤t_stat_info, process_rawdata);
+
+ status = close (fd);
+ if (status != 0)
+ close_error (current_stat_info.file_name);
+}
+