func()

in component/file_cache/file_cache.go [907:1052]


func (fc *FileCache) OpenFile(options internal.OpenFileOptions) (*handlemap.Handle, error) {
	log.Trace("FileCache::OpenFile : name=%s, flags=%d, mode=%s", options.Name, options.Flags, options.Mode)

	localPath := filepath.Join(fc.tmpPath, options.Name)
	var f *os.File
	var err error

	flock := fc.fileLocks.Get(options.Name)
	flock.Lock()
	defer flock.Unlock()

	fc.policy.CacheValid(localPath)
	downloadRequired, fileExists, attr, err := fc.isDownloadRequired(localPath, options.Name, flock)

	// return err in case of authorization permission mismatch
	if err != nil && err == syscall.EACCES {
		return nil, err
	}

	if downloadRequired {
		log.Debug("FileCache::OpenFile : Need to re-download %s", options.Name)

		fileSize := int64(0)
		if attr != nil {
			fileSize = int64(attr.Size)
		}

		if fileExists {
			log.Debug("FileCache::OpenFile : Delete cached file %s", options.Name)

			err := deleteFile(localPath)
			if err != nil && !os.IsNotExist(err) {
				log.Err("FileCache::OpenFile : Failed to delete old file %s", options.Name)
			}
		} else {
			// Create the file if if doesn't already exist.
			err := os.MkdirAll(filepath.Dir(localPath), fc.defaultPermission)
			if err != nil {
				log.Err("FileCache::OpenFile : error creating directory structure for file %s [%s]", options.Name, err.Error())
				return nil, err
			}
		}

		// Open the file in write mode.
		f, err = os.OpenFile(localPath, os.O_CREATE|os.O_RDWR, options.Mode)
		if err != nil {
			log.Err("FileCache::OpenFile : error creating new file %s [%s]", options.Name, err.Error())
			return nil, err
		}

		if options.Flags&os.O_TRUNC != 0 {
			fileSize = 0
		}

		if fileSize > 0 {
			if fc.diskHighWaterMark != 0 {
				currSize, err := common.GetUsage(fc.tmpPath)
				if err != nil {
					log.Err("FileCache::OpenFile : error getting current usage of cache [%s]", err.Error())
				} else {
					if (currSize + float64(fileSize)) > fc.diskHighWaterMark {
						log.Err("FileCache::OpenFile : cache size limit reached [%f] failed to open %s", fc.maxCacheSize, options.Name)
						return nil, syscall.ENOSPC
					}
				}

			}
			// Download/Copy the file from storage to the local file.
			err = fc.NextComponent().CopyToFile(
				internal.CopyToFileOptions{
					Name:   options.Name,
					Offset: 0,
					Count:  fileSize,
					File:   f,
				})
			if err != nil {
				// File was created locally and now download has failed so we need to delete it back from local cache
				log.Err("FileCache::OpenFile : error downloading file from storage %s [%s]", options.Name, err.Error())
				_ = f.Close()
				_ = os.Remove(localPath)
				return nil, err
			}
		}

		// Update the last download time of this file
		flock.SetDownloadTime()

		log.Debug("FileCache::OpenFile : Download of %s is complete", options.Name)
		f.Close()

		// After downloading the file, update the modified times and mode of the file.
		fileMode := fc.defaultPermission
		if attr != nil && !attr.IsModeDefault() {
			fileMode = attr.Mode
		}

		// If user has selected some non default mode in config then every local file shall be created with that mode only
		err = os.Chmod(localPath, fileMode)
		if err != nil {
			log.Err("FileCache::OpenFile : Failed to change mode of file %s [%s]", options.Name, err.Error())
		}
		// TODO: When chown is supported should we update that?

		if attr != nil {
			// chtimes shall be the last api otherwise calling chmod/chown will update the last change time
			err = os.Chtimes(localPath, attr.Atime, attr.Mtime)
			if err != nil {
				log.Err("FileCache::OpenFile : Failed to change times of file %s [%s]", options.Name, err.Error())
			}
		}

		fileCacheStatsCollector.UpdateStats(stats_manager.Increment, dlFiles, (int64)(1))
	} else {
		log.Debug("FileCache::OpenFile : %s will be served from cache", options.Name)
		fileCacheStatsCollector.UpdateStats(stats_manager.Increment, cacheServed, (int64)(1))
	}

	// Open the file and grab a shared lock to prevent deletion by the cache policy.
	f, err = os.OpenFile(localPath, options.Flags, options.Mode)
	if err != nil {
		log.Err("FileCache::OpenFile : error opening cached file %s [%s]", options.Name, err.Error())
		return nil, err
	}

	// Increment the handle count in this lock item as there is one handle open for this now
	flock.Inc()

	handle := handlemap.NewHandle(options.Name)
	if options.Flags&os.O_TRUNC != 0 {
		handle.Flags.Set(handlemap.HandleFlagDirty)
	}
	inf, err := f.Stat()
	if err == nil {
		handle.Size = inf.Size()
	}

	handle.UnixFD = uint64(f.Fd())
	if !fc.offloadIO {
		handle.Flags.Set(handlemap.HandleFlagCached)
	}

	log.Info("FileCache::OpenFile : file=%s, fd=%d", options.Name, f.Fd())
	handle.SetFileObject(f)

	return handle, nil
}