/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2010-2018 Phusion Holding B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Phusion Holding B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#ifndef _PASSENGER_FILE_TOOLS_FILE_MANIP_H_
#define _PASSENGER_FILE_TOOLS_FILE_MANIP_H_

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstddef>
#include <string>
#include <utility>
#include <StaticString.h>

namespace boost {
	class mutex;
}

namespace Passenger {

using namespace std;

// All functions in this file allow non-NULL-terminated StaticStrings.


class CachedFileStat;

static const uid_t USER_NOT_GIVEN = (uid_t) -1;
static const gid_t GROUP_NOT_GIVEN = (gid_t) -1;

/** Enumeration which indicates what kind of file a file is. */
typedef enum {
	/** The file doesn't exist. */
	FT_NONEXISTANT,
	/** A regular file or a symlink to a regular file. */
	FT_REGULAR,
	/** A directory. */
	FT_DIRECTORY,
	/** Something else, e.g. a pipe or a socket. */
	FT_OTHER
} FileType;

/**
 * Given a filename, FileGuard will unlink the file in its destructor, unless
 * commit() was called. Used in file operation functions that don't want to
 * leave behind half-finished files after error conditions.
 */
struct FileGuard {
	string filename;
	bool committed;

	FileGuard(const StaticString &filename);
	~FileGuard();
	void commit();
};


/**
 * Check whether the specified file exists.
 *
 * @param filename The filename to check.
 * @param cstat A CachedFileStat object, if you want to use cached statting.
 * @param cstatMutex A mutex for locking cstat while this function uses it.
 *                   Makes this function thread-safe. May be NULL.
 * @param throttleRate A throttle rate for cstat. Only applicable if cstat is not NULL.
 * @return Whether the file exists.
 * @throws FileSystemException Unable to check because of a filesystem error.
 * @throws TimeRetrievalException
 * @throws boost::thread_interrupted
 */
bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0,
	boost::mutex *cstatMutex = NULL, unsigned int throttleRate = 0);

/**
 * Check whether the specified directory exists.
 *
 * @param dirname The path to check.
 * @param cstat A CachedFileStat object, if you want to use cached statting.
 * @param cstatMutex A mutex for locking cstat while this function uses it.
 *                   Makes this function thread-safe. May be NULL.
 * @param throttleRate A throttle rate for cstat. Only applicable if cstat is not NULL.
 * @return Whether the directory exists.
 * @throws FileSystemException Unable to check because of a filesystem error.
 * @throws TimeRetrievalException
 * @throws boost::thread_interrupted
 */
bool dirExists(const StaticString &dirname, CachedFileStat *cstat = 0,
	boost::mutex *cstatMutex = NULL, unsigned int throttleRate = 0);

/**
* Check whether 'filename' exists and what kind of file it is.
*
* @param filename The filename to check. It MUST be NULL-terminated.
* @param cstat A CachedFileStat object, if you want to use cached statting.
* @param cstatMutex A mutex for locking cstat while this function uses it.
*                   Makes this function thread-safe. May be NULL.
* @param throttleRate A throttle rate for cstat. Only applicable if cstat is not NULL.
* @return The file type.
* @throws FileSystemException Unable to check because of a filesystem error.
* @throws TimeRetrievalException
* @throws boost::thread_interrupted
* @ingroup Support
*/
FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0,
		 boost::mutex *cstatMutex = NULL, unsigned int throttleRate = 0);

/**
* Create the given file with the given contents, permissions and ownership.
* This function does not leave behind junk files: if the ownership cannot be set
* or if not all data can be written then then the file will be deleted.
*
* @param filename The file to create.
* @param contents The contents to write to the file.
* @param permissions The desired file permissions.
* @param owner The desired file owner. Specify USER_NOT_GIVEN if you want to use the current
*              process's owner as the file owner.
* @param group The desired file group. Specify GROUP_NOT_GIVEN if you want to use the current
*              process's group as the file group.
* @param overwrite Whether to overwrite the file if it exists. If set to false
*                  and the file exists then nothing will happen.
* @throws FileSystemException Something went wrong.
*/
void createFile(const string &filename, const StaticString &contents,
	mode_t permissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
	uid_t owner = USER_NOT_GIVEN, gid_t group = GROUP_NOT_GIVEN,
	bool overwrite = true,
	const char *callerFile = NULL, unsigned int callerLine = 0);

/**
 * Read all data from the given file until EOF.
 * This function is "unsafe" in the sense that it lacks the security
 * checks implemented by `safeReadFile()`. Read the docs for that function
 * for more information.
 *
 * @throws SystemException
 */
string unsafeReadFile(const string &path);

/**
 * Read all data from the given file until EOF.
 *
 *  - `dirfd` is a file descriptor of the directory that contains the file
 *    you want read from.
 *  - `basename` is the basename of the file you want to read from. `basename`
 *    may thus not contain slashes.
 *  - `maxSize` is the maximum number of bytes you want to read.
 *
 * Returns a pair `(contents, eof)`.
 *
 *  - `contents` is the read file contents, which is at most `maxSize` bytes.
 *  - `eof` indicates whether the entire file has been read. If false, then it
 *    means the amount of data is larger than `maxSize`.
 *
 * This function is "safe" in following sense:
 *
 *  - It mitigates symbolic link attacks. `open(path)` may not be safe for
 *    processes running as root, because if a user controls any parts of the
 *    path then the user can swap one of the parent directories, or the file
 *    itself, with a symlink. This causes us to read an arbitrary file.
 *
 *    This function mitigates this attack by requiring a `dirfd` and by opening
 *    the file with O_NOFOLLOW. The caller must ensure that `dirfd` is created
 *    at a time when it knows that no user controls any parts of the path to
 *    that directory.
 *
 *    However, this mitigation does mean that safeReadFile() *cannot be used to
 *    read from a symlink*!
 *
 *  - It mitigates DoS attacks through non-regular files which may block the
 *    reader, like FIFOs or block devices. For example if the path refers to a
 *    FIFO which a user created, and the user never opens the FIFO on the other
 *    side, then the open can block indefinitely.
 *
 *    This function mitigates this attack by opening the file with O_NONBLOCK.
 *
 *  - It mitigates DoS attacks by creating a very large file. Since we slurp
 *    the entire file into memory, it is a good idea if we impose a limit
 *    on how much data we read.
 *
 * @throws ArgumentException
 * @throws SystemException
 */
pair<string, bool> safeReadFile(int dirfd, const string &basename, size_t maxSize);

/**
 * Create the directory at the given path, creating intermediate directories
 * if necessary. The created directories' permissions are exactly as specified
 * by the 'mode' parameter (i.e. the umask will be ignored). You can specify
 * this directory's owner and group through the 'owner' and 'group' parameters.
 * A value of USER_NOT_GIVEN for 'owner' and/or GROUP_NOT_GIVEN 'group' means
 * that the owner/group should not be changed.
 *
 * If 'path' already exists, then nothing will happen.
 *
 * @param mode A mode string, as supported by parseModeString().
 * @throws FileSystemException Something went wrong.
 * @throws InvalidModeStringException The mode string cannot be parsed.
 */
void makeDirTree(const string &path, const StaticString &mode = "u=rwx,g=,o=",
	uid_t owner = USER_NOT_GIVEN, gid_t group = GROUP_NOT_GIVEN);

/**
 * Remove an entire directory tree recursively. If the directory doesn't exist then this
 * function does nothing.
 *
 * @throws RuntimeException Something went wrong.
 */
void removeDirTree(const string &path);


} // namespace Passenger

#endif /* _PASSENGER_FILE_TOOLS_FILE_MANIP_H_ */
