| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | package archiver | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-10-01 16:20:45 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/restic/chunker" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/fs" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | 	"golang.org/x/sync/errgroup" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-12 21:40:31 +02:00
										 |  |  | // SaveBlobFn saves a blob to a repo. | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | type SaveBlobFn func(context.Context, restic.BlobType, *Buffer, func(res SaveBlobResponse)) | 
					
						
							| 
									
										
										
										
											2018-05-12 21:40:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | // FileSaver concurrently saves incoming files to the repo. | 
					
						
							|  |  |  | type FileSaver struct { | 
					
						
							|  |  |  | 	saveFilePool *BufferPool | 
					
						
							| 
									
										
										
										
											2018-05-12 21:40:31 +02:00
										 |  |  | 	saveBlob     SaveBlobFn | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pol chunker.Pol | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:20:59 +02:00
										 |  |  | 	ch chan<- saveFileJob | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 15:21:17 +02:00
										 |  |  | 	CompleteBlob func(bytes uint64) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-19 23:08:13 +02:00
										 |  |  | 	NodeFromFileInfo func(snPath, filename string, fi os.FileInfo) (*restic.Node, error) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-29 15:34:41 +02:00
										 |  |  | // NewFileSaver returns a new file saver. A worker pool with fileWorkers is | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | // started, it is stopped when ctx is cancelled. | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | func NewFileSaver(ctx context.Context, wg *errgroup.Group, save SaveBlobFn, pol chunker.Pol, fileWorkers, blobWorkers uint) *FileSaver { | 
					
						
							| 
									
										
										
										
											2018-04-29 15:34:41 +02:00
										 |  |  | 	ch := make(chan saveFileJob) | 
					
						
							| 
									
										
										
										
											2018-04-29 13:20:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-29 15:34:41 +02:00
										 |  |  | 	debug.Log("new file saver with %v file workers and %v blob workers", fileWorkers, blobWorkers) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	poolSize := fileWorkers + blobWorkers | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	s := &FileSaver{ | 
					
						
							| 
									
										
										
										
											2018-05-12 21:40:31 +02:00
										 |  |  | 		saveBlob:     save, | 
					
						
							| 
									
										
										
										
											2022-05-29 17:07:37 +02:00
										 |  |  | 		saveFilePool: NewBufferPool(int(poolSize), chunker.MaxSize), | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		pol:          pol, | 
					
						
							|  |  |  | 		ch:           ch, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 15:21:17 +02:00
										 |  |  | 		CompleteBlob: func(uint64) {}, | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-29 13:20:12 +02:00
										 |  |  | 	for i := uint(0); i < fileWorkers; i++ { | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | 		wg.Go(func() error { | 
					
						
							|  |  |  | 			s.worker(ctx, ch) | 
					
						
							| 
									
										
										
										
											2018-05-08 22:28:37 +02:00
										 |  |  | 			return nil | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return s | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | func (s *FileSaver) TriggerShutdown() { | 
					
						
							|  |  |  | 	close(s.ch) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | // CompleteFunc is called when the file has been saved. | 
					
						
							|  |  |  | type CompleteFunc func(*restic.Node, ItemStats) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Save stores the file f and returns the data once it has been completed. The | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | // file is closed by Save. completeReading is only called if the file was read | 
					
						
							|  |  |  | // successfully. complete is always called. If completeReading is called, then | 
					
						
							|  |  |  | // this will always happen before calling complete. | 
					
						
							|  |  |  | func (s *FileSaver) Save(ctx context.Context, snPath string, target string, file fs.File, fi os.FileInfo, start func(), completeReading func(), complete CompleteFunc) FutureNode { | 
					
						
							| 
									
										
										
										
											2022-05-29 11:57:10 +02:00
										 |  |  | 	fn, ch := newFutureNode() | 
					
						
							| 
									
										
										
										
											2018-05-08 22:28:37 +02:00
										 |  |  | 	job := saveFileJob{ | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | 		snPath: snPath, | 
					
						
							|  |  |  | 		target: target, | 
					
						
							|  |  |  | 		file:   file, | 
					
						
							|  |  |  | 		fi:     fi, | 
					
						
							|  |  |  | 		ch:     ch, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		start:           start, | 
					
						
							|  |  |  | 		completeReading: completeReading, | 
					
						
							|  |  |  | 		complete:        complete, | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-08 22:28:37 +02:00
										 |  |  | 	select { | 
					
						
							|  |  |  | 	case s.ch <- job: | 
					
						
							|  |  |  | 	case <-ctx.Done(): | 
					
						
							|  |  |  | 		debug.Log("not sending job, context is cancelled: %v", ctx.Err()) | 
					
						
							| 
									
										
										
										
											2018-05-12 23:08:00 +02:00
										 |  |  | 		_ = file.Close() | 
					
						
							| 
									
										
										
										
											2018-05-12 21:40:31 +02:00
										 |  |  | 		close(ch) | 
					
						
							| 
									
										
										
										
											2018-05-08 22:28:37 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-29 11:57:10 +02:00
										 |  |  | 	return fn | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type saveFileJob struct { | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | 	snPath string | 
					
						
							|  |  |  | 	target string | 
					
						
							|  |  |  | 	file   fs.File | 
					
						
							|  |  |  | 	fi     os.FileInfo | 
					
						
							|  |  |  | 	ch     chan<- futureNodeResult | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	start           func() | 
					
						
							|  |  |  | 	completeReading func() | 
					
						
							|  |  |  | 	complete        CompleteFunc | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // saveFile stores the file f in the repo, then closes it. | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPath string, target string, f fs.File, fi os.FileInfo, start func(), finishReading func(), finish func(res futureNodeResult)) { | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	start() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-29 11:57:10 +02:00
										 |  |  | 	fnr := futureNodeResult{ | 
					
						
							|  |  |  | 		snPath: snPath, | 
					
						
							|  |  |  | 		target: target, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 	var lock sync.Mutex | 
					
						
							|  |  |  | 	remaining := 0 | 
					
						
							|  |  |  | 	isCompleted := false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	completeBlob := func() { | 
					
						
							|  |  |  | 		lock.Lock() | 
					
						
							|  |  |  | 		defer lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		remaining-- | 
					
						
							|  |  |  | 		if remaining == 0 && fnr.err == nil { | 
					
						
							|  |  |  | 			if isCompleted { | 
					
						
							|  |  |  | 				panic("completed twice") | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-11-05 13:42:17 +01:00
										 |  |  | 			for _, id := range fnr.node.Content { | 
					
						
							|  |  |  | 				if id.IsNull() { | 
					
						
							|  |  |  | 					panic("completed file with null ID") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			isCompleted = true | 
					
						
							|  |  |  | 			finish(fnr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	completeError := func(err error) { | 
					
						
							|  |  |  | 		lock.Lock() | 
					
						
							|  |  |  | 		defer lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if fnr.err == nil { | 
					
						
							|  |  |  | 			if isCompleted { | 
					
						
							|  |  |  | 				panic("completed twice") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			isCompleted = true | 
					
						
							| 
									
										
										
										
											2023-10-01 16:20:45 +02:00
										 |  |  | 			fnr.err = fmt.Errorf("failed to save %v: %w", target, err) | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			fnr.node = nil | 
					
						
							|  |  |  | 			fnr.stats = ItemStats{} | 
					
						
							|  |  |  | 			finish(fnr) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("%v", snPath) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-19 23:08:13 +02:00
										 |  |  | 	node, err := s.NodeFromFileInfo(snPath, f.Name(), fi) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		_ = f.Close() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		completeError(err) | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if node.Type != "file" { | 
					
						
							|  |  |  | 		_ = f.Close() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		completeError(errors.Errorf("node type %q is wrong", node.Type)) | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// reuse the chunker | 
					
						
							|  |  |  | 	chnker.Reset(f, s.pol) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	node.Content = []restic.ID{} | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 	node.Size = 0 | 
					
						
							|  |  |  | 	var idx int | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		buf := s.saveFilePool.Get() | 
					
						
							|  |  |  | 		chunk, err := chnker.Next(buf.Data) | 
					
						
							| 
									
										
										
										
											2022-06-13 20:35:37 +02:00
										 |  |  | 		if err == io.EOF { | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 			buf.Release() | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-04-29 15:34:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		buf.Data = chunk.Data | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		node.Size += uint64(chunk.Length) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			_ = f.Close() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			completeError(err) | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		// test if the context has been cancelled, return the error | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			_ = f.Close() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			completeError(ctx.Err()) | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		// add a place to store the saveBlob result | 
					
						
							|  |  |  | 		pos := idx | 
					
						
							| 
									
										
										
										
											2022-11-10 20:19:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		lock.Lock() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		node.Content = append(node.Content, restic.ID{}) | 
					
						
							| 
									
										
										
										
											2022-11-10 20:19:37 +01:00
										 |  |  | 		lock.Unlock() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		s.saveBlob(ctx, restic.DataBlob, buf, func(sbr SaveBlobResponse) { | 
					
						
							|  |  |  | 			lock.Lock() | 
					
						
							|  |  |  | 			if !sbr.known { | 
					
						
							|  |  |  | 				fnr.stats.DataBlobs++ | 
					
						
							|  |  |  | 				fnr.stats.DataSize += uint64(sbr.length) | 
					
						
							|  |  |  | 				fnr.stats.DataSizeInRepo += uint64(sbr.sizeInRepo) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			node.Content[pos] = sbr.id | 
					
						
							|  |  |  | 			lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			completeBlob() | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		idx++ | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// test if the context has been cancelled, return the error | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			_ = f.Close() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			completeError(ctx.Err()) | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 15:21:17 +02:00
										 |  |  | 		s.CompleteBlob(uint64(len(chunk.Data))) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = f.Close() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 		completeError(err) | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-29 11:57:10 +02:00
										 |  |  | 	fnr.node = node | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 	lock.Lock() | 
					
						
							|  |  |  | 	// require one additional completeFuture() call to ensure that the future only completes | 
					
						
							|  |  |  | 	// after reaching the end of this method | 
					
						
							|  |  |  | 	remaining += idx + 1 | 
					
						
							|  |  |  | 	lock.Unlock() | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | 	finishReading() | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 	completeBlob() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-08 22:28:37 +02:00
										 |  |  | func (s *FileSaver) worker(ctx context.Context, jobs <-chan saveFileJob) { | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	// a worker has one chunker which is reused for each file (because it contains a rather large buffer) | 
					
						
							|  |  |  | 	chnker := chunker.New(nil, s.pol) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		var job saveFileJob | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | 		var ok bool | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2022-05-27 19:08:50 +02:00
										 |  |  | 		case job, ok = <-jobs: | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-29 11:57:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-22 12:05:49 +02:00
										 |  |  | 		s.saveFile(ctx, chnker, job.snPath, job.target, job.file, job.fi, job.start, func() { | 
					
						
							|  |  |  | 			if job.completeReading != nil { | 
					
						
							|  |  |  | 				job.completeReading() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, func(res futureNodeResult) { | 
					
						
							| 
									
										
										
										
											2022-10-07 20:23:38 +02:00
										 |  |  | 			if job.complete != nil { | 
					
						
							|  |  |  | 				job.complete(res.node, res.stats) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			job.ch <- res | 
					
						
							|  |  |  | 			close(job.ch) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |