Go言語でサーバのバックアップの仕組みを作ろう。Walk編


前回、同一のパソコン間で、

あるディレクトリをまるまるバックアップするスクリプトを書いた。


その時、

ディレクトリをWalkという関数で再帰的に調べてディレクトリやファイルをコピーした。


前回のコードではファイルのコピーは必ず行うという無駄な処理が多いため、

今回はファイルの更新日時を調べて、バックアップ元の方が新しければコピーを行う

という処理を追加してみることにした。




Walkで再帰的に調べて、ファイルがあった場合、

infoという変数(FIleInfo構造体)に元データのファイル情報が入っているとする。


FileInfoの構造は下記の通り


type FileInfo interface {
	Name() string
	Size() int64
	Mode() FileMode
	ModTime() time.Time
	IsDir() bool
	Sys() interface{}
}

type FileInfo | os - The Go Programming Language


ModTime()で更新日が格納されているTime型で取れることがわかった。

ということで、


smod := info.ModTime()
ssec := smod.Unix()

※smodはsrcのmodの略


これで、元データの更新時刻のUnixタイムを取得できた。


あと必要なのが、

バックアップ先にあるファイルの更新時刻だけど、

今あるファイルのFileInfoを取得しなければならない。


FileInfoはファイルパスを指定してOpenすれば取得できるので、


dst, err = os.Open(backupDir + fpath)
if err == nil {
	finfo, _ := dst.Stat()
	fmod := finfo.ModTime()
	dsec := fmod.Unix()
}

これで良い。


元データの方と合わせて、


//doCopyがtrueであればファイルのコピーを行う
doCopy = false

//すでにコピーがある場合はファイルの更新日を調べて、コピーするか決める
dst, err = os.Open(backupDir + fpath)
if err == nil {
	finfo, _ := dst.Stat()
	fmod := finfo.ModTime()
	dsec := fmod.Unix()

	//コピー元の更新日を調べる
	smod := info.ModTime()
	ssec := smod.Unix()

	//更新日を比較して、元データの方が新しければtrue
	if dsec < ssec {
		doCopy = true
	}
} else {
	//バックアップ先にファイルがなければ常にtrue
	doCopy = true
}
dst.Close()

これでコピーを行うかどうかの判定ができました。


今回の内容を加味したコードが下記になります。


package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	var dir string
	var fpath string

	var src *os.File
	var dst *os.File

	srcdir := "/home/saito/goraw/"
	current, _ := filepath.Abs(".")
	backupDir := current + "/backup/"

	//バックアップ用ディレクトリがなければ作成
	if _, err := os.Stat(backupDir); err != nil {
		if err = os.Mkdir(backupDir, 0777); err != nil {
			log.Println(err)
		}
	}

	err := filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
		}

		//ディレクトリかどうかを調べる
		if info.IsDir() {
			fmt.Println(path + "はディレクトリです")

			//ディレクトリの場合は、バックアップの方にディレクトリがあるか調べて、なければ作る
			dir = strings.Replace(path, srcdir, "", 1)
			if _, err = os.Stat(backupDir + dir); err != nil {
				fmt.Println(backupDir + dir + "ディレクトリを作成します")
				err = os.Mkdir(backupDir+dir, 0777)
				if err != nil {
					log.Println(err)
				}
			}
		} else {
			//互いのファイルの更新日を調べて、コピーをするか決める
			doCopy := false

			fmt.Println(path + "はファイルです")
			fpath = strings.Replace(path, srcdir, "", 1)

			//すでにコピーがある場合はファイルの更新日を調べて、コピーするか決める
			dst, err = os.Open(backupDir + fpath)
			if err == nil {
				finfo, _ := dst.Stat()
				fmod := finfo.ModTime()
				dsec := fmod.Unix()

				//コピー元の更新日を調べる
				smod := info.ModTime()
				ssec := smod.Unix()

				if dsec < ssec {
					doCopy = true
				}
			} else {
				//バックアップ先にファイルがなければ常にtrue
				doCopy = true
			}
			dst.Close()

			//サーバにあるファイルの更新日が新しい場合はコピーを実行
			if doCopy {
				fmt.Println(info.Name() + "をコピーします")
				src, err = os.Open(path)
				if err != nil {
					log.Println(err)
				}

				dst, err = os.Create(backupDir + fpath)
				if err != nil {
					log.Println(err)
				}

				if _, err = io.Copy(dst, src); err != nil {
					log.Println(err)
				}
				src.Close()
				dst.Close()
			}
		}

		return nil
	})

	if err != nil {
		log.Println(err)
	}
}

一部冗長していますが、頭の整理を兼ねてなのでご愛嬌ください。


あとはSSHを挟んだ処理を追加するってところかな。