1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ 2 3/* GIO - GLib Input, Output and Streaming Library 4 * 5 * Copyright (C) 2006-2007 Red Hat, Inc. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General 18 * Public License along with this library; if not, write to the 19 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 20 * Boston, MA 02111-1307, USA. 21 * 22 * Author: Alexander Larsson <alexl@redhat.com> 23 * David Zeuthen <davidz@redhat.com> 24 */ 25 26#include "config.h" 27 28#include <string.h> 29#include <sys/wait.h> 30#include <unistd.h> 31 32#include <glib.h> 33#include "gunixvolume.h" 34#include "gunixmount.h" 35#include "gunixmounts.h" 36#include "gthemedicon.h" 37#include "gvolume.h" 38#include "gvolumemonitor.h" 39#include "gsimpleasyncresult.h" 40#include "gioerror.h" 41#include "glibintl.h" 42/* for BUFSIZ */ 43#include <stdio.h> 44 45#include "gioalias.h" 46 47struct _GUnixVolume { 48 GObject parent; 49 50 GVolumeMonitor *volume_monitor; 51 GUnixMount *mount; /* owned by volume monitor */ 52 53 char *device_path; 54 char *mount_path; 55 gboolean can_eject; 56 57 char *identifier; 58 char *identifier_type; 59 60 char *name; 61 GIcon *icon; 62}; 63 64static void g_unix_volume_volume_iface_init (GVolumeIface *iface); 65 66#define g_unix_volume_get_type _g_unix_volume_get_type 67G_DEFINE_TYPE_WITH_CODE (GUnixVolume, g_unix_volume, G_TYPE_OBJECT, 68 G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, 69 g_unix_volume_volume_iface_init)) 70 71static void 72g_unix_volume_finalize (GObject *object) 73{ 74 GUnixVolume *volume; 75 76 volume = G_UNIX_VOLUME (object); 77 78 if (volume->volume_monitor != NULL) 79 g_object_unref (volume->volume_monitor); 80 81 if (volume->mount) 82 _g_unix_mount_unset_volume (volume->mount, volume); 83 84 g_object_unref (volume->icon); 85 g_free (volume->name); 86 g_free (volume->mount_path); 87 g_free (volume->device_path); 88 g_free (volume->identifier); 89 g_free (volume->identifier_type); 90 91 G_OBJECT_CLASS (g_unix_volume_parent_class)->finalize (object); 92} 93 94static void 95g_unix_volume_class_init (GUnixVolumeClass *klass) 96{ 97 GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 98 99 gobject_class->finalize = g_unix_volume_finalize; 100} 101 102static void 103g_unix_volume_init (GUnixVolume *unix_volume) 104{ 105} 106 107/** 108 * g_unix_volume_new: 109 * @volume_monitor: a #GVolumeMonitor. 110 * @mountpoint: a #GUnixMountPoint. 111 * 112 * Returns: a #GUnixVolume for the given #GUnixMountPoint. 113 **/ 114GUnixVolume * 115_g_unix_volume_new (GVolumeMonitor *volume_monitor, 116 GUnixMountPoint *mountpoint) 117{ 118 GUnixVolume *volume; 119 120 if (!(g_unix_mount_point_is_user_mountable (mountpoint) || 121 g_str_has_prefix (g_unix_mount_point_get_device_path (mountpoint), "/vol/")) || 122 g_unix_mount_point_is_loopback (mountpoint)) 123 return NULL; 124 125 volume = g_object_new (G_TYPE_UNIX_VOLUME, NULL); 126 volume->volume_monitor = volume_monitor != NULL ? g_object_ref (volume_monitor) : NULL; 127 volume->mount_path = g_strdup (g_unix_mount_point_get_mount_path (mountpoint)); 128 volume->device_path = g_strdup (g_unix_mount_point_get_device_path (mountpoint)); 129 volume->can_eject = g_unix_mount_point_guess_can_eject (mountpoint); 130 131 volume->name = g_unix_mount_point_guess_name (mountpoint); 132 volume->icon = g_unix_mount_point_guess_icon (mountpoint); 133 134 135 if (strcmp (g_unix_mount_point_get_fs_type (mountpoint), "nfs") == 0) 136 { 137 volume->identifier_type = g_strdup (G_VOLUME_IDENTIFIER_KIND_NFS_MOUNT); 138 volume->identifier = g_strdup (volume->device_path); 139 } 140 else if (g_str_has_prefix (volume->device_path, "LABEL=")) 141 { 142 volume->identifier_type = g_strdup (G_VOLUME_IDENTIFIER_KIND_LABEL); 143 volume->identifier = g_strdup (volume->device_path + 6); 144 } 145 else if (g_str_has_prefix (volume->device_path, "UUID=")) 146 { 147 volume->identifier_type = g_strdup (G_VOLUME_IDENTIFIER_KIND_UUID); 148 volume->identifier = g_strdup (volume->device_path + 5); 149 } 150 else if (g_path_is_absolute (volume->device_path)) 151 { 152 volume->identifier_type = g_strdup (G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); 153 volume->identifier = g_strdup (volume->device_path); 154 } 155 156 return volume; 157} 158 159/** 160 * g_unix_volume_disconnected: 161 * @volume: 162 * 163 **/ 164void 165_g_unix_volume_disconnected (GUnixVolume *volume) 166{ 167 if (volume->mount) 168 { 169 _g_unix_mount_unset_volume (volume->mount, volume); 170 volume->mount = NULL; 171 } 172} 173 174/** 175 * g_unix_volume_set_mount: 176 * @volume: 177 * @mount: 178 * 179 **/ 180void 181_g_unix_volume_set_mount (GUnixVolume *volume, 182 GUnixMount *mount) 183{ 184 if (volume->mount == mount) 185 return; 186 187 if (volume->mount) 188 _g_unix_mount_unset_volume (volume->mount, volume); 189 190 volume->mount = mount; 191 192 /* TODO: Emit changed in idle to avoid locking issues */ 193 g_signal_emit_by_name (volume, "changed"); 194 if (volume->volume_monitor != NULL) 195 g_signal_emit_by_name (volume->volume_monitor, "volume-changed", volume); 196} 197 198/** 199 * g_unix_volume_unset_mount: 200 * @volume: 201 * @mount: 202 * 203 **/ 204void 205_g_unix_volume_unset_mount (GUnixVolume *volume, 206 GUnixMount *mount) 207{ 208 if (volume->mount == mount) 209 { 210 volume->mount = NULL; 211 /* TODO: Emit changed in idle to avoid locking issues */ 212 g_signal_emit_by_name (volume, "changed"); 213 if (volume->volume_monitor != NULL) 214 g_signal_emit_by_name (volume->volume_monitor, "volume-changed", volume); 215 } 216} 217 218static GIcon * 219g_unix_volume_get_icon (GVolume *volume) 220{ 221 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 222 return g_object_ref (unix_volume->icon); 223} 224 225static char * 226g_unix_volume_get_name (GVolume *volume) 227{ 228 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 229 return g_strdup (unix_volume->name); 230} 231 232static char * 233g_unix_volume_get_uuid (GVolume *volume) 234{ 235 return NULL; 236} 237 238static gboolean 239g_unix_volume_can_mount (GVolume *volume) 240{ 241 return TRUE; 242} 243 244static gboolean 245g_unix_volume_can_eject (GVolume *volume) 246{ 247 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 248 return unix_volume->can_eject; 249} 250 251static gboolean 252g_unix_volume_should_automount (GVolume *volume) 253{ 254 /* We automount all local volumes because we don't even 255 make the internal stuff visible */ 256 return TRUE; 257} 258 259static GDrive * 260g_unix_volume_get_drive (GVolume *volume) 261{ 262 return NULL; 263} 264 265static GMount * 266g_unix_volume_get_mount (GVolume *volume) 267{ 268 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 269 270 if (unix_volume->mount != NULL) 271 return g_object_ref (unix_volume->mount); 272 273 return NULL; 274} 275 276 277gboolean 278_g_unix_volume_has_mount_path (GUnixVolume *volume, 279 const char *mount_path) 280{ 281 return strcmp (volume->mount_path, mount_path) == 0; 282} 283 284 285typedef struct { 286 GUnixVolume *unix_volume; 287 GAsyncReadyCallback callback; 288 gpointer user_data; 289 GCancellable *cancellable; 290 int error_fd; 291 GIOChannel *error_channel; 292 guint error_channel_source_id; 293 GString *error_string; 294} EjectMountOp; 295 296static void 297eject_mount_cb (GPid pid, gint status, gpointer user_data) 298{ 299 EjectMountOp *data = user_data; 300 GSimpleAsyncResult *simple; 301 302 if (WEXITSTATUS (status) != 0) 303 { 304 GError *error; 305 error = g_error_new_literal (G_IO_ERROR, 306 G_IO_ERROR_FAILED, 307 data->error_string->str); 308 simple = g_simple_async_result_new_from_error (G_OBJECT (data->unix_volume), 309 data->callback, 310 data->user_data, 311 error); 312 g_error_free (error); 313 } 314 else 315 { 316 simple = g_simple_async_result_new (G_OBJECT (data->unix_volume), 317 data->callback, 318 data->user_data, 319 NULL); 320 } 321 322 g_simple_async_result_complete (simple); 323 g_object_unref (simple); 324 325 g_source_remove (data->error_channel_source_id); 326 g_io_channel_unref (data->error_channel); 327 g_string_free (data->error_string, TRUE); 328 close (data->error_fd); 329 g_spawn_close_pid (pid); 330 g_free (data); 331} 332 333static gboolean 334eject_mount_read_error (GIOChannel *channel, 335 GIOCondition condition, 336 gpointer user_data) 337{ 338 EjectMountOp *data = user_data; 339 char buf[BUFSIZ]; 340 gsize bytes_read; 341 GError *error; 342 GIOStatus status; 343 344 error = NULL; 345read: 346 status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error); 347 if (status == G_IO_STATUS_NORMAL) 348 { 349 g_string_append_len (data->error_string, buf, bytes_read); 350 if (bytes_read == sizeof (buf)) 351 goto read; 352 } 353 else if (status == G_IO_STATUS_EOF) 354 g_string_append_len (data->error_string, buf, bytes_read); 355 else if (status == G_IO_STATUS_ERROR) 356 { 357 if (data->error_string->len > 0) 358 g_string_append (data->error_string, "\n"); 359 360 g_string_append (data->error_string, error->message); 361 g_error_free (error); 362 return FALSE; 363 } 364 365 return TRUE; 366} 367 368static void 369eject_mount_do (GVolume *volume, 370 GCancellable *cancellable, 371 GAsyncReadyCallback callback, 372 gpointer user_data, 373 char **argv) 374{ 375 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 376 EjectMountOp *data; 377 GPid child_pid; 378 GError *error; 379 380 data = g_new0 (EjectMountOp, 1); 381 data->unix_volume = unix_volume; 382 data->callback = callback; 383 data->user_data = user_data; 384 data->cancellable = cancellable; 385 386 error = NULL; 387 if (!g_spawn_async_with_pipes (NULL, /* working dir */ 388 argv, 389 NULL, /* envp */ 390 G_SPAWN_DO_NOT_REAP_CHILD|G_SPAWN_SEARCH_PATH, 391 NULL, /* child_setup */ 392 NULL, /* user_data for child_setup */ 393 &child_pid, 394 NULL, /* standard_input */ 395 NULL, /* standard_output */ 396 &(data->error_fd), 397 &error)) { 398 g_assert (error != NULL); 399 goto handle_error; 400 } 401 402 data->error_string = g_string_new (""); 403 404 data->error_channel = g_io_channel_unix_new (data->error_fd); 405 g_io_channel_set_flags (data->error_channel, G_IO_FLAG_NONBLOCK, &error); 406 if (error != NULL) 407 goto handle_error; 408 409 data->error_channel_source_id = g_io_add_watch (data->error_channel, G_IO_IN, eject_mount_read_error, data); 410 g_child_watch_add (child_pid, eject_mount_cb, data); 411 412handle_error: 413 if (error != NULL) { 414 GSimpleAsyncResult *simple; 415 simple = g_simple_async_result_new_from_error (G_OBJECT (data->unix_volume), 416 data->callback, 417 data->user_data, 418 error); 419 g_simple_async_result_complete (simple); 420 g_object_unref (simple); 421 422 if (data->error_string != NULL) 423 g_string_free (data->error_string, TRUE); 424 425 if (data->error_channel != NULL) 426 g_io_channel_unref (data->error_channel); 427 428 g_error_free (error); 429 g_free (data); 430 } 431} 432 433 434static void 435g_unix_volume_mount (GVolume *volume, 436 GMountMountFlags flags, 437 GMountOperation *mount_operation, 438 GCancellable *cancellable, 439 GAsyncReadyCallback callback, 440 gpointer user_data) 441{ 442 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 443 char *argv[] = {"mount", NULL, NULL}; 444 445 if (unix_volume->mount_path != NULL) 446 argv[1] = unix_volume->mount_path; 447 else 448 argv[1] = unix_volume->device_path; 449 450 eject_mount_do (volume, cancellable, callback, user_data, argv); 451} 452 453static gboolean 454g_unix_volume_mount_finish (GVolume *volume, 455 GAsyncResult *result, 456 GError **error) 457{ 458 return TRUE; 459} 460 461static void 462g_unix_volume_eject (GVolume *volume, 463 GMountUnmountFlags flags, 464 GCancellable *cancellable, 465 GAsyncReadyCallback callback, 466 gpointer user_data) 467{ 468 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 469 char *argv[] = {"eject", NULL, NULL}; 470 471 argv[1] = unix_volume->device_path; 472 473 eject_mount_do (volume, cancellable, callback, user_data, argv); 474} 475 476static gboolean 477g_unix_volume_eject_finish (GVolume *volume, 478 GAsyncResult *result, 479 GError **error) 480{ 481 return TRUE; 482} 483 484static char * 485g_unix_volume_get_identifier (GVolume *volume, 486 const char *kind) 487{ 488 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 489 490 if (unix_volume->identifier_type != NULL && 491 strcmp (kind, unix_volume->identifier_type) == 0) 492 return g_strdup (unix_volume->identifier); 493 return NULL; 494} 495 496static char ** 497g_unix_volume_enumerate_identifiers (GVolume *volume) 498{ 499 GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); 500 char **res; 501 502 if (unix_volume->identifier_type) 503 { 504 res = g_new (char *, 2); 505 res[0] = g_strdup (unix_volume->identifier_type); 506 res[1] = NULL; 507 } 508 else 509 { 510 res = g_new (char *, 1); 511 res[0] = NULL; 512 } 513 514 return res; 515} 516 517static void 518g_unix_volume_volume_iface_init (GVolumeIface *iface) 519{ 520 iface->get_name = g_unix_volume_get_name; 521 iface->get_icon = g_unix_volume_get_icon; 522 iface->get_uuid = g_unix_volume_get_uuid; 523 iface->get_drive = g_unix_volume_get_drive; 524 iface->get_mount = g_unix_volume_get_mount; 525 iface->can_mount = g_unix_volume_can_mount; 526 iface->can_eject = g_unix_volume_can_eject; 527 iface->should_automount = g_unix_volume_should_automount; 528 iface->mount_fn = g_unix_volume_mount; 529 iface->mount_finish = g_unix_volume_mount_finish; 530 iface->eject = g_unix_volume_eject; 531 iface->eject_finish = g_unix_volume_eject_finish; 532 iface->get_identifier = g_unix_volume_get_identifier; 533 iface->enumerate_identifiers = g_unix_volume_enumerate_identifiers; 534} 535