Right now, when populating a filesystem with the prototype file, generated inodes will have timestamps set at the creation time. This change enables more accurate filesystem initialization by preserving original file timestamps during inode creation rather than defaulting to the current time. This patch leverages the xfs_protofile "Descending path" comment in order to carry the reference to the original files for files other than regular ones. The origin path is parsed from the comments and only the first instance is registered. To do this, a little change to `parseproto()` has been made to keep track of the current_path each file is in, in order to reconstruct the original file path. If the "Descending path" comment is not found, this will behave like the old implementation, old files not created by `xfs_protofile` tool will simply skip timestamp carry over. [v1] -> [v2] - remove changes to protofile spec - ensure backward compatibility [v2] -> [v3] - use inode_set_[acm]time() as suggested - avoid copying atime and ctime they are often problematic for reproducibility, and mtime is the important information to preserve anyway Signed-off-by: Luca Di Maio <luca.dimaio1@xxxxxxxxx> --- mkfs/proto.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 5 deletions(-) diff --git a/mkfs/proto.c b/mkfs/proto.c index 6dd3a20..df831c5 100644 --- a/mkfs/proto.c +++ b/mkfs/proto.c @@ -5,6 +5,7 @@ */ #include "libxfs.h" #include <sys/stat.h> #include <sys/xattr.h> #include <linux/xattr.h> @@ -22,6 +23,7 @@ static int newregfile(char **pp, char **fname); static void rtinit(xfs_mount_t *mp); static long filesize(int fd); static int slashes_are_spaces; +static char *originpath = NULL; /* * Use this for block reservations needed for mkfs's conditions @@ -142,6 +144,8 @@ getstr( char *p; char *rval; + static char comment[4096]; + p = *pp; while ((c = *p)) { switch (c) { @@ -152,8 +156,35 @@ getstr( continue; case ':': p++; - while (*p++ != '\n') - ; + + // in case we didn't find the descending path yet, let's check + // every comment in order to find it. If found, originpath will + // not be empty and disable this behaviour for future comments. + if (originpath == NULL) { + int i = 0; + while (*p != '\n' && *p != '\0' && i < sizeof(comment) - 1) { + comment[i++] = *p++; + } + if (*p == '\n') + p++; + + comment[i] = '\0'; + + // Check if comment contains "Descending path" + // we will use this information to refer to the original files + // in order to copy over information like timestamps. + char *path_pos = strstr(comment, "Descending path "); + if (path_pos != NULL) { + path_pos += strlen("Descending path "); + // Found it - set our originpath + originpath = strdup(path_pos); + } + } else { + // we don't need to check for descending path anymore, skip + // comments as old behaviour. + while (*p++ != '\n') + ; + } continue; default: rval = p; @@ -262,6 +293,60 @@ writesymlink( } } +static void +write_timestamps( + struct xfs_inode *ip, + char *current_path, + char *name) +{ + int error; + struct stat statbuf; + struct timespec64 ts; + char *origin; + size_t origin_len; + + // ignore timestamps in case of old prototype files or undefined + // descending directory + if (originpath == NULL) { + return; + } + + origin_len = strlen(originpath) + strlen(current_path) + strlen(name) + 3; + origin = malloc(origin_len); + if (!origin) { + fail(_("error allocating origin name buffer"), errno); + } + snprintf(origin, origin_len, "%s/%s/%s", originpath, current_path, name); + + // here for symlinks we use lstat so that we don't follow the symlink. + // we want the timestamp of the original symlink itself, not what it + // points to. + error = lstat(origin, &statbuf); + if (error < 0) { + fprintf(stderr, _("Warning: could not preserve timestamps for %s: %s\n"), + origin, strerror(errno)); + free(origin); + return; + } + + /* Copy timestamps from source file to destination inode. + * In order to not be influenced by our own access timestamp, + * we set atime and ctime to mtime of the source file. + * Usually reproducible archives will delete or not register + * atime and ctime, for example: + * https://www.gnu.org/software/tar/manual/html_section/Reproducibility.html + **/ + ts.tv_sec = statbuf.st_mtime; + ts.tv_nsec = statbuf.st_mtim.tv_nsec; + inode_set_atime_to_ts(VFS_I(ip), ts); + inode_set_ctime_to_ts(VFS_I(ip), ts); + inode_set_mtime_to_ts(VFS_I(ip), ts); + + free(origin); + return; +} + + static void writefile_range( struct xfs_inode *ip, @@ -658,7 +743,8 @@ parseproto( xfs_inode_t *pip, struct fsxattr *fsxp, char **pp, - char *name) + char *name, + char *current_path) { #define IF_REGULAR 0 #define IF_RESERVED 1 @@ -878,6 +964,7 @@ parseproto( libxfs_trans_log_inode(tp, pip, XFS_ILOG_CORE); } newdirectory(mp, tp, ip, pip); + write_timestamps(ip, current_path, name); libxfs_trans_log_inode(tp, ip, flags); error = -libxfs_trans_commit(tp); if (error) @@ -897,7 +984,24 @@ parseproto( error); rtinit(mp); + name = ""; + } + + char *path = NULL; + if (current_path != NULL && name != NULL) { + size_t path_len = strlen(current_path) + (name ? strlen(name) : 0) + 2; + path = malloc(path_len); + if (!path) { + fail(_("error allocating path name buffer"), errno); + } + snprintf(path, path_len, "%s/%s", current_path, name); + } else if (name != NULL) { + path = strdup(name); + if (!path) { + fail(_("error copying name into path buffer"), errno); + } } + tp = NULL; for (;;) { name = getdirentname(pp); @@ -905,14 +1009,17 @@ parseproto( break; if (strcmp(name, "$") == 0) break; - parseproto(mp, ip, fsxp, pp, name); + parseproto(mp, ip, fsxp, pp, name, path); } libxfs_irele(ip); + if (path != NULL) + free(path); return; default: ASSERT(0); fail(_("Unknown format"), EINVAL); } + write_timestamps(ip, current_path, name); libxfs_trans_log_inode(tp, ip, flags); error = -libxfs_trans_commit(tp); if (error) { @@ -924,6 +1031,7 @@ parseproto( if (fmt == IF_REGULAR) { writefile(ip, fname, fd); writeattrs(ip, fname, fd); + write_timestamps(ip, current_path, name); close(fd); } libxfs_irele(ip); @@ -937,7 +1045,10 @@ parse_proto( int proto_slashes_are_spaces) { slashes_are_spaces = proto_slashes_are_spaces; - parseproto(mp, NULL, fsx, pp, NULL); + parseproto(mp, NULL, fsx, pp, NULL, NULL); + + if (originpath != NULL) + free(originpath); } /* Create a sb-rooted metadata file. */ -- 2.49.0