1package files 2 3import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "syscall" 10 11 "github.com/aptly-dev/aptly/aptly" 12 "github.com/aptly-dev/aptly/utils" 13) 14 15// PublishedStorage abstract file system with public dirs (published repos) 16type PublishedStorage struct { 17 rootPath string 18 linkMethod uint 19 verifyMethod uint 20} 21 22// Check interfaces 23var ( 24 _ aptly.PublishedStorage = (*PublishedStorage)(nil) 25 _ aptly.FileSystemPublishedStorage = (*PublishedStorage)(nil) 26) 27 28// Constants defining the type of creating links 29const ( 30 LinkMethodHardLink uint = iota 31 LinkMethodSymLink 32 LinkMethodCopy 33) 34 35// Constants defining the type of file verification for LinkMethodCopy 36const ( 37 VerificationMethodChecksum uint = iota 38 VerificationMethodFileSize 39) 40 41// NewPublishedStorage creates new instance of PublishedStorage which specified root 42func NewPublishedStorage(root string, linkMethod string, verifyMethod string) *PublishedStorage { 43 // Ensure linkMethod is one of 'hardlink', 'symlink', 'copy' 44 var verifiedLinkMethod uint 45 46 if strings.EqualFold(linkMethod, "copy") { 47 verifiedLinkMethod = LinkMethodCopy 48 } else if strings.EqualFold(linkMethod, "symlink") { 49 verifiedLinkMethod = LinkMethodSymLink 50 } else { 51 verifiedLinkMethod = LinkMethodHardLink 52 } 53 54 var verifiedVerifyMethod uint 55 56 if strings.EqualFold(verifyMethod, "size") { 57 verifiedVerifyMethod = VerificationMethodFileSize 58 } else { 59 verifiedVerifyMethod = VerificationMethodChecksum 60 } 61 62 return &PublishedStorage{rootPath: root, linkMethod: verifiedLinkMethod, 63 verifyMethod: verifiedVerifyMethod} 64} 65 66// PublicPath returns root of public part 67func (storage *PublishedStorage) PublicPath() string { 68 return storage.rootPath 69} 70 71// MkDir creates directory recursively under public path 72func (storage *PublishedStorage) MkDir(path string) error { 73 return os.MkdirAll(filepath.Join(storage.rootPath, path), 0777) 74} 75 76// PutFile puts file into published storage at specified path 77func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error { 78 var ( 79 source, f *os.File 80 err error 81 ) 82 source, err = os.Open(sourceFilename) 83 if err != nil { 84 return err 85 } 86 defer source.Close() 87 88 f, err = os.Create(filepath.Join(storage.rootPath, path)) 89 if err != nil { 90 return err 91 } 92 defer f.Close() 93 94 _, err = io.Copy(f, source) 95 return err 96} 97 98// Remove removes single file under public path 99func (storage *PublishedStorage) Remove(path string) error { 100 if len(path) <= 0 { 101 panic("trying to remove empty path") 102 } 103 filepath := filepath.Join(storage.rootPath, path) 104 return os.Remove(filepath) 105} 106 107// RemoveDirs removes directory structure under public path 108func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { 109 if len(path) <= 0 { 110 panic("trying to remove the root directory") 111 } 112 filepath := filepath.Join(storage.rootPath, path) 113 if progress != nil { 114 progress.Printf("Removing %s...\n", filepath) 115 } 116 return os.RemoveAll(filepath) 117} 118 119// LinkFromPool links package file from pool to dist's pool location 120// 121// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) 122// sourcePool is instance of aptly.PackagePool 123// sourcePath is a relative path to package file in package pool 124// 125// LinkFromPool returns relative path for the published file to be included in package index 126func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool, 127 sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { 128 129 baseName := filepath.Base(fileName) 130 poolPath := filepath.Join(storage.rootPath, publishedDirectory, filepath.Dir(fileName)) 131 132 err := os.MkdirAll(poolPath, 0777) 133 if err != nil { 134 return err 135 } 136 137 var dstStat, srcStat os.FileInfo 138 139 dstStat, err = os.Stat(filepath.Join(poolPath, baseName)) 140 if err == nil { 141 // already exists, check source file 142 srcStat, err = sourcePool.Stat(sourcePath) 143 if err != nil { 144 // source file doesn't exist? problem! 145 return err 146 } 147 148 if storage.linkMethod == LinkMethodCopy { 149 if storage.verifyMethod == VerificationMethodFileSize { 150 // if source and destination have the same size, no need to copy 151 if srcStat.Size() == dstStat.Size() { 152 return nil 153 } 154 } else { 155 // if source and destination have the same checksums, no need to copy 156 var dstMD5 string 157 dstMD5, err = utils.MD5ChecksumForFile(filepath.Join(poolPath, baseName)) 158 159 if err != nil { 160 return err 161 } 162 163 if dstMD5 == sourceChecksums.MD5 { 164 return nil 165 } 166 } 167 } else { 168 srcSys := srcStat.Sys().(*syscall.Stat_t) 169 dstSys := dstStat.Sys().(*syscall.Stat_t) 170 171 // if source and destination inodes match, no need to link 172 173 // Symlink can point to different filesystem with identical inodes 174 // so we have to check the device as well. 175 if srcSys.Ino == dstSys.Ino && srcSys.Dev == dstSys.Dev { 176 return nil 177 } 178 } 179 180 // source and destination have different inodes, if !forced, this is fatal error 181 if !force { 182 return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName)) 183 } 184 185 // forced, so remove destination 186 err = os.Remove(filepath.Join(poolPath, baseName)) 187 if err != nil { 188 return err 189 } 190 } 191 192 // destination doesn't exist (or forced), create link or copy 193 if storage.linkMethod == LinkMethodCopy { 194 var r aptly.ReadSeekerCloser 195 r, err = sourcePool.Open(sourcePath) 196 if err != nil { 197 return err 198 } 199 200 var dst *os.File 201 dst, err = os.Create(filepath.Join(poolPath, baseName)) 202 if err != nil { 203 r.Close() 204 return err 205 } 206 207 _, err = io.Copy(dst, r) 208 if err != nil { 209 r.Close() 210 dst.Close() 211 return err 212 } 213 214 err = r.Close() 215 if err != nil { 216 dst.Close() 217 return err 218 } 219 220 err = dst.Close() 221 } else if storage.linkMethod == LinkMethodSymLink { 222 err = sourcePool.(aptly.LocalPackagePool).Symlink(sourcePath, filepath.Join(poolPath, baseName)) 223 } else { 224 err = sourcePool.(aptly.LocalPackagePool).Link(sourcePath, filepath.Join(poolPath, baseName)) 225 } 226 227 return err 228} 229 230// Filelist returns list of files under prefix 231func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { 232 root := filepath.Join(storage.rootPath, prefix) 233 result := []string{} 234 235 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 236 if err != nil { 237 return err 238 } 239 if !info.IsDir() { 240 result = append(result, path[len(root)+1:]) 241 } 242 return nil 243 }) 244 245 if err != nil && os.IsNotExist(err) { 246 // file path doesn't exist, consider it empty 247 return []string{}, nil 248 } 249 250 return result, err 251} 252 253// RenameFile renames (moves) file 254func (storage *PublishedStorage) RenameFile(oldName, newName string) error { 255 return os.Rename(filepath.Join(storage.rootPath, oldName), filepath.Join(storage.rootPath, newName)) 256} 257 258// SymLink creates a symbolic link, which can be read with ReadLink 259func (storage *PublishedStorage) SymLink(src string, dst string) error { 260 return os.Symlink(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst)) 261} 262 263// HardLink creates a hardlink of a file 264func (storage *PublishedStorage) HardLink(src string, dst string) error { 265 return os.Link(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst)) 266} 267 268// FileExists returns true if path exists 269func (storage *PublishedStorage) FileExists(path string) (bool, error) { 270 if _, err := os.Lstat(filepath.Join(storage.rootPath, path)); os.IsNotExist(err) { 271 return false, nil 272 } 273 274 return true, nil 275} 276 277// ReadLink returns the symbolic link pointed to by path (relative to storage 278// root) 279func (storage *PublishedStorage) ReadLink(path string) (string, error) { 280 absPath, err := os.Readlink(filepath.Join(storage.rootPath, path)) 281 if err != nil { 282 return absPath, err 283 } 284 return filepath.Rel(storage.rootPath, absPath) 285} 286