stream-decoder.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //+build ignore
  2. // Copyright 2015, Klaus Post, see LICENSE for details.
  3. //
  4. // Stream decoder example.
  5. //
  6. // The decoder reverses the process of "stream-encoder.go"
  7. //
  8. // To build an executable use:
  9. //
  10. // go build stream-decoder.go
  11. //
  12. // Simple Encoder/Decoder Shortcomings:
  13. // * If the file size of the input isn't dividable by the number of data shards
  14. // the output will contain extra zeroes
  15. //
  16. // * If the shard numbers isn't the same for the decoder as in the
  17. // encoder, invalid output will be generated.
  18. //
  19. // * If values have changed in a shard, it cannot be reconstructed.
  20. //
  21. // * If two shards have been swapped, reconstruction will always fail.
  22. // You need to supply the shards in the same order as they were given to you.
  23. //
  24. // The solution for this is to save a metadata file containing:
  25. //
  26. // * File size.
  27. // * The number of data/parity shards.
  28. // * HASH of each shard.
  29. // * Order of the shards.
  30. //
  31. // If you save these properties, you should abe able to detect file corruption
  32. // in a shard and be able to reconstruct your data if you have the needed number of shards left.
  33. package main
  34. import (
  35. "flag"
  36. "fmt"
  37. "io"
  38. "os"
  39. "path/filepath"
  40. "github.com/klauspost/reedsolomon"
  41. )
  42. var dataShards = flag.Int("data", 4, "Number of shards to split the data into")
  43. var parShards = flag.Int("par", 2, "Number of parity shards")
  44. var outFile = flag.String("out", "", "Alternative output path/file")
  45. func init() {
  46. flag.Usage = func() {
  47. fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
  48. fmt.Fprintf(os.Stderr, " %s [-flags] basefile.ext\nDo not add the number to the filename.\n", os.Args[0])
  49. fmt.Fprintf(os.Stderr, "Valid flags:\n")
  50. flag.PrintDefaults()
  51. }
  52. }
  53. func main() {
  54. // Parse flags
  55. flag.Parse()
  56. args := flag.Args()
  57. if len(args) != 1 {
  58. fmt.Fprintf(os.Stderr, "Error: No filenames given\n")
  59. flag.Usage()
  60. os.Exit(1)
  61. }
  62. fname := args[0]
  63. // Create matrix
  64. enc, err := reedsolomon.NewStream(*dataShards, *parShards)
  65. checkErr(err)
  66. // Open the inputs
  67. shards, size, err := openInput(*dataShards, *parShards, fname)
  68. checkErr(err)
  69. // Verify the shards
  70. ok, err := enc.Verify(shards)
  71. if ok {
  72. fmt.Println("No reconstruction needed")
  73. } else {
  74. fmt.Println("Verification failed. Reconstructing data")
  75. shards, size, err = openInput(*dataShards, *parShards, fname)
  76. checkErr(err)
  77. // Create out destination writers
  78. out := make([]io.Writer, len(shards))
  79. for i := range out {
  80. if shards[i] == nil {
  81. dir, _ := filepath.Split(fname)
  82. outfn := fmt.Sprintf("%s.%d", fname, i)
  83. fmt.Println("Creating", outfn)
  84. out[i], err = os.Create(filepath.Join(dir, outfn))
  85. checkErr(err)
  86. }
  87. }
  88. err = enc.Reconstruct(shards, out)
  89. if err != nil {
  90. fmt.Println("Reconstruct failed -", err)
  91. os.Exit(1)
  92. }
  93. // Close output.
  94. for i := range out {
  95. if out[i] != nil {
  96. err := out[i].(*os.File).Close()
  97. checkErr(err)
  98. }
  99. }
  100. shards, size, err = openInput(*dataShards, *parShards, fname)
  101. ok, err = enc.Verify(shards)
  102. if !ok {
  103. fmt.Println("Verification failed after reconstruction, data likely corrupted:", err)
  104. os.Exit(1)
  105. }
  106. checkErr(err)
  107. }
  108. // Join the shards and write them
  109. outfn := *outFile
  110. if outfn == "" {
  111. outfn = fname
  112. }
  113. fmt.Println("Writing data to", outfn)
  114. f, err := os.Create(outfn)
  115. checkErr(err)
  116. shards, size, err = openInput(*dataShards, *parShards, fname)
  117. checkErr(err)
  118. // We don't know the exact filesize.
  119. err = enc.Join(f, shards, int64(*dataShards)*size)
  120. checkErr(err)
  121. }
  122. func openInput(dataShards, parShards int, fname string) (r []io.Reader, size int64, err error) {
  123. // Create shards and load the data.
  124. shards := make([]io.Reader, dataShards+parShards)
  125. for i := range shards {
  126. infn := fmt.Sprintf("%s.%d", fname, i)
  127. fmt.Println("Opening", infn)
  128. f, err := os.Open(infn)
  129. if err != nil {
  130. fmt.Println("Error reading file", err)
  131. shards[i] = nil
  132. continue
  133. } else {
  134. shards[i] = f
  135. }
  136. stat, err := f.Stat()
  137. checkErr(err)
  138. if stat.Size() > 0 {
  139. size = stat.Size()
  140. } else {
  141. shards[i] = nil
  142. }
  143. }
  144. return shards, size, nil
  145. }
  146. func checkErr(err error) {
  147. if err != nil {
  148. fmt.Fprintf(os.Stderr, "Error: %s", err.Error())
  149. os.Exit(2)
  150. }
  151. }