diff --git a/cmd/stfs/cmd/operation_initialize.go b/cmd/stfs/cmd/operation_initialize.go new file mode 100644 index 0000000..f7d835b --- /dev/null +++ b/cmd/stfs/cmd/operation_initialize.go @@ -0,0 +1,116 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/pojntfx/stfs/internal/check" + "github.com/pojntfx/stfs/internal/keyext" + "github.com/pojntfx/stfs/internal/logging" + "github.com/pojntfx/stfs/pkg/config" + "github.com/pojntfx/stfs/pkg/keys" + "github.com/pojntfx/stfs/pkg/mtio" + "github.com/pojntfx/stfs/pkg/operations" + "github.com/pojntfx/stfs/pkg/persisters" + "github.com/pojntfx/stfs/pkg/tape" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var operationInitializeCmd = &cobra.Command{ + Use: "initialize", + Aliases: []string{"ini", "i", "init"}, + Short: "Truncate and initalize a file or directory", + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if err := check.CheckCompressionLevel(viper.GetString(compressionLevelFlag)); err != nil { + return err + } + + if err := check.CheckKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(recipientFlag)); err != nil { + return err + } + + return check.CheckKeyAccessible(viper.GetString(signatureFlag), viper.GetString(identityFlag)) + }, + RunE: func(cmd *cobra.Command, args []string) error { + pubkey, err := keyext.ReadKey(viper.GetString(encryptionFlag), viper.GetString(recipientFlag)) + if err != nil { + return err + } + + recipient, err := keys.ParseRecipient(viper.GetString(encryptionFlag), pubkey) + if err != nil { + return err + } + + privkey, err := keyext.ReadKey(viper.GetString(signatureFlag), viper.GetString(identityFlag)) + if err != nil { + return err + } + + identity, err := keys.ParseSignerIdentity(viper.GetString(signatureFlag), privkey, viper.GetString(passwordFlag)) + if err != nil { + return err + } + + mt := mtio.MagneticTapeIO{} + tm := tape.NewTapeManager( + viper.GetString(driveFlag), + mt, + viper.GetInt(recordSizeFlag), + true, + ) + + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) + if err := metadataPersister.Open(); err != nil { + return err + } + + ops := operations.NewOperations( + config.BackendConfig{ + GetWriter: tm.GetWriter, + CloseWriter: tm.Close, + + GetReader: tm.GetReader, + CloseReader: tm.Close, + + MagneticTapeIO: mt, + }, + config.MetadataConfig{ + Metadata: metadataPersister, + }, + + config.PipeConfig{ + Compression: viper.GetString(compressionFlag), + Encryption: viper.GetString(encryptionFlag), + Signature: viper.GetString(signatureFlag), + RecordSize: viper.GetInt(recordSizeFlag), + }, + config.CryptoConfig{ + Recipient: recipient, + Identity: identity, + Password: viper.GetString(passwordFlag), + }, + + logging.NewCSVLogger().PrintHeaderEvent, + ) + + return ops.Initialize("/", os.ModePerm, viper.GetString(compressionLevelFlag)) + }, +} + +func init() { + operationInitializeCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") + operationInitializeCmd.PersistentFlags().StringP(compressionLevelFlag, "l", config.CompressionLevelBalancedKey, fmt.Sprintf("Compression level to use (default %v, available are %v)", config.CompressionLevelBalancedKey, config.KnownCompressionLevels)) + operationInitializeCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to public key of recipient to encrypt for") + operationInitializeCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key to sign with") + operationInitializeCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + + viper.AutomaticEnv() + + operationCmd.AddCommand(operationInitializeCmd) +} diff --git a/pkg/operations/archive.go b/pkg/operations/archive.go index 3505347..5ba63ee 100644 --- a/pkg/operations/archive.go +++ b/pkg/operations/archive.go @@ -33,6 +33,16 @@ func (o *Operations) Archive( o.diskOperationLock.Lock() defer o.diskOperationLock.Unlock() + return o.archive(getSrc, compressionLevel, overwrite, initializing) +} + +func (o *Operations) archive( + getSrc func() (config.FileConfig, error), + compressionLevel string, + overwrite bool, + initializing bool, +) ([]*tar.Header, error) { + writer, err := o.backend.GetWriter() if err != nil { return []*tar.Header{}, err diff --git a/pkg/operations/initialize.go b/pkg/operations/initialize.go new file mode 100644 index 0000000..5e4ccc1 --- /dev/null +++ b/pkg/operations/initialize.go @@ -0,0 +1,90 @@ +package operations + +import ( + "archive/tar" + "io" + "os" + "os/user" + "path/filepath" + "strconv" + "time" + + "github.com/pojntfx/stfs/pkg/config" +) + +func (o *Operations) Initialize( + name string, + perm os.FileMode, + compressionLevel string, +) error { + o.diskOperationLock.Lock() + defer o.diskOperationLock.Unlock() + + usr, err := user.Current() + if err != nil { + return err + } + + gid, err := strconv.Atoi(usr.Gid) + if err != nil { + // Some OSes like i.e. Windows don't support numeric GIDs, so use 0 instead + gid = 0 + } + + uid, err := strconv.Atoi(usr.Uid) + if err != nil { + // Some OSes like i.e. Windows don't support numeric UIDs, so use 0 instead + uid = 0 + } + + groups, err := usr.GroupIds() + if err != nil { + return err + } + + gname := "" + if len(groups) >= 1 { + gname = groups[0] + } + + typeflag := tar.TypeDir + + hdr := &tar.Header{ + Typeflag: byte(typeflag), + + Name: name, + + Mode: int64(perm), + Uid: uid, + Gid: gid, + Uname: usr.Username, + Gname: gname, + + ModTime: time.Now(), + } + + done := false + if _, err := o.archive( + func() (config.FileConfig, error) { + // Exit after the first write + if done { + return config.FileConfig{}, io.EOF + } + done = true + + return config.FileConfig{ + GetFile: nil, // Not required as we never replace + Info: hdr.FileInfo(), + Path: filepath.ToSlash(name), + Link: filepath.ToSlash(hdr.Linkname), + }, nil + }, + compressionLevel, + true, + true, + ); err != nil { + return err + } + + return nil +}