Browser
/* $Id$ */
/* Copyright (c) 2012-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Browser */
/* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/* TODO:
* - detect and report errors when (un)mounting */
#if defined(__FreeBSD__)
# include <sys/param.h>
# include <sys/ucred.h>
# include <fstab.h>
#elif defined(__NetBSD__)
# include <sys/param.h>
# include <sys/types.h>
# include <sys/statvfs.h>
# include <fstab.h>
#elif defined(__OpenBSD__)
# include <fstab.h>
#elif defined(__sun)
# include <fcntl.h>
# include <unistd.h>
#endif
#ifndef __GNU__ /* XXX hurd portability */
# include <sys/mount.h>
# if defined(__linux__) || defined(__CYGWIN__)
# define unmount(a, b) umount(a)
# endif
# ifndef unmount
# define unmount unmount
# endif
#endif
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <errno.h>
#include <System/error.h>
#include <System/string.h>
#include "../../include/Browser/vfs.h"
#ifndef PROGNAME_EJECT
# define PROGNAME_EJECT "eject"
#endif
#ifndef PROGNAME_MOUNT
# define PROGNAME_MOUNT "/sbin/mount"
#endif
#ifndef PROGNAME_SUDO
# define PROGNAME_SUDO "sudo"
#endif
#ifndef PROGNAME_UMOUNT
# define PROGNAME_UMOUNT "/sbin/umount"
#endif
/* private */
/* types */
typedef enum _VFSFlag
{
VF_MOUNTED = 0x01,
VF_NETWORK = 0x02,
VF_READONLY = 0x04,
VF_REMOVABLE = 0x08,
VF_SHARED = 0x10
} VFSFlag;
/* prototypes */
/* accessors */
static String * _browser_vfs_get_device(char const * mountpoint);
static unsigned int _browser_vfs_get_flags_mountpoint(char const * mountpoint);
/* public */
/* functions */
/* accessors */
/* browser_vfs_can_eject */
int browser_vfs_can_eject(char const * mountpoint)
{
unsigned int flags;
flags = _browser_vfs_get_flags_mountpoint(mountpoint);
return ((flags & VF_REMOVABLE) != 0) ? 1 : 0;
}
/* browser_vfs_can_mount */
int browser_vfs_can_mount(char const * mountpoint)
{
unsigned int flags;
flags = _browser_vfs_get_flags_mountpoint(mountpoint);
return ((flags & VF_REMOVABLE) != 0
&& (flags & VF_MOUNTED) == 0) ? 1 : 0;
}
/* browser_vfs_can_unmount */
int browser_vfs_can_unmount(char const * mountpoint)
{
unsigned int flags;
flags = _browser_vfs_get_flags_mountpoint(mountpoint);
return ((flags & VF_REMOVABLE) != 0
&& (flags & VF_MOUNTED) != 0) ? 1 : 0;
}
/* browser_vfs_is_mountpoint */
int browser_vfs_is_mountpoint(struct stat * lst, dev_t parent)
{
return (lst->st_dev != parent) ? 1 : 0;
}
/* useful */
/* browser_vfs_lstat */
int browser_vfs_lstat(char const * filename, struct stat * st)
{
return lstat(filename, st);
}
/* browser_vfs_closedir */
int browser_vfs_closedir(DIR * dir)
{
return closedir(dir);
}
/* browser_vfs_eject */
int browser_vfs_eject(char const * mountpoint)
{
int ret = 0;
char * argv[] = { PROGNAME_EJECT, "--", NULL, NULL };
const unsigned int flags = G_SPAWN_SEARCH_PATH;
GError * error = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, mountpoint);
#endif
if(mountpoint == NULL)
return error_set_code(-EINVAL, "%s", strerror(EINVAL));
if((argv[2] = _browser_vfs_get_device(mountpoint)) == NULL)
return error_get_code();
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, argv[2]);
#endif
if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
!= TRUE)
{
ret = -error_set_code(1, "%s: %s", mountpoint, error->message);
g_error_free(error);
}
free(argv[2]);
return ret;
}
/* browser_vfs_mime_icon */
static GdkPixbuf * _mime_icon_emblem(GdkPixbuf * pixbuf, int size,
char const * emblem);
static GdkPixbuf * _mime_icon_folder(Mime * mime, char const * filename,
struct stat * lst, struct stat * st, int size);
static gboolean _mime_icon_folder_in_home(struct stat * pst);
static gboolean _mime_icon_folder_is_home(struct stat * st);
GdkPixbuf * browser_vfs_mime_icon(Mime * mime, char const * filename,
char const * type, struct stat * lst, struct stat * st,
int size)
{
GdkPixbuf * ret = NULL;
mode_t mode = (lst != NULL) ? lst->st_mode : 0;
struct stat s;
char const * emblem;
if(filename == NULL)
return NULL;
if(type == NULL)
type = browser_vfs_mime_type(mime, filename,
S_ISLNK(mode) ? 0 : mode);
if(st == NULL && browser_vfs_stat(filename, &s) == 0)
st = &s;
if(S_ISDIR(mode) || (st != NULL && S_ISDIR(st->st_mode)))
ret = _mime_icon_folder(mime, filename, lst, st, size);
else if(S_ISLNK(mode) && (st != NULL && S_ISDIR(st->st_mode)))
ret = _mime_icon_folder(mime, filename, lst, st, size);
else
mime_icons(mime, type, size, &ret, -1);
if(ret == NULL || lst == NULL)
return ret;
/* determine the emblem */
if(S_ISCHR(lst->st_mode) || S_ISBLK(lst->st_mode))
emblem = "emblem-system";
else if(S_ISLNK(lst->st_mode))
emblem = "emblem-symbolic-link";
else if((lst->st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0)
emblem = "emblem-unreadable";
else if((lst->st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0)
emblem = "emblem-readonly";
else
emblem = NULL;
/* apply the emblem if relevant */
if(emblem != NULL)
ret = _mime_icon_emblem(ret, size, emblem);
return ret;
}
static GdkPixbuf * _mime_icon_emblem(GdkPixbuf * pixbuf, int size,
char const * emblem)
{
int esize;
GdkPixbuf * epixbuf;
GtkIconTheme * icontheme;
#if GTK_CHECK_VERSION(2, 14, 0)
const unsigned int flags = GTK_ICON_LOOKUP_USE_BUILTIN
| GTK_ICON_LOOKUP_FORCE_SIZE;
#else
const unsigned int flags = GTK_ICON_LOOKUP_USE_BUILTIN;
#endif
/* work on a copy */
epixbuf = gdk_pixbuf_copy(pixbuf);
g_object_unref(pixbuf);
pixbuf = epixbuf;
/* determine the size of the emblem */
if(size >= 96)
esize = 32;
else if(size >= 48)
esize = 24;
else
esize = 12;
/* obtain the emblem's icon */
icontheme = gtk_icon_theme_get_default();
if((epixbuf = gtk_icon_theme_load_icon(icontheme, emblem, esize, flags,
NULL)) == NULL)
return pixbuf;
/* blit the emblem */
#if 0 /* XXX does not show anything (bottom right) */
gdk_pixbuf_composite(epixbuf, pixbuf, size - esize, size - esize,
esize, esize, 0, 0, 1.0, 1.0, GDK_INTERP_NEAREST,
255);
#else /* blitting at the top left instead */
gdk_pixbuf_composite(epixbuf, pixbuf, 0, 0, esize, esize, 0, 0,
1.0, 1.0, GDK_INTERP_NEAREST, 255);
#endif
g_object_unref(epixbuf);
return pixbuf;
}
static GdkPixbuf * _mime_icon_folder(Mime * mime, char const * filename,
struct stat * lst, struct stat * st, int size)
{
GdkPixbuf * ret = NULL;
char const * icon = NULL;
struct stat ls;
struct stat ps;
gchar * p;
size_t i;
struct
{
char const * name;
char const * icon;
} name_icon[] =
{
{ "DCIM", "folder-pictures" },
{ "Desktop", "user-desktop" },
{ "Documents", "folder-documents" },
{ "Download", "folder-download" },
{ "Downloads", "folder-download" },
{ "Music", "folder-music" },
{ "Pictures", "folder-pictures" },
{ "public_html","folder-publicshare" },
{ "Templates", "folder-templates" },
{ "Video", "folder-videos" },
{ "Videos", "folder-videos" },
};
GtkIconTheme * icontheme;
const unsigned int flags = GTK_ICON_LOOKUP_FORCE_SIZE;
if(lst == NULL && browser_vfs_lstat(filename, &ls) == 0)
lst = &ls;
/* check if the folder is special */
p = g_path_get_dirname(filename);
if((lst == NULL || !S_ISLNK(lst->st_mode))
&& st != NULL
&& browser_vfs_lstat(p, &ps) == 0)
{
if(st->st_dev != ps.st_dev || st->st_ino == ps.st_ino)
icon = "mount-point";
else if(_mime_icon_folder_is_home(st))
icon = "folder_home";
else if(_mime_icon_folder_in_home(&ps))
{
g_free(p);
p = g_path_get_basename(filename);
/* check if the folder is special */
for(i = 0; i < sizeof(name_icon) / sizeof(*name_icon);
i++)
if(strcasecmp(p, name_icon[i].name) == 0)
{
icon = name_icon[i].icon;
break;
}
}
}
g_free(p);
if(icon != NULL)
{
icontheme = gtk_icon_theme_get_default();
ret = gtk_icon_theme_load_icon(icontheme, icon, size, flags,
NULL);
}
/* generic fallback */
if(ret == NULL)
mime_icons(mime, "inode/directory", size, &ret, -1);
return ret;
}
static gboolean _mime_icon_folder_in_home(struct stat * pst)
{
static char const * homedir = NULL;
static struct stat hst;
if(homedir == NULL)
{
if((homedir = getenv("HOME")) == NULL
&& (homedir = g_get_home_dir()) == NULL)
return FALSE;
if(browser_vfs_stat(homedir, &hst) != 0)
{
homedir = NULL;
return FALSE;
}
}
return (hst.st_dev == pst->st_dev && hst.st_ino == pst->st_ino)
? TRUE : FALSE;
}
static gboolean _mime_icon_folder_is_home(struct stat * st)
{
/* FIXME code duplicated from _mime_icon_folder_in_home() */
static char const * homedir = NULL;
static struct stat hst;
if(homedir == NULL)
{
if((homedir = getenv("HOME")) == NULL
&& (homedir = g_get_home_dir()) == NULL)
return FALSE;
if(browser_vfs_stat(homedir, &hst) != 0)
{
homedir = NULL;
return FALSE;
}
}
return (hst.st_dev == st->st_dev && hst.st_ino == st->st_ino)
? TRUE : FALSE;
}
/* browser_vfs_mime_type */
char const * browser_vfs_mime_type(Mime * mime, char const * filename,
mode_t mode)
{
char const * ret = NULL;
struct stat st;
struct stat pst;
String * p = NULL;
if(S_ISDIR(mode))
{
/* look for mountpoints */
if(filename != NULL
&& browser_vfs_lstat(filename, &st) == 0
&& (p = string_new(filename)) != NULL
&& browser_vfs_lstat(dirname(p), &pst) == 0
&& (st.st_dev != pst.st_dev
|| st.st_ino == pst.st_ino))
ret = "inode/mountpoint";
else
ret = "inode/directory";
string_delete(p);
return ret;
}
else if(S_ISBLK(mode))
return "inode/blockdevice";
else if(S_ISCHR(mode))
return "inode/chardevice";
else if(S_ISFIFO(mode))
return "inode/fifo";
else if(S_ISLNK(mode))
return "inode/symlink";
#ifdef S_ISSOCK
else if(S_ISSOCK(mode))
return "inode/socket";
#endif
if(mime != NULL && filename != NULL)
ret = mime_type(mime, filename);
if(ret == NULL && (mode & S_IXUSR) != 0)
ret = "application/x-executable";
return ret;
}
/* browser_vfs_mkdir */
int browser_vfs_mkdir(char const * path, mode_t mode)
{
if(mkdir(path, mode) != 0)
return error_set_code(-errno, "%s: %s", path, strerror(errno));
return 0;
}
/* browser_vfs_mount */
int browser_vfs_mount(char const * mountpoint)
{
int ret = 0;
char * argv[] = { PROGNAME_SUDO, "-A", PROGNAME_MOUNT, "--", NULL,
NULL };
GError * error = NULL;
gboolean root;
if(mountpoint == NULL)
return error_set_code(-EINVAL, "%s: %s", mountpoint,
strerror(EINVAL));
if((argv[4] = strdup(mountpoint)) == NULL)
return error_set_code(-errno, "%s: %s", mountpoint,
strerror(errno));
root = (geteuid() == 0) ? TRUE : FALSE;
if(g_spawn_async(NULL, root ? &argv[2] : argv, NULL,
root ? 0 : G_SPAWN_SEARCH_PATH,
NULL, NULL, NULL, &error) != TRUE)
{
error_set("%s: %s", mountpoint, error->message);
g_error_free(error);
ret = -1;
}
free(argv[4]);
return ret;
}
/* browser_vfs_opendir */
DIR * browser_vfs_opendir(char const * filename, struct stat * st)
{
DIR * dir;
int fd;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, filename, st);
#endif
if(st == NULL)
return opendir(filename);
#if defined(__sun)
if((fd = open(filename, O_RDONLY)) < 0
|| (dir = fdopendir(fd)) == NULL)
{
if(fd >= 0)
close(fd);
return NULL;
}
#else
if((dir = opendir(filename)) == NULL)
return NULL;
fd = dirfd(dir);
#endif
if(fstat(fd, st) != 0)
{
browser_vfs_closedir(dir);
return NULL;
}
return dir;
}
/* browser_vfs_readdir */
struct dirent * browser_vfs_readdir(DIR * dir)
{
return readdir(dir);
}
/* browser_vfs_stat */
int browser_vfs_stat(char const * filename, struct stat * st)
{
return stat(filename, st);
}
/* browser_vfs_unmount */
int browser_vfs_unmount(char const * mountpoint)
{
int ret = 0;
int res;
char * argv[] = { PROGNAME_SUDO, "-A", PROGNAME_UMOUNT, "--", NULL,
NULL };
GError * error = NULL;
gboolean root;
if(mountpoint == NULL)
return error_set_code(-EINVAL, "%s: %s", mountpoint,
strerror(EINVAL));
#ifdef unmount
if((res = unmount(mountpoint, 0)) == 0)
return 0;
if(errno != EPERM)
return error_set_code(-errno, "%s: %s", mountpoint,
strerror(errno));
#endif
if((argv[4] = strdup(mountpoint)) == NULL)
return error_set_code(-errno, "%s: %s", mountpoint,
strerror(errno));
root = (geteuid() == 0) ? TRUE : FALSE;
if(g_spawn_async(NULL, root ? &argv[2] : argv, NULL,
root ? 0 : G_SPAWN_SEARCH_PATH,
NULL, NULL, NULL, &error) != TRUE)
{
error_set("%s: %s", mountpoint, error->message);
g_error_free(error);
ret = -1;
}
free(argv[4]);
return ret;
}
/* private */
/* functions */
/* browser_vfs_get_device */
static String * _browser_vfs_get_device(char const * mountpoint)
{
#if defined(_PATH_FSTAB)
struct fstab * f;
#endif
#if defined(ST_NOWAIT)
struct statvfs * mnt;
int res;
int i;
if((res = getmntinfo(&mnt, ST_NOWAIT)) > 0)
for(i = 0; i < res; i++)
if(strcmp(mnt[i].f_mntfromname, mountpoint) == 0)
return string_new(mnt[i].f_mntfromname);
#elif defined(MNT_NOWAIT)
struct statfs * mnt;
int res;
int i;
if((res = getmntinfo(&mnt, MNT_NOWAIT)) > 0)
for(i = 0; i < res; i++)
if(strcmp(mnt[i].f_mntfromname, mountpoint) == 0)
return string_new(mnt[i].f_mntfromname);
#endif
#if defined(_PATH_FSTAB)
if(setfsent() != 1)
return NULL;
while((f = getfsent()) != NULL)
if(strcmp(f->fs_file, mountpoint) == 0)
return string_new(f->fs_spec);
#endif
error_set_code(1, "%s: %s", mountpoint, "Device not found");
return NULL;
}
/* browser_vfs_get_flags_mountpoint */
static unsigned int _browser_vfs_get_flags_mountpoint(char const * mountpoint)
{
unsigned int flags;
#if defined(_PATH_FSTAB)
struct fstab * f;
#endif
#if defined(ST_NOWAIT)
struct statvfs * mnt;
int res;
int i;
if((res = getmntinfo(&mnt, ST_NOWAIT)) <= 0)
return 0;
for(i = 0; i < res; i++)
{
if(strcmp(mnt[i].f_mntfromname, mountpoint) != 0)
continue;
flags = VF_MOUNTED;
flags |= (mnt[i].f_flag & ST_LOCAL) ? 0 : VF_NETWORK;
flags |= (mnt[i].f_flag & (ST_EXRDONLY | ST_EXPORTED))
? VF_SHARED : 0;
flags |= (mnt[i].f_flag & ST_RDONLY) ? VF_READONLY : 0;
return flags;
}
#elif defined(MNT_NOWAIT)
struct statfs * mnt;
int res;
int i;
if((res = getmntinfo(&mnt, MNT_NOWAIT)) <= 0)
return 0;
for(i = 0; i < res; i++)
{
if(strcmp(mnt[i].f_mntonname, mountpoint) != 0)
continue;
flags = VF_MOUNTED;
flags |= (mnt[i].f_flags & MNT_LOCAL) ? 0 : VF_NETWORK;
flags |= (mnt[i].f_flags & MNT_RDONLY) ? VF_READONLY : 0;
return flags;
}
#endif
#if defined(_PATH_FSTAB)
if(setfsent() != 1)
return -1;
while((f = getfsent()) != NULL)
{
if(strcmp(f->fs_file, mountpoint) != 0
|| strcmp(f->fs_type, "sw") == 0
|| strcmp(f->fs_type, "xx") == 0)
continue;
flags = (strcmp(f->fs_vfstype, "nfs") == 0
|| strcmp(f->fs_vfstype, "smbfs") == 0)
? VF_NETWORK : 0;
flags |= (strcmp(f->fs_type, "ro") == 0) ? VF_READONLY : 0;
return flags;
}
#endif
return 0;
}