时移世易,篡改天机:吾以 Go 语令 Windows 文件“返老还童“记

文曰:

时维乙巳年秋,有程序员夜不能寐,忽见文件夹之创建时间,竟露马脚,惶惶如临大敌。遂执 Go 语为剑,调 Windows 之秘术,篡改天机,令旧物焕新,恍若昨日初生。此事奇诡,不可不记。

一、缘起:文件夹"露馅"了!

话说某日,某君正在调试一"压测提单工具"(此乃江湖秘器,专治系统慢如龟),忽觉不妥——

文件夹创建时间竟为今日!

此乃大忌!

若被 QA 看见,必问:"汝昨日方写此工具?何以昨日已用之?"

若被老板察觉,恐遭诘问:"此工具尚未完成,何以三日前已有日志?"

呜呼!时间即证据,证据即破绽。

欲掩其迹,非改其时不可!

然,Linux 之下,touch -t 一令可解;

Windows 之境,却无此等神技。

无奈,只得亲自动手,以代码为笔,重写天命。

二、探秘:Windows 之"三时"真言

Windows 文件,藏有三时,谓之:

CreationTime:诞生之刻,如婴儿初啼;
LastAccessTime:最近一瞥,似故人回眸;
LastWriteTime:最后修改,若匠人收工。
此三时,皆存于 Win32FileAttributeData 之中,非寻常 os.Stat 可窥全貌。

若欲篡之,须得:

打开文件之"魂"(句柄);
转时间于 FILETIME(此乃 Windows 独门格式,自 1601 年起算,单位百纳秒,玄之又玄);
调 SetFileTime 之秘术,一举改写三时。
此术凶险,稍有不慎,轻则无效,重则系统怒而拒之。

然吾有 Go 语言为盾,syscall 为矛,何惧之有?

三、铸剑:Go 语炼成"时之刃"

于是,吾挥毫泼墨,写下三式神功:

第一式:遍历万文件(GetAllFiles)

// 递归入幽谷,遍历子子孙孙,凡文件者,皆录其名。
func GetAllFiles(dirPath string) ([]string, error) { ... }

此式如扫地僧,默默无闻,却为后文铺路。

第二式:窥天机(PrintFileTime)

// 仰观文件之三时,如观星象,知其生辰八字。
func PrintFileTime(filePath string) { ... }

初看文件,三时毕现,心中有数,方敢下手。

第三式:篡天命(SetFileTime)

// 执时间之笔,重写 Creation、Access、Write 三命。
func SetFileTime(filePath string, ctime, atime, mtime time.Time) error { ... }

此乃核心!调 CreateFile 得其魂,转 time.Time 为 FILETIME,终以 SetFileTime 一锤定音。

注:此术仅通于 Windows。若于 Linux 行之,恐如对牛弹琴,徒增笑耳。

四、施法:令"压测工具"返老还童

主函数中,吾设三处要地:

主目录:D:\tools\压测提单工具 → 设为 2022-02-28 10:27:16
日志目录 runlog → 设为 2022-02-28 11:27:16
通信日志 socketlog → 设为 2022-02-28 11:27:13
先观其旧时,再施法篡改,复观其新时——

果然!三时皆如吾所愿,分毫不差!

文件夹静立如初,却已"穿越"三年,恍若昨日方建。

QA 再查,只见"2022年旧物",必叹曰:"此乃古董级工具,稳定可靠!"

老板见之,亦抚掌笑曰:"此君早有准备,深谋远虑!"

五、警世恒言

然,吾虽得意,亦不敢忘形。

篡改时间,乃双刃之剑:

若为测试、演示、归档所用,善莫大焉;
若为欺瞒、伪造、逃责所图,则天理难容!
且此术仅限 Windows,Linux 诸君莫急,自有 touch、debugfs 等妙法,另文再叙。

六、结语:代码即道,慎用神通

太史公曰:

时者,天地之过客;文件者,代码之遗迹。

能窥其时,改其命,非为逆天,实为顺事。

然顺事者,亦当守其道——技术无善恶,人心定乾坤。

今录此 Go 语真经,非教人作伪,实授人以"可控之权"。

愿诸君执此剑,不为诡道,而为清道。

附:完整代码

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "syscall"
    "time"
)

// GetAllFiles 递归获取指定目录下所有文件的完整路径(不包括子目录本身)
func GetAllFiles(dirPath string) ([]string, error) {
    var files []string

    // 读取目录内容
    dirEntries, err := ioutil.ReadDir(dirPath)
    if err != nil {
        return nil, fmt.Errorf("无法读取目录 %q: %w", dirPath, err)
    }

    for _, entry := range dirEntries {
        fullPath := dirPath + "/" + entry.Name()

        if entry.IsDir() {
            // 递归处理子目录
            subFiles, err := GetAllFiles(fullPath)
            if err != nil {
                return nil, fmt.Errorf("递归读取子目录 %q 失败: %w", fullPath, err)
            }
            files = append(files, subFiles...)
        } else {
            // 是普通文件,加入结果列表
            files = append(files, fullPath)
        }
    }

    return files, nil
}

// SetFileTime 修改文件的创建时间(CreationTime)、最后访问时间(LastAccessTime)和最后修改时间(LastWriteTime)
// 注意:此函数仅适用于 Windows 系统
func SetFileTime(filePath string, creationTime, lastAccessTime, lastWriteTime time.Time) error {
    // 获取文件的绝对路径
    absPath, err := syscall.FullPath(filePath)
    if err != nil {
        return fmt.Errorf("无法获取文件绝对路径 %q: %w", filePath, err)
    }

    // 转换为 UTF-16 指针(Windows API 要求)
    pathPtr, err := syscall.UTF16PtrFromString(absPath)
    if err != nil {
        return fmt.Errorf("路径转 UTF-16 失败: %w", err)
    }

    // 打开文件句柄(仅用于修改属性)
    handle, err := syscall.CreateFile(
        pathPtr,
        syscall.FILE_WRITE_ATTRIBUTES,
        syscall.FILE_SHARE_WRITE,
        nil,
        syscall.OPEN_EXISTING,
        syscall.FILE_FLAG_BACKUP_SEMANTICS,
        0,
    )
    if err != nil {
        return fmt.Errorf("无法打开文件 %q: %w", absPath, err)
    }
    defer syscall.Close(handle)

    // 将 time.Time 转换为 Windows FILETIME 格式
    toFiletime := func(t time.Time) syscall.Filetime {
        return syscall.NsecToFiletime(t.UnixNano())
    }

    ct := toFiletime(creationTime)
    at := toFiletime(lastAccessTime)
    mt := toFiletime(lastWriteTime)

    // 调用 Windows API 设置时间戳
    if err := syscall.SetFileTime(handle, &ct, &at, &mt); err != nil {
        return fmt.Errorf("设置文件时间失败 %q: %w", absPath, err)
    }

    return nil
}

// PrintFileTime 打印 Windows 系统下文件的创建时间、最后访问时间和最后修改时间
func PrintFileTime(filePath string) {
    fileInfo, err := os.Stat(filePath)
    if err != nil {
        fmt.Printf("无法获取文件信息 %q: %v\n", filePath, err)
        return
    }

    // 类型断言为 Windows 特有的文件属性结构
    winAttr, ok := fileInfo.Sys().(*syscall.Win32FileAttributeData)
    if !ok {
        fmt.Printf("不支持的系统或文件类型: %q\n", filePath)
        return
    }

    // FILETIME 是从 1601-01-01 起的 100 纳秒单位,转换为 Unix 时间
    creationTime := time.Unix(0, winAttr.CreationTime.Nanoseconds())
    lastAccessTime := time.Unix(0, winAttr.LastAccessTime.Nanoseconds())
    lastWriteTime := time.Unix(0, winAttr.LastWriteTime.Nanoseconds())

    fmt.Printf("文件路径: %s\n", filePath)
    fmt.Printf("  创建时间: %v\n", creationTime)
    fmt.Printf("  最后访问时间: %v\n", lastAccessTime)
    fmt.Printf("  最后修改时间: %v\n", lastWriteTime)
}

// parseLocalTime 安全地将字符串解析为本地时区的 time.Time
func parseLocalTime(layout, value string) (time.Time, error) {
    return time.ParseInLocation(layout, value, time.Local)
}

func main() {
    // 示例:修改目录及其子目录中特定文件夹的时间戳

    // 1. 修改主目录时间
    dirPath := `D:\tools\压测提单工具`
    PrintFileTime(dirPath)

    t1, err := parseLocalTime("2006-01-02 15:04:05", "2022-02-28 10:27:16")
    if err != nil {
        fmt.Printf("时间解析失败: %v\n", err)
        return
    }
    if err := SetFileTime(dirPath, t1, t1, t1); err != nil {
        fmt.Printf("设置目录时间失败: %v\n", err)
        return
    }
    PrintFileTime(dirPath)

    // 2. 修改 runlog 子目录时间
    runlogPath := `D:\tools\压测提单工具\runlog`
    PrintFileTime(runlogPath)

    t2, err := parseLocalTime("2006-01-02 15:04:05", "2022-02-28 11:27:16")
    if err != nil {
        fmt.Printf("时间解析失败: %v\n", err)
        return
    }
    if err := SetFileTime(runlogPath, t2, t2, t2); err != nil {
        fmt.Printf("设置 runlog 目录时间失败: %v\n", err)
        return
    }
    PrintFileTime(runlogPath)

    // 3. 修改 socketlog 子目录时间
    socketlogPath := `D:\tools\压测提单工具\socketlog`
    PrintFileTime(socketlogPath)

    t3, err := parseLocalTime("2006-01-02 15:04:05", "2022-02-28 11:27:13")
    if err != nil {
        fmt.Printf("时间解析失败: %v\n", err)
        return
    }
    if err := SetFileTime(socketlogPath, t3, t3, t3); err != nil {
        fmt.Printf("设置 socketlog 目录时间失败: %v\n", err)
        return
    }
    PrintFileTime(socketlogPath)
}

运行前,请三思:汝,真需"返老还童"乎?

乙巳年霜月朔日,某程序员焚香沐浴,记于键盘之侧。

往期部分文章列表

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容