github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/metadata/disk.go (about)

     1  // Copyright 2018-2022 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package metadata
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"io/fs"
    27  	"os"
    28  	"path/filepath"
    29  	"time"
    30  
    31  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    32  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    33  	"github.com/cs3org/reva/v2/pkg/errtypes"
    34  )
    35  
    36  // Disk represents a disk metadata storage
    37  type Disk struct {
    38  	dataDir string
    39  }
    40  
    41  // NewDiskStorage returns a new disk storage instance
    42  func NewDiskStorage(dataDir string) (s Storage, err error) {
    43  	return &Disk{
    44  		dataDir: dataDir,
    45  	}, nil
    46  }
    47  
    48  // Init creates the metadata space
    49  func (disk *Disk) Init(_ context.Context, _ string) (err error) {
    50  	return os.MkdirAll(disk.dataDir, 0777)
    51  }
    52  
    53  // Backend returns the backend name of the storage
    54  func (disk *Disk) Backend() string {
    55  	return "disk"
    56  }
    57  
    58  // Stat returns the metadata for the given path
    59  func (disk *Disk) Stat(ctx context.Context, path string) (*provider.ResourceInfo, error) {
    60  	info, err := os.Stat(disk.targetPath(path))
    61  	if err != nil {
    62  		var pathError *fs.PathError
    63  		if errors.As(err, &pathError) {
    64  			return nil, errtypes.NotFound("path not found: " + path)
    65  		}
    66  		return nil, err
    67  	}
    68  	entry := &provider.ResourceInfo{
    69  		Type:  provider.ResourceType_RESOURCE_TYPE_FILE,
    70  		Path:  "./" + info.Name(),
    71  		Name:  info.Name(),
    72  		Mtime: &typesv1beta1.Timestamp{Seconds: uint64(info.ModTime().Unix()), Nanos: uint32(info.ModTime().Nanosecond())},
    73  	}
    74  	if info.IsDir() {
    75  		entry.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
    76  	}
    77  	entry.Etag, err = calcEtag(info.ModTime(), info.Size())
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return entry, nil
    82  }
    83  
    84  // SimpleUpload stores a file on disk
    85  func (disk *Disk) SimpleUpload(ctx context.Context, uploadpath string, content []byte) error {
    86  	_, err := disk.Upload(ctx, UploadRequest{
    87  		Path:    uploadpath,
    88  		Content: content,
    89  	})
    90  	return err
    91  }
    92  
    93  // Upload stores a file on disk
    94  func (disk *Disk) Upload(_ context.Context, req UploadRequest) (*UploadResponse, error) {
    95  	p := disk.targetPath(req.Path)
    96  	if req.IfMatchEtag != "" {
    97  		info, err := os.Stat(p)
    98  		if err != nil && !errors.Is(err, os.ErrNotExist) {
    99  			return nil, err
   100  		} else if err == nil {
   101  			etag, err := calcEtag(info.ModTime(), info.Size())
   102  			if err != nil {
   103  				return nil, err
   104  			}
   105  			if etag != req.IfMatchEtag {
   106  				return nil, errtypes.PreconditionFailed("etag mismatch")
   107  			}
   108  		}
   109  	}
   110  	if req.IfUnmodifiedSince != (time.Time{}) {
   111  		info, err := os.Stat(p)
   112  		if err != nil && !errors.Is(err, os.ErrNotExist) {
   113  			return nil, err
   114  		} else if err == nil {
   115  			if info.ModTime().After(req.IfUnmodifiedSince) {
   116  				return nil, errtypes.PreconditionFailed(fmt.Sprintf("resource has been modified, mtime: %s > since %s", info.ModTime(), req.IfUnmodifiedSince))
   117  			}
   118  		}
   119  	}
   120  	err := os.WriteFile(p, req.Content, 0644)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	info, err := os.Stat(disk.targetPath(req.Path))
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	res := &UploadResponse{}
   130  	res.Etag, err = calcEtag(info.ModTime(), info.Size())
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return res, nil
   135  }
   136  
   137  // Download reads a file from disk
   138  func (disk *Disk) Download(_ context.Context, req DownloadRequest) (*DownloadResponse, error) {
   139  	var err error
   140  
   141  	f, err := os.Open(disk.targetPath(req.Path))
   142  	if err != nil {
   143  		var pathError *fs.PathError
   144  		if errors.As(err, &pathError) {
   145  			return nil, errtypes.NotFound("path not found: " + disk.targetPath(req.Path))
   146  		}
   147  		return nil, err
   148  	}
   149  	defer f.Close()
   150  
   151  	info, err := f.Stat()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	res := DownloadResponse{}
   157  	res.Mtime = info.ModTime()
   158  	res.Etag, err = calcEtag(info.ModTime(), info.Size())
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	res.Content, err = io.ReadAll(f)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return &res, nil
   168  }
   169  
   170  // SimpleDownload reads a file from disk
   171  func (disk *Disk) SimpleDownload(ctx context.Context, downloadpath string) ([]byte, error) {
   172  	res, err := disk.Download(ctx, DownloadRequest{Path: downloadpath})
   173  	return res.Content, err
   174  }
   175  
   176  // Delete deletes a path
   177  func (disk *Disk) Delete(_ context.Context, path string) error {
   178  	return os.Remove(disk.targetPath(path))
   179  }
   180  
   181  // ReadDir returns the resource infos in a given directory
   182  func (disk *Disk) ReadDir(_ context.Context, p string) ([]string, error) {
   183  	infos, err := os.ReadDir(disk.targetPath(p))
   184  	if err != nil {
   185  		if _, ok := err.(*fs.PathError); ok {
   186  			return []string{}, nil
   187  		}
   188  		return nil, err
   189  	}
   190  
   191  	entries := make([]string, 0, len(infos))
   192  	for _, entry := range infos {
   193  		entries = append(entries, filepath.Join(p, entry.Name()))
   194  	}
   195  	return entries, nil
   196  }
   197  
   198  // ListDir returns a list of ResourceInfos for the entries in a given directory
   199  func (disk *Disk) ListDir(ctx context.Context, path string) ([]*provider.ResourceInfo, error) {
   200  	diskEntries, err := os.ReadDir(disk.targetPath(path))
   201  	if err != nil {
   202  		if _, ok := err.(*fs.PathError); ok {
   203  			return []*provider.ResourceInfo{}, nil
   204  		}
   205  		return nil, err
   206  	}
   207  
   208  	entries := make([]*provider.ResourceInfo, 0, len(diskEntries))
   209  	for _, diskEntry := range diskEntries {
   210  		info, err := diskEntry.Info()
   211  		if err != nil {
   212  			continue
   213  		}
   214  
   215  		entry := &provider.ResourceInfo{
   216  			Type:  provider.ResourceType_RESOURCE_TYPE_FILE,
   217  			Path:  "./" + info.Name(),
   218  			Name:  info.Name(),
   219  			Mtime: &typesv1beta1.Timestamp{Seconds: uint64(info.ModTime().Unix()), Nanos: uint32(info.ModTime().Nanosecond())},
   220  		}
   221  		if info.IsDir() {
   222  			entry.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
   223  		}
   224  		entries = append(entries, entry)
   225  	}
   226  	return entries, nil
   227  }
   228  
   229  // MakeDirIfNotExist will create a root node in the metadata storage. Requires an authenticated context.
   230  func (disk *Disk) MakeDirIfNotExist(_ context.Context, path string) error {
   231  	return os.MkdirAll(disk.targetPath(path), 0777)
   232  }
   233  
   234  // CreateSymlink creates a symlink
   235  func (disk *Disk) CreateSymlink(_ context.Context, oldname, newname string) error {
   236  	return os.Symlink(oldname, disk.targetPath(newname))
   237  }
   238  
   239  // ResolveSymlink resolves a symlink
   240  func (disk *Disk) ResolveSymlink(_ context.Context, path string) (string, error) {
   241  	return os.Readlink(disk.targetPath(path))
   242  }
   243  
   244  func (disk *Disk) targetPath(p string) string {
   245  	return filepath.Join(disk.dataDir, filepath.Join("/", p))
   246  }