| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	pathutil "path" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/lock" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // fsIOPool represents a protected list to keep track of all
 | 
					
						
							|  |  |  | // the concurrent readers at a given path.
 | 
					
						
							|  |  |  | type fsIOPool struct { | 
					
						
							|  |  |  | 	sync.Mutex | 
					
						
							|  |  |  | 	readersMap map[string]*lock.RLockedFile | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | // lookupToRead - looks up an fd from readers map and
 | 
					
						
							|  |  |  | // returns read locked fd for caller to read from, if
 | 
					
						
							|  |  |  | // fd found increments the reference count. If the fd
 | 
					
						
							|  |  |  | // is found to be closed then purges it from the
 | 
					
						
							|  |  |  | // readersMap and returns nil instead.
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | // NOTE: this function is not protected and it is callers
 | 
					
						
							|  |  |  | // responsibility to lock this call to be thread safe. For
 | 
					
						
							|  |  |  | // implementation ideas look at the usage inside Open() call.
 | 
					
						
							|  |  |  | func (fsi *fsIOPool) lookupToRead(path string) (*lock.RLockedFile, bool) { | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 	rlkFile, ok := fsi.readersMap[path] | 
					
						
							|  |  |  | 	// File reference exists on map, validate if its
 | 
					
						
							|  |  |  | 	// really closed and we are safe to purge it.
 | 
					
						
							|  |  |  | 	if ok && rlkFile != nil { | 
					
						
							|  |  |  | 		// If the file is closed and not removed from map is a bug.
 | 
					
						
							|  |  |  | 		if rlkFile.IsClosed() { | 
					
						
							|  |  |  | 			// Log this as an error.
 | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 			reqInfo := (&logger.ReqInfo{}).AppendTags("path", path) | 
					
						
							| 
									
										
										
										
											2020-04-10 00:30:02 +08:00
										 |  |  | 			ctx := logger.SetReqInfo(GlobalContext, reqInfo) | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 			logger.LogIf(ctx, errUnexpected) | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Purge the cached lock path from map.
 | 
					
						
							|  |  |  | 			delete(fsi.readersMap, path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Indicate that we can populate the new fd.
 | 
					
						
							|  |  |  | 			ok = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// Increment the lock ref, since the file is not closed yet
 | 
					
						
							|  |  |  | 			// and caller requested to read the file again.
 | 
					
						
							|  |  |  | 			rlkFile.IncLockRef() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 	return rlkFile, ok | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | // Open is a wrapper call to read locked file which
 | 
					
						
							|  |  |  | // returns a ReadAtCloser.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // ReaderAt is provided so that the fd is non seekable, since
 | 
					
						
							|  |  |  | // we are sharing fd's with concurrent threads, we don't want
 | 
					
						
							|  |  |  | // all readers to change offsets on each other during such
 | 
					
						
							|  |  |  | // concurrent operations. Using ReadAt allows us to read from
 | 
					
						
							|  |  |  | // any offsets.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Closer is implemented to track total readers and to close
 | 
					
						
							|  |  |  | // only when there no more readers, the fd is purged if the lock
 | 
					
						
							|  |  |  | // count has reached zero.
 | 
					
						
							|  |  |  | func (fsi *fsIOPool) Open(path string) (*lock.RLockedFile, error) { | 
					
						
							|  |  |  | 	if err := checkPathLength(path); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fsi.Lock() | 
					
						
							|  |  |  | 	rlkFile, ok := fsi.lookupToRead(path) | 
					
						
							|  |  |  | 	fsi.Unlock() | 
					
						
							|  |  |  | 	// Locked path reference doesn't exist, acquire a read lock again on the file.
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 		// Open file for reading with read lock.
 | 
					
						
							|  |  |  | 		newRlkFile, err := lock.RLockedOpenFile(path) | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			switch { | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 			case osIsNotExist(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 				return nil, errFileNotFound | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 			case osIsPermission(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 				return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			case isSysErrIsDir(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 				return nil, errIsNotRegular | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			case isSysErrNotDir(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 				return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			case isSysErrPathNotFound(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 				return nil, errFileNotFound | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			default: | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 01:28:29 +08:00
										 |  |  | 		// Save new reader on the map.
 | 
					
						
							| 
									
										
										
										
											2017-03-01 10:05:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 		// It is possible by this time due to concurrent
 | 
					
						
							|  |  |  | 		// i/o we might have another lock present. Lookup
 | 
					
						
							|  |  |  | 		// again to check for such a possibility. If no such
 | 
					
						
							|  |  |  | 		// file exists save the newly opened fd, if not
 | 
					
						
							|  |  |  | 		// reuse the existing fd and close the newly opened
 | 
					
						
							|  |  |  | 		// file
 | 
					
						
							|  |  |  | 		fsi.Lock() | 
					
						
							|  |  |  | 		rlkFile, ok = fsi.lookupToRead(path) | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			// Close the new fd, since we already seem to have
 | 
					
						
							|  |  |  | 			// an active reference.
 | 
					
						
							|  |  |  | 			newRlkFile.Close() | 
					
						
							| 
									
										
										
										
											2017-03-01 10:05:52 +08:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 			// Save the new rlk file.
 | 
					
						
							| 
									
										
										
										
											2017-03-01 10:05:52 +08:00
										 |  |  | 			rlkFile = newRlkFile | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 		// Save the new fd on the map.
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 		fsi.readersMap[path] = rlkFile | 
					
						
							|  |  |  | 		fsi.Unlock() | 
					
						
							| 
									
										
										
										
											2017-08-31 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Success.
 | 
					
						
							|  |  |  | 	return rlkFile, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Write - Attempt to lock the file if it exists,
 | 
					
						
							| 
									
										
										
										
											2022-08-27 03:52:29 +08:00
										 |  |  | //   - if the file exists. Then we try to get a write lock this
 | 
					
						
							|  |  |  | //     will block if we can't get a lock perhaps another write
 | 
					
						
							|  |  |  | //     or read is in progress. Concurrent calls are protected
 | 
					
						
							|  |  |  | //     by the global namspace lock within the same process.
 | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | func (fsi *fsIOPool) Write(path string) (wlk *lock.LockedFile, err error) { | 
					
						
							|  |  |  | 	if err = checkPathLength(path); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	wlk, err = lock.LockedOpenFile(path, os.O_RDWR, 0o666) | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		switch { | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 		case osIsNotExist(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errFileNotFound | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 		case osIsPermission(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		case isSysErrIsDir(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errIsNotRegular | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2020-09-22 03:54:23 +08:00
										 |  |  | 			if isSysErrPathNotFound(err) { | 
					
						
							|  |  |  | 				return nil, errFileNotFound | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return wlk, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Create - creates a new write locked file instance.
 | 
					
						
							|  |  |  | // - if the file doesn't exist. We create the file and hold lock.
 | 
					
						
							|  |  |  | func (fsi *fsIOPool) Create(path string) (wlk *lock.LockedFile, err error) { | 
					
						
							|  |  |  | 	if err = checkPathLength(path); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Creates parent if missing.
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	if err = mkdirAll(pathutil.Dir(path), 0o777); err != nil { | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Attempt to create the file.
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	wlk, err = lock.LockedOpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		switch { | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 		case osIsPermission(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		case isSysErrIsDir(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errIsNotRegular | 
					
						
							| 
									
										
										
										
											2022-07-24 15:43:11 +08:00
										 |  |  | 		case isSysErrNotDir(err): | 
					
						
							|  |  |  | 			return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		case isSysErrPathNotFound(err): | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 			return nil, errFileAccessDenied | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Success.
 | 
					
						
							| 
									
										
										
										
											2017-08-11 05:11:57 +08:00
										 |  |  | 	return wlk, nil | 
					
						
							| 
									
										
										
										
											2017-01-17 09:05:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close implements closing the path referenced by the reader in such
 | 
					
						
							|  |  |  | // a way that it makes sure to remove entry from the map immediately
 | 
					
						
							|  |  |  | // if no active readers are present.
 | 
					
						
							|  |  |  | func (fsi *fsIOPool) Close(path string) error { | 
					
						
							|  |  |  | 	fsi.Lock() | 
					
						
							|  |  |  | 	defer fsi.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := checkPathLength(path); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Pop readers from path.
 | 
					
						
							|  |  |  | 	rlkFile, ok := fsi.readersMap[path] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Close the reader.
 | 
					
						
							|  |  |  | 	rlkFile.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the file is closed, remove it from the reader pool map.
 | 
					
						
							|  |  |  | 	if rlkFile.IsClosed() { | 
					
						
							|  |  |  | 		// Purge the cached lock path from map.
 | 
					
						
							|  |  |  | 		delete(fsi.readersMap, path) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Success.
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |