From 75cedceb190d191366c6cdec95f938f497b48aa9 Mon Sep 17 00:00:00 2001 From: raster Date: Thu, 30 Aug 2012 09:54:57 +0000 Subject: [PATCH 1/1] EFL 1.7 svn doobies git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/branches/eeze-1.7@75862 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- .gitignore | 34 ++ AUTHORS | 4 + COPYING | 25 ++ ChangeLog | 120 ++++++ INSTALL | 365 +++++++++++++++++ Makefile.am | 44 ++ NEWS | 31 ++ README | 46 +++ TODO | 9 + autogen.sh | 39 ++ configure.ac | 294 +++++++++++++ doc/Doxyfile.in | 139 +++++++ doc/Makefile.am | 33 ++ doc/e.css | 273 +++++++++++++ doc/eeze.dox.in | 0 doc/foot.html | 18 + doc/head.html | 68 +++ doc/img/edoxy.css | 486 ++++++++++++++++++++++ doc/img/eeze.png | Bin 0 -> 209833 bytes doc/img/foot_bg.png | Bin 0 -> 173 bytes doc/img/head_bg.png | Bin 0 -> 214 bytes doc/img/header_menu_background.png | Bin 0 -> 192 bytes doc/img/header_menu_background_last.png | Bin 0 -> 637 bytes doc/img/header_menu_current_background.png | Bin 0 -> 1200 bytes doc/img/header_menu_unselected_background.png | Bin 0 -> 1596 bytes doc/img/logo.png | Bin 0 -> 3825 bytes eeze.pc.in | 11 + eeze.spec.in | 78 ++++ m4/ac_attribute.m4 | 47 +++ m4/efl_binary.m4 | 78 ++++ m4/efl_compiler_flag.m4 | 57 +++ m4/efl_doxygen.m4 | 99 +++++ src/Makefile.am | 2 + src/bin/Makefile.am | 49 +++ src/bin/eeze_disk_ls.c | 53 +++ src/bin/eeze_mount.c | 130 ++++++ src/bin/eeze_scanner.c | 453 ++++++++++++++++++++ src/bin/eeze_scanner.h | 33 ++ src/bin/eeze_udev_test.c | 238 +++++++++++ src/bin/eeze_umount.c | 113 +++++ src/lib/Eeze.h | 560 +++++++++++++++++++++++++ src/lib/Eeze_Disk.h | 567 ++++++++++++++++++++++++++ src/lib/Eeze_Net.h | 62 +++ src/lib/Makefile.am | 43 ++ src/lib/eeze_disk.c | 476 +++++++++++++++++++++ src/lib/eeze_disk_libmount.c | 495 ++++++++++++++++++++++ src/lib/eeze_disk_libmount_new.c | 525 ++++++++++++++++++++++++ src/lib/eeze_disk_libmount_old.c | 401 ++++++++++++++++++ src/lib/eeze_disk_mount.c | 465 +++++++++++++++++++++ src/lib/eeze_disk_private.h | 93 +++++ src/lib/eeze_disk_udev.c | 90 ++++ src/lib/eeze_main.c | 104 +++++ src/lib/eeze_net.c | 321 +++++++++++++++ src/lib/eeze_net_private.h | 53 +++ src/lib/eeze_udev_find.c | 384 +++++++++++++++++ src/lib/eeze_udev_private.c | 200 +++++++++ src/lib/eeze_udev_private.h | 46 +++ src/lib/eeze_udev_syspath.c | 292 +++++++++++++ src/lib/eeze_udev_walk.c | 65 +++ src/lib/eeze_udev_watch.c | 449 ++++++++++++++++++++ 60 files changed, 9160 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 doc/Doxyfile.in create mode 100644 doc/Makefile.am create mode 100644 doc/e.css create mode 100644 doc/eeze.dox.in create mode 100644 doc/foot.html create mode 100644 doc/head.html create mode 100644 doc/img/edoxy.css create mode 100644 doc/img/eeze.png create mode 100644 doc/img/foot_bg.png create mode 100644 doc/img/head_bg.png create mode 100644 doc/img/header_menu_background.png create mode 100644 doc/img/header_menu_background_last.png create mode 100644 doc/img/header_menu_current_background.png create mode 100644 doc/img/header_menu_unselected_background.png create mode 100644 doc/img/logo.png create mode 100644 eeze.pc.in create mode 100644 eeze.spec.in create mode 100644 m4/ac_attribute.m4 create mode 100644 m4/efl_binary.m4 create mode 100644 m4/efl_compiler_flag.m4 create mode 100644 m4/efl_doxygen.m4 create mode 100644 src/Makefile.am create mode 100644 src/bin/Makefile.am create mode 100644 src/bin/eeze_disk_ls.c create mode 100644 src/bin/eeze_mount.c create mode 100644 src/bin/eeze_scanner.c create mode 100644 src/bin/eeze_scanner.h create mode 100644 src/bin/eeze_udev_test.c create mode 100644 src/bin/eeze_umount.c create mode 100644 src/lib/Eeze.h create mode 100644 src/lib/Eeze_Disk.h create mode 100644 src/lib/Eeze_Net.h create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/eeze_disk.c create mode 100644 src/lib/eeze_disk_libmount.c create mode 100644 src/lib/eeze_disk_libmount_new.c create mode 100644 src/lib/eeze_disk_libmount_old.c create mode 100644 src/lib/eeze_disk_mount.c create mode 100644 src/lib/eeze_disk_private.h create mode 100644 src/lib/eeze_disk_udev.c create mode 100644 src/lib/eeze_main.c create mode 100644 src/lib/eeze_net.c create mode 100644 src/lib/eeze_net_private.h create mode 100644 src/lib/eeze_udev_find.c create mode 100644 src/lib/eeze_udev_private.c create mode 100644 src/lib/eeze_udev_private.h create mode 100644 src/lib/eeze_udev_syspath.c create mode 100644 src/lib/eeze_udev_walk.c create mode 100644 src/lib/eeze_udev_watch.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc7bc81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +*.o +*.lo +*.la +.deps +.libs +Makefile +Makefile.in +config.* +/aclocal.m4 +/autom4te.cache +/compile +/configure +/depcomp +/eeze.pc +/eeze.spec +/install-sh +/libtool +/ltmain.sh +/missing +/stamp-h1 +/doc/eeze.dox +/ABOUT-NLS +/m4/libtool.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/src/bin/eeze_udev_test +/doc/Doxyfile +src/bin/eeze_disk_ls +src/bin/eeze_mount +src/bin/eeze_scanner +src/bin/eeze_umount + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..96a45af --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Mike Blumenkrantz (zmike/discomfitor) +Cedric Bail +Mikael Sans +Christophe Dumez diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..15bae3b --- /dev/null +++ b/COPYING @@ -0,0 +1,25 @@ +Copyright notice for Eeze: + +Copyright (C) 2011 Mike Blumenkrantz and various contributors (see AUTHORS) + +All rights reserved. + +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 "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 +COPYRIGHT HOLDER 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..6fde511 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,120 @@ +2011-01-29 Carsten Haitzler (The Rasterman) + + 1.0.0 release + +2011-01-29 Mike Blumenkrantz (discomfitor/zmike) + + * added disk manipulation functions + (eeze_disk_function, eeze_disk_new, eeze_disk_new_from_mount, eeze_disk_free, eeze_disk_scan, + eeze_disk_data_set, eeze_disk_data_get, eeze_disk_syspath_get, eeze_disk_devpath_get, + eeze_disk_fstype_get, eeze_disk_vendor_get, eeze_disk_model_get, eeze_disk_serial_get, + eeze_disk_uuid_get, eeze_disk_label_get, eeze_disk_type_get, eeze_disk_removable_get, + eeze_disk_mounted_get, eeze_disk_mount, eeze_disk_unmount, eeze_disk_mount_point_get, + eeze_disk_mount_point_set, eeze_mount_tabs_watch, eeze_mount_tabs_unwatch, + eeze_mount_mtab_scan, eeze_mount_fstab_scan) + +2011-02-09 Mike Blumenkrantz (discomfitor/zmike) + + * added handling of mountopts + * iso automounting in eeze_mount + (EEZE_DISK_MOUNTOPT_UTF8, EEZE_DISK_MOUNTOPT_NOEXEC, EEZE_DISK_MOUNTOPT_NOSUID, + EEZE_DISK_MOUNTOPT_REMOUNT, eeze_disk_mountopts_set, eeze_disk_mountopts_get) + +2011-03-03 Mike Blumenkrantz (discomfitor/zmike) + + * fixed bug with watches involving filtering being too aggressive for removed/offlined devices + +2011-03-12 Mike Blumenkrantz (discomfitor/zmike) + + * added EEZE_UDEV_TYPE_NET + +2011-03-12 Mike Blumenkrantz (discomfitor/zmike) + + * added eeze_udev_syspath_get_devname + +2011-05-15 Mike Blumenkrantz (discomfitor/zmike) + + * added eeze_disk_cancel, to cancel the current pending mount/umount operation on a disk + * added a uid=%i mount option using getuid (NOT geteuid) + * added wrapper for mount command exes (such as sudo) with eeze_disk_mount_wrapper_set + and eeze_disk_mount_wrapper_get + * added functions to perform udev lookups directly on disk devices without wasting + unnecessary function calls (eeze_disk_udev_get_parent, eeze_disk_udev_get_property, + eeze_disk_udev_get_sysattr, eeze_disk_udev_walk_check_sysattr, + eeze_disk_udev_walk_get_sysattr) + +2011-05-16 Mike Blumenkrantz (discomfitor/zmike) + + * fixed bug with EEZE_UDEV_TYPE_DRIVE_* detection + * fixed eeze_udev_find_unlisted_similar to be less permissive + * added EEZE_EVENT_DISK_EJECT and eeze_disk_eject, functions for ejecting a disk + +2011-06-29 Mike Blumenkrantz (discomfitor/zmike) + + * fixed bug where EEZE_UDEV_EVENT_NONE would not match all events for watches + * fixed segv when detecting removable drives + * added eeze_scanner utility daemon + * fixed bug where watches would not properly detect disk events + +2011-07-15 Cedric Bail + + * added EEZE_UDEV_TYPE_V4L + +2011-08-01 Mike Blumenkrantz (discomfitor/zmike) + + * added fix for battery/ac detection with very recent versions of udev + +2011-11-17 Mike Blumenkrantz (discomfitor/zmike) + + * added eeze_disk_can_{mount,unmount,eject} to determine at runtime whether eeze + is capable of performing disk operations + +2011-12-02 Carsten Haitzler (The Rasterman) + + 1.1.0 release + +2011-12-02 Mike Blumenkrantz (discomfitor/zmike) + + * added network device api (eeze_net_*) and Eeze_Net.h header + +2012-01-09 Mikael Sans + + * added EEZE_UDEV_TYPE_BLUETOOTH + +2012-04-26 Carsten Haitzler (The Rasterman) + + 1.2.0 release + +2012-06-11 Mike Blumenkrantz + + * eeze_scanner socket is now readable by anyone + +2012-06-12 Mike Blumenkrantz + + * Add fallback mount using device name if a disk has no uuid + +2012-06-29 Mike Blumenkrantz + + * Fix crash in eeze_net_free() + +2012-06-29 Christophe Dumez (christophe.dumez@intel.com) + + * Added joystick detection + +2012-07-10 Mike Blumenkrantz + + * Add a check in event monitoring for disks which ensures that + device changes for loopback devices are picked up + +2012-07-23 Mike Blumenkrantz + + * Add yet another libmount backend for eeze_disk to handle current + setups which do not have mtab and instead use /proc/self/mountinfo + +2012-07-30 Mike Blumenkrantz + + * Add EEZE_DISK_MOUNTOPT_NODEV option for disabling device nodes on mount + +2012-07-31 Mike Blumenkrantz + + * Prevent mount operations from retrying infinitely on failure diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..7d1c323 --- /dev/null +++ b/INSTALL @@ -0,0 +1,365 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007, 2008, 2009 Free Software Foundation, Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..4d9f2c5 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,44 @@ +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src doc + +MAINTAINERCLEANFILES = \ +Makefile.in \ +aclocal.m4 \ +compile \ +config.guess \ +config.h.in \ +config.h.in~ \ +config.sub \ +configure \ +depcomp \ +install-sh \ +ltconfig \ +ltmain.sh \ +missing \ +eeze*doc*tar* \ +eeze.pc \ +eeze.spec \ +m4/l* + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = eeze.pc + +EXTRA_DIST = \ +AUTHORS \ +COPYING \ +README \ +$(pkgconfig_DATA) \ +autogen.sh \ +eeze.pc.in \ +eeze.spec.in \ +eeze.spec \ +m4/efl_doxygen.m4 + +.PHONY: doc + +# Documentation + +doc: all + @echo "entering doc/" + $(MAKE) -C doc doc diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..b953461 --- /dev/null +++ b/NEWS @@ -0,0 +1,31 @@ +Eeze 1.7.0 + +Changes since Eeze 1.2.0: +------------------------- + +Additions: + + * Joystick support + * Support for mtab-less systems + +Changes since Eeze 1.1.0: +------------------------- + +Additions: + + * Detect bluetooth devices. + * Network device API (eeze_net_*). + +Changes since Eeze 1.0.0: +------------------------- + +Additions: + + * more disk-related detection/info functions + * disk mounting API + * eeze_scanner utility for applications to hook for drive detection + +Fixes: + + * bugs with device detection related to newer kernel versions + * device filtering to be more accurate in some cases diff --git a/README b/README new file mode 100644 index 0000000..e1a5393 --- /dev/null +++ b/README @@ -0,0 +1,46 @@ +Eeze 1.7.0 + +****************************************************************************** + + FOR ANY ISSUES PLEASE EMAIL: + enlightenment-devel@lists.sourceforge.net + +****************************************************************************** + + +Requirements: +------------- + +Must: + libc + ecore (at least 1.0.0) + libudev + +Suggested: + libmount + +Eeze is a library for manipulating devices with an extremely simple api. +It interfaces directly with device subsystems, avoiding such middleman daemons as +udisks/upower or hal to immediately gather device information the instant it +becomes known to the OS. This can be used to determine such things as: + * If a cdrom has a disk inserted + * The temperature of a cpu core + * The remaining power left in a battery + * The current power consumption of various parts + * Monitor in realtime the status of peripheral devices + +Each of the above examples can be performed by using only a single eeze +function, as one of the primary focuses of the library is to reduce the +complexity of managing devices. + +Eeze 1.1 adds more detailed disk detection as well as filesystem mount point manipulation. +Using a combination of udev and mount, it is possible to easily write disk-based mount services +which do not rely on any external processes or libraries aside from Eeze. +------------------------------------------------------------------------------ +COMPILING AND INSTALLING: + + ./configure + make +(as root unless you are installing in your users directories): + make install + diff --git a/TODO b/TODO new file mode 100644 index 0000000..a31b4aa --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +udev: switch enum to bitmasks with categories probably + documentation for all types + more functions or something? + +TO BE ADDED +automounter +libdevinfo for solaris +??? +Profit diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..00116ea --- /dev/null +++ b/autogen.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +rm -rf autom4te.cache +rm -f aclocal.m4 ltmain.sh + +touch README +touch ABOUT-NLS + +echo "Running aclocal..." ; aclocal $ACLOCAL_FLAGS -I m4 || exit 1 +echo "Running autoheader..." ; autoheader || exit 1 +echo "Running autoconf..." ; autoconf || exit 1 +echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1 +echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1 + +W=0 + +rm -f config.cache-env.tmp +echo "OLD_PARM=\"$@\"" >> config.cache-env.tmp +echo "OLD_CFLAGS=\"$CFLAGS\"" >> config.cache-env.tmp +echo "OLD_PATH=\"$PATH\"" >> config.cache-env.tmp +echo "OLD_PKG_CONFIG_PATH=\"$PKG_CONFIG_PATH\"" >> config.cache-env.tmp +echo "OLD_LDFLAGS=\"$LDFLAGS\"" >> config.cache-env.tmp + +cmp config.cache-env.tmp config.cache-env >> /dev/null +if [ $? -ne 0 ]; then + W=1; +fi + +if [ $W -ne 0 ]; then + echo "Cleaning configure cache..."; + rm -f config.cache config.cache-env + mv config.cache-env.tmp config.cache-env +else + rm -f config.cache-env.tmp +fi + +if [ -z "$NOCONFIGURE" ]; then + ./configure -C "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..5569970 --- /dev/null +++ b/configure.ac @@ -0,0 +1,294 @@ +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_define([v_maj], [1]) +m4_define([v_min], [7]) +m4_define([v_mic], [0]) +m4_define([v_rev], m4_esyscmd([(svnversion "${SVN_REPO_PATH:-.}" | grep -v '\(export\|Unversioned directory\)' || echo 0) | awk -F : '{printf("%s\n", $1);}' | tr -d ' :MSP\n'])) +m4_if(v_rev, [0], [m4_define([v_rev], m4_esyscmd([git log 2> /dev/null | (grep -m1 git-svn-id || echo 0) | sed -e 's/.*@\([0-9]*\).*/\1/' | tr -d '\n']))]) +##-- When released, remove the dnl on the below line +m4_undefine([v_rev]) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], [m4_define([v_ver], [v_maj.v_min.v_mic.v_rev])], [m4_define([v_ver], [v_maj.v_min.v_mic])]) +m4_define([lt_cur], m4_eval(v_maj + v_min)) +m4_define([lt_rev], v_mic) +m4_define([lt_age], v_min) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## + +AC_INIT([eeze], [v_ver], [enlightenment-devel@lists.sourceforge.net]) +AC_PREREQ([2.52]) +AC_CONFIG_SRCDIR([configure.ac]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CONFIG_HEADERS([config.h]) +AH_TOP([ +#ifndef EFL_CONFIG_H__ +#define EFL_CONFIG_H__ +]) +AH_BOTTOM([ +#endif /* EFL_CONFIG_H__ */ +]) + +AM_INIT_AUTOMAKE([1.6 dist-bzip2]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl +define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl +AC_PROG_LIBTOOL + +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], , [m4_define([v_rev], [0])]) +m4_ifdef([v_rel], , [m4_define([v_rel], [])]) +AC_DEFINE_UNQUOTED(VMAJ, [v_maj], [Major version]) +AC_DEFINE_UNQUOTED(VMIN, [v_min], [Minor version]) +AC_DEFINE_UNQUOTED(VMIC, [v_mic], [Micro version]) +AC_DEFINE_UNQUOTED(VREV, [v_rev], [Revison]) +version_info="lt_cur:lt_rev:lt_age" +release_info="v_rel" +AC_SUBST(version_info) +AC_SUBST(release_info) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +VMAJ=v_maj +AC_SUBST(VMAJ) + +### Needed information + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST + +requirement_eeze="ecore >= 1.6.99 eina >= 1.6.99 libudev" + + +### Checks for programs +AC_PROG_CC +AM_PROG_CC_C_O +AC_C___ATTRIBUTE__ + +# pkg-config +PKG_PROG_PKG_CONFIG + +# Check whether pkg-config supports Requires.private +AS_IF( + [$PKG_CONFIG --atleast-pkgconfig-version 0.22], + [pkgconfig_requires_private="Requires.private"], + [pkgconfig_requires_private="Requires"] +) +AC_SUBST(pkgconfig_requires_private) + +# doxygen program for documentation building + +EFL_CHECK_DOXYGEN([build_doc="yes"], [build_doc="no"]) + + +### Checks for libraries + +PKG_CHECK_MODULES([EEZE], [${requirement_eeze}]) + +udev_version=$(pkg-config libudev --modversion) + +if test $udev_version -lt 143;then + AC_MSG_ERROR([udev version is too old!]) +elif test $udev_version -lt 148;then + AC_MSG_WARN([Old udev version detected, enabling compat code]) + AC_DEFINE([OLD_UDEV_RRRRRRRRRRRRRR],[1],[compat functionality for udev < 148]) +fi + +eeze_mount= +PKG_CHECK_EXISTS([mount >= 2.18.0], + [ + AC_DEFINE([HAVE_EEZE_MOUNT], [1], [Eeze is mount-capable]) + AM_CONDITIONAL([HAVE_EEZE_MOUNT], [true]) + eeze_mount="yes" + ], + AM_CONDITIONAL([HAVE_EEZE_MOUNT], [false]) +) + +if test "x$eeze_mount" = "xyes";then + AC_ARG_WITH([mount], [AS_HELP_STRING([--with-mount], [specify mount bin @<:@default=detect@:>@])], [with_mount=$withval], [with_mount="detect"]) + AC_ARG_WITH([umount], [AS_HELP_STRING([--with-umount], [specify umount bin @<:@default=detect@:>@])], [with_umount=$withval], [with_umount="detect"]) + AC_ARG_WITH([eject], [AS_HELP_STRING([--with-eject], [specify eject bin @<:@default=detect@:>@])], [with_eject=$withval], [with_eject="detect"]) + PKG_CHECK_MODULES([LIBMOUNT], [mount >= 2.18.0]) + mount_v=$(pkg-config --modversion mount) + PKG_CHECK_MODULES([ECORE_FILE], [ecore-file >= 1.6.99]) + PKG_CHECK_MODULES([EET], [eet >= 1.6.99]) + PKG_CHECK_MODULES([ECORE_CON], [ecore-con >= 1.6.99]) + + if test "x$with_mount" = "xdetect";then + AC_PATH_PROG([with_mount], [mount], []) + fi + if test -z "$with_mount" ; then + AC_DEFINE_UNQUOTED([MOUNTABLE], [0], [whether mount is available]) + else + AC_DEFINE_UNQUOTED([MOUNTABLE], [1], [whether mount is available]) + fi + AC_DEFINE_UNQUOTED([EEZE_MOUNT_BIN], ["$with_mount"], [mount bin to use]) + + if test "x$with_umount" = "xdetect";then + AC_PATH_PROG([with_umount], [umount], []) + fi + if test -z "$with_umount" ; then + AC_DEFINE_UNQUOTED([UNMOUNTABLE], [0], [whether umount is available]) + else + AC_DEFINE_UNQUOTED([UNMOUNTABLE], [1], [whether umount is available]) + fi + AC_DEFINE_UNQUOTED([EEZE_UNMOUNT_BIN], ["$with_umount"], [umount bin to use]) + + if test "x$with_eject" = "xdetect";then + AC_PATH_PROG([with_eject], [eject], []) + fi + if test -z "$with_eject" ; then + AC_DEFINE_UNQUOTED([EJECTABLE], [0], [whether eject is available]) + else + AC_DEFINE_UNQUOTED([EJECTABLE], [1], [whether eject is available]) + fi + AC_DEFINE_UNQUOTED([EEZE_EJECT_BIN], ["$with_eject"], [eject bin to use]) +fi + +want_mtab= +AC_ARG_ENABLE([mtab], + [AC_HELP_STRING([--enable-mtab], + [force use of mtab for mount info @<:@default=detect@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + want_mtab="yes" + else + want_mtab="no" + fi], + [want_mtab="auto"]) + + +if test "x${want_mtab}" = "xyes" ; then + AM_CONDITIONAL([OLD_LIBMOUNT], [false]) + AM_CONDITIONAL([NEW_LIBMOUNT], [false]) +else + if test -n "$mount_v";then + AM_CONDITIONAL([OLD_LIBMOUNT], [test "$(echo $mount_v | cut -d'.' -f2)" -lt 19]) + AM_CONDITIONAL([NEW_LIBMOUNT], [test "$(echo $mount_v | cut -d'.' -f2)" -gt 19]) + else + AM_CONDITIONAL([OLD_LIBMOUNT], [false]) + AM_CONDITIONAL([NEW_LIBMOUNT], [false]) + fi +fi +AM_COND_IF([OLD_LIBMOUNT], [ + AC_DEFINE_UNQUOTED([OLD_LIBMOUNT], [1], [using first version of libmount]) + ],[]) + +AC_CHECK_HEADERS([netinet/in.h]) +want_ipv6="yes" +have_ipv6="no" + +AC_ARG_ENABLE([ipv6], + [AC_HELP_STRING([--disable-ipv6], + [disable ipv6 functionality @<:@default=detect@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + want_ipv6="yes" + else + want_ipv6="no" + fi], + [want_ipv6="auto"]) + +# Verify IPV6 availability in headers +if test "x${want_ipv6}" != "xno" ; then + AC_CHECK_TYPES([struct ipv6_mreq], + [have_ipv6="yes"], + [have_ipv6="no"], + [[ +#ifdef HAVE_NETINET_IN_H +# include +#endif + ]]) +fi + +if test "x${have_ipv6}" = "xyes" ; then + AC_DEFINE(HAVE_IPV6, 1, [Define if IPV6 is supported]) +fi + +### Checks for header files + + +### Checks for types + + +### Checks for structures + + +### Checks for compiler characteristics + +AC_HEADER_STDC + +if ! test "x${VMIC}" = "x" ; then + EFL_COMPILER_FLAG([-Wall]) + EFL_COMPILER_FLAG([-W]) +fi + +EFL_COMPILER_FLAG([-Wshadow]) + + +### Binary + +EFL_ENABLE_BIN([eeze-udev-test], ["yes"]) +EFL_ENABLE_BIN([eeze-mount], ["yes"]) +EFL_ENABLE_BIN([eeze-disk-ls], ["yes"]) +EFL_ENABLE_BIN([eeze-umount], ["yes"]) +EFL_ENABLE_BIN([eeze-scanner], ["yes"]) + +AC_SUBST(requirement_eeze) + + +AC_OUTPUT([ +Makefile +doc/eeze.dox +doc/Makefile +doc/Doxyfile +src/Makefile +src/lib/Makefile +src/bin/Makefile +eeze.pc +eeze.spec +]) + + +##################################################################### +## Info + +echo +echo +echo +echo "------------------------------------------------------------------------" +echo "$PACKAGE $VERSION" +echo "------------------------------------------------------------------------" +echo +echo "Configuration Options Summary:" +if test "x$eeze_mount" = "xyes";then + echo + echo "Mount..................: ${with_mount}" + echo "Umount.................: ${with_umount}" + echo "Eject..................: ${with_eject}" + echo +fi +echo "Tests..................: ${have_eeze_udev_test}" +echo +echo "Demos..................:" +echo " eeze_mount...........: ${have_eeze_mount}" +echo " eeze_umount..........: ${have_eeze_umount}" +echo " eeze_disk_ls.........: ${have_eeze_disk_ls}" +echo +echo "Utilities..............:" +echo " eeze_scanner.........: ${have_eeze_scanner}" +echo +echo "IPv6...................: ${have_ipv6}" +echo +echo "Documentation..........: ${build_doc}" +echo +echo "Compilation............: make (or gmake)" +echo " CPPFLAGS.............: $CPPFLAGS" +echo " CFLAGS...............: $CFLAGS" +echo " LDFLAGS..............: $LDFLAGS" +echo +echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')" +echo " prefix...............: $prefix" +echo + diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in new file mode 100644 index 0000000..da43f44 --- /dev/null +++ b/doc/Doxyfile.in @@ -0,0 +1,139 @@ +ALIASES = +ALLEXTERNALS = NO +ALPHABETICAL_INDEX = YES +ALWAYS_DETAILED_SEC = NO +BINARY_TOC = NO +BRIEF_MEMBER_DESC = YES +CASE_SENSE_NAMES = YES +CHM_FILE = +CLASS_DIAGRAMS = NO +CLASS_GRAPH = NO +COLLABORATION_GRAPH = NO +COLS_IN_ALPHA_INDEX = 2 +COMPACT_LATEX = NO +COMPACT_RTF = NO +DISABLE_INDEX = YES +DISTRIBUTE_GROUP_DOC = NO +DOT_CLEANUP = YES +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +DOT_IMAGE_FORMAT = png +DOT_PATH = +ENABLED_SECTIONS = +ENABLE_PREPROCESSING = YES +ENUM_VALUES_PER_LINE = 1 +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +EXCLUDE = +EXCLUDE_PATTERNS = +EXCLUDE_SYMLINKS = NO +EXPAND_AS_DEFINED = +EXPAND_ONLY_PREDEF = NO +EXTERNAL_GROUPS = YES +EXTRACT_ALL = NO +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRA_PACKAGES = +FILE_PATTERNS = +FILTER_SOURCE_FILES = NO +FULL_PATH_NAMES = NO +GENERATE_AUTOGEN_DEF = NO +GENERATE_BUGLIST = YES +GENERATE_CHI = NO +GENERATE_DEPRECATEDLIST= YES +GENERATE_HTMLHELP = NO +GENERATE_HTML = YES +GENERATE_LATEX = YES +GENERATE_LEGEND = YES +GENERATE_MAN = YES +GENERATE_RTF = NO +GENERATE_TAGFILE = +GENERATE_TESTLIST = YES +GENERATE_TODOLIST = YES +GENERATE_TREEVIEW = NO +GENERATE_XML = NO +GRAPHICAL_HIERARCHY = NO +HAVE_DOT = NO +HHC_LOCATION = +HIDE_FRIEND_COMPOUNDS = YES +HIDE_SCOPE_NAMES = NO +HIDE_UNDOC_CLASSES = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_RELATIONS = YES +HTML_ALIGN_MEMBERS = YES +HTML_FILE_EXTENSION = .html +HTML_FOOTER = @srcdir@/foot.html +HTML_HEADER = @srcdir@/head.html +HTML_OUTPUT = html +HTML_STYLESHEET = @srcdir@/e.css +IGNORE_PREFIX = +IMAGE_PATH = img +INCLUDED_BY_GRAPH = NO +INCLUDE_FILE_PATTERNS = +INCLUDE_GRAPH = NO +INCLUDE_PATH = +INHERIT_DOCS = YES +INLINE_INFO = YES +INLINE_INHERITED_MEMB = NO +INLINE_SOURCES = NO +INPUT = @srcdir@/eeze.dox @top_srcdir@/src/lib +INPUT_FILTER = +INTERNAL_DOCS = NO +JAVADOC_AUTOBRIEF = YES +LATEX_BATCHMODE = NO +LATEX_CMD_NAME = latex +LATEX_HEADER = +LATEX_OUTPUT = latex +MACRO_EXPANSION = NO +MAKEINDEX_CMD_NAME = makeindex +MAN_EXTENSION = .3 +MAN_LINKS = YES +MAN_OUTPUT = man +MAX_INITIALIZER_LINES = 30 +MULTILINE_CPP_IS_BRIEF = NO +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OUTPUT_DIRECTORY = . +OUTPUT_LANGUAGE = English +PAPER_TYPE = a4wide +PDF_HYPERLINKS = YES +PERL_PATH = /usr/bin/perl +PREDEFINED = +PROJECT_NAME = Eeze +PROJECT_NUMBER = +QUIET = NO +RECURSIVE = YES +REFERENCES_LINK_SOURCE = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REPEAT_BRIEF = YES +RTF_EXTENSIONS_FILE = +RTF_HYPERLINKS = NO +RTF_OUTPUT = rtf +RTF_STYLESHEET_FILE = +SEARCHENGINE = NO +SEARCH_INCLUDES = YES +SHORT_NAMES = NO +SHOW_INCLUDE_FILES = NO +SHOW_USED_FILES = NO +SKIP_FUNCTION_MACROS = YES +SORT_MEMBER_DOCS = YES +SOURCE_BROWSER = NO +STRIP_CODE_COMMENTS = YES +STRIP_FROM_PATH = src/ +SUBGROUPING = YES +TAB_SIZE = 2 +TAGFILES = +TEMPLATE_RELATIONS = NO +TOC_EXPAND = NO +TREEVIEW_WIDTH = 250 +USE_PDFLATEX = NO +VERBATIM_HEADERS = NO +WARN_FORMAT = "$file:$line: $text" +WARN_IF_UNDOCUMENTED = YES +WARNINGS = YES +WARN_LOGFILE = +XML_DTD = +XML_SCHEMA = diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..fefb763 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,33 @@ +MAINTAINERCLEANFILES = Makefile.in eeze.dox + +.PHONY: doc + +PACKAGE_DOCNAME = $(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-doc + +if EFL_BUILD_DOC + +doc-clean: + rm -rf html/ latex/ man/ xml/ $(top_builddir)/$(PACKAGE_DOCNAME).tar* + +doc: all doc-clean + $(efl_doxygen) + cp $(srcdir)/img/* html/ + cp $(srcdir)/img/* latex/ + rm -rf $(PACKAGE_DOCNAME).tar* + mkdir -p $(PACKAGE_DOCNAME)/doc + cp -R html/ latex/ man/ $(PACKAGE_DOCNAME)/doc + tar cf $(PACKAGE_DOCNAME).tar $(PACKAGE_DOCNAME)/ + bzip2 -9 $(PACKAGE_DOCNAME).tar + rm -rf $(PACKAGE_DOCNAME)/ + mv $(PACKAGE_DOCNAME).tar.bz2 $(top_builddir) + +clean-local: doc-clean + +else + +doc: + @echo "Documentation not built. Run ./configure --help" + +endif + +EXTRA_DIST = Doxyfile.in $(wildcard $(srcdir)img/*.*) e.css head.html foot.html eeze.dox.in diff --git a/doc/e.css b/doc/e.css new file mode 100644 index 0000000..2dd6b44 --- /dev/null +++ b/doc/e.css @@ -0,0 +1,273 @@ +/* + Author: + Andres Blanc + DaveMDS Andreoli + + Supported Browsers: + ie7, opera9, konqueror4 and firefox3 + + Please use a different file for ie6, ie5, etc. hacks. +*/ + + +/* Necessary to place the footer at the bottom of the page */ +html, body { + height: 100%; + margin: 0px; + padding: 0px; +} + +#container { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -53px; +} + +#footer, #push { + height: 53px; +} + + +* html #container { + height: 100%; +} + +/* Prevent floating elements overflowing containers */ +.clear { + clear: both; + width: 0px; + height: 0px; +} + +/* Flexible & centered layout from 750 to 960 pixels */ +.layout { + max-width: 960px; + min-width: 760px; + margin-left: auto; + margin-right: auto; +} + +body { + /*font-family: Lucida Grande, Helvetica, sans-serif;*/ + font-family: "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif +} + +/* Prevent design overflowing the viewport in small resolutions */ +#container { + padding-right: 17px; + padding-left: 17px; + background-image: url(head_bg.png); + background-repeat: repeat-x; +} + +/****************************/ +/* Top main menu */ +/****************************/ +#header_logo { + background-image : url(logo.png); + width : 61px; +} + +#header_logo a { + position : absolute; + border : 0px; + background-color : transparent; + top : 0px; + width : 60px; + height : 60px; +} + +#header_menu { + background-image : url(header_menu_background.png); + font : normal 10pt verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif; + text-align : right; +} + +#header_last { + background-image : url(header_menu_background_last.png); + width : 15px; +} + +td.nav_passive { + background : url(header_menu_unselected_background.png) 0 0 no-repeat; + height : 63px; + font-family : "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif; + font-size : 11px; + padding : 20px 10px 20px 10px; + vertical-align : middle; +} + +td.nav_active { + background : url(header_menu_current_background.png) 0 0 no-repeat; + height : 63px; + color : #646464; + font-family : "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif; + font-size : 11px; + font-weight : bold; + padding : 20px 10px 20px 10px; + vertical-align : middle; +} + +#header_menu a { + display : block; + text-decoration : none; + cursor : pointer; + color : #cdcdcd; +} + + + +#header { + width: 100%; + height: 102px; +} + +#header h1 { + width: 63px; + height: 63px; + position: absolute; + margin: 0px; +} + +#header h1 span { + display: none; +} + +#header h2 { + display: none; +} + +/* .menu-container is used to set properties common to .menu and .submenu */ +#header .menu-container { +} + +#header .menu-container ul { + list-style-type: none; + list-style-position: inside; + margin: 0; +} + +#header .menu-container li { + display: block; + float: right; +} + +#header .menu { + height: 63px; + display: block; + background-image: url(menu_bg.png); + background-repeat: repeat-x; +} + +#header .menu ul { + height: 100%; + display: block; + background-image: url(menu_bg_last.png); + background-repeat: no-repeat; + background-position: top right; + padding-right: 17px; +} + +#header .menu li { + height: 100%; + text-align: center; + background-image: url(menu_bg_unsel.png); + background-repeat: no-repeat; +} + +#header .menu a { + height: 100%; + display: block; + color: #cdcdcd; + text-decoration: none; + font-size: 10pt; + line-height: 59px; + text-align: center; + padding: 0px 15px 0px 15px; +} + +#header .menu li:hover { + background-image: url(menu_bg_hover.png); + background-repeat: no-repeat; +} + +#header .menu li:hover a { + color: #FFFFFF; +} + +#header .menu li.current { + background-image: url(menu_bg_current.png); + background-repeat: no-repeat; +} + +#header .menu li.current a { + color: #646464; +} + + +/* Hide all the submenus but the current */ +#header .submenu ul { + display: none; +} + +#header .submenu .current { + display: block; +} + +#header .submenu { + font: bold 10px verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif; + margin-top: 10px; +} + +#header .submenu a { + color: #888888; + text-decoration: none; + font-size: 0.9em; + line-height: 15px; + padding:0px 5px 0px 5px; +} + +#header .submenu a:hover { + color: #444444; +} + +#header .submenu li { + border-left: 1px solid #DDDDDD; +} + +#header .submenu li:last-child { + border-left: 0; +} + +#header .doxytitle { + position: absolute; + font-size: 1.8em; + font-weight: bold; + color: #444444; + line-height: 35px; +} + +#header small { + font-size: 0.4em; +} + +#footer { + background-image: url(foot_bg.png); + width: 100%; +} + +#footer table { + width: 100%; + text-align: center; + white-space: nowrap; + padding: 5px 30px 5px 30px; + font-size: 0.8em; + font-family: "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif; + color: #888888; +} + +#footer td.copyright { + width: 100%; +} + diff --git a/doc/eeze.dox.in b/doc/eeze.dox.in new file mode 100644 index 0000000..e69de29 diff --git a/doc/foot.html b/doc/foot.html new file mode 100644 index 0000000..d43cf8f --- /dev/null +++ b/doc/foot.html @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + diff --git a/doc/head.html b/doc/head.html new file mode 100644 index 0000000..2d9ea92 --- /dev/null +++ b/doc/head.html @@ -0,0 +1,68 @@ + + + $title + + + + + + + + + + + + + + +
+ + + +
+
diff --git a/doc/img/edoxy.css b/doc/img/edoxy.css new file mode 100644 index 0000000..311ca23 --- /dev/null +++ b/doc/img/edoxy.css @@ -0,0 +1,486 @@ +/* + * This file contain a custom doxygen style to match e.org graphics + */ + + + +/* BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: Geneva, Arial, Helvetica, sans-serif; +}*/ +BODY, TD { + font-size: 12px; +} +H1 { + text-align: center; + font-size: 160%; +} +H2 { + font-size: 120%; +} +H3 { + font-size: 100%; +} +CAPTION { + font-weight: bold +} +DIV.qindex { + width: 100%; + background-color: #e8eef2; + border: 1px solid #84b0c7; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.navpath { + width: 100%; + background-color: #e8eef2; + border: 1px solid #84b0c7; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.navtab { + background-color: #e8eef2; + border: 1px solid #84b0c7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} +TD.navtab { + font-size: 70%; +} +A.qindex { + text-decoration: none; + font-weight: bold; + color: #1A419D; +} +A.qindex:visited { + text-decoration: none; + font-weight: bold; + color: #1A419D +} +A.qindex:hover { + text-decoration: none; + background-color: #ddddff; +} +A.qindexHL { + text-decoration: none; + font-weight: bold; + background-color: #6666cc; + color: #ffffff; + border: 1px double #9295C2; +} +A.qindexHL:hover { + text-decoration: none; + background-color: #6666cc; + color: #ffffff; +} +A.qindexHL:visited { + text-decoration: none; + background-color: #6666cc; + color: #ffffff +} +A.el { + text-decoration: none; + font-weight: bold +} +A.elRef { + font-weight: bold +} +A.code:link { + text-decoration: none; + font-weight: normal; + color: #0000FF +} +A.code:visited { + text-decoration: none; + font-weight: normal; + color: #0000FF +} +A.codeRef:link { + font-weight: normal; + color: #0000FF +} +A.codeRef:visited { + font-weight: normal; + color: #0000FF +} +A:hover, A:visited:hover { + text-decoration: none; + /* background-color: #f2f2ff; */ + color: #000055; +} +A.anchor { + color: #000; +} +DL.el { + margin-left: -1cm +} +.fragment { + font-family: monospace, fixed; + font-size: 95%; +} +PRE.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 2px; + margin-right: 8px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 4px; +} +DIV.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px +} + +DIV.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; +} +DIV.groupText { + margin-left: 16px; + font-style: italic; + font-size: 90% +} +/*BODY { + background: white; + color: black; + margin-right: 20px; + margin-left: 20px; +}*/ +TD.indexkey { + background-color: #e8eef2; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TD.indexvalue { + background-color: #e8eef2; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TR.memlist { + background-color: #f0f0f0; +} +P.formulaDsp { + text-align: center; +} +IMG.formulaDsp { +} +IMG.formulaInl { + vertical-align: middle; +} +SPAN.keyword { color: #008000 } +SPAN.keywordtype { color: #604020 } +SPAN.keywordflow { color: #e08000 } +SPAN.comment { color: #800000 } +SPAN.preprocessor { color: #806020 } +SPAN.stringliteral { color: #002080 } +SPAN.charliteral { color: #008080 } +SPAN.vhdldigit { color: #ff00ff } +SPAN.vhdlchar { color: #000000 } +SPAN.vhdlkeyword { color: #700070 } +SPAN.vhdllogic { color: #ff0000 } + +.mdescLeft { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.mdescRight { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplParams { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + color: #606060; + background-color: #FAFAFA; + font-size: 80%; +} +.search { + color: #003399; + font-weight: bold; +} +FORM.search { + margin-bottom: 0px; + margin-top: 0px; +} +INPUT.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} +TD.tiny { + font-size: 75%; +} +a { + color: #1A41A8; +} +a:visited { + color: #2A3798; +} +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #84b0c7; +} +TH.dirtab { + background: #e8eef2; + font-weight: bold; +} +HR { + height: 1px; + border: none; + border-top: 1px solid black; +} + +/* Style for detailed member documentation */ +.memtemplate { + font-size: 80%; + color: #606060; + font-weight: normal; + margin-left: 3px; +} +.memnav { + background-color: #eeeeee; + border: 1px solid #dddddd; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} +.memitem { + padding: 4px; + background-color: #eeeeee; + border-width: 1px; + border-style: solid; + border-color: #dddddd; + -moz-border-radius: 4px 4px 4px 4px; +} +.memname { + white-space: nowrap; + font-weight: bold; + color: #ffffff; +} +.memdoc{ + padding-left: 10px; +} +.memproto { + background-color: #111111; + width: 100%; + border-width: 1px; + border-style: solid; + border-color: #000000; + font-weight: bold; + -moz-border-radius: 4px 4px 4px 4px; +} +.paramkey { + text-align: right; + color: #ffffff; +} +.paramtype { + white-space: nowrap; + color: #aaaaaa; +} +.paramname { + color: #ff0000; + font-style: italic; + white-space: nowrap; +} +/* End Styling for detailed member documentation */ + +/* for the tree view */ +.ftvtree { + font-family: sans-serif; + margin:0.5em; +} +/* these are for tree view when used as main index */ +.directory { + font-size: 9pt; + font-weight: bold; +} +.directory h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +/* The following two styles can be used to replace the root node title */ +/* with an image of your choice. Simply uncomment the next two styles, */ +/* specify the name of your image and be sure to set 'height' to the */ +/* proper pixel height of your image. */ + +/* .directory h3.swap { */ +/* height: 61px; */ +/* background-repeat: no-repeat; */ +/* background-image: url("yourimage.gif"); */ +/* } */ +/* .directory h3.swap span { */ +/* display: none; */ +/* } */ + +.directory > h3 { + margin-top: 0; +} +.directory p { + margin: 0px; + white-space: nowrap; +} +.directory div { + display: none; + margin: 0px; +} +.directory img { + vertical-align: -30%; +} +/* these are for tree view when not used as main index */ +.directory-alt { + font-size: 100%; + font-weight: bold; +} +.directory-alt h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} +.directory-alt > h3 { + margin-top: 0; +} +.directory-alt p { + margin: 0px; + white-space: nowrap; +} +.directory-alt div { + display: none; + margin: 0px; +} +.directory-alt img { + vertical-align: -30%; +} + diff --git a/doc/img/eeze.png b/doc/img/eeze.png new file mode 100644 index 0000000000000000000000000000000000000000..1aeb5b1522a827f93399dec7f3276e9fe8fe3bc3 GIT binary patch literal 209833 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 #T3LukOF=F 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

Ms#La=9c)-F)h=$(kwo1C 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 mn9~^SszjBE0B4FJ6#gGzGlUrHz2y}E00003 literal 0 HcmV?d00001 diff --git a/doc/img/foot_bg.png b/doc/img/foot_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..b24f3a48b4e076b88cf1e802b46087ffbb3fabd5 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^j6iJ9!3HE1Qx}DR1d4;)ofy`glX(f`uqAoByD;nl zL8a%*b^=8>3p^r=85p>QL70(Y)*K0-AbW|YuPgga7CvEi%SZD}LFVdux;TbNTu#2R zIsJTn+kYTPulo1Ll3AD;2!xfDmD#wLvj}>v&@k-CVP=T_%(cphxuXQAk-^i|&t;uc GLK6Ub>Mc;VkfoEM{Qf z76xHPhFNnYfP(BLp1!W^H+WcuRXD>A=l%i;Wq7(chG?8W+i%Esz<`6f_y7NWHH!5o z63R1{^0cb0vMSkFaE_%x<+7O{*?Xs&Ff%xKT5jO&GtAqcqoa~}b>qg&H07ky z617wnX2HUfTT*8{kPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOZ8 z6%`IG`XD&~00I3;L_t(o!^Kz8j>8}fODg_+AF{uksuh}hxC0rJP`W$mkkHl^jB%XM z>f`Y+`Dtd>`gxwMmr`2KIk$cshdiH8N#}XW6$o}5WA?p%5)sKc%eHO9fR!#HlFUqU z&XQ6Z1~3DUmCHm#rjx?$lu|lm24QA+Eh4g2CJ}X<;QbGo;J_$CohD|s1Tu}z3KIrH zRtpQ8-Z|UyQP4caCB~&Z3QG0mMzcQLYIcQ!(sFW-xf9knF@|(?iY~XAOhHzd%#8OD2m>x}XSSsVf@;^obfpX48<@ZgFF$M4@xtjbM#B=Ojp!9yfFNKXj22K7STvD?00000NkvXXu0mjfB76)l literal 0 HcmV?d00001 diff --git a/doc/img/header_menu_current_background.png b/doc/img/header_menu_current_background.png new file mode 100644 index 0000000000000000000000000000000000000000..de97c9268e4ac68310c8b92d6ab6bd8a245611da GIT binary patch literal 1200 zcmV;h1W)^kP)002J-1^@s6XaB>y00001b5ch_0Itp) z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT3Fc5TrKj#zpOzs?z2=A0p@aw&u*pMn|G)ug+?Vh;0+zHe5dVLoF=!DBW&zJ?E zp99|S_j0@4%Io#|xG+sqdB5NP{rn5Cj%O+78UIn|DpKcsK9>`B2V6JsOB3oD&r;4a z{-e%Sw9ela{A&{jdu5*U-Tgr1~ zQc-ZvZCmQQrCnTG+COg8zWQ3Lv1sjNIKQPlR|Xsq_}(oT0MbDS91lPg3>xeBFKokrh3I+f~!N36>5GNtP zFb=56FWNP#oK=?bNEQ~!rtXW9X1GX=k~FL`OjOeF$R)LGB-wAZi(5xddgID^{pUl5 z$!2zVWE!iC5c%CAFL=jfl{TW1M$7Wjcr9z}SyPKmlC20r0B}G6Q80PClsJ^eQ9Rdb zuHflIJ5saoBdvW)J5o7`LrQ$sb0li*FS@pt^@$vLe#kJ^<1ntHwKLacEE8!V_VYY{ z^jk0Fa-9o0ILfhhgnDcg*`~%yk#UNgFLkMLQ1xp&>oHLMt>=krgKLZ9aoj&)eVyWk z+VI9yb#%knV zBgOT1iW^1q->g9a$6utrxv^4Ql-s5#yS%xS5>LwpJyaYZ1w-06F426k%%gi2p;YTL zn*Zi$*TU&q<}Gq{z5F%GPa}Dt9$}k233)sorIb>h&nNZ+VE-)`LNtIN1b_p=0f8Pk zSwvore6GJZg3A%uh&UU`%8A$z3ApNcvGym`^9BW5y;mkO7OVYaI#wj=im2;bzSt*w z8j*3rXhhk-tiA%ik?Y6P7yM_$}>d^Cl%aa8a!%A!J=gv)gqCjA%RAmjShY zk&jin$jBj)wrhPzE#o_v^@kLW;DnK~k;Uxogc+{8mlL+=3tKP%4hR4$2Lynj z95C}RO)LJ$<@I?2i7h|tH#%lcU&}tk-(ol0<1_foFDdWsgqjV%=7cRA;M<%KLNw4f z2!R6vT{&RuTk7799C>JZ4;!>{ThW;;ZOYD_SA%VO2ZR&C3B68;Eg1NXEf@e0gaF^` zfS#=57JO;X{zkCF($|*uL^8|wn48N;fkQ7R9I3CpA0+QRH%LYRy5b)R!`g7p731Il O0000002J-1^@s6XaB>y00001b5ch_0Itp) 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 +Vendor: %{?_vendorinfo:%{_vendorinfo}}%{!?_vendorinfo:The Enlightenment Project (http://www.enlightenment.org/)} +Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} +Requires: ecore, libudev, libmount, eject +BuildRequires: ecore-devel, libudev-devel, libmount-devel +URL: http://www.enlightenment.org/ +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +Eeze is a library for manipulating devices through udev with a simple +and fast api. It interfaces directly with libudev, avoiding such +middleman daemons as udisks/upower or hal, to immediately gather +device information the instant it becomes known to the system. This +can be used to determine such things as: + * If a cdrom has a disk inserted + * The temperature of a cpu core + * The remaining power left in a battery + * The current power consumption of various parts + * Monitor in realtime the status of peripheral devices + +Each of the above examples can be performed by using only a single +eeze function, as one of the primary focuses of the library is to +reduce the complexity of managing devices. + +%package devel +Summary: Development files for Eeze +Group: System Environment/Libraries +Requires: %{name} = %{version} +Requires: ecore-devel, libudev-devel + +%description devel +Headers, static libraries, test programs and documentation for Eeze + +%prep +%setup -q + +%build +%{configure} --prefix=%{_prefix} +%{__make} %{?_smp_mflags} %{?mflags} + +%install +%{__make} %{?mflags_install} DESTDIR=$RPM_BUILD_ROOT install + +%clean +test "x$RPM_BUILD_ROOT" != "x/" && rm -rf $RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%defattr(-, root, root) +%doc AUTHORS COPYING README +%{_libdir}/*.so.* +#%{_libdir}/enlightenment/utils/eeze_scanner +%{_bindir}/* + +%files devel +%defattr(-, root, root) +%{_includedir}/* +%{_libdir}/*.a +%{_libdir}/*.so +%{_libdir}/*.la +%{_libdir}/pkgconfig/* + +%changelog diff --git a/m4/ac_attribute.m4 b/m4/ac_attribute.m4 new file mode 100644 index 0000000..23479a9 --- /dev/null +++ b/m4/ac_attribute.m4 @@ -0,0 +1,47 @@ +dnl Copyright (C) 2004-2008 Kim Woelders +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. +dnl Originally snatched from somewhere... + +dnl Macro for checking if the compiler supports __attribute__ + +dnl Usage: AC_C___ATTRIBUTE__ +dnl call AC_DEFINE for HAVE___ATTRIBUTE__ and __UNUSED__ +dnl if the compiler supports __attribute__, HAVE___ATTRIBUTE__ is +dnl defined to 1 and __UNUSED__ is defined to __attribute__((unused)) +dnl otherwise, HAVE___ATTRIBUTE__ is not defined and __UNUSED__ is +dnl defined to nothing. + +AC_DEFUN([AC_C___ATTRIBUTE__], +[ + +AC_MSG_CHECKING([for __attribute__]) + +AC_CACHE_VAL([ac_cv___attribute__], + [AC_TRY_COMPILE( + [ +#include + +int func(int x); +int foo(int x __attribute__ ((unused))) +{ + exit(1); +} + ], + [], + [ac_cv___attribute__="yes"], + [ac_cv___attribute__="no"] + )]) + +AC_MSG_RESULT($ac_cv___attribute__) + +if test "x${ac_cv___attribute__}" = "xyes" ; then + AC_DEFINE([HAVE___ATTRIBUTE__], [1], [Define to 1 if your compiler has __attribute__]) + AC_DEFINE([__UNUSED__], [__attribute__((unused))], [Macro declaring a function argument to be unused]) + else + AC_DEFINE([__UNUSED__], [], [Macro declaring a function argument to be unused]) +fi + +]) + +dnl End of ac_attribute.m4 diff --git a/m4/efl_binary.m4 b/m4/efl_binary.m4 new file mode 100644 index 0000000..0ad38ce --- /dev/null +++ b/m4/efl_binary.m4 @@ -0,0 +1,78 @@ +dnl Copyright (C) 2010 Vincent Torri +dnl That code is public domain and can be freely used or copied. + +dnl Macro that checks if a binary is built or not + +dnl Usage: EFL_ENABLE_BIN(binary, dep[, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Call AC_SUBST(BINARY_PRG) (BINARY is the uppercase of binary, - being transformed into _) +dnl Define have_binary (- is transformed into _) +dnl Define conditional BUILD_BINARY (BINARY is the uppercase of binary, - being transformed into _) + +AC_DEFUN([EFL_ENABLE_BIN], +[ + +m4_pushdef([UP], m4_translit([[$1]], [-a-z], [_A-Z]))dnl +m4_pushdef([DOWN], m4_translit([[$1]], [-A-Z], [_a-z]))dnl + +dnl configure option + +AC_ARG_ENABLE([$1], + [AC_HELP_STRING([--disable-$1], [disable building of ]DOWN)], + [ + if test "x${enableval}" = "xyes" ; then + have_[]m4_defn([DOWN])="yes" + else + have_[]m4_defn([DOWN])="no" + fi + ], + [have_[]m4_defn([DOWN])=$2]) + +AC_MSG_CHECKING([whether to build ]DOWN[ binary]) +AC_MSG_RESULT([$have_[]m4_defn([DOWN])]) + +if test "x$have_[]m4_defn([DOWN])" = "xyes"; then + UP[]_PRG=DOWN[${EXEEXT}] +fi + +AC_SUBST(UP[]_PRG) + +AM_CONDITIONAL(BUILD_[]UP, test "x$have_[]m4_defn([DOWN])" = "xyes") + +AS_IF([test "x$have_[]m4_defn([DOWN])" = "xyes"], [$3], [$4]) + +]) + +dnl Macro that specifies the binary to be used + +dnl Usage: EFL_WITH_BIN(binary, package, msg) +dnl Call AC_SUBST(BINARY_PRG) (BINARY is the uppercase of binary, - being transformed into _) +dnl Define with_binary (- is transformed into _) +dnl Define conditional BUILD_BINARY (BINARY is the uppercase of binary, - being transformed into _) + +AC_DEFUN([EFL_WITH_BIN], +[ + +m4_pushdef([UP], m4_translit([[$1]], [-a-z], [_A-Z]))dnl +m4_pushdef([DOWN], m4_translit([[$1]], [-A-Z], [_a-z]))dnl + +AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +AC_MSG_NOTICE([$PKG_CONFIG]) + +with_[]m4_defn([DOWN])=m4_esyscmd($PKG_CONFIG --variable=prefix $2)/bin/m4_defn([DOWN]) + +dnl configure option + +AC_ARG_WITH([$1], + [AC_HELP_STRING([--with-$1-bin=PATH], [specify a specific path to ]DOWN)], + [ + with_[]m4_defn([DOWN])=$withval + _efl_msg="( explicitely set)" + ]) + +AC_MSG_NOTICE([$msg: ]m4_defn([DOWN])[$_efl_msg]) + +AC_SUBST(with_[]m4_defn([DOWN])) + +AS_IF([test "x$have_[]m4_defn([DOWN])" = "xyes"], [$4], [$5]) + +]) diff --git a/m4/efl_compiler_flag.m4 b/m4/efl_compiler_flag.m4 new file mode 100644 index 0000000..618c6a6 --- /dev/null +++ b/m4/efl_compiler_flag.m4 @@ -0,0 +1,57 @@ +dnl Copyright (C) 2010 Vincent Torri +dnl and Albin Tonnerre +dnl That code is public domain and can be freely used or copied. + +dnl Macro that checks if a compiler flag is supported by the compiler. + +dnl Usage: EFL_COMPILER_FLAG(flag) +dnl flag is added to CFLAGS if supported. + +AC_DEFUN([EFL_COMPILER_FLAG], +[ + +CFLAGS_save="${CFLAGS}" +CFLAGS="${CFLAGS} $1" + +AC_LANG_PUSH([C]) +AC_MSG_CHECKING([whether the compiler supports $1]) + +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[]])], + [have_flag="yes"], + [have_flag="no"]) +AC_MSG_RESULT([${have_flag}]) + +if test "x${have_flag}" = "xno" ; then + CFLAGS="${CFLAGS_save}" +fi +AC_LANG_POP([C]) + +]) + +dnl Macro that checks if a linker flag is supported by the compiler. + +dnl Usage: EFL_LINKER_FLAG(flag) +dnl flag is added to CFLAGS if supported (will be passed to ld anyway). + +AC_DEFUN([EFL_LINKER_FLAG], +[ + +CFLAGS_save="${CFLAGS}" +CFLAGS="${CFLAGS} $1" + +AC_LANG_PUSH([C]) +AC_MSG_CHECKING([whether the compiler supports $1]) + +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[]])], + [have_flag="yes"], + [have_flag="no"]) +AC_MSG_RESULT([${have_flag}]) + +if test "x${have_flag}" = "xno" ; then + CFLAGS="${CFLAGS_save}" +fi +AC_LANG_POP([C]) + +]) diff --git a/m4/efl_doxygen.m4 b/m4/efl_doxygen.m4 new file mode 100644 index 0000000..dd6bc3e --- /dev/null +++ b/m4/efl_doxygen.m4 @@ -0,0 +1,99 @@ +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. + +dnl Macro that check if doxygen is available or not. + +dnl EFL_CHECK_DOXYGEN([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for the doxygen program +dnl Defines efl_doxygen +dnl Defines the automake conditionnal EFL_BUILD_DOC +dnl +AC_DEFUN([EFL_CHECK_DOXYGEN], +[ + +dnl +dnl Disable the build of the documentation +dnl +AC_ARG_ENABLE([doc], + [AC_HELP_STRING( + [--disable-doc], + [Disable documentation build @<:@default=enabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + efl_enable_doc="yes" + else + efl_enable_doc="no" + fi + ], + [efl_enable_doc="yes"]) + +AC_MSG_CHECKING([whether to build documentation]) +AC_MSG_RESULT([${efl_enable_doc}]) + +if test "x${efl_enable_doc}" = "xyes" ; then + +dnl +dnl Specify the file name, without path +dnl + + efl_doxygen="doxygen" + + AC_ARG_WITH([doxygen], + [AC_HELP_STRING( + [--with-doxygen=FILE], + [doxygen program to use @<:@default=doxygen@:>@])], +dnl +dnl Check the given doxygen program. +dnl + [efl_doxygen=${withval} + AC_CHECK_PROG([efl_have_doxygen], + [${efl_doxygen}], + [yes], + [no]) + if test "x${efl_have_doxygen}" = "xno" ; then + echo "WARNING:" + echo "The doxygen program you specified:" + echo "${efl_doxygen}" + echo "was not found. Please check the path and make sure " + echo "the program exists and is executable." + AC_MSG_WARN([no doxygen detected. Documentation will not be built]) + fi + ], + [AC_CHECK_PROG([efl_have_doxygen], + [${efl_doxygen}], + [yes], + [no]) + if test "x${efl_have_doxygen}" = "xno" ; then + echo "WARNING:" + echo "The doxygen program was not found in your execute path." + echo "You may have doxygen installed somewhere not covered by your path." + echo "" + echo "If this is the case make sure you have the packages installed, AND" + echo "that the doxygen program is in your execute path (see your" + echo "shell manual page on setting the \$PATH environment variable), OR" + echo "alternatively, specify the program to use with --with-doxygen." + AC_MSG_WARN([no doxygen detected. Documentation will not be built]) + fi + ]) +fi + +dnl +dnl Substitution +dnl +AC_SUBST([efl_doxygen]) + +if ! test "x${efl_have_doxygen}" = "xyes" ; then + efl_enable_doc="no" +fi + +AM_CONDITIONAL(EFL_BUILD_DOC, test "x${efl_enable_doc}" = "xyes") + +if test "x${efl_enable_doc}" = "xyes" ; then + m4_default([$1], [:]) +else + m4_default([$2], [:]) +fi + +]) + +dnl End of doxygen.m4 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..97baf85 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,2 @@ +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = lib bin diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am new file mode 100644 index 0000000..280006d --- /dev/null +++ b/src/bin/Makefile.am @@ -0,0 +1,49 @@ +MAINTAINERCLEANFILES = Makefile.in + +EEZE_CFLAGS = \ +-I$(top_srcdir)/src/lib \ +@EEZE_CFLAGS@ + +noinst_PROGRAMS = @EEZE_UDEV_TEST_PRG@ +EXTRA_PROGRAMS = eeze_udev_test eeze_mount eeze_umount eeze_disk_ls eeze_scanner + +if HAVE_EEZE_MOUNT + DISK_PROGS = @EEZE_MOUNT_PRG@ @EEZE_UMOUNT_PRG@ @EEZE_DISK_LS_PRG@ + SCAN_PROGS = @EEZE_SCANNER_PRG@ +else + DISK_PROGS = + SCAN_PROGS = +endif + +bin_PROGRAMS = $(DISK_PROGS) +util_PROGRAMS = $(SCAN_PROGS) +utildir = $(bindir) + +eeze_udev_test_SOURCES = eeze_udev_test.c +eeze_udev_test_CPPFLAGS = -I$(top_srcdir)/src/lib @EEZE_CFLAGS@ +eeze_udev_test_LDADD = $(top_builddir)/src/lib/libeeze.la @EEZE_LIBS@ + +if HAVE_EEZE_MOUNT + eeze_mount_SOURCES = eeze_mount.c + eeze_mount_CFLAGS = -I$(top_srcdir)/src/lib $(EEZE_CFLAGS) @LIBMOUNT_CFLAGS@ @ECORE_FILE_CFLAGS@ + eeze_mount_LDADD = $(top_builddir)/src/lib/libeeze.la @LIBMOUNT_LIBS@ @ECORE_FILE_LIBS@ @EEZE_LIBS@ + + eeze_umount_SOURCES = eeze_umount.c + eeze_umount_CFLAGS = -I$(top_srcdir)/src/lib $(EEZE_CFLAGS) @LIBMOUNT_CFLAGS@ @ECORE_FILE_CFLAGS@ + eeze_umount_LDADD = $(top_builddir)/src/lib/libeeze.la @LIBMOUNT_LIBS@ @ECORE_FILE_LIBS@ @EEZE_LIBS@ + + eeze_disk_ls_SOURCES = eeze_disk_ls.c + eeze_disk_ls_CFLAGS = -I$(top_srcdir)/src/lib $(EEZE_CFLAGS) @LIBMOUNT_CFLAGS@ @ECORE_FILE_CFLAGS@ + eeze_disk_ls_LDADD = $(top_builddir)/src/lib/libeeze.la @LIBMOUNT_LIBS@ @ECORE_FILE_LIBS@ @EEZE_LIBS@ + + eeze_scanner_SOURCES = eeze_scanner.c + eeze_scanner_CFLAGS = -I$(top_srcdir)/src/lib $(EEZE_CFLAGS) @LIBMOUNT_CFLAGS@ @ECORE_FILE_CFLAGS@ @ECORE_CON_CFLAGS@ @EET_CFLAGS@ + eeze_scanner_LDADD = $(top_builddir)/src/lib/libeeze.la @LIBMOUNT_LIBS@ @ECORE_FILE_LIBS@ @ECORE_CON_LIBS@ @EET_LIBS@ @EEZE_LIBS@ + includesdir = $(includedir)/eeze-@VMAJ@ + includes_HEADERS = eeze_scanner.h + +setuid_root_mode = a=rx,u+xs +install-data-hook: + @chmod $(setuid_root_mode) $(DESTDIR)$(bindir)/eeze_scanner$(EXEEXT) || true + +endif diff --git a/src/bin/eeze_disk_ls.c b/src/bin/eeze_disk_ls.c new file mode 100644 index 0000000..46c4006 --- /dev/null +++ b/src/bin/eeze_disk_ls.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +/* simple app to print disks and their mount points */ + +int +main(void) +{ + Eina_List *disks; + const char *syspath; + + eeze_init(); + eeze_disk_function(); + + disks = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_MOUNTABLE, NULL); + printf("Found the following mountable disks:\n"); + EINA_LIST_FREE(disks, syspath) + { + Eeze_Disk *disk; + + disk = eeze_disk_new(syspath); + printf("\t%s - %s:%s\n", syspath, eeze_disk_devpath_get(disk), eeze_disk_mount_point_get(disk)); + eeze_disk_free(disk); + eina_stringshare_del(syspath); + } + + disks = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_REMOVABLE, NULL); + printf("Found the following removable drives:\n"); + EINA_LIST_FREE(disks, syspath) + { + Eeze_Disk *disk; + + disk = eeze_disk_new(syspath); + printf("\t%s - %s:%s\n", syspath, eeze_disk_devpath_get(disk), eeze_disk_mount_point_get(disk)); + eeze_disk_free(disk); + eina_stringshare_del(syspath); + } + + disks = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_INTERNAL, NULL); + printf("Found the following internal drives:\n"); + EINA_LIST_FREE(disks, syspath) + { + Eeze_Disk *disk; + + disk = eeze_disk_new(syspath); + printf("\t%s - %s\n", syspath, eeze_disk_devpath_get(disk)); + eeze_disk_free(disk); + eina_stringshare_del(syspath); + } + return 0; +} diff --git a/src/bin/eeze_mount.c b/src/bin/eeze_mount.c new file mode 100644 index 0000000..1f1c561 --- /dev/null +++ b/src/bin/eeze_mount.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include + +/** This app can be used as a "dumb" replacement for mount. Just don't try anything fancy yet! */ +static const Ecore_Getopt opts = +{ + "eeze_mount", + "eeze_mount /dev/sdb1 /media/disk", + "1.0", + "(C) 2010 Mike Blumenkrantz", + "LGPL", + "Mount a disk using either its /sys/ path or its /dev/ path\n\n", + 1, + { + ECORE_GETOPT_STORE_TRUE('v', "verbose", "Enable debug output"), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('R', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_SENTINEL + } +}; + +void +_mount_cb(void *data, int type, Eeze_Event_Disk_Mount *e) +{ + (void)data; + (void)type; + printf("Success!\n"); + eeze_disk_free(e->disk); + ecore_main_loop_quit(); +} + +void +_error_cb(void *data, int type, Eeze_Event_Disk_Error *de) +{ + (void)data; + (void)type; + printf("Could not mount disk with /dev/ path: %s!\n", eeze_disk_devpath_get(de->disk)); + eeze_disk_free(de->disk); + ecore_main_loop_quit(); +} + +int +main(int argc, char *argv[]) +{ + int args; + const char *dev, *mount_point = NULL; + Eina_Bool verbose = EINA_FALSE, exit_option = EINA_FALSE; + Eeze_Disk *disk; + + Ecore_Getopt_Value values[] = + { + ECORE_GETOPT_VALUE_BOOL(verbose), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option) + }; + + if (argc < 2) + { + printf("Insufficient args specified!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + + ecore_init(); + eeze_init(); + ecore_app_args_set(argc, (const char **)argv); + args = ecore_getopt_parse(&opts, values, argc, argv); + + if (exit_option) + return 0; + + if (args < 0) + { + printf("No args specified!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + if (verbose) eina_log_domain_level_set("eeze_disk", EINA_LOG_LEVEL_DBG); + dev = argv[args]; + if (args + 1 < argc) + mount_point = argv[args + 1]; + if ((!strncmp(dev, "/sys/", 5)) || (!strncmp(dev, "/dev/", 5))) + disk = eeze_disk_new(dev); + else if ((args == argc - 1) && (ecore_file_is_dir(dev))) + disk = eeze_disk_new_from_mount(dev); + else + { + printf("[Device] must be either a /dev/ path or a /sys/ path!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + if (eeze_disk_mounted_get(disk)) + { + printf("[%s] is already mounted!", dev); + exit(1); + } + if (argc - args > 1) + { + eeze_disk_mount_point_set(disk, mount_point); + if (eina_str_has_extension(dev, "iso")) + { + int f; + f = eeze_disk_mountopts_get(disk); + eeze_disk_mountopts_set(disk, f | EEZE_DISK_MOUNTOPT_LOOP); + } + } + ecore_event_handler_add(EEZE_EVENT_DISK_MOUNT, (Ecore_Event_Handler_Cb)_mount_cb, NULL); + ecore_event_handler_add(EEZE_EVENT_DISK_ERROR, (Ecore_Event_Handler_Cb)_error_cb, NULL); + eeze_disk_mountopts_get(disk); + if (!eeze_disk_mount(disk)) + { + const char *mp; + + mp = eeze_disk_mount_point_get(disk); + if (!mp) fprintf(stderr, "No mount point passed!\n"); + else fprintf(stderr, "Mount operation could not be started!\n"); + exit(1); + } + ecore_main_loop_begin(); + + return 0; +} diff --git a/src/bin/eeze_scanner.c b/src/bin/eeze_scanner.c new file mode 100644 index 0000000..2acd736 --- /dev/null +++ b/src/bin/eeze_scanner.c @@ -0,0 +1,453 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eeze_scanner.h" + +#define DBG(...) EINA_LOG_DOM_DBG(es_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(es_log_dom, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(es_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(es_log_dom, __VA_ARGS__) +#define CRI(...) EINA_LOG_DOM_CRIT(es_log_dom, __VA_ARGS__) + +static int es_log_dom = -1; +static Ecore_Con_Server *svr = NULL; +static Eet_Data_Descriptor *es_edd = NULL; +static Eina_Hash *clients = NULL; + +static Eina_List *storage_devices = NULL; +static Eina_List *storage_cdrom = NULL; + +static Eina_List *volume_cdrom = NULL; +static Eina_List *volume_devices = NULL; + +static void +event_send(const char *device, Eeze_Scanner_Event_Type type, Eina_Bool volume) +{ + Eeze_Scanner_Event ev; + const Eina_List *l; + Ecore_Con_Client *cl; + + ev.device = device; + ev.type = type; + ev.volume = volume; + EINA_LIST_FOREACH(ecore_con_server_clients_get(svr), l, cl) + { + Eet_Connection *ec; + + ec = eina_hash_find(clients, cl); + if (!ec) continue; + INF("Serializing event..."); + eet_connection_send(ec, es_edd, &ev, NULL); + } +} + +static Eina_Bool +event_write(const void *data, size_t size, Ecore_Con_Client *cl) +{ + INF("Event sent!"); + ecore_con_client_send(cl, data, size); + return EINA_TRUE; +} + +static Eina_Bool +disk_mount(void *data __UNUSED__, int type __UNUSED__, Eeze_Disk *disk) +{ + Eina_List *l; + Eeze_Scanner_Device *d; + if (eeze_disk_type_get(disk) != EEZE_DISK_TYPE_CDROM) return ECORE_CALLBACK_RENEW; + + EINA_LIST_FOREACH(storage_cdrom, l, d) + { + if (d->device == eeze_disk_syspath_get(disk)) + { + d->mounted = !d->mounted; + break; + } + } + return ECORE_CALLBACK_RENEW; +} + +static void +cl_setup(Ecore_Con_Client *cl __UNUSED__, Eet_Connection *ec) +{ + Eina_List *l; + Eeze_Scanner_Device *dev; + Eeze_Scanner_Event ev; + const char *sys; + + INF("Sending initial events to new client"); + EINA_LIST_FOREACH(storage_devices, l, sys) + { + ev.device = sys; + ev.type = EEZE_SCANNER_EVENT_TYPE_ADD; + ev.volume = EINA_FALSE; + eet_connection_send(ec, es_edd, &ev, NULL); + } + EINA_LIST_FOREACH(storage_cdrom, l, dev) + { + ev.device = dev->device; + ev.type = EEZE_SCANNER_EVENT_TYPE_ADD; + ev.volume = EINA_FALSE; + eet_connection_send(ec, es_edd, &ev, NULL); + } + EINA_LIST_FOREACH(volume_devices, l, sys) + { + ev.device = sys; + ev.type = EEZE_SCANNER_EVENT_TYPE_ADD; + ev.volume = EINA_TRUE; + eet_connection_send(ec, es_edd, &ev, NULL); + } + EINA_LIST_FOREACH(volume_cdrom, l, dev) + { + ev.device = dev->device; + ev.type = EEZE_SCANNER_EVENT_TYPE_ADD; + ev.volume = EINA_TRUE; + eet_connection_send(ec, es_edd, &ev, NULL); + } +} + +static Eina_Bool +es_read(const void *eet_data __UNUSED__, size_t size __UNUSED__, void *user_data __UNUSED__) +{ + return EINA_TRUE; +} + +static Eina_Bool +cl_add(void *data __UNUSED__, int type __UNUSED__, Ecore_Con_Event_Client_Add *ev) +{ + Eet_Connection *ec; + INF("Added client"); + + ec = eet_connection_new(es_read, (Eet_Write_Cb*)event_write, ev->client); + if (!ec) + { + ERR("Could not create eet serializer! Lost client!"); + ecore_con_client_del(ev->client); + return ECORE_CALLBACK_RENEW; + } + + eina_hash_direct_add(clients, ev->client, ec); + cl_setup(ev->client, ec); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +cl_del(void *data __UNUSED__, int type __UNUSED__, Ecore_Con_Event_Client_Del *ev) +{ + Eet_Connection *ec; + Eina_Bool d; + INF("Removed client"); + ec = eina_hash_find(clients, ev->client); + if (ec) + { + eet_connection_close(ec, &d); + eina_hash_del_by_data(clients, ec); + } + + return ECORE_CALLBACK_RENEW; +} + +static void +eet_setup(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof(eddc), "eeze_scanner_event", sizeof(Eeze_Scanner_Event))) + { + CRI("Could not create eet data descriptor!"); + exit(1); + } + + es_edd = eet_data_descriptor_stream_new(&eddc); +#define DAT(MEMBER, TYPE) EET_DATA_DESCRIPTOR_ADD_BASIC(es_edd, Eeze_Scanner_Event, #MEMBER, MEMBER, EET_T_##TYPE) + DAT(device, INLINED_STRING); + DAT(type, UINT); + DAT(volume, UCHAR); +#undef DAT +} + +static Eina_Bool +cdrom_timer(Eeze_Scanner_Device *dev) +{ + const char *devpath; + int fd; + + /* cdrom already mounted, no need to poll */ + if (dev->mounted) return EINA_TRUE; + devpath = eeze_udev_syspath_get_devpath(dev->device); + fd = open(devpath, O_RDONLY); + if (fd < 0) + { + Eina_List *l; + + l = eina_list_data_find_list(volume_cdrom, dev); + if (l) + { + /* disc removed, delete volume */ + INF("Removed cdrom '%s'", dev->device); + volume_cdrom = eina_list_remove_list(volume_cdrom, l); + event_send(dev->device, EEZE_SCANNER_EVENT_TYPE_CHANGE, EINA_TRUE); + } + /* just in case */ + dev->mounted = EINA_FALSE; + } + else + { + if (!eina_list_data_find(volume_cdrom, dev)) + { + INF("Added cdrom '%s'", dev->device); + volume_cdrom = eina_list_append(volume_cdrom, dev); + event_send(dev->device, EEZE_SCANNER_EVENT_TYPE_CHANGE, EINA_TRUE); + } + close(fd); + } + eina_stringshare_del(devpath); + return EINA_TRUE; +} + +static void +storage_setup(void) +{ + Eina_List *l, *ll; + const char *sys; + + storage_devices = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_INTERNAL, NULL); + if (!storage_devices) + { + ERR("No storage devices found! This is not supposed to happen!"); + exit(1); + } + EINA_LIST_FOREACH(storage_devices, l, sys) + event_send(sys, EEZE_SCANNER_EVENT_TYPE_ADD, EINA_FALSE); + + ll = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_REMOVABLE, NULL); + EINA_LIST_FREE(ll, sys) + { + event_send(sys, EEZE_SCANNER_EVENT_TYPE_ADD, EINA_FALSE); + storage_devices = eina_list_append(storage_devices, sys); + } + + l = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_CDROM, NULL); + EINA_LIST_FREE(l, sys) + { + Eeze_Scanner_Device *dev; + Eeze_Disk *disk; + + dev = calloc(1, sizeof(Eeze_Scanner_Device)); + if (!dev) + { + ERR("Lost cdrom device '%s'!", sys); + eina_stringshare_del(sys); + continue; + } + disk = eeze_disk_new(sys); + if (!disk) + { + ERR("Lost cdrom device '%s'!", sys); + eina_stringshare_del(sys); + free(dev); + continue; + } + dev->device = sys; + dev->mounted = eeze_disk_mounted_get(disk); + eeze_disk_free(disk); + event_send(sys, EEZE_SCANNER_EVENT_TYPE_ADD, EINA_FALSE); + dev->poller = ecore_poller_add(ECORE_POLLER_CORE, 32, (Ecore_Task_Cb)cdrom_timer, dev); + storage_cdrom = eina_list_append(storage_cdrom, dev); + } + volume_devices = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_MOUNTABLE, NULL); + EINA_LIST_FOREACH_SAFE(volume_devices, l, ll, sys) + { + Eina_List *c; + Eeze_Scanner_Device *dev; + + EINA_LIST_FOREACH(storage_cdrom, c, dev) + if (sys == dev->device) + { + eina_stringshare_del(sys); + volume_devices = eina_list_remove_list(volume_devices, l); + volume_cdrom = eina_list_append(volume_cdrom, dev); + event_send(sys, EEZE_SCANNER_EVENT_TYPE_ADD, EINA_TRUE); + l = NULL; + break; + } + if (!l) continue; + event_send(sys, EEZE_SCANNER_EVENT_TYPE_ADD, EINA_TRUE); + } +} + +static void +cb_vol_chg(const char *device, Eeze_Udev_Event ev, void *data __UNUSED__, Eeze_Udev_Watch *watch __UNUSED__) +{ + Eina_List *l; + Eeze_Scanner_Device *dev; + + DBG("device='%s'", device); + + if (ev == EEZE_UDEV_EVENT_ONLINE) ev = EEZE_SCANNER_EVENT_TYPE_ADD; + else if (ev == EEZE_UDEV_EVENT_OFFLINE) ev = EEZE_SCANNER_EVENT_TYPE_REMOVE; + + event_send(device, ev, EINA_TRUE); + switch (ev) + { + case EEZE_UDEV_EVENT_ADD: + case EEZE_UDEV_EVENT_ONLINE: + INF("Added volume '%s'", device); + EINA_LIST_FOREACH(storage_cdrom, l, dev) + if (device == dev->device) + { + volume_cdrom = eina_list_append(volume_cdrom, dev); + return; + } + volume_devices = eina_list_append(volume_devices, eina_stringshare_add(device)); + break; + case EEZE_UDEV_EVENT_REMOVE: + case EEZE_UDEV_EVENT_OFFLINE: + INF("Removed volume '%s'", device); + EINA_LIST_FOREACH(volume_cdrom, l, dev) + if (device == dev->device) + { + volume_cdrom = eina_list_remove_list(volume_cdrom, l); + return; + } + volume_devices = eina_list_remove(volume_devices, device); + eina_stringshare_del(device); + break; + default: + INF("Changed volume '%s'", device); + break; + } +} + +static void +cb_stor_chg(const char *device, Eeze_Udev_Event ev, void *data __UNUSED__, Eeze_Udev_Watch *watch __UNUSED__) +{ + Eina_List *l; + Eeze_Scanner_Device *dev = NULL; + const char *str; + + + DBG("device='%s'", device); + switch (ev) + { + case EEZE_UDEV_EVENT_ADD: + case EEZE_UDEV_EVENT_ONLINE: + INF("Added device '%s'", device); + event_send(device, ev, EINA_FALSE); + str = eeze_udev_syspath_get_property(device, "ID_CDROM"); + if (!str) + { + storage_devices = eina_list_append(storage_devices, eina_stringshare_add(device)); + return; + } + eina_stringshare_del(str); + dev = calloc(1, sizeof(Eeze_Scanner_Device)); + dev->device = eina_stringshare_add(device); + dev->poller = ecore_poller_add(ECORE_POLLER_CORE, 32, + (Ecore_Task_Cb)cdrom_timer, dev); + storage_cdrom = eina_list_append(storage_cdrom, dev); + break; + case EEZE_UDEV_EVENT_REMOVE: + case EEZE_UDEV_EVENT_OFFLINE: + if (!eina_list_data_find(storage_devices, device)) + { + EINA_LIST_FOREACH(storage_cdrom, l, dev) + if (dev->device == device) break; + if ((!dev) || (dev->device != device)) return; + } + INF("Removed device '%s'", device); + event_send(device, ev, EINA_FALSE); + EINA_LIST_FOREACH(storage_cdrom, l, dev) + if (device == dev->device) + { + if (dev->poller) ecore_poller_del(dev->poller); + storage_cdrom = eina_list_remove_list(storage_cdrom, l); + eina_stringshare_del(dev->device); + free(dev); + return; + } + storage_devices = eina_list_remove(storage_devices, device); + eina_stringshare_del(device); + break; + default: + INF("Changed device '%s'", device); + break; + } +} + +static void +es_exit(int sig) +{ + ecore_con_server_del(svr); + exit(sig); +} + +static void +sigs_setup(void) +{ + sigset_t sigs = {{0}}; + struct sigaction s; + + sigfillset(&sigs); + sigdelset(&sigs, SIGSEGV); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGQUIT); + + s.sa_handler = es_exit; + s.sa_flags = 0; + sigaction(SIGTERM, &s, NULL); + sigaction(SIGSEGV, &s, NULL); + sigaction(SIGINT, &s, NULL); +} + +int +main(void) +{ + eina_init(); + ecore_init(); + ecore_con_init(); + eeze_init(); + eeze_disk_function(); + eeze_mount_tabs_watch(); + + sigs_setup(); + es_log_dom = eina_log_domain_register("eeze_scanner", EINA_COLOR_CYAN); + + eet_setup(); + clients = eina_hash_pointer_new(NULL); + EINA_SAFETY_ON_NULL_GOTO(clients, error); + + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb)cl_add, NULL); + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DEL, (Ecore_Event_Handler_Cb)cl_del, NULL); + ecore_event_handler_add(EEZE_EVENT_DISK_UNMOUNT, (Ecore_Event_Handler_Cb)disk_mount, NULL); + ecore_event_handler_add(EEZE_EVENT_DISK_MOUNT, (Ecore_Event_Handler_Cb)disk_mount, NULL); + + eeze_udev_watch_add(EEZE_UDEV_TYPE_DRIVE_INTERNAL, EEZE_UDEV_EVENT_NONE, cb_stor_chg, NULL); + eeze_udev_watch_add(EEZE_UDEV_TYPE_DRIVE_REMOVABLE, EEZE_UDEV_EVENT_NONE, cb_stor_chg, NULL); + eeze_udev_watch_add(EEZE_UDEV_TYPE_DRIVE_CDROM, EEZE_UDEV_EVENT_NONE, cb_stor_chg, NULL); + eeze_udev_watch_add(EEZE_UDEV_TYPE_DRIVE_MOUNTABLE, EEZE_UDEV_EVENT_NONE, cb_vol_chg, NULL); + + svr = ecore_con_server_add(ECORE_CON_LOCAL_SYSTEM, "eeze_scanner", 0, NULL); + EINA_SAFETY_ON_NULL_GOTO(svr, error); + + storage_setup(); + ecore_main_loop_begin(); + + ecore_con_server_del(svr); + return 0; +error: + ERR("Could not start up!"); + exit(1); +} diff --git a/src/bin/eeze_scanner.h b/src/bin/eeze_scanner.h new file mode 100644 index 0000000..a975793 --- /dev/null +++ b/src/bin/eeze_scanner.h @@ -0,0 +1,33 @@ +#ifndef EEZE_SCANNER_H +#define EEZE_SCANNER_H + +#include + +#define EEZE_SCANNER_EDD_SETUP(edd) \ + EET_DATA_DESCRIPTOR_ADD_BASIC((edd), Eeze_Scanner_Event, "device", device, EET_T_INLINED_STRING); \ + EET_DATA_DESCRIPTOR_ADD_BASIC((edd), Eeze_Scanner_Event, "type", type, EET_T_UINT); \ + EET_DATA_DESCRIPTOR_ADD_BASIC((edd), Eeze_Scanner_Event, "volume", volume, EET_T_UCHAR) + +typedef enum +{ + EEZE_SCANNER_EVENT_TYPE_NONE, + EEZE_SCANNER_EVENT_TYPE_ADD = EEZE_UDEV_EVENT_ADD, + EEZE_SCANNER_EVENT_TYPE_REMOVE = EEZE_UDEV_EVENT_REMOVE, + EEZE_SCANNER_EVENT_TYPE_CHANGE = EEZE_UDEV_EVENT_CHANGE +} Eeze_Scanner_Event_Type; + +typedef struct +{ + const char *device; + Eeze_Scanner_Event_Type type; + Eina_Bool volume; +} Eeze_Scanner_Event; + +typedef struct +{ + Ecore_Poller *poller; + const char *device; + Eina_Bool mounted; +} Eeze_Scanner_Device; + +#endif diff --git a/src/bin/eeze_udev_test.c b/src/bin/eeze_udev_test.c new file mode 100644 index 0000000..130771a --- /dev/null +++ b/src/bin/eeze_udev_test.c @@ -0,0 +1,238 @@ +#include +#include +#include + +/** + * This demo program shows how to use some eeze_udev functions. It roughly + * 1kb as of now, TODO is to fix this but I'm too lazy now and it's only + * a demo. + */ + +typedef struct kbdmouse +{ + Eina_List *kbds; + Eina_List *mice; + Eina_Hash *hash; +} kbdmouse; + +static void +/* event will always be a syspath starting with /sys */ +catch_events(const char *device, + Eeze_Udev_Event event, + void *data, + Eeze_Udev_Watch *watch) +{ + kbdmouse *akbdmouse = data; + Eina_List *l; + const char *name, *dev, *type; + + /* the device that comes through will be prefixed by "/sys" + * but the saved name will not, so we check for the saved name + * inside the device name + */ + EINA_LIST_FOREACH(akbdmouse->kbds, l, name) + if (!strncmp(device + 5, name, strlen(device + 5) - 8)) goto end; + EINA_LIST_FOREACH(akbdmouse->mice, l, name) + if (!strncmp(device + 5, name, strlen(device + 5) - 8)) goto end; + + /* check to see if the device was just plugged in */ + if (eeze_udev_syspath_is_kbd(device) || eeze_udev_syspath_is_mouse(device)) + goto end; + /* if we reach here, the device is neither a keyboard nor a mouse that we saw + * previously, so we print a moderately amusing message and bail + */ + printf("Sneaky sneaky! But %s is not a keyboard or a mouse!!\n", device); + return; + +end: + /* we stored the devpaths for all the syspaths previously so that + * we can retrieve them now even though the device has been removed and + * is inaccessible to udev + */ + if ((event & EEZE_UDEV_EVENT_ADD) == EEZE_UDEV_EVENT_ADD) + { + dev = eeze_udev_syspath_get_devpath(device); + type = "plugged in"; + } + else + { + dev = eina_hash_find(akbdmouse->hash, name); + type = "unplugged"; + } + printf("You %s %s!\n", type, dev); + printf("All tests completed, exiting successfully!\n"); + /* and the hash */ + eina_hash_free(akbdmouse->hash); + /* now we free the lists */ + eina_list_free(akbdmouse->kbds); + eina_list_free(akbdmouse->mice); + /* and the random storage struct */ + free(akbdmouse); + /* and delete the watch */ + eeze_udev_watch_del(watch); + /* and shut down eudev */ + eeze_shutdown(); + /* and quit the main loop */ + ecore_main_loop_quit(); +} + +static void +hash_free(void *data) +{ + eina_stringshare_del(data); +} + +int +main() +{ + Eina_List *type, *l; + const char *name, *check, *check2; + kbdmouse *akbdmouse; + Eina_Hash *hash; + + ecore_init(); + eeze_init(); + + hash = eina_hash_stringshared_new(hash_free); + akbdmouse = malloc(sizeof(kbdmouse)); + akbdmouse->hash = hash; + + printf("For my first trick, I will find all of your keyboards and return their syspaths.\n"); + /* find all keyboards using type EEZE_UDEV_TYPE_KEYBOARD */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_KEYBOARD, NULL); + /* add all "link" devices that aren't explicitly found, but are still + * part of the device chain + */ + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FOREACH(type, l, name) + { + /* add the devpath to the hash for use in the cb later */ + if ((check = eeze_udev_syspath_get_devpath(name))) + eina_hash_direct_add(hash, name, check); + printf("Found keyboard: %s\n", name); + } + /* we save this list for later, because once a device is unplugged it can + * no longer be detected by udev, and any related properties are unusable unless + * they have been previously stored + */ + akbdmouse->kbds = type; + + printf("\nNext, I will find all of your mice and print the corresponding manufacturer.\n"); + /* find all mice using type EEZE_UDEV_TYPE_MOUSE */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_MOUSE, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FOREACH(type, l, name) + { /* add the devpath to the hash for use in the cb later */ + if ((check = eeze_udev_syspath_get_devpath(name))) + eina_hash_direct_add(hash, name, check); /* get a property using the device's syspath */ + printf("Found mouse %s with vendor: %s\n", name, eeze_udev_walk_get_sysattr(name, "manufacturer")); + } + /* we save this list for later, because once a device is unplugged it can + * no longer be detected by udev, and any related properties are unusable unless + * they have been previously stored + */ + akbdmouse->mice = type; + + printf("\nNow let's try something a little more difficult. Mountable filesystems!\n"); + /* find all mountable drives using type EEZE_UDEV_TYPE_DRIVE_MOUNTABLE */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_MOUNTABLE, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FREE(type, name) + { + printf("Found device: %s\n", name); /* get a property using the device's syspath */ + if ((check = eeze_udev_syspath_get_property(name, "DEVNAME"))) + { + printf("\tYou probably know it better as %s\n", check); + eina_stringshare_del(check); + } + if ((check = eeze_udev_syspath_get_property(name, "ID_FS_TYPE"))) + { + printf("\tIt's formatted as %s", check); + eina_stringshare_del(check); + check = eeze_udev_syspath_get_property(name, "FSTAB_DIR"); + if (check) + { + printf(", and gets mounted at %s", check); + eina_stringshare_del(check); + } + printf("!\n"); + } + eina_stringshare_del(name); + } + + printf("\nNetwork devices!\n"); + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_NET, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FREE(type, name) + { + printf("Found device: %s\n", name); /* get a property using the device's syspath */ + if ((check = eeze_udev_syspath_get_property(name, "INTERFACE"))) + { + printf("\tYou probably know it better as %s\n", check); + eina_stringshare_del(check); + } + eina_stringshare_del(name); + } + + printf("\nInternal drives, anyone? With serial numbers?\n"); + /* find all internal drives using type EEZE_UDEV_TYPE_DRIVE_INTERNAL */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_INTERNAL, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FREE(type, name) /* get a property using the device's syspath */ + { + if ((check = eeze_udev_syspath_get_property(name, "ID_SERIAL"))) + { + printf("%s: %s\n", name, check); + eina_stringshare_del(check); + } + eina_stringshare_del(name); + } + + printf("\nGot any removables? I'm gonna find em!\n"); + /* find all removable media using type EEZE_UDEV_TYPE_DRIVE_REMOVABLE */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_DRIVE_REMOVABLE, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FREE(type, name) /* get a property using the device's syspath */ + { + if ((check = eeze_udev_syspath_get_sysattr(name, "model"))) + { + check2 = eeze_udev_syspath_get_subsystem(name); + printf("\tOoh, a %s attached to the %s subsytem!\n", check, check2); + eina_stringshare_del(check); + eina_stringshare_del(check2); + } + eina_stringshare_del(name); + } + + printf("\nGot any v4l device ?\n"); + /* find all V4L device, may be a webcam or anything that can get a video + * stream from the real worl in a numerical form */ + type = eeze_udev_find_by_type(EEZE_UDEV_TYPE_V4L, NULL); + type = eeze_udev_find_unlisted_similar(type); + EINA_LIST_FREE(type, name) /* get a device name using the device's syspath */ + { + if ((check = eeze_udev_syspath_get_property(name, "DEVNAME"))) + { + if ((check2 = eeze_udev_syspath_get_sysattr(name, "name"))) + { + printf("%s: '%s' [%s]\n", name, check2, check); + eina_stringshare_del(check2); + } + eina_stringshare_del(check); + } + eina_stringshare_del(name); + } + + /* set a udev watch, grab all events because no EEZE_UDEV_TYPE filter is specified, + * set the events to be sent to callback function catch_events(), and attach + * kbdmouse to the watch as associated data + */ + eeze_udev_watch_add(EEZE_UDEV_TYPE_NONE, (EEZE_UDEV_EVENT_ADD | EEZE_UDEV_EVENT_REMOVE), catch_events, akbdmouse); + printf("\nAnd now for something more complicated. Plug or unplug your keyboard or mouse for me.\n"); + + /* main loop must be started to use ecore fd polling */ + ecore_main_loop_begin(); + + return 0; +} + diff --git a/src/bin/eeze_umount.c b/src/bin/eeze_umount.c new file mode 100644 index 0000000..75d5ebb --- /dev/null +++ b/src/bin/eeze_umount.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include + +/** This app can be used as a "dumb" replacement for unmount. Just don't try anything fancy yet! */ +static const Ecore_Getopt opts = +{ + "eeze_unmount", + "eeze_unmount /dev/sdb1 /media/disk", + "1.0", + "(C) 2010 Mike Blumenkrantz", + "LGPL", + "unmount a disk using either its /sys/ path or its /dev/ path\n\n", + 1, + { + ECORE_GETOPT_STORE_TRUE('v', "verbose", "Enable debug output"), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('R', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_SENTINEL + } +}; + +void +_unmount_cb(void *data, int type, Eeze_Event_Disk_Unmount *e) +{ + (void)data; + (void)type; + printf("Success!\n"); + eeze_disk_free(e->disk); + ecore_main_loop_quit(); +} + +void +_error_cb(void *data, int type, Eeze_Event_Disk_Error *de) +{ + (void)data; + (void)type; + printf("Could not unmount disk with /dev/ path: %s!\n", eeze_disk_devpath_get(de->disk)); + eeze_disk_free(de->disk); + ecore_main_loop_quit(); +} + +int +main(int argc, char *argv[]) +{ + int args; + const char *dev; + Eina_Bool verbose = EINA_FALSE, exit_option = EINA_FALSE; + Eeze_Disk *disk; + + Ecore_Getopt_Value values[] = + { + ECORE_GETOPT_VALUE_BOOL(verbose), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option), + ECORE_GETOPT_VALUE_BOOL(exit_option) + }; + + if (argc < 2) + { + printf("Insufficient args specified!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + + ecore_init(); + eeze_init(); + ecore_app_args_set(argc, (const char **)argv); + args = ecore_getopt_parse(&opts, values, argc, argv); + + if (exit_option) + return 0; + + if (args < 0) + { + printf("No args specified!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + if (verbose) eina_log_domain_level_set("eeze_disk", EINA_LOG_LEVEL_DBG); + dev = argv[args]; + if ((!strncmp(dev, "/sys/", 5)) || (!strncmp(dev, "/dev/", 5))) + disk = eeze_disk_new(dev); + else if ((args == argc - 1) && (ecore_file_is_dir(dev))) + disk = eeze_disk_new_from_mount(dev); + else + { + printf("[Device] must be either a /dev/ path or a /sys/ path!\n"); + ecore_getopt_help(stderr, &opts); + exit(1); + } + if (!eeze_disk_mounted_get(disk)) + { + printf("[%s] is already unmounted!", dev); + exit(1); + } + ecore_event_handler_add(EEZE_EVENT_DISK_UNMOUNT, (Ecore_Event_Handler_Cb)_unmount_cb, NULL); + ecore_event_handler_add(EEZE_EVENT_DISK_ERROR, (Ecore_Event_Handler_Cb)_error_cb, NULL); + if (!eeze_disk_unmount(disk)) + { + printf("unmount operation could not be started!\n"); + exit(1); + } + ecore_main_loop_begin(); + + return 0; +} diff --git a/src/lib/Eeze.h b/src/lib/Eeze.h new file mode 100644 index 0000000..7c8a911 --- /dev/null +++ b/src/lib/Eeze.h @@ -0,0 +1,560 @@ +/** + @brief Eeze Device Library + * + @mainpage Eeze + @image html eeze.png + @version 1.7.0 + @author Mike Blumenkrantz (zmike/discomfitor) + @date 2010-2012 + + @section intro What is Eeze? + + Eeze is a library for manipulating devices through udev with a simple and fast + api. It interfaces directly with libudev, avoiding such middleman daemons as + udisks/upower or hal, to immediately gather device information the instant it + becomes known to the system. This can be used to determine such things as: + @li If a cdrom has a disk inserted + @li The temperature of a cpu core + @li The remaining power left in a battery + @li The current power consumption of various parts + @li Monitor in realtime the status of peripheral devices + + Each of the above examples can be performed by using only a single eeze + function, as one of the primary focuses of the library is to reduce the + complexity of managing devices. + + @li @link Eeze.h Eeze functions @endlink + @li @ref udev UDEV functions + @li @ref watch Functions that watch for events + @li @ref syspath Functions that accept a device /sys/ path + @li @ref find Functions which find types of devices + @li @ref disk Disk functions + @li @ref net Net functions + @verbatim + Pants + @endverbatim + */ +#ifndef EEZE_UDEV_H +#define EEZE_UDEV_H + +#include + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +#else +# define EAPI +#endif + +/** + * @file Eeze.h + * @brief Easy device manipulation. + * + * Eeze is a library for manipulating devices through udev with a simple and fast + * api. It interfaces directly with libudev, avoiding such middleman daemons as + * udisks/upower or hal, to immediately gather device information the instant it + * becomes known to the system. This can be used to determine such things as: + * @li If a cdrom has a disk inserted + * @li The temperature of a cpu core + * @li The remaining power left in a battery + * @li The current power consumption of various parts + * @li Monitor in realtime the status of peripheral devices + * Each of the above examples can be performed by using only a single eeze + * function, as one of the primary focuses of the library is to reduce the + * complexity of managing devices. + * + * + * For udev functions, see @ref udev. + */ + +/** + * @defgroup main main + * + * These are general eeze functions which include init and shutdown. + */ + +/** + * @defgroup udev udev + * + * These are functions which interact directly with udev. + */ + +/** + * @addtogroup udev + * + * These are the device subsystems of udev: + * @li ac97 + * @li acpi + * @li bdi + * @li block + * @li bsg + * @li dmi + * @li graphics + * @li hid + * @li hwmon + * @li i2c + * @li input + * @li mem + * @li misc + * @li net + * @li pci + * @li pci_bus + * @li pci_express + * @li platform + * @li pnp + * @li rtc + * @li scsi + * @li scsi_device + * @li scsi_disk + * @li scsi_generic + * @li scsi_host + * @li serio + * @li sound + * @li thermal + * @li tty + * @li usb + * @li usb_device + * @li vc + * @li vtconsole + * + * These are the devtypes of udev. + * @li atapi + * @li audio + * @li block + * @li cd + * @li char + * @li disk + * @li floppy + * @li generic + * @li hid + * @li hub + * @li media + * @li optical + * @li printer + * @li rbc + * @li scsi + * @li storage + * @li tape + * @li video + */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup udev + * @typedef Eeze_Udev_Event + * @enum Eeze_Udev_Event + * @brief Flags for watch events + * + * These events are used to specify the events to watch in a + * #Eeze_Udev_Watch. They can be ORed together. + *@{ + */ +typedef enum +{ + /** - No event specified */ + EEZE_UDEV_EVENT_NONE = 0xf0, + /** - Device added */ + EEZE_UDEV_EVENT_ADD = (1 << 1), + /** - Device removed */ + EEZE_UDEV_EVENT_REMOVE = (1 << 2), + /** - Device changed */ + EEZE_UDEV_EVENT_CHANGE = (1 << 3), + /** - Device has come online */ + EEZE_UDEV_EVENT_ONLINE = (1 << 4), + /** - Device has gone offline */ + EEZE_UDEV_EVENT_OFFLINE = (1 << 5) +} Eeze_Udev_Event; +/** @} */ + +/** + * @addtogroup udev udev + * @typedef Eeze_Udev_Type Eeze_Udev_Type + * @enum Eeze_Udev_Type + * @brief Convenience types to simplify udev access. + * + * These types allow easy access to certain udev device types. They + * may only be used in specified functions. + * + * @{ + */ +/*FIXME: these probably need to be bitmasks with categories*/ +typedef enum +{ + /** - No type */ + EEZE_UDEV_TYPE_NONE, + /** - Keyboard device */ + EEZE_UDEV_TYPE_KEYBOARD, + /** - Mouse device */ + EEZE_UDEV_TYPE_MOUSE, + /** - Touchpad device */ + EEZE_UDEV_TYPE_TOUCHPAD, + /** - Mountable drive */ + EEZE_UDEV_TYPE_DRIVE_MOUNTABLE, + /** - Internal drive */ + EEZE_UDEV_TYPE_DRIVE_INTERNAL, + /** - Removable drive */ + EEZE_UDEV_TYPE_DRIVE_REMOVABLE, + /** - cd drive */ + EEZE_UDEV_TYPE_DRIVE_CDROM, + /** - AC adapter */ + EEZE_UDEV_TYPE_POWER_AC, + /** - Battery */ + EEZE_UDEV_TYPE_POWER_BAT, + /** - Temperature sensor */ + EEZE_UDEV_TYPE_IS_IT_HOT_OR_IS_IT_COLD_SENSOR, + /** - Network devices */ + EEZE_UDEV_TYPE_NET, + /** - WebCam */ + EEZE_UDEV_TYPE_V4L, + /** - Bluetooth */ + EEZE_UDEV_TYPE_BLUETOOTH, + /** - Joystick + * @since 1.7 + */ + EEZE_UDEV_TYPE_JOYSTICK +} Eeze_Udev_Type; +/**@}*/ + +struct Eeze_Udev_Watch; + +/** + * @addtogroup watch + * @typedef Eeze_Udev_Watch Eeze_Udev_Watch + * @brief Opaque structure to hold data for a udev watch + */ +typedef struct Eeze_Udev_Watch Eeze_Udev_Watch; + +#define EEZE_VERSION_MAJOR 1 +#define EEZE_VERSION_MINOR 7 + + typedef struct _Eeze_Version + { + int major; + int minor; + int micro; + int revision; + } Eeze_Version; + + EAPI extern Eeze_Version *eeze_version; + +/** + * @addtogroup watch + * @typedef Eeze_Udev_Watch_Cb Eeze_Udev_Watch_Cb + * @brief Callback type for use with #Eeze_Udev_Watch + */ +typedef void(*Eeze_Udev_Watch_Cb)(const char *, Eeze_Udev_Event, void *, Eeze_Udev_Watch *); + + +/** + * Initialize the eeze library. + * @return The number of times the function has been called, or -1 on failure. + * + * This function should be called prior to using any eeze functions, and MUST + * be called prior to using any udev functions to avoid a segv. + * + * @ingroup main + */ +EAPI int eeze_init(void); + +/** + * Shut down the eeze library. + * @return The number of times the eeze_init has been called, or -1 when + * all occurrences of eeze have been shut down. + * + * This function should be called when no further eeze functions will be called. + * + * @ingroup main + */ +EAPI int eeze_shutdown(void); + + /** + * @addtogroup find Find + * + * These are functions which find/supplement lists of devices. + * + * @ingroup udev + * + * @{ + */ + +/** + * Returns a stringshared list of all syspaths that are (or should be) the same + * device as the device pointed at by @p syspath. + * + * @param syspath The syspath of the device to find matches for + * @return All devices which are the same as the one passed + */ +EAPI Eina_List *eeze_udev_find_similar_from_syspath(const char *syspath); + +/** + * Updates a list of all syspaths that are (or should be) the same + * device. + * + * @param list The list of devices to update + * @return The updated list + * + * This function will update @p list to include all devices matching + * devices with syspaths currently stored in @p list. All strings are + * stringshared. + * + * @note This is an expensive call, do not use it unless you must. + */ +EAPI Eina_List *eeze_udev_find_unlisted_similar(Eina_List *list); + +/** + * Find a list of devices by a sysattr (and, optionally, a value of that sysattr). + * + * @param sysattr The attribute to find + * @param value Optional: the value that the attribute should have + * + * @return A stringshared list of the devices found with the attribute + * + * @ingroup find + */ +EAPI Eina_List *eeze_udev_find_by_sysattr(const char *sysattr, const char *value); + +/** + * Find devices using an #Eeze_Udev_Type and/or a name. + * + * @param type An #Eeze_Udev_Type or 0 + * @param name A filter for the device name or @c NULL + * @return A stringshared Eina_List of matched devices or @c NULL on failure + * + * Return a list of syspaths (/sys/$syspath) for matching udev devices. + */ +EAPI Eina_List *eeze_udev_find_by_type(Eeze_Udev_Type type, const char *name); + +/** + * A more advanced find, allows finds using udev properties. + * + * @param subsystem The udev subsystem to filter by, or @c NULL + * @param type "ID_INPUT_KEY", "ID_INPUT_MOUSE", "ID_INPUT_TOUCHPAD", @c NULL, etc + * @param name A filter for the device name, or @c NULL + * @return A stringshared Eina_List* of matched devices or @c NULL on failure + * + * Return a list of syspaths (/sys/$syspath) for matching udev devices. + * Requires at least one filter. + */ +EAPI Eina_List *eeze_udev_find_by_filter(const char *subsystem, const char *type, const char *name); + /** + * @} + */ + + /** + * @addtogroup syspath Syspath + * + * These are functions which interact with the syspath (/sys/$PATH) of + * a device. + * + * @ingroup udev + * + * @{ + */ + +/** + * Get the syspath of a device from the /dev/ path. + * + * @param devpath The /dev/ path of the device + * @return A stringshared char* which corresponds to the /sys/ path of the device or @c NULL on failure + * + * Takes "/dev/path" and returns the corresponding /sys/ path (without the "/sys/") + */ +EAPI const char *eeze_udev_devpath_get_syspath(const char *devpath); + +/** + * Find the root device of a device from its syspath. + * + * @param syspath The syspath of a device, with or without "/sys/" + * @return The syspath of the parent device + * + * Return a stringshared syspath (/sys/$syspath) for the parent device. + */ +EAPI const char *eeze_udev_syspath_get_parent(const char *syspath); + +/** + * Returns a list of all parent device syspaths for @p syspath. + * + * @param syspath The device to find parents of + * @return A stringshared list of the parent devices of @p syspath + */ +EAPI Eina_List *eeze_udev_syspath_get_parents(const char *syspath); + +/** + * Get the /dev/ path from the /sys/ path. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return A stringshared char* with the /dev/ path or @c NULL on failure + * + * Takes /sys/$PATH and turns it into the corresponding "/dev/x/y". + */ +EAPI const char *eeze_udev_syspath_get_devpath(const char *syspath); + +/** + * Get the /dev/ name from the /sys/ path. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return A stringshared char* of the device name without the /dev/ path, or @c NULL on failure + * + * Takes /sys/$PATH and turns it into the corresponding /dev/x/"y". + */ +EAPI const char *eeze_udev_syspath_get_devname(const char *syspath); + +/** + * Get the subsystem of a device from the /sys/ path. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return A stringshared char* with the subsystem of the device or @c NULL on failure + * + * Takes /sys/$PATH and returns the corresponding device subsystem, + * such as "input" for keyboards/mice. + */ +EAPI const char *eeze_udev_syspath_get_subsystem(const char *syspath); + +/** + * Get the property value of a device from the /sys/ path. + * + * @param syspath The /sys/ path with or without the /sys/ + * @param property The property to get; full list of these is a FIXME + * @return A stringshared char* with the property or @c NULL on failure + */ +EAPI const char *eeze_udev_syspath_get_property(const char *syspath, const char *property); + +/** + * Get the sysattr value of a device from the /sys/ path. + * + * @param syspath The /sys/ path with or without the /sys/ + * @param sysattr The sysattr to get; full list of these is a FIXME + * @return A stringshared char* with the sysattr or @c NULL on failure + */ +EAPI const char *eeze_udev_syspath_get_sysattr(const char *syspath, const char *sysattr); + +/** + * Checks whether the device is a mouse. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return If true, the device is a mouse + */ +EAPI Eina_Bool eeze_udev_syspath_is_mouse(const char *syspath); + +/** + * Checks whether the device is a keyboard. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return If true, the device is a keyboard + */ +EAPI Eina_Bool eeze_udev_syspath_is_kbd(const char *syspath); + +/** + * Checks whether the device is a touchpad. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return If true, the device is a touchpad + */ +EAPI Eina_Bool eeze_udev_syspath_is_touchpad(const char *syspath); + +/** + * Checks whether the device is a joystick. + * + * @param syspath The /sys/ path with or without the /sys/ + * @return If true, the device is a joystick + * @since 1.7 + */ +EAPI Eina_Bool eeze_udev_syspath_is_joystick(const char *syspath); + /** + * @} + */ + + /** + * @addtogroup walks Walks + * + * These are functions which walk up the device chain. + * + * @ingroup udev + * + * @{ + */ + +/** + * Walks up the device chain starting at @p syspath, + * checking each device for @p sysattr with (optional) @p value. + * + * @param syspath The /sys/ path of the device to start at, with or without the /sys/ + * @param sysattr The attribute to find + * @param value OPTIONAL: The value that @p sysattr should have, or @c NULL + * + * @return If the sysattr (with value) is found, returns TRUE. Else, false. + */ +EAPI Eina_Bool eeze_udev_walk_check_sysattr(const char *syspath, const char *sysattr, const char *value); + +/** + * Walks up the device chain starting at @p syspath, + * checking each device for @p sysattr, and returns the value if found. + * + * @param syspath The /sys/ path of the device to start at, with or without the /sys/ + * @param sysattr The attribute to find + * + * @return The stringshared value of @p sysattr if found, or @c NULL + */ +EAPI const char *eeze_udev_walk_get_sysattr(const char *syspath, const char *sysattr); + /** + * @} + */ + + /** + * @addtogroup watch Watch + * + * @brief These are functions which monitor udev for events. + * + * Eeze watches are simple: you specify a type of device to watch (or all devices), some events (or all) to watch for, a callback, + * and some data, and then udev watches those device types for events of the type you specified. Your callback is called with a + * syspath of the triggering device and the event that happened to the device, along with the data you associated with the watch and + * the watch object itself in case you want to stop the watch easily in a callback. + * + * @ingroup udev + * + * @{ + */ + +/** + * Add a watch for a device type + * + * @param type The #Eeze_Udev_Type to watch + * @param event The events to watch; an OR list of #Eeze_Udev_Event (ie (#EEZE_UDEV_EVENT_ADD | #EEZE_UDEV_EVENT_REMOVE)), or 0 for all events + * @param cb The function to call when the watch receives data of type #Eeze_Udev_Watch_Cb + * @param user_data Data to pass to the callback function + * + * @return A watch struct for the watch type specified, or @c NULL on failure + * + * Eeze watches will monitor udev for changes of type(s) @p event to devices of type @p type. When these changes occur, the stringshared + * syspath of the device will be sent to function @p func, along with the bitmask of the event type which can be detected through + * binary &. + */ +EAPI Eeze_Udev_Watch *eeze_udev_watch_add(Eeze_Udev_Type type, int event, Eeze_Udev_Watch_Cb cb, void *user_data); + +/** + * Deletes a watch. + * + * @param watch An Eeze_Udev_Watch object + * @return The data originally associated with the watch, or @c NULL + * + * Deletes a watch, closing file descriptors and freeing related udev memory. + */ +EAPI void *eeze_udev_watch_del(Eeze_Udev_Watch *watch); + /** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/Eeze_Disk.h b/src/lib/Eeze_Disk.h new file mode 100644 index 0000000..36079b3 --- /dev/null +++ b/src/lib/Eeze_Disk.h @@ -0,0 +1,567 @@ +#ifndef EEZE_DISK_H +#define EEZE_DISK_H + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +#else +# define EAPI +#endif + +#include +#include + +/** + * @file Eeze_Disk.h + * @brief Disk manipulation + * @since 1.1 + * + * Eeze disk functions allow you to quickly and efficiently manipulate disks + * through simple function calls. + * + * @addtogroup disk Disk + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @enum Eeze_Disk_Type + * @since 1.1 + * + * All disk types known to Eeze. + */ +typedef enum +{ + EEZE_DISK_TYPE_UNKNOWN = 0, /**< type could not be determined */ + EEZE_DISK_TYPE_INTERNAL = (1 << 0), /**< internal drive */ + EEZE_DISK_TYPE_CDROM = (1 << 1), /**< cdrom drive */ + EEZE_DISK_TYPE_USB = (1 << 2), /**< usb drive */ + EEZE_DISK_TYPE_FLASH = (1 << 3) /**< flash disk */ +} Eeze_Disk_Type; + +/** + * @enum Eeze_Mount_Opts + * @since 1.1 + * + * All mount options known to Eeze. + */ +typedef enum +{ +#define EEZE_DISK_MOUNTOPT_DEFAULTS (EEZE_DISK_MOUNTOPT_UTF8 | EEZE_DISK_MOUNTOPT_NOEXEC | EEZE_DISK_MOUNTOPT_NOSUID) + EEZE_DISK_MOUNTOPT_LOOP = (1 << 1), + EEZE_DISK_MOUNTOPT_UTF8 = (1 << 2), + EEZE_DISK_MOUNTOPT_NOEXEC = (1 << 3), + EEZE_DISK_MOUNTOPT_NOSUID = (1 << 4), + EEZE_DISK_MOUNTOPT_REMOUNT = (1 << 5), + EEZE_DISK_MOUNTOPT_UID = (1 << 6), /**< use current user's uid */ + EEZE_DISK_MOUNTOPT_NODEV = (1 << 7) /**< @since 1.7 */ +} Eeze_Mount_Opts; + + +EAPI extern int EEZE_EVENT_DISK_MOUNT; +EAPI extern int EEZE_EVENT_DISK_UNMOUNT; +EAPI extern int EEZE_EVENT_DISK_EJECT; +EAPI extern int EEZE_EVENT_DISK_ERROR; + +typedef struct _Eeze_Event_Disk Eeze_Event_Disk_Mount; +typedef struct _Eeze_Event_Disk Eeze_Event_Disk_Unmount; +typedef struct _Eeze_Event_Disk Eeze_Event_Disk_Eject; + +/** + * @typedef Eeze_Disk + * @since 1.1 + * + * Handle for an Eeze Disk. + */ +typedef struct _Eeze_Disk Eeze_Disk; + +struct _Eeze_Event_Disk +{ + Eeze_Disk *disk; +}; + +/** + * @typedef Eeze_Event_Disk_Error + * @since 1.1 + * + * Contains the human readable error message. + */ +typedef struct _Eeze_Event_Disk_Error Eeze_Event_Disk_Error; + +struct _Eeze_Event_Disk_Error +{ + Eeze_Disk *disk; + const char *message; +}; + +/** + * @brief Use this function to determine whether your eeze is disk-capable + * + * Since applications will die if they run/compile against a function that doesn't exist, + * if your application successfully runs/compiles with this function then you have eeze_disk. + * @since 1.1 + */ +EAPI void eeze_disk_function(void); + +/** + * @brief Return whether mount support is available in eeze + * + * Use this function to determine whether your Eeze library was compiled with a mount + * binary available. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_can_mount(void); + +/** + * @brief Return whether unmount support is available in eeze + * + * Use this function to determine whether your Eeze library was compiled with an unmount + * binary available. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_can_unmount(void); + +/** + * @brief Return whether eject support is available in eeze + * + * Use this function to determine whether your Eeze library was compiled with an eject + * binary available. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_can_eject(void); + +/** + * @brief Create a new disk object from a /sys/ path or /dev/ path + * @param path The /sys/ or /dev path of the disk; CANNOT be @c NULL. + * @return The new disk object + * + * This function creates a new #Eeze_Disk from @p path. Note that this function + * does the minimal amount of work in order to save memory, and udev info about the disk + * is not retrieved in this call. + * @since 1.1 + */ +EAPI Eeze_Disk *eeze_disk_new(const char *path); + +/** + * @brief Create a new disk object from a mount point + * @param mount_point The mount point of the disk; CANNOT be @c NULL + * @return The new disk object + * + * This function creates a new #Eeze_Disk from @p mount_point. Note that this function + * does the minimal amount of work in order to save memory, and udev info about the disk + * is not retrieved in this call. If the disk is not currently mounted, it must have an entry + * in /etc/fstab. + * @since 1.1 + */ +EAPI Eeze_Disk *eeze_disk_new_from_mount(const char *mount_point); + +/** + * @brief Frees a disk object + * @param disk The disk object to free + * + * This call frees an #Eeze_Disk. Once freed, the disk can no longer be used. + * @since 1.1 + */ +EAPI void eeze_disk_free(Eeze_Disk *disk); + +/** + * @brief Retrieve all disk information + * @param disk + * + * Use this function to retrieve all of a disk's information at once, then use + * a "get" function to retrieve the value. Data retrieved in this call is cached, + * meaning that subsequent calls will return immediately without performing any work. + * @since 1.1 + */ +EAPI void eeze_disk_scan(Eeze_Disk *disk); + +/** + * @brief Associate data with a disk + * @param disk The disk + * @param data The data + * + * Data can be associated with @p disk with this function. + * @see eeze_disk_data_get + * @since 1.1 + */ +EAPI void eeze_disk_data_set(Eeze_Disk *disk, void *data); + +/** + * @brief Retrieve data previously associated with a disk + * @param disk The disk + * @return The data + * + * Data that has been previously associated with @p disk + * is returned with this function. + * @see eeze_disk_data_set + * @since 1.1 + */ +EAPI void *eeze_disk_data_get(Eeze_Disk *disk); + +/** + * @brief Return the /sys/ path of a disk + * @param disk The disk + * @return The /sys/ path + * + * This retrieves the /sys/ path that udev associates with @p disk. + * @since 1.1 + */ +EAPI const char *eeze_disk_syspath_get(Eeze_Disk *disk); + +/** + * @brief Return the /dev/ path of a disk + * @param disk The disk + * @return The /dev/ path + * + * This retrieves the /dev/ path that udev has created a device node at for @p disk. + * @since 1.1 + */ +EAPI const char *eeze_disk_devpath_get(Eeze_Disk *disk); + +/** + * @brief Return the filesystem of the disk (if known) + * @param disk The disk + * @return The filesystem type + * + * This retrieves the filesystem that the disk is using, or @c NULL if unknown. + * @since 1.1 + */ +EAPI const char *eeze_disk_fstype_get(Eeze_Disk *disk); + +/** + * @brief Return the manufacturing vendor of the disk + * @param disk The disk + * @return The vendor + * + * This retrieves the vendor which manufactured the disk, or @c NULL if unknown. + * @since 1.1 + */ +EAPI const char *eeze_disk_vendor_get(Eeze_Disk *disk); + +/** + * @brief Return the model of the disk + * @param disk The disk + * @return The model + * + * This retrieves the model of the disk, or @c NULL if unknown. + * @since 1.1 + */ +EAPI const char *eeze_disk_model_get(Eeze_Disk *disk); + +/** + * @brief Return the serial number of the disk + * @param disk The disk + * @return The serial number + * + * This retrieves the serial number the disk, or @c NULL if unknown. + * @since 1.1 + */ +EAPI const char *eeze_disk_serial_get(Eeze_Disk *disk); + +/** + * @brief Return the UUID of the disk + * @param disk The disk + * @return The UUID + * + * This retrieves the UUID of the disk, or @c NULL if unknown. + * A UUID is a 36 character (hopefully) unique identifier which can + * be used to store persistent information about a disk. + * @since 1.1 + */ +EAPI const char *eeze_disk_uuid_get(Eeze_Disk *disk); + +/** + * @brief Return the label of the disk + * @param disk The disk + * @return The label + * + * This retrieves the label (name) of the disk, or @c NULL if unknown. + * @since 1.1 + */ +EAPI const char *eeze_disk_label_get(Eeze_Disk *disk); + +/** + * @brief Return the #Eeze_Disk_Type of the disk + * @param disk The disk + * @return The type + * + * This retrieves the #Eeze_Disk_Type of the disk. This call is useful for determining + * the bus that the disk is connected through. + * @since 1.1 + */ +EAPI Eeze_Disk_Type eeze_disk_type_get(Eeze_Disk *disk); + +/** + * @brief Return whether the disk is removable + * @param disk The disk + * @return @c EINA_TRUE if removable, @c EINA_FALSE otherwise. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_removable_get(Eeze_Disk *disk); + + +/** + * @brief Return the mount state of a disk + * @param disk The disk + * @return The mount state + * + * This returns the mounted state of the disk. @c EINA_TRUE if mounted, + * @c EINA_FALSE otherwise. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_mounted_get(Eeze_Disk *disk); + +/** + * @brief Get the previously set mount wrapper for a disk + * @param disk The disk + * @return The wrapper, or @c NULL on failure. + * + * This returns the wrapper previously set with eeze_disk_mount_wrapper_set + * @since 1.1 + */ +EAPI const char *eeze_disk_mount_wrapper_get(Eeze_Disk *disk); + +/** + * @brief Set a wrapper to run mount commands with + * @param disk The disk to wrap mount commands for + * @param wrapper The wrapper executable + * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise. + * + * Use this function to set up a wrapper for running mount/umount commands. The wrapper must + * NOT use any of the standard mount/umount error code return values, and it must return 0 on success. + * Note that this function will call stat() on @p wrapper if not @c NULL to test for existence. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_mount_wrapper_set(Eeze_Disk *disk, const char *wrapper); + +/** + * @brief Begin a mount operation on the disk + * @param disk The disk + * @return @c EINA_TRUE if the operation was started, @c EINA_FALSE otherwise. + * + * This call is used to begin a mount operation on @p disk. The operation will + * run asynchronously in a pipe, emitting an EEZE_EVENT_DISK_MOUNT event with the disk object + * as its event on completion. If any errors are encountered, they will automatically logged + * to the eeze_disk domain and an EEZE_EVENT_DISK_ERROR event will be generated with an #Eeze_Event_Disk_Error + * struct as its event. + * + * NOTE: The return value of this function does not in any way reflect the mount state of a disk. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_mount(Eeze_Disk *disk); + +/** + * @brief Begin an unmount operation on the disk + * @param disk The disk + * @return @c EINA_TRUE if the operation was started, @c EINA_FALSE otherwise. + * + * This call is used to begin an unmount operation on @p disk. The operation will + * run asynchronously in a pipe, emitting an EEZE_EVENT_DISK_UNMOUNT event with the disk object + * as its event on completion. If any errors are encountered, they will automatically logged + * to the eeze_disk domain and an EEZE_EVENT_DISK_ERROR event will be generated with + * an #Eeze_Event_Disk_Error struct as its event. + * + * NOTE: The return value of this function does not in any way reflect the mount state of a disk. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_unmount(Eeze_Disk *disk); + +/** + * @brief Begin an eject operation on the disk + * @param disk The disk + * @return @c EINA_TRUE if the operation was started, @c EINA_FALSE otherwise. + * + * This call is used to begin an eject operation on @p disk. The operation will + * run asynchronously in a pipe, emitting an EEZE_EVENT_DISK_EJECT event with the disk object + * as its event on completion. If any errors are encountered, they will automatically logged + * to the eeze_disk domain and an EEZE_EVENT_DISK_ERROR event will be generated with + * an #Eeze_Event_Disk_Error struct as its event. + * + * NOTE: The return value of this function does not in any way reflect the mount state of a disk. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_eject(Eeze_Disk *disk); +/** + * @brief Cancel a pending operation on the disk + * @param disk The disk + * + * This function cancels the current pending operation on @p disk which was previously + * started with eeze_disk_mount or eeze_disk_unmount. + * @since 1.1 + */ +EAPI void eeze_disk_cancel(Eeze_Disk *disk); + +/** + * @brief Return the mount point of a disk + * @param disk The disk + * @return The mount point + * + * This function returns the mount point associated with @p disk. + * Note that to determine whether the disk is actually mounted, eeze_disk_mounted_get should be used. + * @since 1.1 + */ +EAPI const char *eeze_disk_mount_point_get(Eeze_Disk *disk); + +/** + * @brief Set the mount point of a disk + * @param disk The disk + * @param mount_point The mount point + * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise. + * + * This function sets the mount point associated with @p disk. + * Note that to determine whether the disk is actually mounted, eeze_disk_mounted_get should be used. + * Also note that this function cannot be used while the disk is mounted to avoid losing the current mount point. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_mount_point_set(Eeze_Disk *disk, const char *mount_point); + +/** + * @brief Set the mount options using flags + * @param disk The disk + * @param opts An ORed set of #Eeze_Mount_Opts + * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise. + * + * This function replaces the current mount opts of a disk with the ones in @p opts. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_mountopts_set(Eeze_Disk *disk, unsigned long opts); + +/** + * @brief Get the flags of a disk's current mount options + * @param disk The disk + * @return An ORed set of #Eeze_Mount_Opts, 0 on failure + * + * This function returns the current mount opts of a disk. + * @since 1.1 + */ +EAPI unsigned long eeze_disk_mountopts_get(Eeze_Disk *disk); + + +/** + * @brief Begin watching mtab and fstab + * @return @c EINA_TRUE if watching was started, @c EINA_FALSE otherwise. + * + * This function creates inotify watches on /etc/mtab and /etc/fstab and watches + * them for changes. This function should be used when expecting a lot of disk + * mounting/unmounting while you need disk data since it will automatically update + * certain necessary data instead of waiting. + * @see eeze_mount_mtab_scan, eeze_mount_fstab_scan + * @since 1.1 + */ +EAPI Eina_Bool eeze_mount_tabs_watch(void); + +/** + * @brief Stop watching /etc/fstab and /etc/mtab + * + * This function stops watching fstab and mtab. Data obtained previously will be saved. + * @since 1.1 + */ +EAPI void eeze_mount_tabs_unwatch(void); + +/** + * @brief Scan /etc/mtab a single time + * @return @c EINA_TRUE if mtab could be scanned, @c EINA_FALSE otherwise. + * + * This function is used to perform a single scan on /etc/mtab. It is used to gather + * information about mounted filesystems which can then be used with your #Eeze_Disk objects + * where appropriate. These files will automatically be scanned any time a mount point or mount state + * is requested unless eeze_mount_tabs_watch has been called previously, in which case data is stored for + * use. + * If this function is called after eeze_mount_tabs_watch, @c EINA_TRUE will be returned. + * @see eeze_mount_tabs_watch, eeze_mount_fstab_scan + * @since 1.1 + */ +EAPI Eina_Bool eeze_mount_mtab_scan(void); + +/** + * @brief Scan /etc/fstab a single time + * @return @c EINA_TRUE if mtab could be scanned, @c EINA_FALSE otherwise. + * + * This function is used to perform a single scan on /etc/fstab. It is used to gather + * information about mounted filesystems which can then be used with your #Eeze_Disk objects + * where appropriate. These files will automatically be scanned any time a mount point or mount state + * is requested unless eeze_mount_tabs_watch has been called previously, in which case data is stored for + * use. + * If this function is called after eeze_mount_tabs_watch, @c EINA_TRUE will be returned. + * @see eeze_mount_tabs_watch, eeze_mount_mtab_scan + * @since 1.1 + */ +EAPI Eina_Bool eeze_mount_fstab_scan(void); + +/** + * @brief Get the property value of a disk + * + * @param disk The disk + * @param property The property to get; full list of these is a FIXME + * @return A stringshared char* with the property or @c NULL on failure. + * @since 1.1 + */ + +EAPI const char *eeze_disk_udev_get_property(Eeze_Disk *disk, const char *property); + +/** + * @brief Get the sysattr value of a disk. + * + * @param disk The disk + * @param sysattr The sysattr to get; full list of these is a FIXME + * @return A stringshared char* with the sysattr or @c NULL on failure. + * @since 1.1 + */ + +EAPI const char *eeze_disk_udev_get_sysattr(Eeze_Disk *disk, const char *sysattr); + +/** + * Find the root device of a disk. + * + * @param disk The disk + * @return The syspath of the parent device + * + * Return a stringshared syspath (/sys/$syspath) for the parent device. + * @since 1.1 + */ +EAPI const char *eeze_disk_udev_get_parent(Eeze_Disk *disk); + +/** + * Walks up the device chain using the device from @p disk, + * checking each device for @p sysattr with (optional) @p value. + * + * @param disk The disk to walk + * @param sysattr The attribute to find + * @param value OPTIONAL: The value that @p sysattr should have, or @c NULL. + * + * @return If the sysattr (with value) is found, returns @c EINA_TRUE, + * @c EINA_FALSE otherwise. + * @since 1.1 + */ +EAPI Eina_Bool eeze_disk_udev_walk_check_sysattr(Eeze_Disk *disk, const char *sysattr, const char *value); + +/** + * @brief Walks up the device chain of @p disk + * checking each device for @p sysattr and returns the value if found. + * + * @param disk The disk + * @param sysattr The attribute to find + * + * @return The stringshared value of @p sysattr if found, or @c NULL. + * @since 1.1 + */ +EAPI const char *eeze_disk_udev_walk_get_sysattr(Eeze_Disk *disk, const char *sysattr); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ +#endif diff --git a/src/lib/Eeze_Net.h b/src/lib/Eeze_Net.h new file mode 100644 index 0000000..97a17ca --- /dev/null +++ b/src/lib/Eeze_Net.h @@ -0,0 +1,62 @@ +#ifndef EEZE_NET_H +#define EEZE_NET_H + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +#else +# define EAPI +#endif + +#include +#include + +/** + * @file Eeze_Net.h + * @brief Network manipulation + * + * Eeze net functions allow you to gather information about network objects + * + * @addtogroup net Net + * @{ + */ + +typedef struct Eeze_Net Eeze_Net; + +typedef enum +{ + EEZE_NET_ADDR_TYPE_IP, + EEZE_NET_ADDR_TYPE_IP6, + EEZE_NET_ADDR_TYPE_BROADCAST, + EEZE_NET_ADDR_TYPE_BROADCAST6, + EEZE_NET_ADDR_TYPE_NETMASK, + EEZE_NET_ADDR_TYPE_NETMASK6, +} Eeze_Net_Addr_Type; + +#ifdef __cplusplus +extern "C" { +#endif + +EAPI Eeze_Net *eeze_net_new(const char *name); +EAPI void eeze_net_free(Eeze_Net *net); +EAPI const char *eeze_net_mac_get(Eeze_Net *net); +EAPI int eeze_net_idx_get(Eeze_Net *net); +EAPI Eina_Bool eeze_net_scan(Eeze_Net *net); +EAPI const char *eeze_net_addr_get(Eeze_Net *net, Eeze_Net_Addr_Type type); +EAPI const char *eeze_net_attribute_get(Eeze_Net *net, const char *attr); +EAPI const char *eeze_net_syspath_get(Eeze_Net *net); +EAPI Eina_List *eeze_net_list(void); + +#ifdef __cplusplus +} +#endif +/** @} */ + +#endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..b14e44d --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,43 @@ +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = @EEZE_CFLAGS@ + +includes_HEADERS = Eeze.h Eeze_Net.h + +libeeze_la_SOURCES = \ +eeze_main.c \ +eeze_net.c \ +eeze_net_private.h \ +eeze_udev_find.c \ +eeze_udev_private.h \ +eeze_udev_private.c \ +eeze_udev_syspath.c \ +eeze_udev_walk.c \ +eeze_udev_watch.c + +if HAVE_EEZE_MOUNT + AM_CFLAGS = @EEZE_CFLAGS@ @LIBMOUNT_CFLAGS@ @ECORE_FILE_CFLAGS@ + libeeze_la_SOURCES += eeze_disk.c eeze_disk_udev.c eeze_disk_mount.c eeze_disk_private.h +if OLD_LIBMOUNT + libeeze_la_SOURCES += eeze_disk_libmount_old.c +else +if NEW_LIBMOUNT + libeeze_la_SOURCES += eeze_disk_libmount_new.c +else + libeeze_la_SOURCES += eeze_disk_libmount.c +endif +endif + includes_HEADERS += Eeze_Disk.h +else + AM_CFLAGS = @EEZE_CFLAGS@ +endif + +lib_LTLIBRARIES = libeeze.la +includesdir = $(includedir)/eeze-@VMAJ@ + +if HAVE_EEZE_MOUNT + libeeze_la_LIBADD = @EEZE_LIBS@ @LIBMOUNT_LIBS@ @ECORE_FILE_LIBS@ +else + libeeze_la_LIBADD = @EEZE_LIBS@ +endif +libeeze_la_LDFLAGS = -no-undefined -version-info @version_info@ @release_info@ diff --git a/src/lib/eeze_disk.c b/src/lib/eeze_disk.c new file mode 100644 index 0000000..8d1aeec --- /dev/null +++ b/src/lib/eeze_disk.c @@ -0,0 +1,476 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" + +int _eeze_disk_log_dom = -1; +Eina_List *_eeze_disks = NULL; + +static Eeze_Disk_Type +_eeze_disk_type_find(Eeze_Disk *disk) +{ + const char *test; + Eeze_Disk_Type ret; + Eina_Bool filesystem = EINA_FALSE; /* this will have no children */ + + if (udev_device_get_property_value(disk->device, "ID_CDROM")) + return EEZE_DISK_TYPE_CDROM; + test = udev_device_get_property_value(disk->device, "ID_FS_USAGE"); + if ((!test) || strcmp(test, "filesystem")) + { + test = _walk_children_get_attr(disk->syspath, "ID_CDROM", "block", EINA_TRUE); + if (test) + { + eina_stringshare_del(test); + return EEZE_DISK_TYPE_CDROM; + } + } + else + filesystem = EINA_TRUE; + if (udev_device_get_property_value(disk->device, "ID_ATA")) + return EEZE_DISK_TYPE_INTERNAL; + if (!filesystem) + { + test = _walk_children_get_attr(disk->syspath, "ID_ATA", "block", EINA_TRUE); + if (test) + { + eina_stringshare_del(test); + return EEZE_DISK_TYPE_INTERNAL; + } + } + test = udev_device_get_property_value(disk->device, "ID_BUS"); + if (test) + { + if (!strcmp(test, "ata")) return EEZE_DISK_TYPE_INTERNAL; + if (!strcmp(test, "usb")) return EEZE_DISK_TYPE_USB; + return EEZE_DISK_TYPE_UNKNOWN; /* FIXME */ + } + if ((!test) && (!filesystem)) + test = _walk_children_get_attr(disk->syspath, "ID_BUS", "block", EINA_TRUE); + if (!test) + { + _udev_device *dev; + + for (dev = udev_device_get_parent(disk->device); dev; dev = udev_device_get_parent(dev)) + { + test = udev_device_get_subsystem(dev); + if (!test) return EEZE_DISK_TYPE_UNKNOWN; + if (!strcmp(test, "block")) continue; + if (!strcmp(test, "mmc")) return EEZE_DISK_TYPE_FLASH; + break; + } + return EEZE_DISK_TYPE_UNKNOWN; /* FIXME */ + } + + if (!strcmp(test, "ata")) ret = EEZE_DISK_TYPE_INTERNAL; + else if (!strcmp(test, "usb")) ret = EEZE_DISK_TYPE_USB; + else ret = EEZE_DISK_TYPE_UNKNOWN; /* FIXME */ + + eina_stringshare_del(test); + + return ret; +} + +static _udev_device * +_eeze_disk_device_from_property(const char *prop, Eina_Bool uuid) +{ + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + _udev_device *device = NULL; + const char *devname; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + if (uuid) + udev_enumerate_add_match_property(en, "ID_FS_UUID", prop); + else + udev_enumerate_add_match_property(en, "ID_FS_LABEL", prop); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + device = udev_device_new_from_syspath(udev, devname); + break; + } + udev_enumerate_unref(en); + return device; + +} + +void +eeze_disk_shutdown(void) +{ + eeze_mount_shutdown(); + ecore_file_shutdown(); + eina_log_domain_unregister(_eeze_disk_log_dom); + _eeze_disk_log_dom = -1; +} + +Eina_Bool +eeze_disk_init(void) +{ + _eeze_disk_log_dom = eina_log_domain_register("eeze_disk", EINA_COLOR_LIGHTBLUE); + + if (_eeze_disk_log_dom < 0) + { + EINA_LOG_ERR("Could not register 'eeze_disk' log domain."); + goto disk_fail; + } + + if (!ecore_file_init()) + goto disk_fail; + if (!eeze_mount_init()) + goto ecore_file_fail; + + return EINA_TRUE; + +ecore_file_fail: + ecore_file_shutdown(); +disk_fail: + eina_log_domain_unregister(_eeze_disk_log_dom); + _eeze_disk_log_dom = -1; + return EINA_FALSE; +} + +EAPI void +eeze_disk_function(void) +{ +} + +EAPI Eeze_Disk * +eeze_disk_new(const char *path) +{ + Eeze_Disk *disk; + _udev_device *dev; + const char *syspath = NULL; + Eina_Bool is_dev = EINA_FALSE; + + EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL); + + if (!strncmp(path, "/dev/", 5)) + { + is_dev = EINA_TRUE; + syspath = eeze_udev_devpath_get_syspath(path); + if (!syspath) + return NULL; + + if (!(dev = _new_device(syspath))) + { + eina_stringshare_del(syspath); + return NULL; + } + } + else if (!(dev = _new_device(path))) + return NULL; + + + if (!(disk = calloc(1, sizeof(Eeze_Disk)))) + return NULL; + + + if (is_dev) + { + disk->devpath = eina_stringshare_add(path); + disk->syspath = syspath; + } + else + disk->syspath = eina_stringshare_add(udev_device_get_syspath(dev)); + + + disk->device = dev; + disk->mount_opts = EEZE_DISK_MOUNTOPT_DEFAULTS; + disk->mount_cmd_changed = EINA_TRUE; + disk->unmount_cmd_changed = EINA_TRUE; + + _eeze_disks = eina_list_append(_eeze_disks, disk); + + return disk; +} + +EAPI Eeze_Disk * +eeze_disk_new_from_mount(const char *mount_point) +{ + Eeze_Disk *disk = NULL; + _udev_device *dev = NULL; + const char *syspath = NULL, *source, *uuid = NULL, *label = NULL, *devpath = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(mount_point, NULL); + + if (!(source = eeze_disk_libmount_mp_find_source(mount_point))) + return NULL; + + if (source[4] == '=') + { + source += 5; + uuid = eina_stringshare_add(source); + dev = _eeze_disk_device_from_property(uuid, EINA_TRUE); + } + else if (source[5] == '=') + { + source += 6; + label = eina_stringshare_add(source); + dev = _eeze_disk_device_from_property(label, EINA_FALSE); + } + else + { + const char *spath; + + devpath = eina_stringshare_add(source); + spath = eeze_udev_devpath_get_syspath(devpath); + dev = _new_device(spath); + eina_stringshare_del(spath); + } + + if (!dev) + goto error; + + if (!(disk = calloc(1, sizeof(Eeze_Disk)))) + goto error; + + disk->syspath = udev_device_get_syspath(dev); + + disk->device = dev; + disk->mount_cmd_changed = EINA_TRUE; + disk->unmount_cmd_changed = EINA_TRUE; + if (uuid) + disk->cache.uuid = uuid; + else if (label) + disk->cache.label = label; + else + disk->devpath = devpath; + disk->mount_point = eina_stringshare_add(mount_point); + + _eeze_disks = eina_list_append(_eeze_disks, disk); + + return disk; +error: + if (uuid) + eina_stringshare_del(uuid); + else if (label) + eina_stringshare_del(label); + else if (devpath) + eina_stringshare_del(devpath); + if (syspath) + eina_stringshare_del(syspath); + if (dev) + udev_device_unref(dev); + return NULL; +} + +EAPI void +eeze_disk_free(Eeze_Disk *disk) +{ + extern Eina_List *eeze_events; + EINA_SAFETY_ON_NULL_RETURN(disk); + + udev_device_unref(disk->device); + if (disk->mount_cmd) + eina_strbuf_free(disk->mount_cmd); + if (disk->unmount_cmd) + eina_strbuf_free(disk->unmount_cmd); + if (disk->eject_cmd) + eina_strbuf_free(disk->eject_cmd); + if (disk->mounter) ecore_exe_kill(disk->mounter); + _eeze_disks = eina_list_remove(_eeze_disks, disk); + eeze_events = eina_list_remove(eeze_events, disk); + free(disk); +} + +EAPI void +eeze_disk_scan(Eeze_Disk *disk) +{ + const char *test; + EINA_SAFETY_ON_NULL_RETURN(disk); + /* never rescan; if these values change then something is seriously wrong */ + if (disk->cache.filled) return; + + if (!disk->cache.vendor) + disk->cache.vendor = udev_device_get_property_value(disk->device, "ID_VENDOR"); + if (!disk->cache.vendor) + if (!disk->cache.vendor) disk->cache.vendor = udev_device_get_sysattr_value(disk->device, "vendor"); + if (!disk->cache.model) + disk->cache.model = udev_device_get_property_value(disk->device, "ID_MODEL"); + if (!disk->cache.model) + if (!disk->cache.model) disk->cache.model = udev_device_get_sysattr_value(disk->device, "model"); + if (!disk->cache.serial) + disk->cache.serial = udev_device_get_property_value(disk->device, "ID_SERIAL_SHORT"); + if (!disk->cache.uuid) + disk->cache.uuid = udev_device_get_property_value(disk->device, "ID_FS_UUID"); + if (!disk->cache.type) + disk->cache.type = _eeze_disk_type_find(disk); + if (!disk->cache.label) + disk->cache.label = udev_device_get_property_value(disk->device, "ID_FS_LABEL"); + test = udev_device_get_sysattr_value(disk->device, "removable"); + if (test) disk->cache.removable = !!strtol(test, NULL, 10); + else + test = _walk_children_get_attr(disk->syspath, "removable", "block", EINA_FALSE); + if (test) + { + disk->cache.removable = !!strtol(test, NULL, 10); + eina_stringshare_del(test); + } + + disk->cache.filled = EINA_TRUE; +} + +EAPI void +eeze_disk_data_set(Eeze_Disk *disk, void *data) +{ + EINA_SAFETY_ON_NULL_RETURN(disk); + + disk->data = data; +} + +EAPI void * +eeze_disk_data_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + return disk->data; +} + +EAPI const char * +eeze_disk_syspath_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + return disk->syspath; +} + +EAPI const char * +eeze_disk_devpath_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->devpath) + return disk->devpath; + disk->devpath = udev_device_get_devnode(disk->device); + return disk->devpath; +} + +EAPI const char * +eeze_disk_fstype_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + return disk->fstype; +} + +EAPI const char * +eeze_disk_vendor_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->cache.vendor) + return disk->cache.vendor; + + disk->cache.vendor = udev_device_get_property_value(disk->device, "ID_VENDOR"); + if (!disk->cache.vendor) disk->cache.vendor = udev_device_get_sysattr_value(disk->device, "vendor"); + return disk->cache.vendor; +} + +EAPI const char * +eeze_disk_model_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->cache.model) + return disk->cache.model; + + disk->cache.model = udev_device_get_property_value(disk->device, "ID_MODEL"); + if (!disk->cache.model) disk->cache.model = udev_device_get_sysattr_value(disk->device, "model"); + return disk->cache.model; +} + +EAPI const char * +eeze_disk_serial_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->cache.serial) + return disk->cache.serial; + disk->cache.serial = udev_device_get_property_value(disk->device, "ID_SERIAL_SHORT"); + return disk->cache.serial; +} + +EAPI const char * +eeze_disk_uuid_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->cache.uuid) + return disk->cache.uuid; + disk->cache.uuid = udev_device_get_property_value(disk->device, "ID_FS_UUID"); + return disk->cache.uuid; +} + +EAPI const char * +eeze_disk_label_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->cache.label) + return disk->cache.label; + disk->cache.label = udev_device_get_property_value(disk->device, "ID_FS_LABEL"); + return disk->cache.label; +} + +EAPI Eeze_Disk_Type +eeze_disk_type_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EEZE_DISK_TYPE_UNKNOWN); + + if (disk->cache.type) + return disk->cache.type; + disk->cache.type = _eeze_disk_type_find(disk); + return disk->cache.type; +} + +EAPI Eina_Bool +eeze_disk_removable_get(Eeze_Disk *disk) +{ + const char *test; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + if (disk->cache.filled) + return disk->cache.removable; + + test = udev_device_get_sysattr_value(disk->device, "removable"); + if (test) disk->cache.removable = !!strtol(test, NULL, 10); + else + test = _walk_children_get_attr(disk->syspath, "removable", "block", EINA_FALSE); + if (test) + { + disk->cache.removable = !!strtol(test, NULL, 10); + eina_stringshare_del(test); + } + return disk->cache.removable; +} + +EAPI Eina_Bool +eeze_disk_can_mount(void) +{ + return MOUNTABLE; +} + +EAPI Eina_Bool +eeze_disk_can_unmount(void) +{ + return UNMOUNTABLE; +} + +EAPI Eina_Bool +eeze_disk_can_eject(void) +{ + return EJECTABLE; +} diff --git a/src/lib/eeze_disk_libmount.c b/src/lib/eeze_disk_libmount.c new file mode 100644 index 0000000..885f313 --- /dev/null +++ b/src/lib/eeze_disk_libmount.c @@ -0,0 +1,495 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef USE_UNSTABLE_LIBMOUNT_API +# define USE_UNSTABLE_LIBMOUNT_API 1 +#endif + +#include +#include +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" + +/* + * + * PRIVATE + * + */ + +static struct libmnt_optmap eeze_optmap[] = +{ + { "loop[=]", EEZE_DISK_MOUNTOPT_LOOP, 0 }, + { "utf8", EEZE_DISK_MOUNTOPT_UTF8, 0 }, + { "noexec", EEZE_DISK_MOUNTOPT_NOEXEC, 0 }, + { "nosuid", EEZE_DISK_MOUNTOPT_NOSUID, 0 }, + { "remount", EEZE_DISK_MOUNTOPT_REMOUNT, 0 }, + { "uid[=]", EEZE_DISK_MOUNTOPT_UID, 0 }, + { "nodev", EEZE_DISK_MOUNTOPT_NODEV, 0 }, + { NULL, 0, 0 } +}; +typedef struct libmnt_table libmnt_table; +typedef struct libmnt_lock libmnt_lock; +typedef struct libmnt_fs libmnt_fs; +typedef struct libmnt_cache libmnt_cache; +static Ecore_File_Monitor *_mtab_mon = NULL; +static Ecore_File_Monitor *_fstab_mon = NULL; +static Eina_Bool _watching = EINA_FALSE; +static Eina_Bool _mtab_scan_active = EINA_FALSE; +static Eina_Bool _mtab_locked = EINA_FALSE; +static Eina_Bool _fstab_scan_active = EINA_FALSE; +static libmnt_cache *_eeze_mount_mtab_cache = NULL; +static libmnt_cache *_eeze_mount_fstab_cache = NULL; +static libmnt_table *_eeze_mount_mtab = NULL; +static libmnt_table *_eeze_mount_fstab = NULL; +static libmnt_lock *_eeze_mtab_lock = NULL; +extern Eina_List *_eeze_disks; + +static libmnt_table *_eeze_mount_tab_parse(const char *filename); +static void _eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path); + +static Eina_Bool +_eeze_mount_lock_mtab(void) +{ +// DBG("Locking mlock: %s", mnt_lock_get_linkfile(_eeze_mtab_lock)); + if (EINA_LIKELY(access("/etc/mtab", W_OK))) + { + INF("Insufficient privs for mtab lock, continuing without lock"); + return EINA_TRUE; + } + if (mnt_lock_file(_eeze_mtab_lock)) + { + ERR("Couldn't lock mtab!"); + return EINA_FALSE; + } + _mtab_locked = EINA_TRUE; + return EINA_TRUE; +} + +static void +_eeze_mount_unlock_mtab(void) +{ +// DBG("Unlocking mlock: %s", mnt_lock_get_linkfile(_eeze_mtab_lock)); + if (_mtab_locked) mnt_unlock_file(_eeze_mtab_lock); + _mtab_locked = EINA_FALSE; +} + + +static int +_eeze_mount_tab_parse_errcb(libmnt_table *tab __UNUSED__, const char *filename, int line) +{ + ERR("%s:%d: could not parse line!", filename, line); /* most worthless error reporting ever. */ + return -1; +} + +/* + * I could use mnt_new_table_from_file() but this way gives much more detailed output + * on failure so why not + */ +static libmnt_table * +_eeze_mount_tab_parse(const char *filename) +{ + libmnt_table *tab; + + if (!(tab = mnt_new_table())) return NULL; + if (mnt_table_set_parser_errcb(tab, _eeze_mount_tab_parse_errcb)) + { + ERR("Alloc!"); + mnt_free_table(tab); + return NULL; + } + + if (!mnt_table_parse_file(tab, filename)) + return tab; + + mnt_free_table(tab); + return NULL; +} + +static void +_eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path) +{ + libmnt_table *bak; + + if ( + ((_mtab_scan_active) && (data)) || /* mtab has non-null data to avoid needing strcmp */ + ((_fstab_scan_active) && (!data)) + ) + /* prevent scans from triggering a scan */ + return; + + bak = _eeze_mount_mtab; + if (data) + if (!_eeze_mount_lock_mtab()) + { /* FIXME: maybe queue job here? */ + ERR("Losing events..."); + return; + } + _eeze_mount_mtab = _eeze_mount_tab_parse(path); + if (data) + _eeze_mount_unlock_mtab(); + if (!_eeze_mount_mtab) + { + ERR("Could not parse %s! keeping old tab...", path); + goto error; + } + if (data) + { + Eina_List *l; + Eeze_Disk *disk; + + /* catch externally initiated mounts on existing disks by comparing known mount state to current state */ + EINA_LIST_FOREACH(_eeze_disks, l, disk) + { + Eina_Bool mounted; + + mounted = disk->mounted; + + if ((eeze_disk_libmount_mounted_get(disk) != mounted) && (!disk->mount_status)) + { + if (!mounted) + { + Eeze_Event_Disk_Mount *e; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_MOUNT, e, NULL, NULL); + } + } + else + { + Eeze_Event_Disk_Unmount *e; + e = malloc(sizeof(Eeze_Event_Disk_Unmount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + } + } + } + } + } + + mnt_free_table(bak); + if (data) + { + mnt_free_cache(_eeze_mount_mtab_cache); + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + } + else + { + mnt_free_cache(_eeze_mount_fstab_cache); + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + } + return; + +error: + mnt_free_table(_eeze_mount_mtab); + _eeze_mount_mtab = bak; +} + +/* + * + * INVISIBLE + * + */ + +Eina_Bool +eeze_libmount_init(void) +{ + if (_eeze_mtab_lock) + return EINA_TRUE; + if (!(_eeze_mtab_lock = mnt_new_lock("/etc/mtab", 0))) + return EINA_FALSE; + return EINA_TRUE; +} + +void +eeze_libmount_shutdown(void) +{ + if (_eeze_mount_fstab) + { + mnt_free_table(_eeze_mount_fstab); + mnt_free_cache(_eeze_mount_fstab_cache); + } + if (_eeze_mount_mtab) + { + mnt_free_table(_eeze_mount_mtab); + mnt_free_cache(_eeze_mount_mtab_cache); + } + eeze_mount_tabs_unwatch(); + if (!_eeze_mtab_lock) + return; + + mnt_unlock_file(_eeze_mtab_lock); + mnt_free_lock(_eeze_mtab_lock); + _eeze_mtab_lock = NULL; +} + +unsigned long +eeze_disk_libmount_opts_get(Eeze_Disk *disk) +{ + libmnt_fs *mnt; + const char *opts; + unsigned long f = 0; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return 0; + + mnt = mnt_table_find_tag(_eeze_mount_mtab, "UUID", eeze_disk_uuid_get(disk), MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_tag(_eeze_mount_fstab, "UUID", eeze_disk_uuid_get(disk), MNT_ITER_BACKWARD); + + if (!mnt) return 0; + + opts = mnt_fs_get_fs_options(mnt); + if (!opts) return 0; + if (!mnt_optstr_get_flags(opts, &f, eeze_optmap)) return 0; + return f; +} + +/* + * helper function to return whether a disk is mounted + */ +Eina_Bool +eeze_disk_libmount_mounted_get(Eeze_Disk *disk) +{ + libmnt_fs *mnt; + + if (!disk) + return EINA_FALSE; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return EINA_FALSE; + + mnt = mnt_table_find_srcpath(_eeze_mount_mtab, eeze_disk_devpath_get(disk), MNT_ITER_BACKWARD); + if (!mnt) + { + disk->mounted = EINA_FALSE; + return EINA_FALSE; + } + + eina_stringshare_replace(&disk->mount_point, mnt_fs_get_target(mnt)); + disk->mounted = EINA_TRUE; + return EINA_TRUE; +} + + +/* + * helper function to return the device that is mounted at a mount point + */ +const char * +eeze_disk_libmount_mp_find_source(const char *mount_point) +{ + libmnt_fs *mnt; + + if (!mount_point) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_target(_eeze_mount_mtab, mount_point, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_target(_eeze_mount_fstab, mount_point, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_source(mnt); +} + +/* + * helper function to return a mount point from a uuid + */ +const char * +eeze_disk_libmount_mp_lookup_by_uuid(const char *uuid) +{ + libmnt_fs *mnt; + + if (!uuid) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_tag(_eeze_mount_fstab, "UUID", uuid, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a label + */ +const char * +eeze_disk_libmount_mp_lookup_by_label(const char *label) +{ + libmnt_fs *mnt; + + if (!label) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_tag(_eeze_mount_fstab, "LABEL", label, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a /dev/ path + */ +const char * +eeze_disk_libmount_mp_lookup_by_devpath(const char *devpath) +{ + libmnt_fs *mnt; + + if (!devpath) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_srcpath(_eeze_mount_mtab, devpath, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_srcpath(_eeze_mount_fstab, devpath, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * + * API + * + */ + +EAPI Eina_Bool +eeze_mount_tabs_watch(void) +{ + libmnt_table *bak; + + if (_watching) + return EINA_TRUE; + + if (!_eeze_mount_lock_mtab()) + return EINA_FALSE; + + bak = _eeze_mount_tab_parse("/etc/mtab"); + _eeze_mount_unlock_mtab(); + if (!bak) + goto error; + + mnt_free_table(_eeze_mount_mtab); + _eeze_mount_mtab = bak; + if (!(bak = _eeze_mount_tab_parse("/etc/fstab"))) + goto error; + + mnt_free_table(_eeze_mount_fstab); + _eeze_mount_fstab = bak; + + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + + _mtab_mon = ecore_file_monitor_add("/etc/mtab", _eeze_mount_tab_watcher, (void*)1); + _fstab_mon = ecore_file_monitor_add("/etc/fstab", _eeze_mount_tab_watcher, NULL); + _watching = EINA_TRUE; + + return EINA_TRUE; + +error: + if (!_eeze_mount_mtab) + ERR("Could not parse /etc/mtab!"); + else + { + ERR("Could not parse /etc/fstab!"); + mnt_free_table(_eeze_mount_mtab); + } + return EINA_FALSE; +} + +EAPI void +eeze_mount_tabs_unwatch(void) +{ + if (!_watching) + return; + + ecore_file_monitor_del(_mtab_mon); + _mtab_mon = NULL; + ecore_file_monitor_del(_fstab_mon); + _fstab_mon = NULL; + _watching = EINA_FALSE; +} + +EAPI Eina_Bool +eeze_mount_mtab_scan(void) +{ + libmnt_table *bak; + + if (_watching) + return EINA_TRUE; + + if (!_eeze_mount_lock_mtab()) + return EINA_FALSE; + bak = _eeze_mount_tab_parse("/etc/mtab"); + _eeze_mount_unlock_mtab(); + if (!bak) + goto error; + if (_eeze_mount_mtab) + { + mnt_free_table(_eeze_mount_mtab); + mnt_free_cache(_eeze_mount_mtab_cache); + } + _eeze_mount_mtab = bak; + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + + return EINA_TRUE; + +error: + return EINA_FALSE; +} + +EAPI Eina_Bool +eeze_mount_fstab_scan(void) +{ + libmnt_table *bak; + if (_watching) + return EINA_TRUE; + + bak = _eeze_mount_tab_parse("/etc/fstab"); + if (!bak) + goto error; + if (_eeze_mount_fstab) + { + mnt_free_table(_eeze_mount_fstab); + mnt_free_cache(_eeze_mount_fstab_cache); + } + _eeze_mount_fstab = bak; + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + + return EINA_TRUE; + +error: + return EINA_FALSE; +} diff --git a/src/lib/eeze_disk_libmount_new.c b/src/lib/eeze_disk_libmount_new.c new file mode 100644 index 0000000..5811518 --- /dev/null +++ b/src/lib/eeze_disk_libmount_new.c @@ -0,0 +1,525 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef USE_UNSTABLE_LIBMOUNT_API +# define USE_UNSTABLE_LIBMOUNT_API 1 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" + +/* + * + * PRIVATE + * + */ + +static struct libmnt_optmap eeze_optmap[] = +{ + { "loop[=]", EEZE_DISK_MOUNTOPT_LOOP, 0 }, + { "utf8", EEZE_DISK_MOUNTOPT_UTF8, 0 }, + { "noexec", EEZE_DISK_MOUNTOPT_NOEXEC, 0 }, + { "nosuid", EEZE_DISK_MOUNTOPT_NOSUID, 0 }, + { "remount", EEZE_DISK_MOUNTOPT_REMOUNT, 0 }, + { "uid[=]", EEZE_DISK_MOUNTOPT_UID, 0 }, + { "nodev", EEZE_DISK_MOUNTOPT_NODEV, 0 }, + { NULL, 0, 0 } +}; +typedef struct libmnt_table libmnt_table; +typedef struct libmnt_fs libmnt_fs; +typedef struct libmnt_cache libmnt_cache; +static Ecore_File_Monitor *_fstab_mon = NULL; +static Eina_Bool _watching = EINA_FALSE; +static Eina_Bool _fstab_scan_active = EINA_FALSE; +static libmnt_cache *_eeze_mount_mtab_cache = NULL; +static libmnt_cache *_eeze_mount_fstab_cache = NULL; +static libmnt_table *_eeze_mount_mtab = NULL; +static libmnt_table *_eeze_mount_fstab = NULL; +extern Eina_List *_eeze_disks; + +static Ecore_Fd_Handler *_mountinfo_fdh = NULL; +static int _mountinfo = -1; + +static libmnt_table *_eeze_mount_tab_parse(const char *filename); +static void _eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path); + +static int +_eeze_mount_tab_parse_errcb(libmnt_table *tab __UNUSED__, const char *filename, int line) +{ + ERR("%s:%d: could not parse line!", filename, line); /* most worthless error reporting ever. */ + return -1; +} + +/* + * I could use mnt_new_table_from_file() but this way gives much more detailed output + * on failure so why not + */ +static libmnt_table * +_eeze_mount_tab_parse(const char *filename) +{ + libmnt_table *tab; + + if (!(tab = mnt_new_table())) return NULL; + if (mnt_table_set_parser_errcb(tab, _eeze_mount_tab_parse_errcb)) + { + ERR("Alloc!"); + mnt_free_table(tab); + return NULL; + } + + if (!mnt_table_parse_file(tab, filename)) + return tab; + + mnt_free_table(tab); + return NULL; +} + +static void +_eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path) +{ + libmnt_table *bak; + + if (_fstab_scan_active) + /* prevent scans from triggering a scan */ + return; + + bak = _eeze_mount_mtab; + _eeze_mount_mtab = _eeze_mount_tab_parse(path); + if (!_eeze_mount_mtab) + { + ERR("Could not parse %s! keeping old tab...", path); + goto error; + } + + mnt_free_table(bak); + if (data) + { + mnt_free_cache(_eeze_mount_mtab_cache); + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + } + else + { + mnt_free_cache(_eeze_mount_fstab_cache); + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + } + return; + +error: + mnt_free_table(_eeze_mount_mtab); + _eeze_mount_mtab = bak; +} + +/* on tab change, check differences + * based on code from findmnt + */ +static Eina_Bool +_eeze_mount_fdh(void *d __UNUSED__, Ecore_Fd_Handler *fdh __UNUSED__) +{ + libmnt_table *tb_new; + libmnt_fs *old, *new; + int change; + struct libmnt_iter *itr = NULL; + struct libmnt_tabdiff *diff = NULL; + + tb_new = mnt_new_table(); + EINA_SAFETY_ON_NULL_RETURN_VAL(tb_new, ECORE_CALLBACK_RENEW); + EINA_SAFETY_ON_TRUE_GOTO(mnt_table_set_parser_errcb(tb_new, _eeze_mount_tab_parse_errcb), err); + itr = mnt_new_iter(MNT_ITER_BACKWARD); + EINA_SAFETY_ON_NULL_GOTO(itr, err); + diff = mnt_new_tabdiff(); + EINA_SAFETY_ON_NULL_GOTO(diff, err); + if (mnt_table_parse_file(tb_new, "/proc/self/mountinfo")) + { + ERR("PARSING FAILED FOR /proc/self/mountinfo! THIS IS WEIRD!"); + goto err; + } + change = mnt_diff_tables(diff, _eeze_mount_mtab, tb_new); + if (change < 0) + { + ERR("DIFFING FAILED FOR /proc/self/mountinfo! THIS IS ALSO WEIRD!"); + goto err; + } + if (!change) goto err; + while (!mnt_tabdiff_next_change(diff, itr, &old, &new, &change)) + { + const char *src; + Eeze_Disk *disk; + Eina_Bool found = EINA_FALSE; + Eeze_Event_Disk_Mount *e; + Eina_List *l; + + src = mnt_fs_get_source(new); + if (!src) continue; + EINA_LIST_FOREACH(_eeze_disks, l, disk) + { + if (!strcmp(src, eeze_disk_devpath_get(disk))) + { + found = EINA_TRUE; + break; + } + } + if (!found) continue; + switch (change) + { + case MNT_TABDIFF_MOUNT: + disk->mounted = EINA_TRUE; + eina_stringshare_replace(&disk->mount_point, mnt_fs_get_target(new)); + if (disk->mount_status) break; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_MOUNT, e, NULL, NULL); + } + break; + case MNT_TABDIFF_UMOUNT: + if (!mnt_fs_get_target(new)) + disk->mounted = EINA_FALSE; + eina_stringshare_replace(&disk->mount_point, NULL); + if (disk->mount_status) break; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + } + break; + /* anything could have happened here, send both events to flush */ + case MNT_TABDIFF_REMOUNT: + case MNT_TABDIFF_MOVE: + if (!mnt_fs_get_target(new)) + disk->mounted = EINA_FALSE; + eina_stringshare_replace(&disk->mount_point, mnt_fs_get_target(new)); + if (disk->mount_status) break; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + } + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_MOUNT, e, NULL, NULL); + } + default: + break; + } + } + + mnt_free_cache(_eeze_mount_mtab_cache); + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + mnt_free_table(_eeze_mount_mtab); + _eeze_mount_mtab = tb_new; + return ECORE_CALLBACK_RENEW; +err: + if (tb_new) mnt_free_table(tb_new); + if (itr) mnt_free_iter(itr); + if (diff) mnt_free_tabdiff(diff); + return ECORE_CALLBACK_RENEW; +} + +/* + * + * INVISIBLE + * + */ + +Eina_Bool +eeze_libmount_init(void) +{ + /* placeholder */ + return EINA_TRUE; +} + +void +eeze_libmount_shutdown(void) +{ + if (_eeze_mount_fstab) + { + mnt_free_table(_eeze_mount_fstab); + mnt_free_cache(_eeze_mount_fstab_cache); + } + if (_eeze_mount_mtab) + { + mnt_free_table(_eeze_mount_mtab); + mnt_free_cache(_eeze_mount_mtab_cache); + } + eeze_mount_tabs_unwatch(); +} + +unsigned long +eeze_disk_libmount_opts_get(Eeze_Disk *disk) +{ + libmnt_fs *mnt; + const char *opts; + unsigned long f = 0; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return 0; + + mnt = mnt_table_find_tag(_eeze_mount_mtab, "UUID", eeze_disk_uuid_get(disk), MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_tag(_eeze_mount_fstab, "UUID", eeze_disk_uuid_get(disk), MNT_ITER_BACKWARD); + + if (!mnt) return 0; + + opts = mnt_fs_get_fs_options(mnt); + if (!opts) return 0; + if (!mnt_optstr_get_flags(opts, &f, eeze_optmap)) return 0; + return f; +} + +/* + * helper function to return whether a disk is mounted + */ +Eina_Bool +eeze_disk_libmount_mounted_get(Eeze_Disk *disk) +{ + libmnt_fs *mnt; + + if (!disk) + return EINA_FALSE; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return EINA_FALSE; + + mnt = mnt_table_find_source(_eeze_mount_mtab, eeze_disk_devpath_get(disk), MNT_ITER_BACKWARD); + if (!mnt) + { + disk->mounted = EINA_FALSE; + return EINA_FALSE; + } + + eina_stringshare_replace(&disk->mount_point, mnt_fs_get_target(mnt)); + disk->mounted = EINA_TRUE; + return EINA_TRUE; +} + + +/* + * helper function to return the device that is mounted at a mount point + */ +const char * +eeze_disk_libmount_mp_find_source(const char *mount_point) +{ + libmnt_fs *mnt; + + if (!mount_point) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_target(_eeze_mount_mtab, mount_point, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_target(_eeze_mount_fstab, mount_point, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_source(mnt); +} + +/* + * helper function to return a mount point from a uuid + */ +const char * +eeze_disk_libmount_mp_lookup_by_uuid(const char *uuid) +{ + libmnt_fs *mnt; + + if (!uuid) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_tag(_eeze_mount_fstab, "UUID", uuid, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a label + */ +const char * +eeze_disk_libmount_mp_lookup_by_label(const char *label) +{ + libmnt_fs *mnt; + + if (!label) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_tag(_eeze_mount_fstab, "LABEL", label, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a /dev/ path + */ +const char * +eeze_disk_libmount_mp_lookup_by_devpath(const char *devpath) +{ + libmnt_fs *mnt; + + if (!devpath) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_table_find_srcpath(_eeze_mount_mtab, devpath, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_table_find_srcpath(_eeze_mount_fstab, devpath, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * + * API + * + */ + +EAPI Eina_Bool +eeze_mount_tabs_watch(void) +{ + libmnt_table *bak; + + if (_watching) + return EINA_TRUE; + + bak = _eeze_mount_tab_parse("/proc/self/mountinfo"); + EINA_SAFETY_ON_NULL_GOTO(bak, error); + + mnt_free_table(_eeze_mount_mtab); + _eeze_mount_mtab = bak; + bak = _eeze_mount_tab_parse("/etc/fstab"); + EINA_SAFETY_ON_NULL_GOTO(bak, error); + + mnt_free_table(_eeze_mount_fstab); + _eeze_mount_fstab = bak; + + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + + _mountinfo = open("/proc/self/mountinfo", O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (_mountinfo < 0) goto error; + if (fcntl(_mountinfo, F_SETFL, O_NONBLOCK) < 0) goto error; + + _mountinfo_fdh = ecore_main_fd_handler_file_add(_mountinfo, ECORE_FD_ERROR, _eeze_mount_fdh, NULL, NULL, NULL); + if (!_mountinfo_fdh) goto error; + _fstab_mon = ecore_file_monitor_add("/etc/fstab", _eeze_mount_tab_watcher, NULL); + _watching = EINA_TRUE; + + return EINA_TRUE; + +error: + if (_mountinfo >= 0) close(_mountinfo); + _mountinfo = -1; + if (!_eeze_mount_mtab) + ERR("Could not parse /proc/self/mountinfo!"); + else + { + ERR("Could not parse /etc/fstab!"); + mnt_free_table(_eeze_mount_mtab); + } + _eeze_mount_mtab = _eeze_mount_fstab = NULL; + return EINA_FALSE; +} + +EAPI void +eeze_mount_tabs_unwatch(void) +{ + if (!_watching) + return; + + ecore_main_fd_handler_del(_mountinfo_fdh); + ecore_file_monitor_del(_fstab_mon); + close(_mountinfo); + _mountinfo = -1; + _fstab_mon = NULL; + _watching = EINA_FALSE; +} + +EAPI Eina_Bool +eeze_mount_mtab_scan(void) +{ + libmnt_table *bak; + + if (_watching) + return EINA_TRUE; + + bak = _eeze_mount_tab_parse("/proc/self/mountinfo"); + if (!bak) + goto error; + if (_eeze_mount_mtab) + { + mnt_free_table(_eeze_mount_mtab); + mnt_free_cache(_eeze_mount_mtab_cache); + } + _eeze_mount_mtab = bak; + _eeze_mount_mtab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_mtab, _eeze_mount_mtab_cache); + + return EINA_TRUE; + +error: + return EINA_FALSE; +} + +EAPI Eina_Bool +eeze_mount_fstab_scan(void) +{ + libmnt_table *bak; + if (_watching) + return EINA_TRUE; + + bak = _eeze_mount_tab_parse("/etc/fstab"); + if (!bak) + goto error; + if (_eeze_mount_fstab) + { + mnt_free_table(_eeze_mount_fstab); + mnt_free_cache(_eeze_mount_fstab_cache); + } + _eeze_mount_fstab = bak; + _eeze_mount_fstab_cache = mnt_new_cache(); + mnt_table_set_cache(_eeze_mount_fstab, _eeze_mount_fstab_cache); + + return EINA_TRUE; + +error: + return EINA_FALSE; +} diff --git a/src/lib/eeze_disk_libmount_old.c b/src/lib/eeze_disk_libmount_old.c new file mode 100644 index 0000000..20df62e --- /dev/null +++ b/src/lib/eeze_disk_libmount_old.c @@ -0,0 +1,401 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef USE_UNSTABLE_LIBMOUNT_API +# define USE_UNSTABLE_LIBMOUNT_API 1 +#endif + +#include +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" +/* + * + * PRIVATE + * + */ +static Ecore_File_Monitor *_mtab_mon = NULL; +static Ecore_File_Monitor *_fstab_mon = NULL; +static Eina_Bool _watching = EINA_FALSE; +static Eina_Bool _mtab_scan_active = EINA_FALSE; +static Eina_Bool _fstab_scan_active = EINA_FALSE; +static mnt_tab *_eeze_mount_mtab = NULL; +static mnt_tab *_eeze_mount_fstab = NULL; +static mnt_lock *_eeze_mtab_lock = NULL; +extern Eina_List *_eeze_disks; + +static mnt_tab *_eeze_mount_tab_parse(const char *filename); +static void _eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path); + +static Eina_Bool +_eeze_mount_lock_mtab(void) +{ + DBG("Locking mlock: %s", mnt_lock_get_linkfile(_eeze_mtab_lock)); +#if 0 +#warning this code is broken with current libmount! + if (mnt_lock_file(_eeze_mtab_lock)) + { + ERR("Couldn't lock mtab!"); + return EINA_FALSE; + } +#endif + return EINA_TRUE; +} + +static void +_eeze_mount_unlock_mtab(void) +{ + DBG("Unlocking mlock: %s", mnt_lock_get_linkfile(_eeze_mtab_lock)); + mnt_unlock_file(_eeze_mtab_lock); +} + +/* + * I could use mnt_new_tab_from_file() but this way gives much more detailed output + * on failure so why not + */ +static mnt_tab * +_eeze_mount_tab_parse(const char *filename) +{ + mnt_tab *tab; + + if (!(tab = mnt_new_tab(filename))) + return NULL; + if (!mnt_tab_parse_file(tab)) + return tab; + + if (mnt_tab_get_nerrs(tab)) + { /* parse error */ + char buf[1024]; + + mnt_tab_strerror(tab, buf, sizeof(buf)); + ERR("%s", buf); + } + else + /* system error */ + ERR("%s", mnt_tab_get_name(tab)); + mnt_free_tab(tab); + return NULL; +} + +static void +_eeze_mount_tab_watcher(void *data, Ecore_File_Monitor *mon __UNUSED__, Ecore_File_Event event __UNUSED__, const char *path) +{ + mnt_tab *bak; + + if ( + ((_mtab_scan_active) && (data)) || /* mtab has non-null data to avoid needing strcmp */ + ((_fstab_scan_active) && (!data)) + ) + /* prevent scans from triggering a scan */ + return; + + bak = _eeze_mount_mtab; + if (data) + if (!_eeze_mount_lock_mtab()) + { /* FIXME: maybe queue job here? */ + ERR("Losing events..."); + return; + } + _eeze_mount_mtab = _eeze_mount_tab_parse(path); + if (data) + _eeze_mount_unlock_mtab(); + if (!_eeze_mount_mtab) + { + ERR("Could not parse %s! keeping old tab...", path); + goto error; + } + + if (data) + { + Eina_List *l; + Eeze_Disk *disk; + + /* catch externally initiated mounts on existing disks by comparing known mount state to current state */ + EINA_LIST_FOREACH(_eeze_disks, l, disk) + { + Eina_Bool mounted; + + mounted = disk->mounted; + + if ((eeze_disk_libmount_mounted_get(disk) != mounted) && (!disk->mount_status)) + { + if (!mounted) + { + Eeze_Event_Disk_Mount *e; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_MOUNT, e, NULL, NULL); + } + } + else + { + Eeze_Event_Disk_Unmount *e; + e = malloc(sizeof(Eeze_Event_Disk_Unmount)); + if (e) + { + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + } + } + } + } + } + + mnt_free_tab(bak); + return; + +error: + mnt_free_tab(_eeze_mount_mtab); + _eeze_mount_mtab = bak; +} + +/* + * + * INVISIBLE + * + */ + +Eina_Bool +eeze_libmount_init(void) +{ + if (_eeze_mtab_lock) + return EINA_TRUE; + if (!(_eeze_mtab_lock = mnt_new_lock(NULL, 0))) + return EINA_FALSE; + return EINA_TRUE; +} + +void +eeze_libmount_shutdown(void) +{ + if (!_eeze_mtab_lock) + return; + + mnt_unlock_file(_eeze_mtab_lock); + mnt_free_lock(_eeze_mtab_lock); + _eeze_mtab_lock = NULL; +} + +/* + * helper function to return whether a disk is mounted + */ +Eina_Bool +eeze_disk_libmount_mounted_get(Eeze_Disk *disk) +{ + mnt_fs *mnt; + + if (!disk) + return EINA_FALSE; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return EINA_FALSE; + + mnt = mnt_tab_find_srcpath(_eeze_mount_mtab, eeze_disk_devpath_get(disk), MNT_ITER_BACKWARD); + if (!mnt) + { + disk->mounted = EINA_FALSE; + return EINA_FALSE; + } + + disk->mount_point = eina_stringshare_add(mnt_fs_get_target(mnt)); + disk->mounted = EINA_TRUE; + return EINA_TRUE; +} + + +/* + * helper function to return the device that is mounted at a mount point + */ +const char * +eeze_disk_libmount_mp_find_source(const char *mount_point) +{ + mnt_fs *mnt; + + if (!mount_point) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_tab_find_target(_eeze_mount_mtab, mount_point, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_tab_find_target(_eeze_mount_fstab, mount_point, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_source(mnt); +} + +/* + * helper function to return a mount point from a uuid + */ +const char * +eeze_disk_libmount_mp_lookup_by_uuid(const char *uuid) +{ + mnt_fs *mnt; + + if (!uuid) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_tab_find_tag(_eeze_mount_fstab, "UUID", uuid, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a label + */ +const char * +eeze_disk_libmount_mp_lookup_by_label(const char *label) +{ + mnt_fs *mnt; + + if (!label) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_tab_find_tag(_eeze_mount_fstab, "LABEL", label, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * helper function to return a mount point from a /dev/ path + */ +const char * +eeze_disk_libmount_mp_lookup_by_devpath(const char *devpath) +{ + mnt_fs *mnt; + + if (!devpath) + return NULL; + + if (!eeze_mount_mtab_scan() || !eeze_mount_fstab_scan()) + return NULL; + + mnt = mnt_tab_find_srcpath(_eeze_mount_mtab, devpath, MNT_ITER_BACKWARD); + if (!mnt) + mnt = mnt_tab_find_srcpath(_eeze_mount_fstab, devpath, MNT_ITER_BACKWARD); + + if (!mnt) + return NULL; + + return mnt_fs_get_target(mnt); +} + +/* + * + * API + * + */ +EAPI Eina_Bool +eeze_mount_tabs_watch(void) +{ + mnt_tab *bak; + + if (_watching) + return EINA_TRUE; + + if (!_eeze_mount_lock_mtab()) + return EINA_FALSE; + + bak = _eeze_mount_tab_parse("/etc/mtab"); + _eeze_mount_unlock_mtab(); + if (!bak) + goto error; + + mnt_free_tab(_eeze_mount_mtab); + _eeze_mount_mtab = bak; + if (!(bak = _eeze_mount_tab_parse("/etc/fstab"))) + goto error; + + mnt_free_tab(_eeze_mount_fstab); + _eeze_mount_fstab = bak; + + _mtab_mon = ecore_file_monitor_add("/etc/mtab", _eeze_mount_tab_watcher, (void*)1); + _fstab_mon = ecore_file_monitor_add("/etc/fstab", _eeze_mount_tab_watcher, NULL); + _watching = EINA_TRUE; + + return EINA_TRUE; + +error: + if (!_eeze_mount_mtab) + ERR("Could not parse /etc/mtab!"); + else + { + ERR("Could not parse /etc/fstab!"); + mnt_free_tab(_eeze_mount_mtab); + } + return EINA_FALSE; +} + +EAPI void +eeze_mount_tabs_unwatch(void) +{ + if (!_watching) + return; + + ecore_file_monitor_del(_mtab_mon); + ecore_file_monitor_del(_fstab_mon); +} + +EAPI Eina_Bool +eeze_mount_mtab_scan(void) +{ + mnt_tab *bak; + + if (_watching) + return EINA_TRUE; + + if (!_eeze_mount_lock_mtab()) + return EINA_FALSE; + bak = _eeze_mount_tab_parse("/etc/mtab"); + _eeze_mount_unlock_mtab(); + if (!bak) + goto error; + if (_eeze_mount_mtab) + mnt_free_tab(_eeze_mount_mtab); + _eeze_mount_mtab = bak; + return EINA_TRUE; + +error: + return EINA_FALSE; +} + +EAPI Eina_Bool +eeze_mount_fstab_scan(void) +{ + mnt_tab *bak; + if (_watching) + return EINA_TRUE; + + bak = _eeze_mount_tab_parse("/etc/fstab"); + if (!bak) + goto error; + if (_eeze_mount_fstab) + mnt_free_tab(_eeze_mount_fstab); + _eeze_mount_fstab = bak; + + return EINA_TRUE; + +error: + return EINA_FALSE; +} diff --git a/src/lib/eeze_disk_mount.c b/src/lib/eeze_disk_mount.c new file mode 100644 index 0000000..24c0ae4 --- /dev/null +++ b/src/lib/eeze_disk_mount.c @@ -0,0 +1,465 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" + +#define EEZE_MOUNT_DEFAULT_OPTS "noexec,nosuid,utf8" + +EAPI int EEZE_EVENT_DISK_MOUNT = 0; +EAPI int EEZE_EVENT_DISK_UNMOUNT = 0; +EAPI int EEZE_EVENT_DISK_EJECT = 0; +EAPI int EEZE_EVENT_DISK_ERROR = 0; +static Ecore_Event_Handler *_mount_handler = NULL; +Eina_List *eeze_events = NULL; + +/* + * + * PRIVATE + * + */ + +static void +_eeze_disk_mount_error_free(void *data __UNUSED__, Eeze_Event_Disk_Error *de) +{ + if (!de) + return; + + eina_stringshare_del(de->message); + free(de); +} + +static void +_eeze_disk_mount_error_handler(Eeze_Disk *disk, const char *error) +{ + Eeze_Event_Disk_Error *de; + + ERR("%s", error); + if (!(de = calloc(1, sizeof(Eeze_Event_Disk_Error)))) + return; + + de->disk = disk; + de->message = eina_stringshare_add(error); + /* FIXME: placeholder since currently there are only mount-type errors */ + ecore_event_add(EEZE_EVENT_DISK_ERROR, de, (Ecore_End_Cb)_eeze_disk_mount_error_free, NULL); +} + +static Eina_Bool +_eeze_disk_mount_result_handler(void *data __UNUSED__, int type __UNUSED__, Ecore_Exe_Event_Del *ev) +{ + Eeze_Disk *disk; + Eina_List *l; + Eeze_Event_Disk_Mount *e; + + if ((!ev) || (!ev->exe)) + return ECORE_CALLBACK_RENEW; + disk = ecore_exe_data_get(ev->exe); + + if ((!disk) || (!eeze_events) || (!(l = eina_list_data_find_list(eeze_events, disk)))) + return ECORE_CALLBACK_RENEW; + + eeze_events = eina_list_remove_list(eeze_events, l); + if (!disk->mounter) /* killed */ + { + disk->mount_status = EEZE_DISK_NULL; + return ECORE_CALLBACK_RENEW; + } + if (disk->mount_status == EEZE_DISK_MOUNTING) + { + disk->mounter = NULL; + if (!ev->exit_code) + { + disk->mounted = EINA_TRUE; + e = malloc(sizeof(Eeze_Event_Disk_Mount)); + EINA_SAFETY_ON_NULL_RETURN_VAL(e, ECORE_CALLBACK_RENEW); + e->disk = disk; + ecore_event_add(EEZE_EVENT_DISK_MOUNT, e, NULL, NULL); + } + else if (ev->exit_code & 2) + _eeze_disk_mount_error_handler(disk, "system error (out of memory, cannot fork, no more loop devices)"); + else if (ev->exit_code & 4) + _eeze_disk_mount_error_handler(disk, "internal mount bug"); + else if (ev->exit_code & 8) + _eeze_disk_mount_error_handler(disk, "user interrupt"); + else if (ev->exit_code & 16) + _eeze_disk_mount_error_handler(disk, "problems writing or locking /etc/mtab"); + else if (ev->exit_code & 32) + _eeze_disk_mount_error_handler(disk, "mount failure"); + else if (ev->exit_code & 64) + _eeze_disk_mount_error_handler(disk, "some mount succeeded"); + else + _eeze_disk_mount_error_handler(disk, "incorrect invocation or permissions"); + } + else if (disk->mount_status == EEZE_DISK_UNMOUNTING) + switch (ev->exit_code) + { + case 0: + e = malloc(sizeof(Eeze_Event_Disk_Unmount)); + EINA_SAFETY_ON_NULL_RETURN_VAL(e, ECORE_CALLBACK_RENEW); + e->disk = disk; + disk->mounter = NULL; + disk->mounted = EINA_FALSE; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + break; + + default: + if (disk->mount_fail_count++ < 3) + { + INF("Could not unmount disk, retrying"); + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->unmount_cmd), disk); + eeze_events = eina_list_append(eeze_events, disk); + } + else + { + disk->mount_fail_count = 0; + _eeze_disk_mount_error_handler(disk, "Maximimum number of mount-related failures reached"); + } + return ECORE_CALLBACK_RENEW; + } + else + switch (ev->exit_code) + { + case 0: + e = malloc(sizeof(Eeze_Event_Disk_Eject)); + EINA_SAFETY_ON_NULL_RETURN_VAL(e, ECORE_CALLBACK_RENEW); + e->disk = disk; + disk->mounter = NULL; + if (disk->mount_status & EEZE_DISK_UNMOUNTING) + { + disk->mount_status |= EEZE_DISK_UNMOUNTING; + disk->mounted = EINA_FALSE; + ecore_event_add(EEZE_EVENT_DISK_UNMOUNT, e, NULL, NULL); + eeze_disk_eject(disk); + } + else + ecore_event_add(EEZE_EVENT_DISK_EJECT, e, NULL, NULL); + break; + + default: + if (disk->mount_fail_count++ < 3) + { + INF("Could not eject disk, retrying"); + if (disk->mount_status & EEZE_DISK_UNMOUNTING) + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->unmount_cmd), disk); + else + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->eject_cmd), disk); + eeze_events = eina_list_append(eeze_events, disk); + } + else + { + disk->mount_fail_count = 0; + _eeze_disk_mount_error_handler(disk, "Maximimum number of mount-related failures reached"); + } + return ECORE_CALLBACK_RENEW; + } + return ECORE_CALLBACK_RENEW; +} + +/* + * + * INVISIBLE + * + */ + +Eina_Bool +eeze_mount_init(void) +{ + EEZE_EVENT_DISK_MOUNT = ecore_event_type_new(); + EEZE_EVENT_DISK_UNMOUNT = ecore_event_type_new(); + EEZE_EVENT_DISK_EJECT = ecore_event_type_new(); + EEZE_EVENT_DISK_ERROR = ecore_event_type_new(); + _mount_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, + (Ecore_Event_Handler_Cb)_eeze_disk_mount_result_handler, NULL); + return eeze_libmount_init(); +} + +void +eeze_mount_shutdown(void) +{ + eeze_libmount_shutdown(); + ecore_event_handler_del(_mount_handler); + _mount_handler = NULL; +} + +/* + * + * API + * + */ + +EAPI Eina_Bool +eeze_disk_mounted_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + return eeze_disk_libmount_mounted_get(disk); +} + +EAPI Eina_Bool +eeze_disk_mountopts_set(Eeze_Disk *disk, unsigned long opts) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + if (opts != disk->mount_opts) + disk->mount_cmd_changed = EINA_TRUE; + disk->mount_opts = opts; + if (opts & EEZE_DISK_MOUNTOPT_UID) + disk->uid = getuid(); + return EINA_TRUE; +} + +EAPI unsigned long +eeze_disk_mountopts_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, 0); +#ifndef OLD_LIBMOUNT + if (!disk->mount_opts) + disk->mount_opts = eeze_disk_libmount_opts_get(disk); +#endif + return disk->mount_opts; +} + +EAPI Eina_Bool +eeze_disk_mount_wrapper_set(Eeze_Disk *disk, const char *wrapper) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + if (wrapper) EINA_SAFETY_ON_TRUE_RETURN_VAL(!*wrapper, EINA_FALSE); + else + { + eina_stringshare_del(disk->mount_wrapper); + disk->mount_wrapper = NULL; + return EINA_TRUE; + } + eina_stringshare_replace(&disk->mount_wrapper, wrapper); + return EINA_TRUE; +} + +EAPI const char * +eeze_disk_mount_wrapper_get(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + return disk->mount_wrapper; +} + +EAPI Eina_Bool +eeze_disk_mount(Eeze_Disk *disk) +{ + struct stat st; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + if ((!disk->mount_point) && eeze_disk_libmount_mounted_get(disk)) + return EINA_FALSE; + + if (!disk->mount_cmd) + disk->mount_cmd = eina_strbuf_new(); + + if (disk->mount_cmd_changed) + { + const char *dev, *str; + eina_strbuf_string_free(disk->mount_cmd); + dev = eeze_disk_uuid_get(disk); + if (dev) str = "UUID="; + else + { + dev = eeze_disk_devpath_get(disk); + str = NULL; + } + + if (!disk->mount_point) + { + const char *mp; + /* here we attempt to guess the mount point using libmount */ + mp = eeze_disk_libmount_mp_lookup_by_uuid(disk->cache.uuid); + if (!mp) + { + const char *devpath; + + devpath = eeze_disk_devpath_get(disk); + if (devpath) + { + mp = eeze_disk_libmount_mp_lookup_by_devpath(devpath); + eina_stringshare_del(devpath); + } + } + if (!eeze_disk_mount_point_set(disk, mp)) + /* sometimes we fail */ + return EINA_FALSE; + } + + if ((!disk->mount_point) || (!disk->mount_point[0])) return EINA_FALSE; + if (disk->mount_wrapper) + eina_strbuf_append_printf(disk->mount_cmd, "%s ", disk->mount_wrapper); + if (disk->mount_opts == EEZE_DISK_MOUNTOPT_DEFAULTS) + eina_strbuf_append_printf(disk->mount_cmd, EEZE_MOUNT_BIN" -o "EEZE_MOUNT_DEFAULT_OPTS" %s%s %s", str ?: "", dev, disk->mount_point); + else if (!disk->mount_opts) + eina_strbuf_append_printf(disk->mount_cmd, EEZE_MOUNT_BIN" %s%s %s", str ?: "", dev, disk->mount_point); + else + { + eina_strbuf_append(disk->mount_cmd, EEZE_MOUNT_BIN" -o "); + /* trailing commas are okay */ + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_LOOP) + eina_strbuf_append(disk->mount_cmd, "loop,"); + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_UTF8) + { + const char *fstype; + eina_strbuf_append(disk->mount_cmd, "utf8,"); + fstype = eeze_disk_fstype_get(disk); + if (fstype && (!strcmp(fstype, "jfs"))) + eina_strbuf_append(disk->mount_cmd, "iocharset=utf8,"); + } + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_NOEXEC) + eina_strbuf_append(disk->mount_cmd, "noexec,"); + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_NODEV) + eina_strbuf_append(disk->mount_cmd, "nodev,"); + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_NOSUID) + eina_strbuf_append(disk->mount_cmd, "nosuid,"); + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_REMOUNT) + eina_strbuf_append(disk->mount_cmd, "remount,"); + if (disk->mount_opts & EEZE_DISK_MOUNTOPT_UID) + eina_strbuf_append_printf(disk->mount_cmd, "uid=%i,", (int)disk->uid); + eina_strbuf_append_printf(disk->mount_cmd, " %s%s %s", str ?: "", dev, disk->mount_point); + } + disk->mount_cmd_changed = EINA_FALSE; + } + + if (stat(disk->mount_point, &st)) + { + INF("Creating not-existing mount point directory '%s'", disk->mount_point); + if (mkdir(disk->mount_point, S_IROTH | S_IWOTH | S_IXOTH)) + ERR("Could not create directory: %s; hopefully this is handled by your mounter!", strerror(errno)); + } + else if (!S_ISDIR(st.st_mode)) + { + ERR("%s is not a directory!", disk->mount_point); + return EINA_FALSE; + } + INF("Mounting: %s", eina_strbuf_string_get(disk->mount_cmd)); + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->mount_cmd), disk); + if (!disk->mounter) + return EINA_FALSE; + eeze_events = eina_list_append(eeze_events, disk); + disk->mount_status = EEZE_DISK_MOUNTING; + + return EINA_TRUE; +} + +EAPI Eina_Bool +eeze_disk_unmount(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + if (!eeze_disk_libmount_mounted_get(disk)) + return EINA_TRUE; + + if (!disk->unmount_cmd) + disk->unmount_cmd = eina_strbuf_new(); + + if (disk->unmount_cmd_changed) + { + eina_strbuf_string_free(disk->unmount_cmd); + if (disk->mount_wrapper) + eina_strbuf_append_printf(disk->unmount_cmd, "%s ", disk->mount_wrapper); + eina_strbuf_append_printf(disk->unmount_cmd, EEZE_UNMOUNT_BIN" %s", eeze_disk_devpath_get(disk)); + disk->unmount_cmd_changed = EINA_FALSE; + } + + INF("Unmounting: %s", eina_strbuf_string_get(disk->unmount_cmd)); + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->unmount_cmd), disk); + if (!disk->mounter) + return EINA_FALSE; + + eeze_events = eina_list_append(eeze_events, disk); + disk->mount_status = EEZE_DISK_UNMOUNTING; + return EINA_TRUE; +} + +EAPI Eina_Bool +eeze_disk_eject(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + if (!disk->eject_cmd) + { + disk->eject_cmd = eina_strbuf_new(); + if (disk->mount_wrapper) + eina_strbuf_append_printf(disk->eject_cmd, "%s ", disk->mount_wrapper); + eina_strbuf_append_printf(disk->eject_cmd, EEZE_EJECT_BIN" %s", eeze_disk_devpath_get(disk)); + } + + INF("Ejecting: %s", eina_strbuf_string_get(disk->eject_cmd)); + if (eeze_disk_libmount_mounted_get(disk)) + { + Eina_Bool ret; + + ret = eeze_disk_unmount(disk); + if (ret) disk->mount_status |= EEZE_DISK_EJECTING; + return ret; + } + disk->mounter = ecore_exe_run(eina_strbuf_string_get(disk->eject_cmd), disk); + if (!disk->mounter) + return EINA_FALSE; + + eeze_events = eina_list_append(eeze_events, disk); + disk->mount_status = EEZE_DISK_EJECTING; + return EINA_TRUE; +} + +EAPI void +eeze_disk_cancel(Eeze_Disk *disk) +{ + EINA_SAFETY_ON_NULL_RETURN(disk); + if ((!disk->mount_status) || (!disk->mounter)) return; + disk->mount_status = EEZE_DISK_NULL; + ecore_exe_kill(disk->mounter); + disk->mounter = NULL; +} + +EAPI const char * +eeze_disk_mount_point_get(Eeze_Disk *disk) +{ + const char *mp; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + if (disk->mount_point) + return disk->mount_point; + + mp = eeze_disk_libmount_mp_lookup_by_devpath(eeze_disk_devpath_get(disk)); + if (mp) + { + disk->mount_point = eina_stringshare_add(mp); + return disk->mount_point; + } + mp = eeze_disk_libmount_mp_lookup_by_uuid(eeze_disk_uuid_get(disk)); + if (mp) + { + disk->mount_point = eina_stringshare_add(mp); + return disk->mount_point; + } + mp = eeze_disk_libmount_mp_lookup_by_label(eeze_disk_label_get(disk)); + if (mp) + { + disk->mount_point = eina_stringshare_add(mp); + return disk->mount_point; + } + return NULL; +} + +EAPI Eina_Bool +eeze_disk_mount_point_set(Eeze_Disk *disk, const char *mount_point) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + + eina_stringshare_replace(&disk->mount_point, mount_point); + disk->mount_cmd_changed = EINA_TRUE; + disk->unmount_cmd_changed = EINA_TRUE; + return EINA_TRUE; +} diff --git a/src/lib/eeze_disk_private.h b/src/lib/eeze_disk_private.h new file mode 100644 index 0000000..0174b9e --- /dev/null +++ b/src/lib/eeze_disk_private.h @@ -0,0 +1,93 @@ +#ifndef EEZE_DISK_PRIVATE_H +#define EEZE_DISK_PRIVATE_H +#include +#include + +#ifndef EEZE_DISK_COLOR_DEFAULT +#define EEZE_DISK_COLOR_DEFAULT EINA_COLOR_LIGHTBLUE +#endif +extern int _eeze_disk_log_dom; +#ifdef CRI +#undef CRI +#endif + +#ifdef ERR +#undef ERR +#endif +#ifdef INF +#undef INF +#endif +#ifdef WARN +#undef WARN +#endif +#ifdef DBG +#undef DBG +#endif + +#define CRI(...) EINA_LOG_DOM_CRIT(_eeze_disk_log_dom, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_eeze_disk_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_eeze_disk_log_dom, __VA_ARGS__) +#define WARN(...) EINA_LOG_DOM_WARN(_eeze_disk_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_eeze_disk_log_dom, __VA_ARGS__) + +typedef enum +{ + EEZE_DISK_NULL = 0, + EEZE_DISK_MOUNTING = 1, + EEZE_DISK_UNMOUNTING = 2, + EEZE_DISK_EJECTING = 4 +} Eeze_Disk_Status; + +struct _Eeze_Disk +{ + _udev_device *device; + void *data; + + int mount_status; + Eina_Strbuf *mount_cmd; + Eina_Strbuf *unmount_cmd; + Eina_Strbuf *eject_cmd; + Eina_Bool mount_cmd_changed : 1; + Eina_Bool unmount_cmd_changed : 1; + Eina_Bool mounted : 1; + Ecore_Exe *mounter; + unsigned int mount_fail_count; + + const char *syspath; + const char *devpath; + const char *fstype; + const char *mount_point; + const char *mount_wrapper; + unsigned long mount_opts; + uid_t uid; + + struct + { + Eeze_Disk_Type type; + Eina_Bool removable : 1; + const char *vendor; + const char *model; + const char *serial; + const char *uuid; + const char *label; + Eina_Bool filled : 1; + } cache; +}; + +Eina_Bool eeze_disk_init(void); +void eeze_disk_shutdown(void); + +Eina_Bool eeze_mount_init(void); +void eeze_mount_shutdown(void); + +Eina_Bool eeze_libmount_init(void); +void eeze_libmount_shutdown(void); +Eina_Bool eeze_disk_libmount_mounted_get(Eeze_Disk *disk); +unsigned long eeze_disk_libmount_opts_get(Eeze_Disk *disk); +const char *eeze_disk_libmount_mp_find_source(const char *mount_point); + +const char *eeze_disk_libmount_mp_lookup_by_uuid(const char *uuid); +const char *eeze_disk_libmount_mp_lookup_by_label(const char *label); +const char *eeze_disk_libmount_mp_lookup_by_devpath(const char *devpath); + +#endif diff --git a/src/lib/eeze_disk_udev.c b/src/lib/eeze_disk_udev.c new file mode 100644 index 0000000..ef7b160 --- /dev/null +++ b/src/lib/eeze_disk_udev.c @@ -0,0 +1,90 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_disk_private.h" + +EAPI const char * +eeze_disk_udev_get_property(Eeze_Disk *disk, const char *property) +{ + const char *ret; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(property, NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(!*property, NULL); + + ret = udev_device_get_property_value(disk->device, property); + return eina_stringshare_add(ret); +} + +EAPI const char * +eeze_disk_udev_get_sysattr(Eeze_Disk *disk, const char *sysattr) +{ + const char *ret; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(sysattr, NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(!*sysattr, NULL); + + ret = udev_device_get_sysattr_value(disk->device, sysattr); + return eina_stringshare_add(ret); +} + +EAPI const char * +eeze_disk_udev_get_parent(Eeze_Disk *disk) +{ + _udev_device *parent; + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + + parent = udev_device_get_parent(disk->device); + return eina_stringshare_add(udev_device_get_syspath(parent)); +} + +EAPI Eina_Bool +eeze_disk_udev_walk_check_sysattr(Eeze_Disk *disk, + const char *sysattr, + const char *value) +{ + _udev_device *child, *parent; + const char *test = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(sysattr, EINA_FALSE); + EINA_SAFETY_ON_TRUE_RETURN_VAL(!*sysattr, EINA_FALSE); + + for (parent = disk->device; parent; + child = parent, parent = udev_device_get_parent(child)) + { + if (!(test = udev_device_get_sysattr_value(parent, sysattr))) + continue; + if ((value && (!strcmp(test, value))) || (!value)) + { + return EINA_TRUE; + break; + } + } + return EINA_FALSE; +} + +EAPI const char * +eeze_disk_udev_walk_get_sysattr(Eeze_Disk *disk, + const char *sysattr) +{ + _udev_device *child, *parent; + const char *test = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(disk, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(sysattr, NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(!*sysattr, NULL); + + for (parent = disk->device; parent; + child = parent, parent = udev_device_get_parent(child)) + { + test = udev_device_get_sysattr_value(parent, sysattr); + if (test) return eina_stringshare_add(test); + } + return EINA_FALSE; +} diff --git a/src/lib/eeze_main.c b/src/lib/eeze_main.c new file mode 100644 index 0000000..b9954cf --- /dev/null +++ b/src/lib/eeze_main.c @@ -0,0 +1,104 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "eeze_udev_private.h" +#include "eeze_net_private.h" +#include "eeze_disk_private.h" + +_udev *udev; + +int _eeze_udev_log_dom = -1; +int _eeze_net_log_dom = -1; +int _eeze_init_count = 0; + +static Eeze_Version _version = { VMAJ, VMIN, VMIC, VREV }; +EAPI Eeze_Version *eeze_version = &_version; + +EAPI int +eeze_init(void) +{ + if (++_eeze_init_count != 1) + return _eeze_init_count; + + if (!eina_init()) + return 0; + + _eeze_udev_log_dom = eina_log_domain_register("eeze_udev", EINA_COLOR_CYAN); + if (_eeze_udev_log_dom < 0) + { + EINA_LOG_ERR("Could not register 'eeze_udev' log domain."); + goto eina_fail; + } + _eeze_net_log_dom = eina_log_domain_register("eeze_net", EINA_COLOR_GREEN); + if (_eeze_net_log_dom < 0) + { + EINA_LOG_ERR("Could not register 'eeze_net' log domain."); + goto eina_net_fail; + } + + + if (!ecore_init()) + goto ecore_fail; +#ifdef HAVE_EEZE_MOUNT + if (!eeze_disk_init()) + goto eeze_fail; +#endif + if (!(udev = udev_new())) + { + EINA_LOG_ERR("Could not initialize udev library!"); + goto fail; + } + if (!eeze_net_init()) + { + EINA_LOG_ERR("Error initializing eeze_net subsystems!"); + goto net_fail; + } + + return _eeze_init_count; + +net_fail: + udev_unref(udev); +fail: +#ifdef HAVE_EEZE_MOUNT + eeze_disk_shutdown(); +eeze_fail: +#endif + ecore_shutdown(); +ecore_fail: + eina_log_domain_unregister(_eeze_net_log_dom); + _eeze_net_log_dom = -1; +eina_net_fail: + eina_log_domain_unregister(_eeze_udev_log_dom); + _eeze_udev_log_dom = -1; +eina_fail: + eina_shutdown(); + return 0; +} + +EAPI int +eeze_shutdown(void) +{ + if (_eeze_init_count <= 0) + { + EINA_LOG_ERR("Init count not greater than 0 in shutdown."); + return 0; + } + if (--_eeze_init_count != 0) + return _eeze_init_count; + + udev_unref(udev); +#ifdef HAVE_EEZE_MOUNT + eeze_disk_shutdown(); +#endif + eeze_net_shutdown(); + ecore_shutdown(); + eina_log_domain_unregister(_eeze_udev_log_dom); + _eeze_udev_log_dom = -1; + eina_shutdown(); + return _eeze_init_count; +} + diff --git a/src/lib/eeze_net.c b/src/lib/eeze_net.c new file mode 100644 index 0000000..415f794 --- /dev/null +++ b/src/lib/eeze_net.c @@ -0,0 +1,321 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "eeze_udev_private.h" +#include "eeze_net_private.h" + +static Eina_Hash *eeze_nets = NULL; + +Eina_Bool +eeze_net_init(void) +{ + eeze_nets = eina_hash_string_superfast_new(NULL); + return !!eeze_nets; +} + +void +eeze_net_shutdown(void) +{ + eina_hash_free(eeze_nets); + eeze_nets = NULL; +} + +/** @addtogroup net Net + * @{ + */ + +/** + * @brief Create a new net object + * @param name The name of the underlying device (eth0, br1, etc) + * @return A newly allocated net object, or NULL on failure + * + * This function creates a new net object based on @p name. + * Only the most minimal lookups are performed at creation in order + * to save memory. + */ +Eeze_Net * +eeze_net_new(const char *name) +{ + const char *syspath = NULL; + const char *idx; + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + _udev_device *device = NULL; + Eeze_Net *net; + + net = eina_hash_find(eeze_nets, name); + if (net) + { + EINA_REFCOUNT_REF(net); + return net; + } + + en = udev_enumerate_new(udev); + udev_enumerate_add_match_sysname(en, name); + udev_enumerate_add_match_subsystem(en, "net"); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + const char *devname, *test; + + devname = udev_list_entry_get_name(cur); + test = strrchr(devname, '/'); + if (strcmp(++test, name)) continue; + device = _new_device(devname); + syspath = eina_stringshare_add(name); + break; + } + if (!device) return NULL; + net = calloc(1, sizeof(Eeze_Net)); + if (!net) return NULL; + EINA_REFCOUNT_INIT(net); + net->device = device; + net->syspath = syspath; + net->name = eina_stringshare_add(name); + idx = udev_device_get_sysattr_value(net->device, "ifindex"); + net->index = atoi(idx); + eina_hash_add(eeze_nets, name, net); + udev_enumerate_unref(en); + return net; +} + +/** + * @brief Free a net object + * @param net The object to free + * + * Use this function to free a net object. + * @see eeze_net_new() + * @see eeze_net_list() + */ +void +eeze_net_free(Eeze_Net *net) +{ + EINA_SAFETY_ON_NULL_RETURN(net); + + EINA_REFCOUNT_UNREF(net) + { + eina_hash_del_by_key(eeze_nets, net->name); + udev_device_unref(net->device); + eina_stringshare_del(net->syspath); + eina_stringshare_del(net->name); + eina_stringshare_del(net->ip); + eina_stringshare_del(net->broadip); + eina_stringshare_del(net->netmask); +#ifdef HAVE_IPV6 + eina_stringshare_del(net->ip6); + eina_stringshare_del(net->broadip6); + eina_stringshare_del(net->netmask6); +#endif + free(net); + } +} + +/** + * @brief Get the MAC address of a net object + * @param net The net object + * @return The MAC address, NULL on failure + * Use this function to retrieve the non-stringshared MAC address of @p net. + */ +const char * +eeze_net_mac_get(Eeze_Net *net) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(net, NULL); + + return udev_device_get_sysattr_value(net->device, "address"); +} + +/** + * @brief Get the index of a net object + * @param net The net object + * @return The ifindex of the object, -1 on failure + * Use this function to get the hardware index of @p net + */ +int +eeze_net_idx_get(Eeze_Net *net) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(net, -1); + return net->index; +} + +/** + * @brief Scan an interface to cache its network addresses + * @param net The net object to scan + * @return EINA_TRUE on success, EINA_FALSE on failure + * Use this function to scan and cache the ip address, netmask, and broadcast + * address for an interface. This function will perform a full scan every time + * it is called, and IPv6 addresses will be cached if Eeze was compiled with IPv6 + * support was enabled at compile time. + * @see eeze_net_addr_get() + */ +Eina_Bool +eeze_net_scan(Eeze_Net *net) +{ + char ip[INET_ADDRSTRLEN]; +#ifdef HAVE_IPV6 + char ip6[INET6_ADDRSTRLEN]; + struct sockaddr_in6 *sa6; +#endif + int sock; + int ioctls[3] = {SIOCGIFADDR, SIOCGIFBRDADDR, SIOCGIFNETMASK}, *i = ioctls; + struct ifreq ifr; + struct sockaddr_in *sa; + + EINA_SAFETY_ON_NULL_RETURN_VAL(net, EINA_FALSE); + + /* ensure that we have the right name, mostly for hahas */ + if_indextoname((unsigned int)net->index, ifr.ifr_name); + ifr.ifr_addr.sa_family = AF_INET; + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) return EINA_FALSE; + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa = (struct sockaddr_in*) & (ifr.ifr_addr); + inet_ntop(AF_INET, (struct in_addr*)&sa->sin_addr, ip, INET_ADDRSTRLEN); + eina_stringshare_replace_length(&net->ip, ip, INET_ADDRSTRLEN); + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa = (struct sockaddr_in*) & (ifr.ifr_broadaddr); + inet_ntop(AF_INET, (struct in_addr*)&sa->sin_addr, ip, INET_ADDRSTRLEN); + eina_stringshare_replace_length(&net->broadip, ip, INET_ADDRSTRLEN); + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa = (struct sockaddr_in*) & (ifr.ifr_netmask); + inet_ntop(AF_INET, (struct in_addr*)&sa->sin_addr, ip, INET_ADDRSTRLEN); + eina_stringshare_replace_length(&net->netmask, ip, INET_ADDRSTRLEN); + + close(sock); +#ifdef HAVE_IPV6 + i = ioctls; + ifr.ifr_addr.sa_family = AF_INET6; + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock < 0) return EINA_FALSE; + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa6 = (struct sockaddr_in6*) & (ifr.ifr_addr); + inet_ntop(AF_INET6, (struct in6_addr*)&sa6->sin6_addr, ip6, INET6_ADDRSTRLEN); + eina_stringshare_replace_length(&net->ip6, ip6, INET6_ADDRSTRLEN); + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa6 = (struct sockaddr_in6*) & (ifr.ifr_broadaddr); + inet_ntop(AF_INET6, (struct in6_addr*)&sa6->sin6_addr, ip6, INET6_ADDRSTRLEN); + eina_stringshare_replace_length(&net->broadip6, ip6, INET6_ADDRSTRLEN); + + if (ioctl(sock, *i++, &ifr) < 0) goto error; + sa6 = (struct sockaddr_in6*) & (ifr.ifr_netmask); + inet_ntop(AF_INET6, (struct in6_addr*)&sa6->sin6_addr, ip6, INET6_ADDRSTRLEN); + eina_stringshare_replace_length(&net->netmask6, ip6, INET6_ADDRSTRLEN); + + close(sock); +#endif + + return EINA_TRUE; +error: + close(sock); + return EINA_FALSE; +} + +/** + * @brief Get the address of a net object + * @param net The net object + * @param type The type of address to retrieve + * @return The stringshared address for @p net corresponding to @p type, NULL on failure + * This function returns a value previously cached. + * @see eeze_net_scan() + */ +const char * +eeze_net_addr_get(Eeze_Net *net, Eeze_Net_Addr_Type type) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(net, NULL); + + switch (type) + { + case EEZE_NET_ADDR_TYPE_IP6: +#ifdef HAVE_IPV6 + return net->ip6; +#else + return NULL; +#endif + case EEZE_NET_ADDR_TYPE_BROADCAST: + return net->broadip; + case EEZE_NET_ADDR_TYPE_BROADCAST6: +#ifdef HAVE_IPV6 + return net->broadip6; +#else + return NULL; +#endif + case EEZE_NET_ADDR_TYPE_NETMASK: + return net->netmask; + case EEZE_NET_ADDR_TYPE_NETMASK6: +#ifdef HAVE_IPV6 + return net->netmask6; +#else + return NULL; +#endif + default: + break; + } + return net->ip; +} + +/** + * @brief Get a system attribute of a net object + * @param net The net object + * @param attr The attribute to retrieve + * @return The non-stringshared value of the attribute, NULL on failure + * Use this function to perform a udev sysattr lookup on the underlying device of @p net + */ +const char * +eeze_net_attribute_get(Eeze_Net *net, const char *attr) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(net, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(attr, NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(!attr[0], NULL); + + return udev_device_get_sysattr_value(net->device, attr); +} + +/** + * @brief Get the /sys/ path of a net object + * @param net The net object + * @return The stringshared /sys/ path of the interface, NULL on failure + */ +const char * +eeze_net_syspath_get(Eeze_Net *net) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(net, NULL); + + return net->syspath; +} + +/** + * @brief Get a list of all the network interfaces available + * @return A list of Eeze_Net objects + * Use this function to get all network interfaces available to the application. + * This list must be freed by the user. + */ +Eina_List * +eeze_net_list(void) +{ + struct if_nameindex *ifs, *i; + Eina_List *ret = NULL; + Eeze_Net *net; + + ifs = if_nameindex(); + for (i = ifs; i && i->if_name && i->if_name[0]; i++) + { + net = eeze_net_new(i->if_name); + if (net) ret = eina_list_append(ret, net); + } + + if_freenameindex(ifs); + return ret; +} +/** @} */ diff --git a/src/lib/eeze_net_private.h b/src/lib/eeze_net_private.h new file mode 100644 index 0000000..d9b8faf --- /dev/null +++ b/src/lib/eeze_net_private.h @@ -0,0 +1,53 @@ +#ifndef EEZE_NET_PRIVATE_H +#define EEZE_NET_PRIVATE_H +#include +#include "eeze_udev_private.h" + +#ifndef EEZE_NET_COLOR_DEFAULT +#define EEZE_NET_COLOR_DEFAULT EINA_COLOR_GREEN +#endif +extern int _eeze_net_log_dom; +#ifdef CRI +#undef CRI +#endif + +#ifdef ERR +#undef ERR +#endif +#ifdef INF +#undef INF +#endif +#ifdef WARN +#undef WARN +#endif +#ifdef DBG +#undef DBG +#endif + +#define CRI(...) EINA_LOG_DOM_CRIT(_eeze_net_log_dom, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_eeze_net_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_eeze_net_log_dom, __VA_ARGS__) +#define WARN(...) EINA_LOG_DOM_WARN(_eeze_net_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_eeze_net_log_dom, __VA_ARGS__) + +struct Eeze_Net +{ + EINA_REFCOUNT; + int index; + _udev_device *device; + const char *syspath; + const char *name; + + const char *ip; + const char *broadip; + const char *netmask; +#ifdef HAVE_IPV6 + const char *ip6; + const char *broadip6; + const char *netmask6; +#endif +}; + +Eina_Bool eeze_net_init(void); +void eeze_net_shutdown(void); +#endif diff --git a/src/lib/eeze_udev_find.c b/src/lib/eeze_udev_find.c new file mode 100644 index 0000000..3bd06ab --- /dev/null +++ b/src/lib/eeze_udev_find.c @@ -0,0 +1,384 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include "eeze_udev_private.h" + +EAPI Eina_List * +eeze_udev_find_similar_from_syspath(const char *syspath) +{ + _udev_device *device; + _udev_list_entry *devs, *cur; + _udev_enumerate *en; + Eina_List *l, *ret = NULL; + const char *vendor, *model, *revision, *devname, *dev; + + if (!syspath) + return NULL; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + vendor = udev_device_get_property_value(device, "ID_VENDOR_ID"); + + if (vendor) + udev_enumerate_add_match_property(en, "ID_VENDOR_ID", vendor); + + model = udev_device_get_property_value(device, "ID_MODEL_ID"); + + if (model) + udev_enumerate_add_match_property(en, "ID_MODEL_ID", model); + + revision = udev_device_get_property_value(device, "ID_REVISION"); + + if (revision) + udev_enumerate_add_match_property(en, "ID_REVISION", revision); + + udev_enumerate_scan_devices(en); + udev_device_unref(device); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + /* verify unlisted device */ + + EINA_LIST_FOREACH(ret, l, dev) + if (!strcmp(dev, devname)) + continue; + + ret = eina_list_prepend(ret, eina_stringshare_add(devname)); + device = udev_device_new_from_syspath(udev, devname); + + /* only device roots have this sysattr, + * and we only need to check parents of the roots + */ + if (udev_device_get_sysattr_value(device, "idVendor")) + ret = _get_unlisted_parents(ret, device); + + udev_device_unref(device); + } + udev_enumerate_unref(en); + return ret; +} + +EAPI Eina_List * +eeze_udev_find_unlisted_similar(Eina_List *list) +{ + _udev_device *device; + _udev_list_entry *devs, *cur; + _udev_enumerate *en; + Eina_List *l; + const char *vendor, *model, *revision, *devname, *dev; + + if (!list) + return NULL; + + EINA_LIST_FOREACH(list, l, dev) + { + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + device = _new_device(dev); + if (!device) continue; + + if ((vendor = udev_device_get_property_value(device, "ID_VENDOR_ID"))) + udev_enumerate_add_match_property(en, "ID_VENDOR_ID", vendor); + else if ((vendor = udev_device_get_property_value(device, "ID_VENDOR"))) + udev_enumerate_add_match_property(en, "ID_VENDOR", vendor); + else if ((vendor = udev_device_get_sysattr_value(device, "vendor"))) + udev_enumerate_add_match_sysattr(en, "vendor", vendor); + else if ((vendor = udev_device_get_sysattr_value(device, "manufacturer"))) + udev_enumerate_add_match_sysattr(en, "manufacturer", vendor); + + if ((model = udev_device_get_property_value(device, "ID_MODEL_ID"))) + udev_enumerate_add_match_property(en, "ID_MODEL_ID", model); + else if ((model = udev_device_get_property_value(device, "ID_MODEL"))) + udev_enumerate_add_match_property(en, "ID_MODEL", model); + else if ((model = udev_device_get_sysattr_value(device, "model"))) + udev_enumerate_add_match_sysattr(en, "model", model); + else if ((model = udev_device_get_sysattr_value(device, "product"))) + udev_enumerate_add_match_sysattr(en, "product", model); + + if ((revision = udev_device_get_property_value(device, "ID_REVISION"))) + udev_enumerate_add_match_property(en, "ID_REVISION", revision); + else if ((revision = udev_device_get_sysattr_value(device, "revision"))) + udev_enumerate_add_match_sysattr(en, "revision", revision); + + udev_enumerate_add_match_subsystem(en, udev_device_get_subsystem(device)); + + udev_enumerate_scan_devices(en); + udev_device_unref(device); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + device = udev_device_new_from_syspath(udev, devname); + + /* only device roots have this sysattr, + * and we only need to check parents of the roots + */ + if (udev_device_get_sysattr_value(device, "idVendor")) + list = _get_unlisted_parents(list, device); + + udev_device_unref(device); + } + udev_enumerate_unref(en); + } + return list; +} + +EAPI Eina_List * +eeze_udev_find_by_type(Eeze_Udev_Type etype, + const char *name) +{ + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + _udev_device *device, *parent; + const char *devname; + Eina_List *ret = NULL; + + if ((!etype) && (!name)) + return NULL; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + switch (etype) + { + case EEZE_UDEV_TYPE_NONE: + break; + + case EEZE_UDEV_TYPE_KEYBOARD: + udev_enumerate_add_match_subsystem(en, "input"); +#ifndef OLD_UDEV_RRRRRRRRRRRRRR + udev_enumerate_add_match_property(en, "ID_INPUT_KEYBOARD", "1"); +#else + udev_enumerate_add_match_property(en, "ID_CLASS", "kbd"); +#endif + break; + + case EEZE_UDEV_TYPE_MOUSE: + udev_enumerate_add_match_subsystem(en, "input"); +#ifndef OLD_UDEV_RRRRRRRRRRRRRR + udev_enumerate_add_match_property(en, "ID_INPUT_MOUSE", "1"); +#else + udev_enumerate_add_match_property(en, "ID_CLASS", "mouse"); +#endif + break; + + case EEZE_UDEV_TYPE_TOUCHPAD: + udev_enumerate_add_match_subsystem(en, "input"); +#ifndef OLD_UDEV_RRRRRRRRRRRRRR + udev_enumerate_add_match_property(en, "ID_INPUT_TOUCHPAD", "1"); +#endif + break; + + case EEZE_UDEV_TYPE_JOYSTICK: + udev_enumerate_add_match_subsystem(en, "input"); +#ifndef OLD_UDEV_RRRRRRRRRRRRRR + udev_enumerate_add_match_property(en, "ID_INPUT_JOYSTICK", "1"); +#endif + break; + + case EEZE_UDEV_TYPE_DRIVE_MOUNTABLE: + udev_enumerate_add_match_subsystem(en, "block"); + udev_enumerate_add_match_property(en, "ID_FS_USAGE", "filesystem"); + break; + + case EEZE_UDEV_TYPE_DRIVE_INTERNAL: + udev_enumerate_add_match_subsystem(en, "block"); + udev_enumerate_add_match_property(en, "ID_TYPE", "disk"); + udev_enumerate_add_match_property(en, "ID_BUS", "ata"); + udev_enumerate_add_match_sysattr(en, "removable", "0"); + break; + + case EEZE_UDEV_TYPE_DRIVE_REMOVABLE: + udev_enumerate_add_match_sysattr(en, "removable", "1"); + udev_enumerate_add_match_property(en, "ID_TYPE", "disk"); + break; + + case EEZE_UDEV_TYPE_DRIVE_CDROM: + udev_enumerate_add_match_property(en, "ID_CDROM", "1"); + break; + + case EEZE_UDEV_TYPE_POWER_AC: + udev_enumerate_add_match_subsystem(en, "power_supply"); + udev_enumerate_add_match_sysattr(en, "type", "Mains"); + break; + + case EEZE_UDEV_TYPE_POWER_BAT: + udev_enumerate_add_match_subsystem(en, "power_supply"); + udev_enumerate_add_match_sysattr(en, "type", "Battery"); + udev_enumerate_add_match_sysattr(en, "present", "1"); + break; + + case EEZE_UDEV_TYPE_NET: + udev_enumerate_add_match_subsystem(en, "net"); + break; + + case EEZE_UDEV_TYPE_IS_IT_HOT_OR_IS_IT_COLD_SENSOR: + udev_enumerate_add_match_subsystem(en, "hwmon"); + break; + + /* + case EEZE_UDEV_TYPE_ANDROID: + udev_enumerate_add_match_subsystem(en, "block"); + udev_enumerate_add_match_property(en, "ID_MODEL", "Android_*"); + break; + */ + case EEZE_UDEV_TYPE_V4L: + udev_enumerate_add_match_subsystem(en, "video4linux"); + break; + case EEZE_UDEV_TYPE_BLUETOOTH: + udev_enumerate_add_match_subsystem(en, "bluetooth"); + break; + default: + break; + } + + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + device = udev_device_new_from_syspath(udev, devname); + + if (etype == EEZE_UDEV_TYPE_IS_IT_HOT_OR_IS_IT_COLD_SENSOR) /* ensure that temp input exists somewhere in this device chain */ + { + Eina_Bool one, two; + const char *t; + + one = _walk_parents_test_attr(device, "temp1_input", NULL); + two = _walk_parents_test_attr(device, "temp2_input", NULL); + if ((!one) && (!two)) goto out; + + t = one ? "temp1_input" : "temp2_input"; + /* if device is not the one which has the temp input, we must go up the chain */ + if (!udev_device_get_sysattr_value(device, t)) + { + devname = NULL; + + for (parent = udev_device_get_parent(device); parent; parent = udev_device_get_parent(parent)) /*check for parent */ + if ((udev_device_get_sysattr_value(parent, t))) + { + devname = udev_device_get_syspath(parent); + break; + } + + if (!devname) + goto out; + } + } + else if (etype == EEZE_UDEV_TYPE_DRIVE_REMOVABLE) + { + /* this yields the actual hw device, not to be confused with the filesystem */ + devname = udev_device_get_syspath(udev_device_get_parent(device)); + } + else if (etype == EEZE_UDEV_TYPE_DRIVE_MOUNTABLE) + { + int devcheck; + + devcheck = open(udev_device_get_devnode(device), O_RDONLY); + if (devcheck < 0) goto out; + close(devcheck); + } + + if (name && (!strstr(devname, name))) + goto out; + + ret = eina_list_append(ret, eina_stringshare_add(devname)); +out: + udev_device_unref(device); + } + udev_enumerate_unref(en); + return ret; +} + +EAPI Eina_List * +eeze_udev_find_by_filter(const char *subsystem, + const char *type, + const char *name) +{ + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + _udev_device *device; + const char *devname; + Eina_List *ret = NULL; + + if ((!subsystem) && (!type) && (!name)) + return NULL; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + if (subsystem) + udev_enumerate_add_match_subsystem(en, subsystem); + + udev_enumerate_add_match_property(en, type, "1"); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + device = udev_device_new_from_syspath(udev, devname); + + if (name) + if (!strstr(devname, name)) + goto out; + + ret = eina_list_append(ret, eina_stringshare_add(devname)); +out: + udev_device_unref(device); + } + udev_enumerate_unref(en); + return ret; +} + +EAPI Eina_List * +eeze_udev_find_by_sysattr(const char *sysattr, + const char *value) +{ + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + _udev_device *device; + const char *devname; + Eina_List *ret = NULL; + + if (!sysattr) + return NULL; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + udev_enumerate_add_match_sysattr(en, sysattr, value); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + devname = udev_list_entry_get_name(cur); + device = udev_device_new_from_syspath(udev, devname); + ret = eina_list_append(ret, eina_stringshare_add(devname)); + udev_device_unref(device); + } + udev_enumerate_unref(en); + return ret; +} diff --git a/src/lib/eeze_udev_private.c b/src/lib/eeze_udev_private.c new file mode 100644 index 0000000..7e5b5dd --- /dev/null +++ b/src/lib/eeze_udev_private.c @@ -0,0 +1,200 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "eeze_udev_private.h" + +/* + * helper function to set up a new device from a syspath + * which may or may not include /sys at the beginning + */ +_udev_device * +_new_device(const char *syspath) +{ + _udev_device *device; + + device = udev_device_new_from_syspath(udev, syspath); + if (!device) + ERR("device %s does not exist!", syspath); + return device; +} + +/* + * copies a device + */ +_udev_device * +_copy_device(_udev_device *device) +{ + return udev_device_ref(device); +} + +/* + * private function to simulate udevadm info -a + * walks up the device tree checking each node for sysattr + * with value $value + */ +Eina_Bool +_walk_parents_test_attr(_udev_device *device, + const char *sysattr, + const char *value) +{ + _udev_device *parent, *child = device; + const char *test; + + if (udev_device_get_sysattr_value(device, sysattr)) + return EINA_TRUE; + + parent = udev_device_get_parent(child); + + for (; parent; child = parent, parent = udev_device_get_parent(child)) + { + if (!(test = udev_device_get_sysattr_value(parent, sysattr))) + continue; + + if (!value) + return EINA_TRUE; + else + if (!strcmp(test, value)) + return EINA_TRUE; + } + + return EINA_FALSE; +} + +const char * +_walk_parents_get_attr(_udev_device *device, + const char *sysattr, + Eina_Bool property) +{ + _udev_device *parent, *child = device; + const char *test; + + if (property) + test = udev_device_get_property_value(device, sysattr); + else + test = udev_device_get_sysattr_value(device, sysattr); + if (test) return eina_stringshare_add(test); + + parent = udev_device_get_parent(child); + + for (; parent; child = parent, parent = udev_device_get_parent(child)) + { + if (property) + test = udev_device_get_property_value(parent, sysattr); + else + test = udev_device_get_sysattr_value(parent, sysattr); + if (test) return eina_stringshare_add(test); + } + + return NULL; +} + +const char * +_walk_children_get_attr(const char *syspath, + const char *sysattr, + const char *subsystem, + Eina_Bool property) +{ + char buf[PATH_MAX]; + const char *path, *ret = NULL; + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + + en = udev_enumerate_new(udev); + EINA_SAFETY_ON_NULL_RETURN_VAL(en, NULL); + path = strrchr(syspath, '/'); + if (path) path++; + else path = syspath; + snprintf(buf, sizeof(buf), "%s*", path); + udev_enumerate_add_match_sysname(en, buf); + if (subsystem) udev_enumerate_add_match_subsystem(en, subsystem); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + const char *devname, *test; + _udev_device *device; + + devname = udev_list_entry_get_name(cur); + device = _new_device(devname); + if (property) + test = udev_device_get_property_value(device, sysattr); + else + test = udev_device_get_sysattr_value(device, sysattr); + if (test) + { + ret = eina_stringshare_add(test); + udev_device_unref(device); + break; + } + udev_device_unref(device); + } + udev_enumerate_unref(en); + return ret; +} + +/* + * check a list for all parents of a device, + * stringshare adding all devices that are not in the list + */ +Eina_List * +_get_unlisted_parents(Eina_List *list, + _udev_device *device) +{ + _udev_device *parent, *child = device; + const char *test, *devname, *vendor, *vendor2, *model, *model2; + Eina_List *l; + Eina_Bool found; + + if (!(vendor = udev_device_get_property_value(child, "ID_VENDOR_ID"))) + vendor = udev_device_get_property_value(child, "ID_VENDOR"); + if (!vendor) vendor = udev_device_get_sysattr_value(child, "vendor"); + if (!vendor) vendor = udev_device_get_sysattr_value(child, "manufacturer"); + + if (!(model = udev_device_get_property_value(child, "ID_MODEL_ID"))) + model = udev_device_get_property_value(child, "ID_MODEL"); + if (!model) model = udev_device_get_sysattr_value(child, "model"); + if (!model) model = udev_device_get_sysattr_value(child, "product"); + + parent = udev_device_get_parent(child); + + for (; parent; child = parent, parent = udev_device_get_parent(child)) + { + found = EINA_FALSE; + + if (!(vendor2 = udev_device_get_property_value(child, "ID_VENDOR_ID"))) + vendor2 = udev_device_get_property_value(child, "ID_VENDOR"); + if (!vendor2) vendor2 = udev_device_get_sysattr_value(child, "vendor"); + if (!vendor2) vendor2 = udev_device_get_sysattr_value(child, "manufacturer"); + + if (!(model2 = udev_device_get_property_value(child, "ID_MODEL_ID"))) + model2 = udev_device_get_property_value(child, "ID_MODEL"); + if (!model2) model2 = udev_device_get_sysattr_value(child, "model"); + if (!model2) model2 = udev_device_get_sysattr_value(child, "product"); + + if ((!model2 && model) || (model2 && !model) || (!vendor2 && vendor) + || (vendor2 && !vendor)) + break; + else + if (((model && model2) && (strcmp(model, model2))) || + ((vendor && vendor2) && (strcmp(vendor, vendor2)))) + break; + + devname = udev_device_get_syspath(parent); + EINA_LIST_FOREACH(list, l, test) + { + if (!strcmp(test, devname)) + { + found = EINA_TRUE; + break; + } + } + + if (!found) + list = eina_list_prepend(list, eina_stringshare_add(devname)); + } + + return list; +} + diff --git a/src/lib/eeze_udev_private.h b/src/lib/eeze_udev_private.h new file mode 100644 index 0000000..59aacbd --- /dev/null +++ b/src/lib/eeze_udev_private.h @@ -0,0 +1,46 @@ +#ifndef EEZE_UDEV_PRIVATE_H +#define EEZE_UDEV_PRIVATE_H +#include + +#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE 1 +#include + +#ifndef EEZE_UDEV_COLOR_DEFAULT +#define EEZE_UDEV_COLOR_DEFAULT EINA_COLOR_CYAN +#endif +extern int _eeze_udev_log_dom; +#ifdef ERR +#undef ERR +#endif +#ifdef INF +#undef INF +#endif +#ifdef WARN +#undef WARN +#endif +#ifdef DBG +#undef DBG +#endif + +#define DBG(...) EINA_LOG_DOM_DBG(_eeze_udev_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_eeze_udev_log_dom, __VA_ARGS__) +#define WARN(...) EINA_LOG_DOM_WARN(_eeze_udev_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_eeze_udev_log_dom, __VA_ARGS__) + +/* typedefs because I'm lazy */ +typedef struct udev _udev; +typedef struct udev_list_entry _udev_list_entry; +typedef struct udev_device _udev_device; +typedef struct udev_enumerate _udev_enumerate; +typedef struct udev_monitor _udev_monitor; + +extern _udev *udev; + +_udev_device *_new_device(const char *syspath); +const char *_walk_children_get_attr(const char *syspath, const char *sysattr, const char *subsystem, Eina_Bool property); +Eina_Bool _walk_parents_test_attr(_udev_device *device, const char *sysattr, const char* value); +const char *_walk_parents_get_attr(_udev_device *device, const char *sysattr, Eina_Bool property); +Eina_List *_get_unlisted_parents(Eina_List *list, _udev_device *device); +_udev_device *_copy_device(_udev_device *device); + +#endif diff --git a/src/lib/eeze_udev_syspath.c b/src/lib/eeze_udev_syspath.c new file mode 100644 index 0000000..df858e6 --- /dev/null +++ b/src/lib/eeze_udev_syspath.c @@ -0,0 +1,292 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "eeze_udev_private.h" + +EAPI const char * +eeze_udev_syspath_get_parent(const char *syspath) +{ + _udev_device *device, *parent; + const char *ret; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + parent = udev_device_get_parent(device); + ret = eina_stringshare_add(udev_device_get_syspath(parent)); + udev_device_unref(device); + return ret; +} + +EAPI Eina_List * +eeze_udev_syspath_get_parents(const char *syspath) +{ + _udev_device *child, *parent, *device; + const char *path; + Eina_List *devlist = NULL; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + if (!(parent = udev_device_get_parent(device))) + return NULL; + + for (; parent; child = parent, parent = udev_device_get_parent(child)) + { + path = udev_device_get_syspath(parent); + devlist = eina_list_append(devlist, eina_stringshare_add(path)); + } + + udev_device_unref(device); + return devlist; +} + +EAPI const char * +eeze_udev_syspath_get_devpath(const char *syspath) +{ + _udev_device *device; + const char *name = NULL; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + if (!(name = udev_device_get_devnode(device))) + return NULL; + + name = eina_stringshare_add(name); + udev_device_unref(device); + return name; +} + +EAPI const char * +eeze_udev_syspath_get_devname(const char *syspath) +{ + _udev_device *device; + const char *name = NULL; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + if (!(name = udev_device_get_sysname(device))) + return NULL; + + name = eina_stringshare_add(name); + udev_device_unref(device); + return name; +} + +EAPI const char * +eeze_udev_syspath_get_subsystem(const char *syspath) +{ + _udev_device *device; + const char *subsystem; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + subsystem = eina_stringshare_add(udev_device_get_property_value(device, "SUBSYSTEM")); + udev_device_unref(device); + return subsystem; +} + +EAPI const char * +eeze_udev_syspath_get_property(const char *syspath, + const char *property) +{ + _udev_device *device; + const char *value = NULL, *test; + + if (!syspath || !property) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + if ((test = udev_device_get_property_value(device, property))) + value = eina_stringshare_add(test); + + udev_device_unref(device); + return value; +} + +EAPI const char * +eeze_udev_syspath_get_sysattr(const char *syspath, + const char *sysattr) +{ + _udev_device *device; + const char *value = NULL, *test; + + if (!syspath || !sysattr) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + if ((test = udev_device_get_sysattr_value(device, sysattr))) + value = eina_stringshare_add(test); + + udev_device_unref(device); + return value; +} + +EAPI Eina_Bool +eeze_udev_syspath_is_mouse(const char *syspath) +{ + _udev_device *device = NULL; + Eina_Bool mouse = EINA_FALSE; + const char *test = NULL; + + if (!syspath) + return EINA_FALSE; + + if (!(device = _new_device(syspath))) + return EINA_FALSE; +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + mouse = _walk_parents_test_attr(device, "bInterfaceProtocol", "02"); + + if (!mouse) + { + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((test) && (!strcmp(test, "mouse"))) + mouse = EINA_TRUE; + } + +#else + test = udev_device_get_property_value(device, "ID_INPUT_MOUSE"); + + if (test && (test[0] == '1')) + mouse = EINA_TRUE; + +#endif + udev_device_unref(device); + return mouse; +} + +EAPI Eina_Bool +eeze_udev_syspath_is_kbd(const char *syspath) +{ + _udev_device *device = NULL; + Eina_Bool kbd = EINA_FALSE; + const char *test = NULL; + + if (!syspath) + return EINA_FALSE; + + if (!(device = _new_device(syspath))) + return EINA_FALSE; +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + kbd = _walk_parents_test_attr(device, "bInterfaceProtocol", "01"); + + if (!kbd) + { + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((test) && (!strcmp(test, "kbd"))) + kbd = EINA_TRUE; + } + +#else + test = udev_device_get_property_value(device, "ID_INPUT_KEYBOARD"); + + if (test && (test[0] == '1')) + kbd = EINA_TRUE; + +#endif + udev_device_unref(device); + return kbd; +} + +EAPI Eina_Bool +eeze_udev_syspath_is_touchpad(const char *syspath) +{ + _udev_device *device = NULL; + Eina_Bool touchpad = EINA_FALSE; + + if (!syspath) + return EINA_FALSE; + + if (!(device = _new_device(syspath))) + return EINA_FALSE; +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + touchpad = _walk_parents_test_attr(device, "resolution", NULL); +#else + const char *test; + test = udev_device_get_property_value(device, "ID_INPUT_TOUCHPAD"); + + if (test && (test[0] == '1')) + touchpad = EINA_TRUE; + +#endif + udev_device_unref(device); + return touchpad; +} + +EAPI Eina_Bool +eeze_udev_syspath_is_joystick(const char *syspath) +{ + _udev_device *device = NULL; + Eina_Bool joystick = EINA_FALSE; + const char *test; + + if (!syspath) + return EINA_FALSE; + + if (!(device = _new_device(syspath))) + return EINA_FALSE; +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((test) && (!strcmp(test, "joystick"))) + joystick = EINA_TRUE; +#else + test = udev_device_get_property_value(device, "ID_INPUT_JOYSTICK"); + + if (test && (test[0] == '1')) + joystick = EINA_TRUE; + +#endif + udev_device_unref(device); + return joystick; +} + +EAPI const char * +eeze_udev_devpath_get_syspath(const char *devpath) +{ + _udev_enumerate *en; + _udev_list_entry *devs, *cur; + const char *ret = NULL; + + if (!devpath) + return NULL; + + en = udev_enumerate_new(udev); + + if (!en) + return NULL; + + udev_enumerate_add_match_property(en, "DEVNAME", devpath); + udev_enumerate_scan_devices(en); + devs = udev_enumerate_get_list_entry(en); + udev_list_entry_foreach(cur, devs) + { + ret = eina_stringshare_add(udev_list_entry_get_name(cur)); + break; /*just in case there's more than one somehow */ + } + udev_enumerate_unref(en); + return ret; +} diff --git a/src/lib/eeze_udev_walk.c b/src/lib/eeze_udev_walk.c new file mode 100644 index 0000000..78e2aab --- /dev/null +++ b/src/lib/eeze_udev_walk.c @@ -0,0 +1,65 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "eeze_udev_private.h" + +EAPI Eina_Bool +eeze_udev_walk_check_sysattr(const char *syspath, + const char *sysattr, + const char *value) +{ + _udev_device *device, *child, *parent; + Eina_Bool ret = EINA_FALSE; + const char *test = NULL; + + if (!udev) + return EINA_FALSE; + + if (!(device = _new_device(syspath))) + return EINA_FALSE; + + for (parent = device; parent; + child = parent, parent = udev_device_get_parent(child)) + { + if (!(test = udev_device_get_sysattr_value(parent, sysattr))) + continue; + if ((value && (!strcmp(test, value))) || (!value)) + { + ret = EINA_TRUE; + break; + } + } + + udev_device_unref(device); + return ret; +} + +EAPI const char * +eeze_udev_walk_get_sysattr(const char *syspath, + const char *sysattr) +{ + _udev_device *device, *child, *parent; + const char *test = NULL; + + if (!syspath) + return NULL; + + if (!(device = _new_device(syspath))) + return NULL; + + for (parent = device; parent; + child = parent, parent = udev_device_get_parent(child)) + { + if ((test = udev_device_get_sysattr_value(parent, sysattr))) + { + test = eina_stringshare_add(test); + udev_device_unref(device); + return test; + } + } + + udev_device_unref(device); + return NULL; +} diff --git a/src/lib/eeze_udev_watch.c b/src/lib/eeze_udev_watch.c new file mode 100644 index 0000000..0083b81 --- /dev/null +++ b/src/lib/eeze_udev_watch.c @@ -0,0 +1,449 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include "eeze_udev_private.h" + +/* opaque */ +struct Eeze_Udev_Watch +{ + _udev_monitor *mon; + Ecore_Fd_Handler *handler; + Eeze_Udev_Type type; + void *data; +}; + +/* private */ +struct _store_data +{ + void (*func)(const char *, + Eeze_Udev_Event, + void *, + Eeze_Udev_Watch *); + void *data; + Eeze_Udev_Event event; + _udev_monitor *mon; + Eeze_Udev_Type type; + Eeze_Udev_Watch *watch; +}; + +/* private function to further filter watch results based on Eeze_Udev_Type + * specified; helpful for new udev versions, but absolutely required for + * old udev, which does not implement filtering in device monitors. + */ +static Eina_Bool +_get_syspath_from_watch(void *data, + Ecore_Fd_Handler *fd_handler) +{ + struct _store_data *store = data; + _udev_device *device = NULL, *parent, *tmpdev; + const char *ret, *test; + Eeze_Udev_Watch_Cb func = store->func; + void *sdata = store->data; + Eeze_Udev_Watch *watch = store->watch; + int event = 0; + + if (!ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ)) + return EINA_TRUE; + + device = udev_monitor_receive_device(store->mon); + + if (!device) + return EINA_TRUE; + + if ((!(test = udev_device_get_action(device))) + || (!(ret = udev_device_get_syspath(device)))) + goto error; + + if (store->event) + { + if (!strcmp(test, "add")) + { + if ((store->event != EEZE_UDEV_EVENT_NONE) && + ((store->event & EEZE_UDEV_EVENT_ADD) != EEZE_UDEV_EVENT_ADD)) + goto error; + + event |= EEZE_UDEV_EVENT_ADD; + } + else if (!strcmp(test, "remove")) + { + if ((store->event != EEZE_UDEV_EVENT_NONE) && + ((store->event & EEZE_UDEV_EVENT_REMOVE) != EEZE_UDEV_EVENT_REMOVE)) + goto error; + + event |= EEZE_UDEV_EVENT_REMOVE; + } + else if (!strcmp(test, "change")) + { + if ((store->event != EEZE_UDEV_EVENT_NONE) && + ((store->event & EEZE_UDEV_EVENT_CHANGE) != EEZE_UDEV_EVENT_CHANGE)) + goto error; + + event |= EEZE_UDEV_EVENT_CHANGE; + } + else if (!strcmp(test, "online")) + { + if ((store->event != EEZE_UDEV_EVENT_NONE) && + ((store->event & EEZE_UDEV_EVENT_ONLINE) != EEZE_UDEV_EVENT_ONLINE)) + goto error; + + event |= EEZE_UDEV_EVENT_ONLINE; + } + else + { + if ((store->event != EEZE_UDEV_EVENT_NONE) && + ((store->event & EEZE_UDEV_EVENT_OFFLINE) != EEZE_UDEV_EVENT_OFFLINE)) + goto error; + + event |= EEZE_UDEV_EVENT_OFFLINE; + } + } + + if ((event & EEZE_UDEV_EVENT_OFFLINE) || (event & EEZE_UDEV_EVENT_REMOVE)) + goto out; + switch (store->type) + { + case EEZE_UDEV_TYPE_KEYBOARD: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "input"))) + goto error; + + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((_walk_parents_test_attr(device, "bInterfaceProtocol", "01")) + || ((test) && (!strcmp(test, "kbd")))) + break; + + goto error; +#endif + if ((!udev_device_get_property_value(device, "ID_INPUT_KEYBOARD")) && + (!udev_device_get_property_value(device, "ID_INPUT_KEY"))) + goto error; + + break; + + case EEZE_UDEV_TYPE_MOUSE: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "input"))) + goto error; + + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((_walk_parents_test_attr(device, "bInterfaceProtocol", "02")) + || ((test) && (!strcmp(test, "mouse")))) + break; + + goto error; +#endif + + if (!udev_device_get_property_value(device, "ID_INPUT_MOUSE")) + goto error; + + break; + + case EEZE_UDEV_TYPE_TOUCHPAD: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "input"))) + goto error; + + if (_walk_parents_test_attr(device, "resolution", NULL)) + break; + + goto error; +#endif + if (!udev_device_get_property_value(device, "ID_INPUT_TOUCHPAD")) + goto error; + + break; + + case EEZE_UDEV_TYPE_JOYSTICK: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "input"))) + goto error; + + test = udev_device_get_property_value(device, "ID_CLASS"); + + if ((test) && (!strcmp(test, "joystick"))) + break; + + goto error; +#endif + if (!udev_device_get_property_value(device, "ID_INPUT_JOYSTICK")) + goto error; + + break; + + case EEZE_UDEV_TYPE_DRIVE_MOUNTABLE: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "block"))) + goto error; +#endif + if (!(test = (udev_device_get_property_value(device, "ID_FS_USAGE"))) || + (strcmp("filesystem", test))) + { + if (event & EEZE_UDEV_EVENT_CHANGE) + { + test = udev_device_get_sysname(device); + if (!test) goto error; + if (!strncmp(test, "loop", 4)) break; + } + goto error; + } + { + int devcheck; + + devcheck = open(udev_device_get_devnode(device), O_RDONLY); + if (devcheck < 0) goto error; + close(devcheck); + } + + break; + + case EEZE_UDEV_TYPE_DRIVE_INTERNAL: + if (udev_device_get_property_value(device, "ID_FS_USAGE")) goto error; + test = udev_device_get_sysattr_value(device, "removable"); + if (test && test[0] == '1') goto error; + test = udev_device_get_property_value(device, "ID_BUS"); + if ((!test) || strcmp(test, "ata")) goto error; + test = udev_device_get_property_value(device, "ID_TYPE"); + if (!(event & EEZE_UDEV_EVENT_CHANGE) && ((!test) || strcmp(test, "disk"))) goto error; + break; + + case EEZE_UDEV_TYPE_DRIVE_REMOVABLE: + if (udev_device_get_sysattr_value(device, "partition")) goto error; + test = udev_device_get_sysattr_value(device, "removable"); + if ((!test) || (test[0] == '0')) goto error; + test = udev_device_get_property_value(device, "ID_TYPE"); + if ((!test) || strcmp(test, "disk")) goto error; + + break; + + case EEZE_UDEV_TYPE_DRIVE_CDROM: + if (!udev_device_get_property_value(device, "ID_CDROM")) + goto error; + + break; + + case EEZE_UDEV_TYPE_POWER_AC: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "power_supply"))) + goto error; +#endif + test = udev_device_get_property_value(device, "POWER_SUPPLY_ONLINE"); + if (!test) goto error; + break; + + case EEZE_UDEV_TYPE_POWER_BAT: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "power_supply"))) + goto error; +#endif + test = udev_device_get_property_value(device, "POWER_SUPPLY_PRESENT"); + if ((!test) || (strcmp(test, "1"))) goto error; + break; + + case EEZE_UDEV_TYPE_NET: +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "net"))) + goto error; +#endif + break; + + case EEZE_UDEV_TYPE_IS_IT_HOT_OR_IS_IT_COLD_SENSOR: + { + Eina_Bool one, two; + const char *t; + +#ifdef OLD_UDEV_RRRRRRRRRRRRRR + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "hwmon"))) + goto error; +#endif /* have to do stuff up here since we need info from the parent */ + one = _walk_parents_test_attr(device, "temp1_input", NULL); + two = _walk_parents_test_attr(device, "temp2_input", NULL); + if ((!one) && (!two)) goto error; + + t = one ? "temp1_input" : "temp2_input"; + /* if device is not the one which has the temp input, we must go up the chain */ + if (!udev_device_get_sysattr_value(device, t)) + { + for (parent = udev_device_get_parent(device); parent; parent = udev_device_get_parent(parent)) /*check for parent */ + if (udev_device_get_sysattr_value(parent, t)) + { + tmpdev = device; + + if (!(device = _copy_device(parent))) + goto error; + + udev_device_unref(tmpdev); + break; + } + } + + break; + } + case EEZE_UDEV_TYPE_V4L: + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "video4linux"))) + goto error; + break; + + case EEZE_UDEV_TYPE_BLUETOOTH: + if ((!(test = udev_device_get_subsystem(device))) + || (strcmp(test, "bluetooth"))) + goto error; + break; + + default: + break; + } +out: + (*func)(eina_stringshare_add(ret), event, sdata, watch); +error: + if (device) + udev_device_unref(device); + return EINA_TRUE; +} + +EAPI Eeze_Udev_Watch * +eeze_udev_watch_add(Eeze_Udev_Type type, + int event, + Eeze_Udev_Watch_Cb cb, + void *user_data) +{ + _udev_monitor *mon = NULL; + int fd; + Ecore_Fd_Handler *handler; + Eeze_Udev_Watch *watch = NULL; + struct _store_data *store = NULL; + + if (!(store = calloc(1, sizeof(struct _store_data)))) + return NULL; + + if (!(watch = malloc(sizeof(Eeze_Udev_Watch)))) + goto error; + + if (!(mon = udev_monitor_new_from_netlink(udev, "udev"))) + goto error; + +#ifndef OLD_UDEV_RRRRRRRRRRRRRR + + switch (type) + { + case EEZE_UDEV_TYPE_JOYSTICK: + case EEZE_UDEV_TYPE_KEYBOARD: + case EEZE_UDEV_TYPE_MOUSE: + case EEZE_UDEV_TYPE_TOUCHPAD: + udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL); + break; + + case EEZE_UDEV_TYPE_DRIVE_MOUNTABLE: + case EEZE_UDEV_TYPE_DRIVE_INTERNAL: + udev_monitor_filter_add_match_subsystem_devtype(mon, "block", NULL); + break; + + case EEZE_UDEV_TYPE_DRIVE_REMOVABLE: + case EEZE_UDEV_TYPE_DRIVE_CDROM: + break; + + case EEZE_UDEV_TYPE_POWER_AC: + case EEZE_UDEV_TYPE_POWER_BAT: + udev_monitor_filter_add_match_subsystem_devtype(mon, "power_supply", + NULL); + break; + + case EEZE_UDEV_TYPE_NET: + udev_monitor_filter_add_match_subsystem_devtype(mon, "net", NULL); + break; + + case EEZE_UDEV_TYPE_IS_IT_HOT_OR_IS_IT_COLD_SENSOR: + udev_monitor_filter_add_match_subsystem_devtype(mon, "hwmon", NULL); + break; + + /* + case EEZE_UDEV_TYPE_ANDROID: + udev_monitor_filter_add_match_subsystem_devtype(mon, "input", "usb_interface"); + break; + */ + + case EEZE_UDEV_TYPE_V4L: + udev_monitor_filter_add_match_subsystem_devtype(mon, "video4linux", + NULL); + break; + + case EEZE_UDEV_TYPE_BLUETOOTH: + udev_monitor_filter_add_match_subsystem_devtype(mon, "bluetooth", + NULL); + break; + + default: + break; + } + +#endif + + if (udev_monitor_enable_receiving(mon)) + goto error; + + fd = udev_monitor_get_fd(mon); + store->func = cb; + store->data = user_data; + store->mon = mon; + store->type = type; + store->watch = watch; + store->event = event; + + if (!(handler = ecore_main_fd_handler_add(fd, ECORE_FD_READ, + _get_syspath_from_watch, store, NULL, NULL))) + goto error; + + watch->mon = mon; + watch->handler = handler; + return watch; +error: + if (store) + free(store); + if (watch) + free(watch); + if (mon) + udev_monitor_unref(mon); + ERR("Could not create watch!"); + return NULL; +} + +EAPI void * +eeze_udev_watch_del(Eeze_Udev_Watch *watch) +{ + struct _store_data *sdata; + void *ret = NULL; + + if ((!watch) || (!watch->mon) || (!watch->handler)) + return NULL; + + sdata = ecore_main_fd_handler_del(watch->handler); + udev_monitor_unref(watch->mon); + + if (sdata) + { + ret = sdata->data; + free(sdata); + } + + free(watch); + return ret; +} -- 2.7.4

MP;|&ymtFmYbDBJ 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