+2005-07-06 Miklos Szeredi <miklos@szeredi.hu>
+
+ * fusermount: check if mountpoint is empty (only '.' and '..' for
+ directories, and size = 0 for regular files). If "nonempty"
+ option is given, omit this check. This is useful, so users don't
+ accidentally hide data (e.g. from backup programs). Thanks to
+ Frank van Maarseveen for pointing this out.
+
+ * kernel: check if mandatory mount options ('fd', 'rootmode',
+ 'user_id', 'group_id') are all given
+
2005-07-03 Miklos Szeredi <miklos@szeredi.hu>
* kernel: clean up 'direct_io' code
Sets the filesystem name. The default is the program name.
+nonempty
+
+ Allows mounts over a non-empty file or directory. By default these
+ mounts are rejected (from version 2.3.1) to prevent accidental
+ covering up of data, which could for example prevent automatic
+ backup.
* is permitted for the given flags. Optionally open may also
* return an arbitary filehandle in the fuse_file_info structure,
* which will be passed to all file operations.
+ *
+ * Changed in version 2.2
*/
int (*open) (const char *, struct fuse_file_info *);
* 'direct_io' mount option is specified, in which case the return
* value of the read system call will reflect the return value of
* this operation.
+ *
+ * Changed in version 2.2
*/
int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
* Write should return exactly the number of bytes requested
* except on error. An exception to this is when the 'direct_io'
* mount option is specified (see read operation).
+ *
+ * Changed in version 2.2
*/
int (*write) (const char *, const char *, size_t, off_t,
struct fuse_file_info *);
* not possible to determine if a flush is final, so each flush
* should be treated equally. Multiple write-flush sequences are
* relatively rare, so this shouldn't be a problem.
+ *
+ * Changed in version 2.2
*/
int (*flush) (const char *, struct fuse_file_info *);
* have a file opened more than once, in which case only the last
* release will mean, that no more reads/writes will happen on the
* file. The return value of release is ignored.
+ *
+ * Changed in version 2.2
*/
int (*release) (const char *, struct fuse_file_info *);
*
* If the datasync parameter is non-zero, then only the user data
* should be flushed, not the meta data.
+ *
+ * Changed in version 2.2
*/
int (*fsync) (const char *, int, struct fuse_file_info *);
*
* This method should check if the open operation is permitted for
* this directory
+ *
+ * Introduced in version 2.3
*/
int (*opendir) (const char *, struct fuse_file_info *);
* passes non-zero offset to the filler function. When the buffer
* is full (or an error happens) the filler function will return
* '1'.
+ *
+ * Introduced in version 2.3
*/
int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
struct fuse_file_info *);
- /** Release directory */
+ /** Release directory
+ *
+ * Introduced in version 2.3
+ */
int (*releasedir) (const char *, struct fuse_file_info *);
/** Synchronize directory contents
*
* If the datasync parameter is non-zero, then only the user data
* should be flushed, not the meta data
+ *
+ * Introduced in version 2.3
*/
int (*fsyncdir) (const char *, int, struct fuse_file_info *);
* The return value will passed in the private_data field of
* fuse_context to all file operations and as a parameter to the
* destroy() method.
+ *
+ * Introduced in version 2.3
*/
void *(*init) (void);
* Clean up filesystem
*
* Called on filesystem exit.
+ *
+ * Introduced in version 2.3
*/
void (*destroy) (void *);
};
unsigned rootmode;
unsigned user_id;
unsigned group_id;
+ unsigned fd_present : 1;
+ unsigned rootmode_present : 1;
+ unsigned user_id_present : 1;
+ unsigned group_id_present : 1;
unsigned flags;
unsigned max_read;
};
{
char *p;
memset(d, 0, sizeof(struct fuse_mount_data));
- d->fd = -1;
d->max_read = ~0;
while ((p = strsep(&opt, ",")) != NULL) {
if (match_int(&args[0], &value))
return 0;
d->fd = value;
+ d->fd_present = 1;
break;
case OPT_ROOTMODE:
if (match_octal(&args[0], &value))
return 0;
d->rootmode = value;
+ d->rootmode_present = 1;
break;
case OPT_USER_ID:
if (match_int(&args[0], &value))
return 0;
d->user_id = value;
+ d->user_id_present = 1;
break;
case OPT_GROUP_ID:
if (match_int(&args[0], &value))
return 0;
d->group_id = value;
+ d->group_id_present = 1;
break;
case OPT_DEFAULT_PERMISSIONS:
return 0;
}
}
- if (d->fd == -1)
+
+ if (!d->fd_present || !d->rootmode_present ||
+ !d->user_id_present || !d->group_id_present)
return 0;
return 1;
#include <fcntl.h>
#include <pwd.h>
#include <mntent.h>
+#include <dirent.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/stat.h>
return 0;
}
+static int check_mountpoint_empty(const char *mnt, mode_t rootmode,
+ off_t rootsize)
+{
+ int isempty = 1;
+
+ if (S_ISDIR(rootmode)) {
+ struct dirent *ent;
+ DIR *dp = opendir(mnt);
+ if (dp == NULL) {
+ fprintf(stderr, "%s: failed to mountpoint for reading: %s\n",
+ progname, strerror(errno));
+ return -1;
+ }
+ while ((ent = readdir(dp)) != NULL) {
+ if (strcmp(ent->d_name, ".") != 0 &&
+ strcmp(ent->d_name, "..") != 0) {
+ isempty = 0;
+ break;
+ }
+ }
+ closedir(dp);
+ } else if (rootsize)
+ isempty = 0;
+
+ if (!isempty) {
+ fprintf(stderr, "%s: mountpoint is not empty\n", progname);
+ fprintf(stderr, "%s: if you are sure this is safe, use the 'nonempty' mount option\n", progname);
+ return -1;
+ }
+ return 0;
+}
+
static int do_mount(const char *mnt, const char *type, mode_t rootmode,
int fd, const char *opts, const char *dev, char **fsnamep,
- char **mnt_optsp)
+ char **mnt_optsp, off_t rootsize)
{
int res;
int flags = MS_NOSUID | MS_NODEV;
const char *s;
char *d;
char *fsname = NULL;
+ int check_empty = 1;
optbuf = malloc(strlen(opts) + 128);
if (!optbuf) {
fsname = malloc(len - fsname_str_len + 1);
if (!fsname) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
- free(optbuf);
- return -1;
+ goto err;
}
memcpy(fsname, s + fsname_str_len, len - fsname_str_len);
fsname[len - fsname_str_len] = '\0';
+ } else if (opt_eq(s, len, "nonempty")) {
+ check_empty = 0;
} else if (!begins_with(s, "fd=") &&
!begins_with(s, "rootmode=") &&
!begins_with(s, "user_id=") &&
(opt_eq(s, len, "allow_other") ||
opt_eq(s, len, "allow_root"))) {
fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in /etc/fuse.conf\n", progname, len, s);
- free(optbuf);
- return -1;
+ goto err;
}
if (!skip_option) {
if (find_mount_flag(s, len, &on, &flag)) {
}
*d = '\0';
res = get_mnt_opts(flags, optbuf, &mnt_opts);
- if (res == -1) {
- free(mnt_opts);
- free(optbuf);
- return -1;
- }
+ if (res == -1)
+ goto err;
+
sprintf(d, "fd=%i,rootmode=%o,user_id=%i,group_id=%i",
fd, rootmode, getuid(), getgid());
if (fsname == NULL) {
fsname = strdup(dev);
if (!fsname) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
- free(optbuf);
- return -1;
+ goto err;
}
}
+ if (check_empty && check_mountpoint_empty(mnt, rootmode, rootsize) == -1)
+ goto err;
+
res = mount(fsname, mnt, type, flags, optbuf);
if (res == -1 && errno == EINVAL) {
/* It could be an old version not supporting group_id */
}
if (res == -1) {
fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno));
- free(fsname);
- free(mnt_opts);
+ goto err;
} else {
*fsnamep = fsname;
*mnt_optsp = mnt_opts;
free(optbuf);
return res;
+
+ err:
+ free(fsname);
+ free(mnt_opts);
+ free(optbuf);
+ return -1;
}
static int check_version(const char *dev)
restore_privs();
if (res != -1)
res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT, fd, opts,
- dev, &fsname, &mnt_opts);
+ dev, &fsname, &mnt_opts, stbuf.st_size);
} else
restore_privs();