ste/downloader-blob_linux.go (149 lines of code) (raw):
//go:build linux
// +build linux
package ste
import (
"fmt"
"github.com/Azure/azure-storage-azcopy/v10/common"
"golang.org/x/sys/unix"
"io"
"os"
"syscall"
"time"
)
// CreateFile covers the following UNIX properties:
// File Mode, File Type
func (bd *blobDownloader) CreateFile(jptm IJobPartTransferMgr, destination string, size int64, writeThrough bool, t FolderCreationTracker) (file io.WriteCloser, needChunks bool, err error) {
var sip ISourceInfoProvider
sip, err = newBlobSourceInfoProvider(jptm)
if err != nil {
return
}
unixSIP := sip.(IUNIXPropertyBearingSourceInfoProvider) // Blob may have unix properties.
err = common.CreateParentDirectoryIfNotExist(destination, t)
if err != nil {
return
}
// try to remove the file before we create something else over it
_ = os.Remove(destination)
needChunks = (size > 0 || jptm.ShouldDecompress())
needMakeFile := true
var mode = uint32(common.DEFAULT_FILE_PERM)
if jptm.Info().PreservePOSIXProperties && unixSIP.HasUNIXProperties() {
var stat common.UnixStatAdapter
stat, err = unixSIP.GetUNIXProperties()
if stat.Extended() {
if stat.StatxMask()&common.STATX_MODE == common.STATX_MODE { // We need to retain access to the file until we're well & done with it
mode = stat.FileMode() | common.DEFAULT_FILE_PERM
}
} else {
mode = stat.FileMode() | common.DEFAULT_FILE_PERM
}
if mode != 0 { // Folders & Symlinks are not necessary to handle
switch {
case mode&common.S_IFBLK == common.S_IFBLK || mode&common.S_IFCHR == common.S_IFCHR:
// the file is representative of a device and does not need to be written to
err = unix.Mknod(destination, mode, int(stat.RDevice()))
needChunks = false
needMakeFile = false
case mode&common.S_IFIFO == common.S_IFIFO || mode&common.S_IFSOCK == common.S_IFSOCK:
// the file is a pipe and does not need to be written to
err = unix.Mknod(destination, mode, 0)
needChunks = false
needMakeFile = false
}
}
}
if !needMakeFile {
return
}
flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
if writeThrough {
flags |= os.O_SYNC
}
file, err = os.OpenFile(destination, flags, os.FileMode(mode)) // os.FileMode is uint32 on Linux.
if err != nil {
return
}
if size == 0 {
return
}
for i := 0; i < common.EINTR_RETRY_COUNT; i++ {
err = syscall.Fallocate(int(file.(*os.File).Fd()), 0, 0, size)
if err == nil || err != syscall.EINTR {
break
}
}
if err == syscall.ENOTSUP {
err = file.(*os.File).Truncate(size) // err will get returned at the end
}
return
}
func (bd *blobDownloader) ApplyUnixProperties(adapter common.UnixStatAdapter) (stage string, err error) {
// At this point, mode has already been applied. Let's work out what we need to apply, and apply the rest.
destination := bd.txInfo.Destination
// First, grab our file descriptor and such.
fi, err := os.Stat(destination)
if err != nil {
return "stat", err
}
// At this point, mode has already been applied. Let's work out what we need to apply, and apply the rest.
if adapter.Extended() {
stat := fi.Sys().(*syscall.Stat_t)
mask := adapter.StatxMask()
// stx_attributes is not persisted.
mode := os.FileMode(common.DEFAULT_FILE_PERM)
if common.StatXReturned(mask, common.STATX_MODE) {
mode = os.FileMode(adapter.FileMode())
}
err = os.Chmod(destination, mode)
if err != nil {
return "chmod", err
}
uid := stat.Uid
if common.StatXReturned(mask, common.STATX_UID) {
uid = adapter.Owner()
}
gid := stat.Gid
if common.StatXReturned(mask, common.STATX_GID) {
gid = adapter.Group()
}
// set ownership
err = os.Chown(destination, int(uid), int(gid))
if err != nil {
return "chown", err
}
atime := time.Unix(stat.Atim.Unix())
if common.StatXReturned(mask, common.STATX_ATIME) || !adapter.ATime().IsZero() { // workaround for noatime when underlying fs supports atime
atime = adapter.ATime()
}
mtime := time.Unix(stat.Mtim.Unix())
if common.StatXReturned(mask, common.STATX_MTIME) {
mtime = adapter.MTime()
}
// adapt times
err = os.Chtimes(destination, atime, mtime)
if err != nil {
return "chtimes", err
}
} else {
err = os.Chmod(destination, os.FileMode(adapter.FileMode())) // only write permissions
if err != nil {
return "chmod", err
}
err = os.Chown(destination, int(adapter.Owner()), int(adapter.Group()))
if err != nil {
return "chown", err
}
err = os.Chtimes(destination, adapter.ATime(), adapter.MTime())
if err != nil {
return "chtimes", err
}
}
return
}
func (bd *blobDownloader) SetFolderProperties(jptm IJobPartTransferMgr) error {
sip, err := newBlobSourceInfoProvider(jptm)
if err != nil {
return err
}
bd.txInfo = jptm.Info() // inform our blobDownloader a bit.
usip := sip.(IUNIXPropertyBearingSourceInfoProvider)
if usip.HasUNIXProperties() {
props, err := usip.GetUNIXProperties()
if err != nil {
return err
}
stage, err := bd.ApplyUnixProperties(props)
if err != nil {
return fmt.Errorf("set unix properties: %s; %w", stage, err)
}
}
return nil
}