2020-08-18 12:23:45 +08:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2020-08-18 12:23:45 +08:00
package storage
import (
2020-10-13 04:58:34 +01:00
"context"
2026-03-05 16:49:01 +01:00
"errors"
2023-03-22 04:02:49 +08:00
"fmt"
2020-08-18 12:23:45 +08:00
"io"
"net/url"
"os"
"path/filepath"
2020-10-16 04:51:06 +01:00
"code.gitea.io/gitea/modules/log"
2023-06-14 11:42:38 +08:00
"code.gitea.io/gitea/modules/setting"
2020-08-18 12:23:45 +08:00
"code.gitea.io/gitea/modules/util"
)
2022-01-20 18:46:10 +01:00
var _ ObjectStorage = & LocalStorage { }
2020-08-18 12:23:45 +08:00
// LocalStorage represents a local files storage
type LocalStorage struct {
2021-03-05 13:19:17 +00:00
ctx context . Context
dir string
tmpdir string
2020-08-18 12:23:45 +08:00
}
// NewLocalStorage returns a local files
2023-06-14 11:42:38 +08:00
func NewLocalStorage ( ctx context . Context , config * setting . Storage ) ( ObjectStorage , error ) {
2026-03-05 16:49:01 +01:00
// prepare storage root path
2023-03-22 04:02:49 +08:00
if ! filepath . IsAbs ( config . Path ) {
2026-03-05 16:49:01 +01:00
return nil , fmt . Errorf ( "LocalStorage config.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q" , config . Path )
2020-08-18 12:23:45 +08:00
}
2026-03-05 16:49:01 +01:00
storageRoot := util . FilePathJoinAbs ( config . Path )
2020-08-18 12:23:45 +08:00
2026-03-05 16:49:01 +01:00
// prepare storage temporary path
storageTmp := config . TemporaryPath
if storageTmp == "" {
storageTmp = filepath . Join ( storageRoot , "tmp" )
}
if ! filepath . IsAbs ( storageTmp ) {
return nil , fmt . Errorf ( "LocalStorage config.TemporaryPath should be an absolute path, but not: %q" , config . TemporaryPath )
2023-03-22 04:02:49 +08:00
}
2026-03-05 16:49:01 +01:00
storageTmp = util . FilePathJoinAbs ( storageTmp )
// create the storage root if not exist
log . Info ( "Creating new Local Storage at %s" , storageRoot )
if err := os . MkdirAll ( storageRoot , os . ModePerm ) ; err != nil {
return nil , err
2021-03-05 13:19:17 +00:00
}
2020-08-18 12:23:45 +08:00
return & LocalStorage {
2021-03-05 13:19:17 +00:00
ctx : ctx ,
2026-03-05 16:49:01 +01:00
dir : storageRoot ,
tmpdir : storageTmp ,
2020-08-18 12:23:45 +08:00
} , nil
}
2022-03-22 21:02:26 +00:00
func ( l * LocalStorage ) buildLocalPath ( p string ) string {
2023-03-22 04:02:49 +08:00
return util . FilePathJoinAbs ( l . dir , p )
2022-03-22 21:02:26 +00:00
}
2020-08-18 12:23:45 +08:00
// Open a file
2020-09-08 23:45:10 +08:00
func ( l * LocalStorage ) Open ( path string ) ( Object , error ) {
2022-03-22 21:02:26 +00:00
return os . Open ( l . buildLocalPath ( path ) )
2020-08-18 12:23:45 +08:00
}
// Save a file
2021-04-03 17:19:59 +01:00
func ( l * LocalStorage ) Save ( path string , r io . Reader , size int64 ) ( int64 , error ) {
2022-03-22 21:02:26 +00:00
p := l . buildLocalPath ( path )
2020-08-18 12:23:45 +08:00
if err := os . MkdirAll ( filepath . Dir ( p ) , os . ModePerm ) ; err != nil {
return 0 , err
}
2021-03-05 13:19:17 +00:00
// Create a temporary file to save to
if err := os . MkdirAll ( l . tmpdir , os . ModePerm ) ; err != nil {
2020-08-18 12:23:45 +08:00
return 0 , err
}
2021-09-22 13:38:34 +08:00
tmp , err := os . CreateTemp ( l . tmpdir , "upload-*" )
2021-03-05 13:19:17 +00:00
if err != nil {
return 0 , err
}
tmpRemoved := false
defer func ( ) {
if ! tmpRemoved {
_ = util . Remove ( tmp . Name ( ) )
}
} ( )
2020-08-18 12:23:45 +08:00
2021-03-05 13:19:17 +00:00
n , err := io . Copy ( tmp , r )
2020-08-18 12:23:45 +08:00
if err != nil {
return 0 , err
}
2021-03-05 13:19:17 +00:00
if err := tmp . Close ( ) ; err != nil {
return 0 , err
}
2021-07-15 16:46:07 +01:00
if err := util . Rename ( tmp . Name ( ) , p ) ; err != nil {
2021-03-05 13:19:17 +00:00
return 0 , err
}
2022-09-24 13:04:14 +00:00
// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
2022-12-19 00:50:36 +00:00
// but we don't want to make these files executable - so ensure that we mask out the executable bits
if err := util . ApplyUmask ( p , os . ModePerm & 0 o666 ) ; err != nil {
2022-09-24 13:04:14 +00:00
return 0 , err
}
2021-03-05 13:19:17 +00:00
tmpRemoved = true
return n , nil
2020-08-18 12:23:45 +08:00
}
2020-09-08 23:45:10 +08:00
// Stat returns the info of the file
2020-09-29 17:05:13 +08:00
func ( l * LocalStorage ) Stat ( path string ) ( os . FileInfo , error ) {
2022-03-22 21:02:26 +00:00
return os . Stat ( l . buildLocalPath ( path ) )
2022-03-14 23:18:27 +08:00
}
2026-03-05 16:49:01 +01:00
func ( l * LocalStorage ) deleteEmptyParentDirs ( localFullPath string ) {
for parent := filepath . Dir ( localFullPath ) ; len ( parent ) > len ( l . dir ) ; parent = filepath . Dir ( parent ) {
if err := os . Remove ( parent ) ; err != nil {
// since the target file has been deleted, parent dir error is not related to the file deletion itself.
break
}
}
}
// Delete deletes the file in storage and removes the empty parent directories (if possible)
2020-08-18 12:23:45 +08:00
func ( l * LocalStorage ) Delete ( path string ) error {
2026-03-05 16:49:01 +01:00
localFullPath := l . buildLocalPath ( path )
err := util . Remove ( localFullPath )
l . deleteEmptyParentDirs ( localFullPath )
return err
2020-08-18 12:23:45 +08:00
}
2026-03-22 05:26:13 +01:00
func ( l * LocalStorage ) ServeDirectURL ( path , name , _ string , reqParams * ServeDirectOptions ) ( * url . URL , error ) {
2020-08-18 12:23:45 +08:00
return nil , ErrURLNotSupported
}
2020-09-29 17:05:13 +08:00
2026-03-05 16:49:01 +01:00
func ( l * LocalStorage ) normalizeWalkError ( err error ) error {
if errors . Is ( err , os . ErrNotExist ) {
// ignore it because the file may be deleted during the walk, and we don't care about it
return nil
}
return err
}
2020-09-29 17:05:13 +08:00
// IterateObjects iterates across the objects in the local storage
2023-05-14 06:33:25 +08:00
func ( l * LocalStorage ) IterateObjects ( dirName string , fn func ( path string , obj Object ) error ) error {
dir := l . buildLocalPath ( dirName )
2026-03-05 16:49:01 +01:00
return filepath . WalkDir ( dir , func ( path string , d os . DirEntry , errWalk error ) error {
if err := l . ctx . Err ( ) ; err != nil {
2020-09-29 17:05:13 +08:00
return err
}
2026-03-05 16:49:01 +01:00
if errWalk != nil {
return l . normalizeWalkError ( errWalk )
2020-10-13 04:58:34 +01:00
}
2026-03-05 16:49:01 +01:00
if path == l . dir || d . IsDir ( ) {
2020-09-29 17:05:13 +08:00
return nil
}
2026-03-05 16:49:01 +01:00
2020-09-29 17:05:13 +08:00
relPath , err := filepath . Rel ( l . dir , path )
if err != nil {
2026-03-05 16:49:01 +01:00
return l . normalizeWalkError ( err )
2020-09-29 17:05:13 +08:00
}
obj , err := os . Open ( path )
if err != nil {
2026-03-05 16:49:01 +01:00
return l . normalizeWalkError ( err )
2020-09-29 17:05:13 +08:00
}
defer obj . Close ( )
2026-03-05 16:49:01 +01:00
return fn ( filepath . ToSlash ( relPath ) , obj )
2020-09-29 17:05:13 +08:00
} )
}
2020-10-13 04:58:34 +01:00
func init ( ) {
2023-06-14 11:42:38 +08:00
RegisterStorageType ( setting . LocalStorageType , NewLocalStorage )
2020-10-13 04:58:34 +01:00
}