| package hcsshim |
| |
| import ( |
| "errors" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "runtime" |
| |
| "github.com/Microsoft/go-winio" |
| "github.com/Sirupsen/logrus" |
| ) |
| |
| // ImportLayer will take the contents of the folder at importFolderPath and import |
| // that into a layer with the id layerId. Note that in order to correctly populate |
| // the layer and interperet the transport format, all parent layers must already |
| // be present on the system at the paths provided in parentLayerPaths. |
| func ImportLayer(info DriverInfo, layerID string, importFolderPath string, parentLayerPaths []string) error { |
| title := "hcsshim::ImportLayer " |
| logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerID, importFolderPath) |
| |
| // Generate layer descriptors |
| layers, err := layerPathsToDescriptors(parentLayerPaths) |
| if err != nil { |
| return err |
| } |
| |
| // Convert info to API calling convention |
| infop, err := convertDriverInfo(info) |
| if err != nil { |
| logrus.Error(err) |
| return err |
| } |
| |
| err = importLayer(&infop, layerID, importFolderPath, layers) |
| if err != nil { |
| err = makeErrorf(err, title, "layerId=%s flavour=%d folder=%s", layerID, info.Flavour, importFolderPath) |
| logrus.Error(err) |
| return err |
| } |
| |
| logrus.Debugf(title+"succeeded flavour=%d layerId=%s folder=%s", info.Flavour, layerID, importFolderPath) |
| return nil |
| } |
| |
| // LayerWriter is an interface that supports writing a new container image layer. |
| type LayerWriter interface { |
| // Add adds a file to the layer with given metadata. |
| Add(name string, fileInfo *winio.FileBasicInfo) error |
| // AddLink adds a hard link to the layer. The target must already have been added. |
| AddLink(name string, target string) error |
| // Remove removes a file that was present in a parent layer from the layer. |
| Remove(name string) error |
| // Write writes data to the current file. The data must be in the format of a Win32 |
| // backup stream. |
| Write(b []byte) (int, error) |
| // Close finishes the layer writing process and releases any resources. |
| Close() error |
| } |
| |
| // FilterLayerWriter provides an interface to write the contents of a layer to the file system. |
| type FilterLayerWriter struct { |
| context uintptr |
| } |
| |
| // Add adds a file or directory to the layer. The file's parent directory must have already been added. |
| // |
| // name contains the file's relative path. fileInfo contains file times and file attributes; the rest |
| // of the file metadata and the file data must be written as a Win32 backup stream to the Write() method. |
| // winio.BackupStreamWriter can be used to facilitate this. |
| func (w *FilterLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error { |
| if name[0] != '\\' { |
| name = `\` + name |
| } |
| err := importLayerNext(w.context, name, fileInfo) |
| if err != nil { |
| return makeError(err, "ImportLayerNext", "") |
| } |
| return nil |
| } |
| |
| // AddLink adds a hard link to the layer. The target of the link must have already been added. |
| func (w *FilterLayerWriter) AddLink(name string, target string) error { |
| return errors.New("hard links not yet supported") |
| } |
| |
| // Remove removes a file from the layer. The file must have been present in the parent layer. |
| // |
| // name contains the file's relative path. |
| func (w *FilterLayerWriter) Remove(name string) error { |
| if name[0] != '\\' { |
| name = `\` + name |
| } |
| err := importLayerNext(w.context, name, nil) |
| if err != nil { |
| return makeError(err, "ImportLayerNext", "") |
| } |
| return nil |
| } |
| |
| // Write writes more backup stream data to the current file. |
| func (w *FilterLayerWriter) Write(b []byte) (int, error) { |
| err := importLayerWrite(w.context, b) |
| if err != nil { |
| err = makeError(err, "ImportLayerWrite", "") |
| return 0, err |
| } |
| return len(b), err |
| } |
| |
| // Close completes the layer write operation. The error must be checked to ensure that the |
| // operation was successful. |
| func (w *FilterLayerWriter) Close() (err error) { |
| if w.context != 0 { |
| err = importLayerEnd(w.context) |
| if err != nil { |
| err = makeError(err, "ImportLayerEnd", "") |
| } |
| w.context = 0 |
| } |
| return |
| } |
| |
| type legacyLayerWriterWrapper struct { |
| *legacyLayerWriter |
| info DriverInfo |
| layerID string |
| path string |
| parentLayerPaths []string |
| } |
| |
| func (r *legacyLayerWriterWrapper) Close() error { |
| defer os.RemoveAll(r.root) |
| err := r.legacyLayerWriter.Close() |
| if err != nil { |
| return err |
| } |
| |
| // Use the original path here because ImportLayer does not support long paths for the source in TP5. |
| // But do use a long path for the destination to work around another bug with directories |
| // with MAX_PATH - 12 < length < MAX_PATH. |
| info := r.info |
| fullPath, err := makeLongAbsPath(filepath.Join(info.HomeDir, r.layerID)) |
| if err != nil { |
| return err |
| } |
| |
| info.HomeDir = "" |
| if err = ImportLayer(info, fullPath, r.path, r.parentLayerPaths); err != nil { |
| return err |
| } |
| // Add any hard links that were collected. |
| for _, lnk := range r.PendingLinks { |
| if err = os.Remove(lnk.Path); err != nil && !os.IsNotExist(err) { |
| return err |
| } |
| if err = os.Link(lnk.Target, lnk.Path); err != nil { |
| return err |
| } |
| } |
| // Prepare the utility VM for use if one is present in the layer. |
| if r.HasUtilityVM { |
| err = ProcessUtilityVMImage(filepath.Join(fullPath, "UtilityVM")) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // NewLayerWriter returns a new layer writer for creating a layer on disk. |
| // The caller must have taken the SeBackupPrivilege and SeRestorePrivilege privileges |
| // to call this and any methods on the resulting LayerWriter. |
| func NewLayerWriter(info DriverInfo, layerID string, parentLayerPaths []string) (LayerWriter, error) { |
| if len(parentLayerPaths) == 0 { |
| // This is a base layer. It gets imported differently. |
| return &baseLayerWriter{ |
| root: filepath.Join(info.HomeDir, layerID), |
| }, nil |
| } |
| |
| if procImportLayerBegin.Find() != nil { |
| // The new layer reader is not available on this Windows build. Fall back to the |
| // legacy export code path. |
| path, err := ioutil.TempDir("", "hcs") |
| if err != nil { |
| return nil, err |
| } |
| return &legacyLayerWriterWrapper{ |
| legacyLayerWriter: newLegacyLayerWriter(path, parentLayerPaths, filepath.Join(info.HomeDir, layerID)), |
| info: info, |
| layerID: layerID, |
| path: path, |
| parentLayerPaths: parentLayerPaths, |
| }, nil |
| } |
| layers, err := layerPathsToDescriptors(parentLayerPaths) |
| if err != nil { |
| return nil, err |
| } |
| |
| infop, err := convertDriverInfo(info) |
| if err != nil { |
| return nil, err |
| } |
| |
| w := &FilterLayerWriter{} |
| err = importLayerBegin(&infop, layerID, layers, &w.context) |
| if err != nil { |
| return nil, makeError(err, "ImportLayerStart", "") |
| } |
| runtime.SetFinalizer(w, func(w *FilterLayerWriter) { w.Close() }) |
| return w, nil |
| } |