Compare commits

...
44 Commits
Author SHA1 Message Date
Alexander Shaduri a4232e3a50 Tagged 1.1.1. 2017-09-25 16:23:51 +00:00
Alexander Shaduri a95be84eac Fixed make dist. 2017-09-25 15:00:50 +00:00
Alexander Shaduri b76b5f2227 Removed Help window.
Moved most of the documentation to website.
The Help menu items now open the website pages.
2017-09-25 14:58:58 +00:00
Alexander Shaduri 7a12c0880f Don't package gtkrc in Windows (it's unused).
Added French translations to desktop file.
2017-09-23 17:14:45 +00:00
Alexander Shaduri 58b0b1fab2 Windows: Changed GTK+ theme changing to use gtkmm functions.
Added some special cases to SCT parser.
2017-09-23 16:06:09 +00:00
Alexander Shaduri 16b5c322d5 Bumped version to 1.1.1.
Windows: Default to Adwaita theme for Windows 7 and Windows Server, since
"Classic" theme is broken in GTK+3 with win32 theme.
Format Statistics entry values with commas for readability.
2017-09-15 12:26:45 +00:00
Alexander Shaduri 16d5b82ac8 Fixed compilation under C++98.
Fixed smartmontools URLs.
2017-09-07 13:51:26 +00:00
Alexander Shaduri be66a8749c Bumped version to 1.1.0.
Updated NEWS file.
Clarified a message for self-test log entry count.
2017-09-07 12:43:38 +00:00
Alexander Shaduri 5192a9d447 Implemented ability to copy rows from Attribute, Statistics and Self-Test Log
tables.
2017-09-06 20:11:39 +00:00
Alexander Shaduri b9160ec173 Improved parser so that all the "-x" test data is parsed. 2017-09-05 20:53:19 +00:00
Alexander Shaduri 675fad3fc1 Documented RAID support. 2017-09-05 17:27:31 +00:00
Alexander Shaduri f7c01d6c7b Implemented "Update Drive Database" item in main window menu. 2017-09-05 16:12:19 +00:00
Alexander Shaduri 5bc6318dcd Implemented parsing of "==> Warning" in Info section. 2017-09-05 15:45:21 +00:00
Alexander Shaduri 3d8b0c94df Allow loading multiple virtual drives from one Open dialog. 2017-09-05 15:14:02 +00:00
Alexander Shaduri 73fec7fa3f Rate individual error log entries for error severity. 2017-09-05 14:53:47 +00:00
Alexander Shaduri 22971e8567 Show scan time on virtual drive icons.
Show windows drive letters if "device name under icon" is enabled.
2017-09-04 18:52:41 +00:00
Alexander Shaduri 3df4cdae56 Implemented devstat warnings. 2017-09-04 18:12:27 +00:00
Alexander Shaduri 062096a0b4 Improve GtkNotebook appearances in Windows.
Don't show Info window tabs if only one tab is present.
2017-09-04 16:57:08 +00:00
Alexander Shaduri 978e7d41b7 Added filters to file chooser dialogs.
Add .txt when saving files if it's not present (fixes a Windows bug when the
default drive name is cleared by typing something else).
2017-09-04 14:38:10 +00:00
Alexander Shaduri 33028f81d9 Fixed a few parser issues.
SCT temperature overrides all others now.
2017-09-04 13:43:40 +00:00
Alexander Shaduri 4cb12c5e0c Show temperature (from sct, devstat, or attributes) in Temperature Log tab,
including warnings.
Don't warn if SCT is unsupported.
2017-09-01 19:21:15 +00:00
Alexander Shaduri b01acfeef3 Added entry descriptions to Statistics table. 2017-09-01 17:53:09 +00:00
Alexander Shaduri b7676aaba8 Added Page,Offset column to Statistics.
Enabled multiple selection in treeviews.
Renamed ERC tab to Error Recovery.
2017-09-01 14:20:34 +00:00
Alexander Shaduri e0a13c6867 Implemented parsing of devstat section.
Implemented Statistics tab.
Increased borders in some tabs.
Some code style changes.
2017-09-01 13:42:51 +00:00
Alexander Shaduri 9f5db6d034 Visual improvements in Error Log tab. 2017-08-31 20:50:41 +00:00
Alexander Shaduri 5c3bfe8db0 Fixed not detecting the number of errors in extended error log. 2017-08-31 20:44:00 +00:00
Alexander Shaduri 5926e43bc5 Refactored Info Window tabs (again).
Implemented support for Temperature Log, ERC, Physical, Directory tabs.
Added detection of a few "not supported" messages.
Added some more --get=all output lines.
Smartctl invocation now corresponds to -x, and -x output can be loaded as
a virtual drive (parsed fully except for devstat).
2017-08-31 20:03:04 +00:00
Alexander Shaduri f21f076386 Refactored Info window UI (added tabs for various logs).
Hid annoying gcc warning.
2017-08-29 19:06:46 +00:00
Alexander Shaduri da403b28c9 Require smartmontools version 5.43 at runtime. 2017-08-29 15:13:13 +00:00
Alexander Shaduri faba9270a0 Use --format=brief attribute format by default.
Removed "Updated" column from attributes; advanced users can look at flags to
get that information.
2017-08-29 13:41:38 +00:00
Alexander Shaduri 9ca87eb296 Use -l xerror,error and -l xselftest,selftest by default.
Implemented parsing of xerror, xselftest sections.
2017-08-29 13:26:25 +00:00
Alexander Shaduri b2b97f2c9a Added more info output detection (APM feature is...).
Enable -v by default on Windows (makes debugging easier).
2017-08-29 11:47:14 +00:00
Alexander Shaduri 8494880943 Fixes in spec file.
Fixes with polkit's pkexec invocation.
2017-08-28 12:29:54 +00:00
Alexander Shaduri 0f8f38f0e1 Fixed compilation. 2017-08-28 10:12:39 +00:00
Alexander Shaduri 8544163c46 Make sure we bundle the polkit file on make dist. 2017-08-28 09:59:42 +00:00
Alexander Shaduri fdcd190592 Added --get=all information to General section.
Made General section scrollable
Changed all smartctl invocation parameters to full versions (very old smartctl
versions didn't support these, but we can drop their support from now on).
2017-08-27 20:00:43 +00:00
Alexander Shaduri 40e08796d1 Use pkexec --disable-internal-agent for polkit.
Don't warn on Unknown property "Form Factor".
2017-08-27 19:07:52 +00:00
Alexander Shaduri 1e72729056 Implemented PolKit support.
Install gsmartcontrol binary to /usr/sbin.
Removed Fedora consolehelper support from spec file (in favor of PolKit).
2017-08-27 17:33:09 +00:00
Alexander Shaduri 731122e891 Bumped version number to 1.0.3. 2017-08-26 17:23:43 +00:00
Alexander Shaduri ebb3ab6017 Removed local copy of pcrecpp, always use system-installed one (it's not a
rarity anymore).
2017-08-26 17:23:14 +00:00
Alexander Shaduri 3d2fe7fd50 Windows: Show drive letters for each device. 2017-08-26 16:47:11 +00:00
Alexander Shaduri 9a70345f4a Format large numbers according to locale (usually with commas).
Save Open/Save paths across windows and program restarts.
2017-08-26 13:18:32 +00:00
Alexander Shaduri 897a4efa96 Save / load default window size and position (both for main window, size only
for info window).
2017-08-26 12:09:51 +00:00
Alexander Shaduri 6ab53c5436 Updated site URLs to https://gsmartcontrol.sourceforge.io/ . 2017-08-18 09:33:55 +00:00
308 changed files with 4179 additions and 6244 deletions
View File
View File
View File
@@ -23,10 +23,6 @@ examples are covered under the Unlicense. See LICENSE_unlicense.txt for
details.
Portions of this software are copyright Google Inc. See LICENSE_pcrecpp.txt
for details.
This product includes the HZ library (or portions of it). See LICENSE_hz.txt
for details.
+8 -6
View File
@@ -6,7 +6,7 @@ SUBDIRS = data debian.dist src
# These files are actually needed for compilation.
noinst_DATA = AUTHORS.txt LICENSE_gsmartcontrol.txt README.txt
noinst_DATA = AUTHORS.txt LICENSE_gsmartcontrol.txt
# For the files to be bundled with the distribution, specify them in
@@ -22,7 +22,6 @@ dist_doc_DATA = AUTHORS.txt ChangeLog NEWS README.txt \
LICENSE_gpl3.txt \
LICENSE_gsmartcontrol.txt \
LICENSE_hz.txt \
LICENSE_pcrecpp.txt \
LICENSE_unlicense.txt \
LICENSE_zlib.txt
@@ -51,7 +50,7 @@ src-rpm: dist
# NSIS installer support.
# Requires installed NSIS, smartctl-nc.exe, smartctl.exe, update-smart-drivedb.exe.
# Gtkmm dlls and pcre dlls are required if linking against them.
# Gtkmm and pcre dlls are required if linking against them.
# dos2unix on build machine.
# Execute only with win32 build present.
@@ -142,11 +141,13 @@ win-dist-prepare: all
for file in $(GTK_BIN_FILES); do for f in "@WINDOWS_SYSROOT@/bin/"$$file; do if test -f "$${f}"; then cp -p "$${f}" win-dist/; fi; done; done
# <prefix>/etc should contain gtk-3.0/settings.ini
# <prefix>/etc/gtk-3.0/ should contain settings.ini with a win32 theme.
$(MKDIR_P) win-dist/etc
$(MKDIR_P) win-dist/etc/fonts
cp -p "@WINDOWS_SYSROOT@"/etc/fonts/fonts.conf win-dist/etc/fonts
cp -p -r "@WINDOWS_SYSROOT@"/etc/gtk-3.0 win-dist/etc
$(MKDIR_P) win-dist/etc/gtk-3.0
cp -p "@WINDOWS_SYSROOT@"/etc/fonts/fonts.conf win-dist/etc/fonts/
# cp -p "@WINDOWS_SYSROOT@"/etc/gtk-3.0/im-multipress.conf win-dist/etc/gtk-3.0/
cp -p "@WINDOWS_SYSROOT@"/etc/gtk-3.0/settings.ini win-dist/etc/gtk-3.0/
$(MKDIR_P) win-dist/share
cp -p -r "@WINDOWS_SYSROOT@"/share/themes win-dist/share
@@ -222,6 +223,7 @@ win-dist-prepare: all
# other
cp -p "@WINDOWS_SYSROOT@"/bin/libgcc_s_*.dll win-dist/
cp -p "@WINDOWS_SYSROOT@"/bin/libpcre-1.dll win-dist/
cp -p "@WINDOWS_SYSROOT@"/bin/libpcrecpp-0.dll win-dist/
nsis-dist-prepare: win-dist-prepare
+20
View File
@@ -1,3 +1,23 @@
Version 1.1.0, released on 2017-09-07
New Statistics, Temperature Log, Error Recovery, Physical and Directory
tabs.
General tab shows non-SMART device settings as well.
Attributes tab shows entries in "brief" format.
Error Log tab shows Extended error log by default (if supported).
Self-Test Log tab now shows Extended self-test log by default (if
supported).
GSmartControl now uses "-x" equivalent for retrieving data (as upposed to
"-a"); loading "-x" outputs as virtual drives is also supported.
Implemented ability to copy rows in CSV format from Attribute, Statistics
and Self-Test Log tables.
Implemented "Update Drive Database" functionality.
Windows: Drive letters are shown for each drive.
Scan time is shown under virtual drive icons.
Polkit is supported with gsmartcontrol-root script now.
Pcrecpp is no longer bundled, use system-installed one instead.
Smartmontools version 5.43 is required at runtime.
Other minor improvements and fixes.
Version 1.0.2, released on 2017-07-21
Fixed incomplete capturing of smartctl output under Windows.
Added missing icons under Windows.
+101
View File
@@ -0,0 +1,101 @@
About GSmartControl
GSmartControl - Hard disk drive and SSD health inspection tool.
GSmartControl is a graphical user interface for smartctl (from smartmontools
package, see https://www.smartmontools.org/), which is a tool for
querying and controlling SMART (Self-Monitoring, Analysis, and Reporting
Technology) data on modern hard disk and solid-state drives. It allows you to
inspect the drive's SMART data to determine its health, as well as run various
tests on it.
GSmartControl supports ATA drives (both PATA and SATA), various USB to
ATA bridges and drives behind some RAID controllers:
* Adaptec (Linux, some models only)
* Areca (Linux, Windows)
* HP CCISS (Linux)
* HP hpsa / hpahcisr (Linux)
* Intel Matrix Storage (CSMI) (Linux, Windows, FreeBSD)
* LSI 3ware (Linux, Windows)
* LSI MegaRAID (Windows)
Note: Smartmontools supports even more RAID Controllers. The drives
behind such controllers can be manually added to GSmartControl using
"Add Device..." functionality or --add-device command-line option.
See https://www.smartmontools.org/wiki/Supported_RAID-Controllers .
https://gsmartcontrol.sourceforge.io/
Software Requirements
Note: If using the official Windows package, no additional software is required.
Build requirements:
* pcre 1 - http://www.pcre.org .
* GTK+, version 3.4 or higher - see http://www.gtk.org .
* Gtkmm, version 3.4 or higher - see http://www.gtkmm.org .
Runtime requirements:
* smartmontools - see https://www.smartmontools.org/ .
* xterm
The following operating systems are supported:
* Linux - All the popular configurations should work.
* FreeBSD - Tested with DesktopBSD / x86.
* NetBSD - Tested with NetBSD / x86.
* OpenBSD - Tested with OpenBSD / x86-64.
* DragonFlyBSD - Code written but no testing has been performed yet. Expected
to work without any issues.
* Windows Vista SP2 (32-bit and 64-bit), Windows 7 SP1, Windows Server 2008,
Windows 8.1, Windows 10. The Windows port uses pd0, pd1, etc...
for physical drives 0, 1, etc... .
* Mac OS X.
* Solaris.
* QNX - Code written but no testing has been performed yet.
Command Line Options
GSmartControl inherits options from GTK+ and other libraries, so be sure to
run it with --help option to get a full list of accepted parameters.
Note: The Windows version may not have a text output at all, so --help and
similar arguments won't have any effect.
The most important parameters are:
-?, --help - Show help options.
-l, --no-locale - Don't use system locale.
-V, --version - Display version information.
--no-scan - Don't scan devices on startup.
--no-hide-tabs - Don't hide non-identity tabs when SMART is disabled. Useful
for debugging.
--add-virtual - Load smartctl data from file, creating a virtual drive. You
can specify this option multiple times.
--add-device - Add this device to device list. The format of the device is
"<device>::<type>::<extra_args>", where type and extra_args are optional. This
option is useful with --no-scan to list certain drives only. You can specify
this option multiple times.
Example: --add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device
'/dev/sdb::::-T permissive'.
-v, --verbose - Enable verbose logging; same as --verbosity-level 5.
-q, --quiet - Disable logging; same as --verbosity-level 0.
-b, --verbosity-level - Set verbosity level [0-5].
License and Copyright
For license information, see LICENSE_gsmartcontrol.txt file.
+47
View File
@@ -0,0 +1,47 @@
GTKMM Notes:
To transfer an ownership from your local code to a container, use
Gtk::Button* pButton = Gtk::manage(new Gtk::Button("Test"));
container->add(*pButton);
(add() takes an object by reference).
otherwise, you will need to delete() it.
local scope destruction also works if using stack variables.
{
Gtk::Button aButton;
// destroyed here
}
--------------------------------------------------------
TODO:
Don't rely on smartctl return code (2), parse the output instead.
This will allow us to show the Info page.
Need usage cases.
Testing:
If ETA time has elapsed, but it's still only at 10% completion,
ETA 0 is displayed. Fix.
Detect running tests on launch (maybe ask the user too? some tests
may be stuck due to bad firmware, e.g. 3ware/windows).
If smartctl outputs gibberish while testing, the GUI hangs.
Support RAID for these controllers (supported by smartctl 6.5):
https://www.smartmontools.org/wiki/Supported_RAID-Controllers
Adaptec (Windows, Linux using "-d aacraid")
HighPoint RocketRAID (Linux, FreeBSD (analyze sent info))
LSI MegaRAID (Linux, FreeBSD)
3ware (FreeBSD)
Areca (FreeBSD)
HP CCISS (FreeBSD)
+14 -19
View File
@@ -1,5 +1,5 @@
AC_INIT([GSmartControl],[1.0.2],[],[gsmartcontrol])
AC_INIT([GSmartControl],[1.1.1],[],[gsmartcontrol])
AC_CONFIG_SRCDIR([configure.ac])
AC_CONFIG_HEADERS([config.h])
@@ -128,9 +128,6 @@ APP_CXX_EXTERN_C_OVERLOAD
# This may be needed for pch support
AC_CHECK_HEADERS([stdc++.h])
# pcrecpp needs this (break on first function found)
AC_CHECK_FUNCS([strtoll _strtoi64], [break])
# Non-C99 systems don't have strtof and strtold. some have strtodf for strtof,
# but let's ignore it. Note: They may be supported by system libc,
# but not enabled by default (e.g. by glibc feature macros). In that case,
@@ -212,27 +209,27 @@ LIBS="$GTKMM_LIBS $LIBS"
# -------------------------------------------------------------------------------------
PCRE_CFLAGS="";
PCRE_LIBS="";
PCRECPP_CFLAGS="";
PCRECPP_LIBS="";
# try pkg-config first
PKG_CHECK_MODULES(PCRE, [libpcre], [pcre_found=yes], [pcre_found=no])
PKG_CHECK_MODULES(PCRECPP, [libpcrecpp], [pcrecpp_found=yes], [pcrecpp_found=no])
# if not found, try pcre-config (solaris had it but not pkg-config).
if test "x$pcre_found" = "xno"; then
if test "x$pcrecpp_found" = "xno"; then
AC_PATH_PROG(pcre_config_binary, [pcre-config])
if test "x$pcre_config_binary" != "x"; then
PCRE_CFLAGS="`pcre-config --cflags`"
PCRE_LIBS="`pcre-config --libs`"
PCRE_LIBS="`pcre-config --libs-cpp`"
else
# AC_MSG_ERROR([Could not find pcre through pkg-config and pcre-config. Make sure the pcre libraries are installed])
AC_MSG_ERROR([$PCRE_PKG_ERRORS])
# AC_MSG_ERROR([Could not find pcrecpp through pkg-config and pcre-config. Make sure the pcre and pcrecpp libraries are installed])
AC_MSG_ERROR([$PCRECPP_PKG_ERRORS])
fi
fi
CFLAGS="$CFLAGS $PCRE_CFLAGS"
CXXFLAGS="$CXXFLAGS $PCRE_CFLAGS"
LIBS="$LIBS $PCRE_LIBS"
CFLAGS="$CFLAGS $PCRECPP_CFLAGS"
CXXFLAGS="$CXXFLAGS $PCRECPP_CFLAGS"
LIBS="$LIBS $PCRECPP_LIBS"
# -------------------------------------------------------------------------------------
@@ -312,12 +309,11 @@ AC_SUBST(WINDOWS_ARCH)
RES_FILES="src/res/gsc_about_dialog.ui \
src/res/gsc_add_device_window.ui \
src/res/gsc_executor_log_window.ui \
src/res/gsc_help_window.ui \
src/res/gsc_info_window.ui \
src/res/gsc_main_window.ui \
src/res/gsc_preferences_window.ui \
src/res/gsc_text_window.ui \
AUTHORS.txt LICENSE_gsmartcontrol.txt README.txt"
AUTHORS.txt LICENSE_gsmartcontrol.txt"
# Unfortunately, _SOURCES can't contain substitutions, so we use LIBADD.
RES_LIBADD="" # .o file to put into .a, libres_a_LIBADD, libres_a_DEPENDENCIES.
@@ -389,8 +385,7 @@ CXXFLAGS="$CXXFLAGS $ADDITIONAL_FLAGS"
# Add global configuration file. Note: It must be searched in top_builddir
# first (that's why -I is the first flag), for precompiled headers to work.
# Add pcrecpp too, because its files use #<file> on each other.
ADDITIONAL_INCLUDES="-I\$(top_builddir) -I\$(top_srcdir)/src -I\$(top_srcdir)/src/pcrecpp"
ADDITIONAL_INCLUDES="-I\$(top_builddir) -I\$(top_srcdir)/src"
# -include works with gcc, intel, pathscale and sunstudio since 12u1, but doesn't work
# with pgi and older sunstudio (the one in solaris10, for example).
@@ -486,7 +481,7 @@ AC_CONFIG_FILES([data/gsmartcontrol-root], [chmod +x data/gsmartcontrol-root])
# these are all the makefiles to generate
AC_CONFIG_FILES([Makefile src/Makefile src/applib/Makefile src/res/Makefile src/hz/Makefile \
src/libdebug/Makefile src/pcrecpp/Makefile src/rconfig/Makefile src/rmn/Makefile \
src/libdebug/Makefile src/rconfig/Makefile src/rmn/Makefile \
data/Makefile data/16/Makefile data/22/Makefile data/24/Makefile data/32/Makefile \
data/48/Makefile data/64/Makefile data/128/Makefile data/256/Makefile data/nsis/Makefile \
debian.dist/Makefile])

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -44,6 +44,10 @@ desktop_DATA = gsmartcontrol.desktop
metainfodir = $(datadir)/metainfo
metainfo_DATA = gsmartcontrol.appdata.xml
# PolKit files
polkitdir = $(datadir)/polkit-1/actions
polkit_DATA = org.gsmartcontrol.policy
# Application pixmap (fallback icon for desktop files).
# Same as 48/gsmartcontrol.png.
@@ -53,7 +57,7 @@ pixmaps_DATA = 48/gsmartcontrol.png gsmartcontrol.xpm
# Put everything inside the distribution
EXTRA_DIST = $(appdata_DATA) gsmartcontrol.ico gsmartcontrol.xpm
EXTRA_DIST = $(appdata_DATA) $(polkit_DATA) gsmartcontrol.ico gsmartcontrol.xpm
bin_SCRIPTS = gsmartcontrol-root
@@ -8,7 +8,7 @@
# Run gsmartcontrol with root, asking for root password first.
# export GSMARTCONTROL_SU to override a su command (e.g. "kdesu -c").
EXEC_BIN="@prefix@/bin/gsmartcontrol";
EXEC_BIN="@prefix@/sbin/gsmartcontrol";
prog_name="gsmartcontrol"
@@ -71,11 +71,12 @@ fi
# They're basically the same, only the order is different.
# pkexec is for PolKit
# sux requires xterm to ask for the password.
# xdg-su is basically like this script, except worse :)
# su-to-root is a debian/ubuntu official method (although gksu is available).
gnome_sus="su-to-root gnomesu gksu kdesu beesu xdg-su sux";
kde_sus="su-to-root kdesu gnomesu gksu beesu xdg-su sux";
gnome_sus="pkexec su-to-root gnomesu gksu kdesu beesu xdg-su sux";
kde_sus="pkexec su-to-root kdesu gnomesu gksu beesu xdg-su sux";
other_sus="$gnome_sus";
@@ -136,6 +137,9 @@ full_cmd="";
if [ "$GSMARTCONTROL_SU" != "" ]; then
full_cmd="$GSMARTCONTROL_SU '$EXEC_BIN $final_args_quoted'";
elif [ "$found_su" = "pkexec" ]; then
full_cmd="pkexec --disable-internal-agent $EXEC_BIN $final_args_quoted";
elif [ "$found_su" = "sux" ]; then
full_cmd="xterm -e sux -c '$EXEC_BIN $final_args_quoted'";
@@ -16,19 +16,19 @@
</p>
</description>
<launchable type="desktop-id">gsmartcontrol.desktop</launchable>
<url type="homepage">http://gsmartcontrol.sourceforge.net</url>
<url type="homepage">https://gsmartcontrol.sourceforge.io</url>
<screenshots>
<screenshot type="default">
<caption>Main window</caption>
<image>http://gsmartcontrol.sourceforge.net/home/images/main_ok.png</image>
<image>https://gsmartcontrol.sourceforge.io/home/images/main_ok.png</image>
</screenshot>
<screenshot>
<caption>Drive identity information</caption>
<image>http://gsmartcontrol.sourceforge.net/home/images/info_identity.png</image>
<image>https://gsmartcontrol.sourceforge.io/home/images/info_identity.png</image>
</screenshot>
<screenshot>
<caption>Attribute list of a failing drive</caption>
<image>http://gsmartcontrol.sourceforge.net/home/images/info_failing.png</image>
<image>https://gsmartcontrol.sourceforge.io/home/images/info_failing.png</image>
</screenshot>
</screenshots>
</component>
@@ -9,8 +9,11 @@ Categories=System;Monitor;
Name=GSmartControl
# short description, usually shown in parentheses after Name
GenericName=Hard Disk and SSD Health Inspection
GenericName[fr]=Inspecteur de santé de disque dur et de SSD
# tooltip
Comment=Monitor and control SMART data on hard disk and solid-state drives
Comment[fr]=Surveille et contrôle les données SMART des disques durs et des SSD
# If it's a name only, it looks for "name.[png|xpm]" file in
# $XDG_DATA_DIRS/icons and /usr/share/pixmaps.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -4,7 +4,7 @@ Version @VERSION@
The latest version of this distribution, as well as the source code of
GSmartControl, can be found at
http://gsmartcontrol.sourceforge.net
https://gsmartcontrol.sourceforge.io
and
https://sourceforge.net/projects/gsmartcontrol/files/
@@ -8,7 +8,7 @@
!define PRODUCT_NAME "GSmartControl"
!define PRODUCT_NAME_SMALL "gsmartcontrol"
!define PRODUCT_PUBLISHER "Alexander Shaduri"
!define PRODUCT_WEB_SITE "http://gsmartcontrol.sourceforge.net"
!define PRODUCT_WEB_SITE "https://gsmartcontrol.sourceforge.io"
;!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\AppMainExe.exe"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="org.gsmartcontrol">
<message>Authentication is required to run GSmartControl</message>
<icon_name>gsmartcontrol</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/gsmartcontrol</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>
@@ -1,7 +1,7 @@
Source: gsmartcontrol
Section: utils
Priority: extra
Homepage: http://gsmartcontrol.sourceforge.net/
Homepage: https://gsmartcontrol.sourceforge.io/
Maintainer: Alexander Shaduri <ashaduriREMOVETHIS@gmail.com>
Build-Depends: debhelper (>= 5), autotools-dev,
libpcre3-dev, libgtkmm-3.0-dev (>= 3.4.0)
@@ -9,8 +9,7 @@ Standards-Version: 3.7.3
Package: gsmartcontrol
Architecture: any
#Depends: ${shlibs:Depends}, ${misc:Depends}, smartmontools, menu
Depends: ${shlibs:Depends}, smartmontools, menu
Depends: ${shlibs:Depends}, smartmontools (>= 5.43), xterm, menu
Description: Hard disk drive and SSD health inspection tool
GSmartControl is a graphical user interface for smartctl (from smartmontools
package), which is a tool for querying and controlling SMART
@@ -1,7 +1,7 @@
This package was debianized by Alexander Shaduri <ashaduri 'at' gmail.com> on
Sat, 15 Nov 2008 00:12:04 +0400.
It was downloaded from <http://gsmartcontrol.sourceforge.net/>
It was downloaded from <https://gsmartcontrol.sourceforge.io/>
Copyright:
@@ -1,34 +1,30 @@
# This spec file is for openSUSE Build Service.
# Supported distributions: openSUSE, Fedora, CentOS, RHEL.
Name: gsmartcontrol
Version: @VERSION@
Release: 1as
Release: 0
License: GPL-2.0 AND GPL-3.0
Url: http://gsmartcontrol.sourceforge.net/
Url: https://gsmartcontrol.sourceforge.io/
Vendor: Alexander Shaduri <ashaduri 'at' gmail.com>
# Packager: Alexander Shaduri <ashaduri 'at' gmail.com>
Source: http://sourceforge.net/projects/gsmartcontrol/files/%{version}/%{name}-%{version}.tar.bz2
BuildRoot: %{_tmppath}/%{name}-%{version}-build
Summary: GSmartControl - Hard Disk Drive and SSD Health Inspection Tool
%if 0%{?mandriva_version}
Group: System/Kernel and hardware
%else
Group: Hardware/Other
%endif
# Empty debug packages cause errors in new RPM. Disable them.
%global debug_package %{nil}
# Dependencies for various distributions. The actual deps are:
# smartmontools, pcre, gtkmm3 >= 3.4.0
# (with the respective -devel packages of the libraries for build requirements).
# For non-specified distributions we don't specify any dependencies to avoid errors.
# SUSE / OpenSUSE. SLES also defines the correct suse_version.
%if 0%{?suse_version}
Requires: smartmontools
Requires: smartmontools >= 5.43, polkit, bash, xterm
BuildRequires: gcc-c++, libstdc++-devel, pcre-devel, gtkmm3-devel >= 3.4.0
BuildRequires: update-desktop-files
BuildRequires: fdupes
@@ -39,16 +35,9 @@ BuildRequires: fdupes
# Fedora, CentOS, RHEL
%if 0%{?fedora_version} || 0%{?rhel_version} || 0%{?centos_version}
# I'm not sure about centos and rhel versions
Requires: smartmontools
Requires: smartmontools >= 5.43, polkit, bash, xterm
BuildRequires: gcc-c++, pcre-devel, gtkmm30-devel >= 3.4.0
# Fedora with consolehelper.
# usermode-gtk is for consolehelper-gtk.
%if 0%{?fedora_version}
Requires: usermode-gtk
%endif
%endif
@@ -97,43 +86,6 @@ rm -f $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/icon-theme.cache
%endif
%if 0%{?fedora_version}
# We still retain gsmartcontrol-root for consistency with other distributions.
# Make the desktop file run gsmartcontrol instead of gsmartcontrol-root
sed "s|gsmartcontrol-root|gsmartcontrol|g" %{buildroot}%{_datadir}/applications/gsmartcontrol.desktop > %{buildroot}%{_datadir}/applications/gsmartcontrol.desktop.new
mv -f %{buildroot}%{_datadir}/applications/gsmartcontrol.desktop.new %{buildroot}%{_datadir}/applications/gsmartcontrol.desktop
# The rest is taken from the official fedora spec:
# http://pkgs.fedoraproject.org/cgit/gsmartcontrol.git/tree/
# Set up for console-helper
mkdir -p %{buildroot}%{_sbindir}
# Add a copy as "gsmartcontrol-noroot", since gsmartcontrol will always
# ask for root password.
cp %{buildroot}%{_bindir}/%{name} %{buildroot}%{_bindir}/%{name}-noroot
mv %{buildroot}%{_bindir}/%{name} %{buildroot}%{_sbindir}/%{name}
ln -s consolehelper %{buildroot}%{_bindir}/%{name}
mkdir -p %{buildroot}%{_sysconfdir}/pam.d
cat <<EOF >%{buildroot}%{_sysconfdir}/pam.d/%{name}
#%%PAM-1.0
auth include config-util
account include config-util
session include config-util
EOF
mkdir -p %{buildroot}%{_sysconfdir}/security/console.apps
cat <<EOF >%{buildroot}%{_sysconfdir}/security/console.apps/%{name}
USER=root
PROGRAM=/usr/sbin/gsmartcontrol
SESSION=true
EOF
# endif fedora
%endif
%clean
rm -rf %buildroot
@@ -141,12 +93,8 @@ rm -rf %buildroot
%files
%defattr(-,root,root)
# In fedora /usr/bin/gsmartcontrol is linked to consolehelper, cannot set attrs on symlink.
%if 0%{?fedora_version}
%{_bindir}/*
%else
%attr(0755,root,root) %{_bindir}/*
%endif
%attr(0755,root,root) %{_bindir}/gsmartcontrol-root
%attr(0755,root,root) %{_sbindir}/gsmartcontrol
# %%attr(0644,root,root) %%config(noreplace) %%{_sysconfdir}/*
@@ -157,13 +105,11 @@ rm -rf %buildroot
# %%{_datadir}/gsmartcontrol/*
%{_datadir}/applications/*.desktop
%{_datadir}/metainfo
%{_datadir}/metainfo/gsmartcontrol.appdata.xml
%{_datadir}/polkit-1
%{_datadir}/polkit-1/actions
%{_datadir}/polkit-1/actions/org.gsmartcontrol.policy
%{_datadir}/icons/*
%{_datadir}/pixmaps/*
%if 0%{?fedora_version}
%config(noreplace) %{_sysconfdir}/pam.d/%{name}
%config(noreplace) %{_sysconfdir}/security/console.apps/%{name}
%{_sbindir}/gsmartcontrol
%endif
%changelog
-28
View File
@@ -1,28 +0,0 @@
Copyright (c) 2005 - 2010, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER 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.
-407
View File
@@ -1,407 +0,0 @@
About GSmartControl
GSmartControl - Hard disk drive and SSD health inspection tool.
GSmartControl is a graphical user interface for smartctl (from smartmontools
package, see http://smartmontools.sourceforge.net), which is a tool for
querying and controlling SMART (Self-Monitoring, Analysis, and Reporting
Technology) data on modern hard disk and solid-state drives. It allows you to
inspect the drive's SMART data to determine its health, as well as run various
tests on it.
Note: GSmartControl supports ATA drives (both PATA and SATA), various USB to
ATA bridges and drives behind some RAID controllers (selected models of
3ware, Areca, Adaptec, Intel RAID controllers).
http://gsmartcontrol.sourceforge.net
Features
* automatically reports and highlights any anomalies;
* allows enabling/disabling SMART;
* allows enabling/disabling Automatic Offline Data Collection - a short
self-check that the drive will perform automatically every four hours with no
impact on performance;
* supports configuration of global and per-drive options for smartctl;
* performs SMART self-tests;
* displays drive identity information, capabilities, attributes, and
self-test/error logs;
* can read in smartctl output from a saved file, interpreting it as a
read-only virtual device;
* works on most smartctl-supported operating systems;
* has extensive help information.
What Is SMART?
Short answer: SMART is a technology which provides hard disk drives with
methods to predict certain kinds of failures with certain chance of success.
Long answer: read below.
Self-Monitoring, Analysis, and Reporting Technology, or SMART, is a
monitoring system for hard drives and SSDs to detect and report various
indicators of reliability, in the hope of anticipating failures. SMART is
implemented inside the drives, providing several ways of monitoring the drive
health. It may present information about general health, various drive
attributes (for example, number of unreadable sectors), error logs, and so on.
It may also provide ways to instruct the drive to run various self-tests,
which may report valuable information. It may even automatically scan the disk
surface in when the drive is idle, repairing the defects while reallocating
the data to more safe areas.
While having SMART sounds really good, there are some nuances to consider. One
of the common pitfalls is that it may create a false sense of security. That
is, a perfectly good SMART data is NOT an indication that the drive won't fail
the next minute. The reverse is also true - some drives may function perfectly
even with not-so-good-looking SMART data. However, as studies indicate, given
a large population of drives, some SMART attributes may reliably predict
drive failures within up to two months.
Another common mistake is to assume that the attribute values are the real
physical values, as experienced by the drive. As manufacturers do not
necessarily agree on precise attribute definitions and measurement units, the
exact meaning of the attributes may vary greatly across different drive
models.
At present SMART is implemented individually by manufacturers. While some
aspects are standardized for compatibility, others are not. In fact, most
manufacturers refer the users to their own health monitoring utilities and
advice against taking SMART data seriously. Nevertheless, SMART may prove an
effective measure against data loss.
Yet another issue is that quite often the drives have bugs which prevent
correct SMART usage. This is usually due to buggy firmware, or the
manufacturer ignoring the standards. Luckily, smartmontools usually detects
these bugs and works around them.
Software Requirements
You need to have the following software installed:
* pcre - http://www.pcre.org .
* smartmontools - see http://smartmontools.sourceforge.net .
* GTK+, version 3.4 or higher - see http://www.gtk.org .
* Gtkmm, version 3.4 or higher - see http://www.gtkmm.org .
Most of these packages are probably already provided by your distribution.
Here are the packages you need to have to build GSmartControl on some Linux
distributions:
openSUSE, SLES, SLED:
gcc-c++, pcre-devel, gtkmm3-devel.
Fedora, CentOS, RHEL:
gcc-c++, pcre-devel, gtkmm30-devel.
Ubuntu, Debian GNU/Linux:
g++, libpcre3-dev, libgtkmm-3.0-dev.
Note that usually you need to specify only these packages - the rest is
installed automatically by the package manager's dependency resolver. Keep in
mind that you also need smartmontools to run the program.
Note: If using the official Windows package, the requirements are listed on
GSmartControl's website.
The following operating systems are supported:
* Linux - All the popular configurations should work.
* FreeBSD - Tested with DesktopBSD / x86.
* NetBSD - Tested with NetBSD / x86.
* OpenBSD - Tested with OpenBSD / x86-64.
* DragonFlyBSD - Code written but no testing has been performed yet. Expected
to work without any issues.
* Windows Vista SP2 (32-bit and 64-bit), Windows 7 SP1, Windows Server 2008,
Windows 8.1, Windows 10. The Windows port uses pd0, pd1, etc...
for physical drives 0, 1, etc... .
* Solaris.
* Mac OS X - 3rd-party testing by the MacPorts and Fink projects.
* QNX - Code written but no testing has been performed yet.
Installation
Short answer: build and install via: ./configure; make; make install
Run gsmartcontrol-root to invoke gsmartcontrol with your desktop's su
mechanism, or use the desktop menu entry.
Long answer: read below.
First, check if you can find a pre-built package for your distribution or
operating system - they usually provide the best integration and the easiest
installation procedure. For Linux, one option is to try the openSUSE Build
Service - it provides ready-to-install packages for various popular Linux
distributions (openSUSE, Fedora, etc...). See
http://download.opensuse.org/repositories/home:/alex_sh/ .
If you want to compile from source, check that you have all the required
dependencies (see Software Requirements section). Then the usual
./configure; make; make install
will build and install it. Installation usually requires administrative
privileges, but you don't need to install the program in order to run it
directly from the build directory.
Smartctl Options
GSmartControl tries its best to guard the user from having to specify smartctl
options. However, this is not always possible due to drive firmware bugs,
unimplemented features, and so on. The smartctl manual page contains all the
information you may need when dealing with smartctl.
See http://smartmontools.sourceforge.net/man/smartctl.8.html
Additional information is available at http://smartmontools.sourceforge.net
Command Line Options
GSmartControl inherits options from GTK+ and other libraries, so be sure to
run it with --help option to get a full list of accepted parameters.
Note: The Windows version may not have a text output at all, so --help and
similar arguments won't have any effect. However, --verbose will still
increase the verbosity of a log saved via "Options -> View Execution Log ->
Save All".
The most important parameters are:
-?, --help - Show help options.
-l, --no-locale - Don't use system locale.
-V, --version - Display version information.
--no-scan - Don't scan devices on startup.
--no-hide-tabs - Don't hide non-identity tabs when SMART is disabled. Useful
for debugging.
--add-virtual - Load smartctl data from file, creating a virtual drive. You
can specify this option multiple times.
--add-device - Add this device to device list. The format of the device is
"<device>::<type>::<extra_args>", where type and extra_args are optional. This
option is useful with --no-scan to list certain drives only. You can specify
this option multiple times.
Example: --add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device
'/dev/sdb::::-T permissive'.
-v, --verbose - Enable verbose logging; same as --verbosity-level 5.
-q, --quiet - Disable logging; same as --verbosity-level 0.
-b, --verbosity-level - Set verbosity level [0-5].
Permission Problems
Short answer: you need to be root (that's Administrator in Windows).
In X11, use kdesu, gnomesu, sux, xdg-su or similar.
Long answer: read below.
Most operating systems prohibit direct access to hardware to users with
non-administrative privileges. Unfortunately, to access SMART data, smartctl
needs to directly access the hard drive.
The provided X11 desktop and menu icons should show the "Please enter the root
password" dialog boxes, and, after correct information is entered, should run
this program with root privileges. The dialogs should be available in most
commonly used X11 desktop environments.
Another way is to use the included gsmartcontrol-root script, which finds the
available su program and runs gsmartcontrol with it.
Yet another way is to manually invoke the program with kdesu, gnomesu, sux or
similar programs. For example,
kdesu -u root -c gsmartcontrol
will ask for root password and run gsmartcontrol with root privileges. Replace
"kdesu" with "gnomesu" if using Gnome. The "sux" or "xdg-su" commands may also
help, if neither KDE or GNOME are available.
Please don't set the "setuid" flag on smartctl binary. It is considered a
security risk.
Enable SMART Permanently
Specifications say that once you set a SMART-related property, it will be
preserved across reboots. So, when you, say, enable SMART and Automatic
Offline Data Collection, both will stay enabled until you disable them.
However, BIOS, your operating system, your other operating systems (if
present), and various startup programs may affect that. For example, BIOS may
enable SMART each time you start your computer, so if you disabled SMART
previously, it will be re-enabled on reboot.
The easiest way to work around this is to set the desired settings on system
startup. You may use smartctl or smartd to do that. For example, to enable
both SMART and Automatic Offline Data Collection on /dev/sda, one would write
the following to the system startup script (e.g. boot.local, rc.local or
similar on Linux):
smartctl -s on -o on /dev/sda
For more information, see smartctl and smartd documentation.
Known Issues
Only ATA drives (both PATA and SATA), various USB to ATA bridges and drives
behind some RAID controllers are supported for now. The main reasons are:
* We can't support drives which don't work with smartmontools. This affects
drives which don't support SMART or don't export SMART data correctly (e.g.
some USB enclosures, RAIDs, etc...).
* Smartctl's output for SCSI drives is completely different compared to ATA.
Also, SCSI drives are rarely found in desktop systems and the servers rarely
have X11 / Gtkmm running, so this is a low priority task.
* I only have ATA drives, so testing anything else is very difficult.
Immediate Offline Tests are not supported. I haven't found a way to reliably
monitor them yet. Besides, they run automatically anyway if Automatic Offline
Data Collection is enabled.
Testing is only supported on drives which correctly report their progress
information in capabilities.
Not all drives support disabling Automatic Offline Data Collection, even if
they report otherwise. Unfortunately, there's no way to detect such drives.
The texts probably contain a lot of grammatical errors, English being my third
language and all.
Reporting Bugs
If it is a SMART or drive-related problem, please try to test it with smartctl
first. Chances are, the problem you're experiencing is not tied to
GSmartControl, but is a drive firmware or smartctl problem. For example, to
see a complete information about your /dev/sda drive, type the following in a
terminal emulator (e.g., xterm, konsole or gnome-terminal):
smartctl -a /dev/sda
Note: If using Windows, the device name should be /dev/pd1 for the second
physical drive, etc... .
If you still think it's a GSmartControl issue, please collect the following
information about your system. Without it, it may be very hard or impossible
to fix the bug.
* Which operating system you use (for example, openSUSE Linux 11.4).
* Which version of GTK and Gtkmm you have installed. Finding this out is very
distribution-specific. For example, on openSUSE it would be
"rpm -q gtk3 gtkmm3". Some distributions have gtkmm30 instead. You may also
search them in your distribution's graphical package manager, if there is one.
* Execution log from the program, if possible. To obtain it, run the program
with -v option, e.g. (type the following in a terminal emulator or Run
dialog):
gsmartcontrol-root auto -v
Note: On Windows, run gsmartcontrol.exe with "-v" switch as Administrator.
Perform the steps needed to reproduce the bug, then go to
"Options -> View Execution Log", and click "Save All".
* Detailed description of steps you performed when the bug occurred.
Once you have this information, send an email to me, Alexander Shaduri
<ashaduri 'at' gmail.com>. Note that I may refer you to smartmontools support
if it's a bug in smartmontools and not GSmartControl. Normally, I won't
redirect your support request to them myself, because they may ask questions
which only you have the answers to.
Please read the "License and Copyright" and "Patch Licensing" sections before
sending any patches.
License and Copyright
For license information, see LICENSE_gsmartcontrol.txt file.
You may notice that GSmartControl is not licensed under "GNU GPL version X or
later", but under "GNU GPL version X and Y". I firmly believe that it's unwise
to license a piece of code under non-existent licenses, whatever anyone else
might say. The reason for this is that one simply CANNOT know that, say, in 20
years FSF won't be bought by some corporation who will release GPL version Z
which will completely reverse the reasons GPL was created for.
The removal of "or later" clause somewhat imposes responsibility on the
copyright holders to review every future version of the license once it's
released, and, if deemed acceptable, re-license the code under the new license
(possibly retaining the old licenses). Unfortunately, this is a necessary
inconvenience we will have to deal with.
Patch Licensing
Due to reasons described in "License and Copyright" section, to make it
possible to re-license the code without tracking down all the people who ever
wrote a patch, the copyrights of all minor patches must be assigned to the
central copyright holder of the project. If the patch is major enough (that
is, it forms a significant part of the program source code), the author may
retain the copyright, if he or she chooses to do so. However, unless the
author plans to maintain his part of the source code, he / she is humbly asked
to consider assigning away his / her copyright. A simple "I disclaim all
copyright to this patch" by the author is sufficient. All credits will be
mentioned in product documentation, whatever the size of the patch is.
Please note that centralization of copyright is needed to maintain a
reasonably healthy legal status of the project. Also note that this method is
not unique - FSF and many other organizations require exactly the same thing.
Some contributors may have reasonable doubts about the future status of this
project. Let me assure you that this project will never have more restrictive
license than GPLv2. If, some time in the future, the GPL is somehow
invalidated in court, the project will be re-licensed under similar (in
spirit) license, if possible, or a license less restrictive than GPL (for
example, the three-clause BSD license).
-243
View File
@@ -1,243 +0,0 @@
GTKMM Notes:
To transfer an ownership from your local code to a container, use
Gtk::Button* pButton = Gtk::manage(new Gtk::Button("Test"));
container->add(*pButton);
(add() takes an object by reference).
otherwise, you will need to delete() it.
local scope destruction also works if using stack variables.
{
Gtk::Button aButton;
// destroyed here
}
A treeview (also iconview, combobox) from glade comes
with existing model, which should be unset.
Bugs / patches:
Patch pangomm for Pango::strip_markup().
--------------------------------------------------------
TODO:
Don't rely on smartctl return code (2), parse the output instead.
This will allow us to show the Info page.
?Fix not being able to quit while executing commands.
Cannot reproduce.
https://www.google.com/search?client=opera&q=IA__gtk_main_quit:+assertion+%60main_loops+!=+NULL'+failed&sourceid=opera&ie=utf-8&oe=utf-8&channel=suggest
https://developer.gnome.org/gtk2/2.24/gtk2-General.html#gtk-main-quit
https://www.google.com/search?num=50&hl=en&safe=off&client=opera&hs=Dr5&channel=suggest&q=calling+gtk_main_quit+during+gtk_main_iteration+warning&oq=calling+gtk_main_quit+during+gtk_main_iteration+warning&gs_l=serp.12...7064.8155.0.12753.8.8.0.0.0.0.216.935.0j6j1.7.0.les%3B..0.0...1c.1.4.serp.GUiDL5IM2U8
https://developer.gnome.org/glib/2.34/glib-The-Main-Event-Loop.html#g-main-loop-quit
In main window add multi-selection support and right-click menu with various
tests, so that it's possible to run multiple tests in parallel from there.
Areca detection
Linux:
Somehow differentiate between the cards with expanders (-d areca,N/E) and without (-d areca,N).
The expander ones seem to have "ix" (case-insensitive) as their name suffixes.
areca,N - N is [1,24].
areca,N/E - N is [1,128], E is [1,8].
We should limit these using some config key (36 for N and 4 for E seems ok).
N/E syntax is since smartmontools 5.43.
For older versions we get: /dev/<something>: Option -d areca,N requires N to be a non-negative integer
Add ability (through ctrl-C?) to copy individual rows from attributes page,
in tab-separated format (for easy pasting into libreoffice).
Investiage the new "-l ssd" and "-l devstat" options (since 5.42)
Check also other options in 5.42 and 5.43.
"-g all" looks promising alongside -i (shows APM and other stuff as well).
Add --identify option to context menu, for convenience.
Add menu item - "update drive database" (not sure about linux, should
check if it's supported first; also need to run it in terminal).
Testing:
If ETA time has elapsed, but it's still only at 10% completion,
ETA 0 is displayed. Fix.
If smartctl -t ... returns an error, testing=true should not be set.
Can't reproduce it?
Detect running tests on launch (maybe ask the user too? some tests
may be stuck due to bad firmware, e.g. 3ware/windows).
48-bit ATA command support:
'-l xerror' -> Extended Comprehensive Error log: An multi-sector and
LBA48 capable version of '-l error'. Output is similar, except more
entries can exists and the high bytes of the registers are also printed.
Some newer drives (Samsung) only write to this log.
'-l xselftest' -> Extended Self Test Log: An multi-sector and LBA48
capable version of '-l selftest'. Output will be similar, except more
entries can exists and the LBA values can be somewhat longer.
"smartctl -l this_wont_exist" gives the list of supported logs - this
is needed to detect xerror / xselftest support in smartctl.
Rework drive properties UI:
use 48-bit or not or auto.
Enable/disable/dont_touch SMART on startup for this drive.
Enable/disable/dont_touch AODC on startup for this drive.
Add --init-smart-only option to only apply the SMART/AODC enabling status
from the drive properties.
Add also --no-init-smart to avoid doing that on GUI startup.
Notifications:
Tied to (drive::subdrive, notif_key, exact_value(optional)).
If exact_value isn't specified when ignoring, notify on every value change.
Ignore options:
Per-drive: Ignore all notifications for this key, ignore only this value.
Don't generate multiple notifications for the same drive/key combo.
Clearing in preferences: Clear all ignored, clear ignored for this drive, clear individual.
Soft-warnings require "-a" by default, tricky for scsi (maybe try -a, then -i?).
Monitor:
Needs root access, so not gonna happen until the dbus/udisks stuff
is finalized.
Same program, add --tray option to start in tray only.
Option to enable the tray icon.
Option to continue running in the tray on window close.
Use freedesktop notifications when notifications are generated
and we're running in tray only.
Don't generate the same notification at a single run time if it was
ignored (untouched) by the user; Change the tray icon and
show them in the main window instead.
Periodically re-run smartctl to refresh the notifications.
Periodically see if drives were added / removed (possibly use OS callbacks
instead of polling?)
Win7 widget:
Can be done in html/js.
Shows temperature
Detects drives
Opens GSmartControl
If a drive is not recognized, suggest using -d (sat, sat,12, ...)
Refer the user to
http://sourceforge.net/apps/trac/smartmontools/wiki/Supported_USB-Devices
RAID:
http://sourceforge.net/apps/trac/smartmontools/wiki/Supported_RAID-Controllers
Areca Windows:
Detection not supported by smartctl --scan-open.
Areca FreeBSD: since 5.42.
Call as: smartctl -a -d areca,[1-24] /dev/arcmsr1
3ware FreeBSD:
Call as: smartctl -i -d 3ware,[0-127] /dev/twa0 (or twe0)
Detection: unknown.
LSI MegaRAID (SCSI/SAS), Linux:
Call as: smartctl -i -d megaraid,[0-?] /dev/sda
We should differentiate between ordinary sda and RAID one (not sure if sda is detected).
Detection: unknown.
HighPoint RocketRAID ((S)ATA) Linux:
Call as: smartctl -i -d hpt,[1-8]/[1-8] /dev/sda
Call as: smartctl -i -d hpt,[1-8]/[1-8]/[1-15] /dev/sda
Note: /dev/sda should be derived from this controller.
We should differentiate between ordinary sda and RAID one (not sure if sda is detected).
Smartctl docs specify smaller limits, but the code says these are the ones.
Detection: unknown.
HighPoint RocketRAID ((S)ATA) FreeBSD:
Call as: smartctl -i -d hpt,[1-8]/[1-8] /dev/hptrr
Call as: smartctl -i -d hpt,[1-8]/[1-8]/[1-15] /dev/hptrr
Note: /dev/hptrr should be derived from this controller.
Smartctl docs specify smaller limits, but the code says these are the ones.
Detection: unknown.
TODO: Analyze sent info.
CCISS (HP (Compaq) Smart Array Controller) FreeBSD:
Seems to be the same as Linux (uses the same driver?).
Detection: unknown.
libdebug:
per-channel disabling.
per-channel formatting.
all flags should be per-channel.
bug: if -q is passed, Execution log doesn't contain libdebug log. (requires per-channel status control).
use format_time() instead of ctime() and friends, with ISO formatting.
'-l sataphy' -> SATA Phy event counters: Non-SMART feature, useful to detect SATA cabling problems.
SCT Temperatures
-l scttemp prints temperature and history.
-t scttempint,N[,p] (N - count, p - preserve across reset or not).
This configures temperature logging interval (1 min by default) and clears the history.
-l scterc[,...] to get/set the SCT Error Recovery Control time limit (5.40 and newer).
Add detection for Interix (i586-pc-interix3), maybe with debian?
----------- SMART stuff ----------
Run one-time Immediate Offline test (-t offline).
Offline tests only update Attributes, and if errors are found they will
appear in SMART error log. Self-test logs are unaffected.
Abort (-X). Abort works with Offline only if there's
"Abort Offline collection upon new command" capability.
If the drive has "Suspend Offline collection upon new command" capability,
immediate offline test may be tracked through --capabilities (not on mine!).
If it's "Abort Offline collection upon new command", then the test
will abort on --capabilities or --abort.
Polling time
The self-test routine recommended polling time shall be equal to the number of minutes that is the minimum
recommended time before which the host should first poll for test completion status. Actual test time could
be several times this value. Polling before this time could extend the self-test execution time or abort the test
depending on the state of bit 2 of the off-line data capability bits.
Autosave
This command enables and disables the optional attribute autosave feature of the device. This command
may either allow the device, after some vendor specified event, to save the device updated attribute values
to non-volatile memory; or this command may cause the autosave feature to be disabled. The state of the
attribute autosave feature (either enabled or disabled) shall be preserved by the device across power cycles.
A value of zero written by the host into the device’s Sector Count register before issuing this command shall
cause this feature to be disabled. Disabling this feature does not preclude the device from saving SMART
data to non-volatile memory during some other normal operation such as during a power-on or power-off
sequence or during an error recovery sequence.
A value of F1h written by the host into the device’s Sector Count register before issuing this command shall
cause this feature to be enabled. Any other meaning of this value or any other non-zero value written by the
host into this register before issuing this command may differ from device to device. The meaning of any
non-zero value written to this register at this time shall be preserved by the device across power cycles.
@@ -1,191 +0,0 @@
/**************************************************************************
Copyright:
(C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com>
License: See LICENSE_gsmartcontrol.txt
***************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib
/// \weakgroup applib
/// @{
#include <map>
#include <ostream> // not iosfwd - it doesn't work
#include "hz/string_num.h" // number_to_string
#include "hz/stream_cast.h" // stream_cast<>
#include "hz/format_unit.h" // format_time_length
#include "hz/string_algo.h" // string_join
#include "storage_property.h"
std::ostream& operator<< (std::ostream& os, const StorageCapability& p)
{
os
// << p.name << ": "
<< p.flag_value;
for (StorageCapability::strvalue_list_t::const_iterator iter = p.strvalues.begin(); iter != p.strvalues.end(); ++iter) {
os << "\n\t" << *iter;
}
return os;
}
std::ostream& operator<< (std::ostream& os, const StorageAttribute& p)
{
// os << p.name << ": "
if (p.value.defined()) {
os << static_cast<int>(p.value.value());
} else {
os << "-";
}
os << " (" << p.raw_value_int << ")";
return os;
}
std::string StorageErrorBlock::get_readable_error_types(const std::vector<std::string>& types)
{
std::map<std::string, std::string> m;
m["ABRT"] = "Command aborted";
m["AMNF"] = "Address mark not found";
m["CCTO"] = "Command completion timed out";
m["EOM"] = "End of media";
m["ICRC"] = "Interface CRC error";
m["IDNF"] = "Identity not found";
m["ILI"] = "(Packet command-set specific)";
m["MC"] = "Media changed";
m["MCR"] = "Media change request";
m["NM"] = "No media";
m["obs"] = "Obsolete";
m["TK0NF"] = "Track 0 not found";
m["UNC"] = "Uncorrectable error in data";
m["WP"] = "Media is write protected";
std::vector<std::string> sv;
for (std::vector<std::string>::const_iterator iter = types.begin(); iter != types.end(); ++iter) {
if (m.find(*iter) != m.end()) {
sv.push_back(m[*iter]);
} else {
sv.push_back("[unknown type" + (iter->empty() ? "" : (": " + (*iter))) + "]");
}
}
return hz::string_join(sv, ", ");
}
std::ostream& operator<< (std::ostream& os, const StorageErrorBlock& b)
{
os << "Error number " << b.error_num << ": "
<< hz::string_join(b.reported_types, ", ")
<< " [" << StorageErrorBlock::get_readable_error_types(b.reported_types) << "]";
return os;
}
std::ostream& operator<< (std::ostream& os, const StorageSelftestEntry& b)
{
os << "Test entry " << b.test_num << ": "
<< b.type << ", status: " << b.get_status_str() << ", remaining: " << int(b.remaining_percent);
return os;
}
void StorageProperty::dump(std::ostream& os, int internal_offset) const
{
std::string offset(internal_offset, ' ');
os << offset << "[" << get_section_name(section)
<< (section == section_data ? (", " + get_subsection_name(subsection)) : "") << "]"
<< " " << generic_name
// << (generic_name == reported_name ? "" : (" (" + reported_name + ")"))
<< ": [" << get_value_type_name(value_type) << "] ";
// if (!readable_value.empty())
// os << readable_value;
if (value_type == StorageProperty::value_type_unknown) {
os << "[empty]";
} else if (value_type == StorageProperty::value_type_string) {
os << "\"" << value_string << "\"";
} else if (value_type == StorageProperty::value_type_integer) {
os << value_integer << " [" << reported_value << "]";
} else if (value_type == StorageProperty::value_type_bool) {
os << value_bool << " [" << reported_value << "]";
} else if (value_type == StorageProperty::value_type_time_length) {
os << value_time_length << " [" << reported_value << "]";
} else if (value_type == StorageProperty::value_type_capability) {
os << value_capability;
} else if (value_type == StorageProperty::value_type_attribute) {
os << value_attribute;
} else if (value_type == StorageProperty::value_type_error_block) {
os << value_error_block;
} else if (value_type == StorageProperty::value_type_selftest_entry) {
os << value_selftest_entry;
}
}
std::string StorageProperty::format_value(bool add_reported_too) const
{
if (!readable_value.empty())
return readable_value;
if (value_type == StorageProperty::value_type_unknown) {
return "[unknown]";
} else if (value_type == StorageProperty::value_type_string) {
return value_string;
} else if (value_type == StorageProperty::value_type_integer) {
return hz::number_to_string(value_integer) + (add_reported_too ? (" [" + reported_value + "]") : "");
} else if (value_type == StorageProperty::value_type_bool) {
return std::string(value_bool ? "Yes" : "No") + (add_reported_too ? (" [" + reported_value + "]") : "");
} else if (value_type == StorageProperty::value_type_time_length) {
return hz::format_time_length(value_time_length) + (add_reported_too ? (" [" + reported_value + "]") : "");
} else if (value_type == StorageProperty::value_type_capability) {
return hz::stream_cast<std::string>(value_capability);
} else if (value_type == StorageProperty::value_type_attribute) {
return hz::stream_cast<std::string>(value_attribute);
} else if (value_type == StorageProperty::value_type_error_block) {
return hz::stream_cast<std::string>(value_error_block);
} else if (value_type == StorageProperty::value_type_error_block) {
return hz::stream_cast<std::string>(value_selftest_entry);
}
return "[error]";
}
/// @}
-223
View File
@@ -1,223 +0,0 @@
/**************************************************************************
Copyright:
(C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com>
License: See LICENSE_gsmartcontrol.txt
***************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup gsc
/// \weakgroup gsc
/// @{
#include <vector>
#include <gtkmm.h>
#include <gdk/gdk.h> // GDK_KEY_Escape
#include "applib/app_gtkmm_features.h"
#include "hz/string_algo.h"
#include "applib/app_gtkmm_utils.h" // app_gtkmm_create_tree_view_column
#include "applib/app_pcrecpp.h"
#include "gsc_help_window.h"
HZ_RES_DATA_INIT_NAMED(README_txt, "README.txt", ReadmeTextResData);
GscHelpWindow::GscHelpWindow(BaseObjectType* gtkcobj, const app_ui_res_ref_t& ref_ui)
: AppUIResWidget<GscHelpWindow, false>(gtkcobj, ref_ui), selection_callback_enabled(true)
{
// Connect callbacks
APP_GTKMM_CONNECT_VIRTUAL(delete_event); // make sure the event handler is called
Gtk::Button* window_close_button = 0;
APP_UI_RES_AUTO_CONNECT(window_close_button, clicked);
// Accelerators
Glib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();
if (window_close_button) {
window_close_button->add_accelerator("clicked", accel_group, GDK_KEY_Escape,
Gdk::ModifierType(0), Gtk::AccelFlags(0));
}
// --------------- Make a treeview
Gtk::TreeView* treeview = this->lookup_widget<Gtk::TreeView*>("topics_treeview");
if (treeview) {
Gtk::TreeModelColumnRecord model_columns;
// Topic
model_columns.add(col_topic);
app_gtkmm_create_tree_view_column(col_topic, *treeview, "Topic", "Topic");
// create a TreeModel (ListStore)
list_store = Gtk::ListStore::create(model_columns);
treeview->set_model(list_store);
selection = treeview->get_selection();
selection->signal_changed().connect(sigc::mem_fun(*this,
&self_type::on_tree_selection_changed) );
}
// --------------- Parse help text
/*
README.txt File Format
The whole text is converted to unix newline format before parsing.
Sections are separated by 3 newlines (two empty lines).
The first line of the section is its header.
When splitting the file to sections and headers, any leading or trailing
whitespace is removed.
If there is a single newline inside a section, it is converted to
space to enable correct wrapping.
If there are two consequent newlines, they are left as they are,
essentially making a paragraph break.
*/
std::string readme = hz::string_any_to_unix_copy(ReadmeTextResData().get_string());
// Paragraphs are delimited by 3 empty lines
std::vector<std::string> topics;
hz::string_split(readme, "\n\n\n\n", topics, true); // skip empty
// Add to treeview and textview
Gtk::TextView* content = this->lookup_widget<Gtk::TextView*>("content_textview");
if (treeview && content) {
Glib::RefPtr<Gtk::TextBuffer> buffer = content->get_buffer();
buffer->create_mark("Begin", buffer->begin(), true);
for (unsigned int i = 0; i < topics.size(); ++i) {
std::string topic = hz::string_trim_copy(topics[i]);
// The first line of topic is its title
std::vector<std::string> topic_split;
hz::string_split(topic, "\n\n", topic_split, true, 2); // skip empty, get 2 elements only
if (topic_split.size() < 2) {
debug_out_warn("app", DBG_FUNC_MSG << "Cannot extract topic title in topic " << i << "\n");
continue;
}
std::string topic_title = hz::string_trim_copy(topic_split[0]);
std::string topic_body = hz::string_trim_copy(topic_split[1]);
buffer->create_mark(topic_title, buffer->end(), true); // set topic mark to the end of what's there
// add the title and make it bold
buffer->insert(buffer->end(), "\n" + topic_title);
Gtk::TextIter first = buffer->end(), last = first;
first.backward_lines(1);
Glib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();
tag->property_weight() = Pango::WEIGHT_BOLD;
tag->property_size_points() = 14;
buffer->apply_tag(tag, first, last);
// add the rest
// single newlines to spaces, to allow proper wrapping.
app_pcre_replace("/([^\\n])\\n([^\\n])/", "\\1 \\2", topic_body);
buffer->insert(buffer->end(), "\n\n" + topic_body + "\n\n");
// Add to treeview
Gtk::TreeRow row = *(list_store->append());
row[col_topic] = topic_title;
}
}
// ---------------
// show();
}
void GscHelpWindow::set_topic(const Glib::ustring& topic)
{
this->selection_callback_enabled = false; // temporarily disable it
// scroll to it
Gtk::TextView* content = this->lookup_widget<Gtk::TextView*>("content_textview");
if (content) {
Glib::RefPtr<Gtk::TextBuffer> buffer = content->get_buffer();
Glib::RefPtr<Gtk::TextMark> mark = buffer->get_mark(topic);
if (mark)
content->scroll_to(mark, 0., 0., 0.);
}
// select it in tree view
Gtk::TreeView* treeview = this->lookup_widget<Gtk::TreeView*>("topics_treeview");
if (treeview && !list_store->children().empty()) {
for (Gtk::TreeIter iter = list_store->children().begin(); iter != list_store->children().end(); ++iter) {
if (iter->get_value(col_topic) == topic) {
selection->select(*iter);
// about 30% from top
treeview->scroll_to_cell(list_store->get_path(iter), *(treeview->get_column(0)), 0.3f, 0.f);
break;
}
}
}
this->selection_callback_enabled = true; // enable it back
}
bool GscHelpWindow::on_delete_event_before(GdkEventAny* e)
{
destroy(this);
return true; // event handled, don't call default virtual handler
}
void GscHelpWindow::on_window_close_button_clicked()
{
destroy(this);
}
void GscHelpWindow::on_tree_selection_changed()
{
if (!this->selection_callback_enabled)
return;
if (selection->count_selected_rows()) {
Gtk::TreeIter iter = selection->get_selected();
Gtk::TreeRow row = *iter;
set_topic(row[col_topic]);
}
}
/// @}
-79
View File
@@ -1,79 +0,0 @@
/**************************************************************************
Copyright:
(C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com>
License: See LICENSE_gsmartcontrol.txt
***************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup gsc
/// \weakgroup gsc
/// @{
#ifndef GSC_HELP_WINDOW_H
#define GSC_HELP_WINDOW_H
#include <gtkmm.h>
#include "applib/app_ui_res_utils.h"
/// The Help window.
/// Use create() / destroy() with this class instead of new / delete!
class GscHelpWindow : public AppUIResWidget<GscHelpWindow, false> {
public:
// name of glade/ui file without a .glade/.ui extension and quotes
APP_UI_RES_DATA_INIT(gsc_help_window);
/// Constructor, gtkbuilder/glade needs this.
GscHelpWindow(BaseObjectType* gtkcobj, const app_ui_res_ref_t& ref_ui);
/// Virtual destructor
virtual ~GscHelpWindow()
{ }
/// Set the current help topic
void set_topic(const Glib::ustring& topic);
protected:
// ---------- overriden virtual methods
/// Destroy this object on delete event (by default it calls hide()).
/// Reimplemented from Gtk::Window.
bool on_delete_event_before(GdkEventAny* e);
// ---------- other callbacks
/// Button click callback
void on_window_close_button_clicked();
/// Callback
void on_tree_selection_changed();
private:
Glib::RefPtr<Gtk::ListStore> list_store; ///< List store
Glib::RefPtr<Gtk::TreeSelection> selection; ///< Tree selection
Gtk::TreeModelColumn<Glib::ustring> col_topic; /// Tree column
bool selection_callback_enabled; ///< Helper for set_topic(), temporarily disables the tree selection changed callback
};
#endif
/// @}
-10
View File
@@ -1,10 +0,0 @@
AM_CPPFLAGS = $(all_includes)
METASOURCES = AUTO
noinst_LIBRARIES = libpcrecpp.a
libpcrecpp_a_SOURCES = pcre_scanner.cpp pcre_stringpiece.cpp pcrecpp.cpp
noinst_HEADERS = pcre_scanner.h pcre_stringpiece.h pcrecpp.h pcrecpp_internal.h \
pcrecpparg.h
# Distribute original, unpatched files too. Append .orig_dist to prevent conflict
# with debian .orig files.
EXTRA_DIST = pcrecpp.cc.orig_dist
-13
View File
@@ -1,13 +0,0 @@
Part of PCRE library, version 8.01.
pcrecpparg.h and pcre_stringpiece.h generated using:
./configure \
--with-link-size=2 \
--with-match-limit=10000000 \
--enable-newline-is-lf \
--enable-utf8 \
--enable-unicode-properties
The system was OpenSUSE 11.1, with gcc-4.3-34.243 and glibc-2.9-2.12.1.
-200
View File
@@ -1,200 +0,0 @@
// Copyright (c) 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: Sanjay Ghemawat
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <vector>
#include <cassert> // Custom
#include <assert.h>
#include "pcrecpp_internal.h"
#include "pcre_scanner.h"
using std::vector;
namespace pcrecpp {
Scanner::Scanner()
: data_(),
input_(data_),
skip_(NULL),
should_skip_(false),
skip_repeat_(false),
save_comments_(false),
comments_(NULL),
comments_offset_(0) {
}
Scanner::Scanner(const string& in)
: data_(in),
input_(data_),
skip_(NULL),
should_skip_(false),
skip_repeat_(false),
save_comments_(false),
comments_(NULL),
comments_offset_(0) {
}
Scanner::~Scanner() {
delete skip_;
delete comments_;
}
void Scanner::SetSkipExpression(const char* re) {
delete skip_;
if (re != NULL) {
skip_ = new RE(re);
should_skip_ = true;
skip_repeat_ = true;
ConsumeSkip();
} else {
skip_ = NULL;
should_skip_ = false;
skip_repeat_ = false;
}
}
void Scanner::Skip(const char* re) {
delete skip_;
if (re != NULL) {
skip_ = new RE(re);
should_skip_ = true;
skip_repeat_ = false;
ConsumeSkip();
} else {
skip_ = NULL;
should_skip_ = false;
skip_repeat_ = false;
}
}
void Scanner::DisableSkip() {
assert(skip_ != NULL);
should_skip_ = false;
}
void Scanner::EnableSkip() {
assert(skip_ != NULL);
should_skip_ = true;
ConsumeSkip();
}
int Scanner::LineNumber() const {
// TODO: Make it more efficient by keeping track of the last point
// where we computed line numbers and counting newlines since then.
// We could use std:count, but not all systems have it. :-(
int count = 1;
for (const char* p = data_.data(); p < input_.data(); ++p)
if (*p == '\n')
++count;
return count;
}
int Scanner::Offset() const {
return input_.data() - data_.c_str();
}
bool Scanner::LookingAt(const RE& re) const {
int consumed;
return re.DoMatch(input_, RE::ANCHOR_START, &consumed, 0, 0);
}
bool Scanner::Consume(const RE& re,
const Arg& arg0,
const Arg& arg1,
const Arg& arg2) {
const bool result = re.Consume(&input_, arg0, arg1, arg2);
if (result && should_skip_) ConsumeSkip();
return result;
}
// helper function to consume *skip_ and honour save_comments_
void Scanner::ConsumeSkip() {
const char* start_data = input_.data();
while (skip_->Consume(&input_)) {
if (!skip_repeat_) {
// Only one skip allowed.
break;
}
}
if (save_comments_) {
if (comments_ == NULL) {
comments_ = new vector<StringPiece>;
}
// already pointing one past end, so no need to +1
int length = input_.data() - start_data;
if (length > 0) {
comments_->push_back(StringPiece(start_data, length));
}
}
}
void Scanner::GetComments(int start, int end, vector<StringPiece> *ranges) {
// short circuit out if we've not yet initialized comments_
// (e.g., when save_comments is false)
if (!comments_) {
return;
}
// TODO: if we guarantee that comments_ will contain StringPieces
// that are ordered by their start, then we can do a binary search
// for the first StringPiece at or past start and then scan for the
// ones contained in the range, quit early (use equal_range or
// lower_bound)
for (vector<StringPiece>::const_iterator it = comments_->begin();
it != comments_->end(); ++it) {
if ((it->data() >= data_.c_str() + start &&
it->data() + it->size() <= data_.c_str() + end)) {
ranges->push_back(*it);
}
}
}
void Scanner::GetNextComments(vector<StringPiece> *ranges) {
// short circuit out if we've not yet initialized comments_
// (e.g., when save_comments is false)
if (!comments_) {
return;
}
for (vector<StringPiece>::const_iterator it =
comments_->begin() + comments_offset_;
it != comments_->end(); ++it) {
ranges->push_back(*it);
++comments_offset_;
}
}
} // namespace pcrecpp
-173
View File
@@ -1,173 +0,0 @@
// Copyright (c) 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: Sanjay Ghemawat
//
// Regular-expression based scanner for parsing an input stream.
//
// Example 1: parse a sequence of "var = number" entries from input:
//
// Scanner scanner(input);
// string var;
// int number;
// scanner.SetSkipExpression("\\s+"); // Skip any white space we encounter
// while (scanner.Consume("(\\w+) = (\\d+)", &var, &number)) {
// ...;
// }
#ifndef _PCRE_SCANNER_H
#define _PCRE_SCANNER_H
#include <cassert> // Custom
#include <assert.h>
#include <string>
#include <vector>
#include <pcrecpp.h>
#include <pcre_stringpiece.h>
namespace pcrecpp {
class PCRECPP_EXP_DEFN Scanner {
public:
Scanner();
explicit Scanner(const std::string& input);
~Scanner();
// Return current line number. The returned line-number is
// one-based. I.e. it returns 1 + the number of consumed newlines.
//
// Note: this method may be slow. It may take time proportional to
// the size of the input.
int LineNumber() const;
// Return the byte-offset that the scanner is looking in the
// input data;
int Offset() const;
// Return true iff the start of the remaining input matches "re"
bool LookingAt(const RE& re) const;
// Return true iff all of the following are true
// a. the start of the remaining input matches "re",
// b. if any arguments are supplied, matched sub-patterns can be
// parsed and stored into the arguments.
// If it returns true, it skips over the matched input and any
// following input that matches the "skip" regular expression.
bool Consume(const RE& re,
const Arg& arg0 = RE::no_arg,
const Arg& arg1 = RE::no_arg,
const Arg& arg2 = RE::no_arg
// TODO: Allow more arguments?
);
// Set the "skip" regular expression. If after consuming some data,
// a prefix of the input matches this RE, it is automatically
// skipped. For example, a programming language scanner would use
// a skip RE that matches white space and comments.
//
// scanner.SetSkipExpression("\\s+|//.*|/[*](.|\n)*?[*]/");
//
// Skipping repeats as long as it succeeds. We used to let people do
// this by writing "(...)*" in the regular expression, but that added
// up to lots of recursive calls within the pcre library, so now we
// control repetition explicitly via the function call API.
//
// You can pass NULL for "re" if you do not want any data to be skipped.
void Skip(const char* re); // DEPRECATED; does *not* repeat
void SetSkipExpression(const char* re);
// Temporarily pause "skip"ing. This
// Skip("Foo"); code ; DisableSkip(); code; EnableSkip()
// is similar to
// Skip("Foo"); code ; Skip(NULL); code ; Skip("Foo");
// but avoids creating/deleting new RE objects.
void DisableSkip();
// Reenable previously paused skipping. Any prefix of the input
// that matches the skip pattern is immediately dropped.
void EnableSkip();
/***** Special wrappers around SetSkip() for some common idioms *****/
// Arranges to skip whitespace, C comments, C++ comments.
// The overall RE is a disjunction of the following REs:
// \\s whitespace
// //.*\n C++ comment
// /[*](.|\n)*?[*]/ C comment (x*? means minimal repetitions of x)
// We get repetition via the semantics of SetSkipExpression, not by using *
void SkipCXXComments() {
SetSkipExpression("\\s|//.*\n|/[*](?:\n|.)*?[*]/");
}
void set_save_comments(bool comments) {
save_comments_ = comments;
}
bool save_comments() {
return save_comments_;
}
// Append to vector ranges the comments found in the
// byte range [start,end] (inclusive) of the input data.
// Only comments that were extracted entirely within that
// range are returned: no range splitting of atomically-extracted
// comments is performed.
void GetComments(int start, int end, std::vector<StringPiece> *ranges);
// Append to vector ranges the comments added
// since the last time this was called. This
// functionality is provided for efficiency when
// interleaving scanning with parsing.
void GetNextComments(std::vector<StringPiece> *ranges);
private:
std::string data_; // All the input data
StringPiece input_; // Unprocessed input
RE* skip_; // If non-NULL, RE for skipping input
bool should_skip_; // If true, use skip_
bool skip_repeat_; // If true, repeat skip_ as long as it works
bool save_comments_; // If true, aggregate the skip expression
// the skipped comments
// TODO: later consider requiring that the StringPieces be added
// in order by their start position
std::vector<StringPiece> *comments_;
// the offset into comments_ that has been returned by GetNextComments
int comments_offset_;
// helper function to consume *skip_ and honour
// save_comments_
void ConsumeSkip();
};
} // namespace pcrecpp
#endif /* _PCRE_SCANNER_H */
@@ -1,43 +0,0 @@
// Copyright (c) 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: wilsonh@google.com (Wilson Hsieh)
//
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <iostream>
#include "pcrecpp_internal.h"
#include "pcre_stringpiece.h"
std::ostream& operator<<(std::ostream& o, const pcrecpp::StringPiece& piece) {
return (o << piece.as_string());
}
@@ -1,178 +0,0 @@
// Copyright (c) 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: Sanjay Ghemawat
//
// A string like object that points into another piece of memory.
// Useful for providing an interface that allows clients to easily
// pass in either a "const char*" or a "string".
//
// Arghh! I wish C++ literals were automatically of type "string".
#ifndef _PCRE_STRINGPIECE_H
#define _PCRE_STRINGPIECE_H
#include <cstring> // Custom
#include <string.h>
#include <string>
#include <iosfwd> // for ostream forward-declaration
#if 0
#define HAVE_TYPE_TRAITS
#include <type_traits.h>
#elif 0
#define HAVE_TYPE_TRAITS
#include <bits/type_traits.h>
#endif
#include <pcre.h>
using std::string;
namespace pcrecpp {
class PCRECPP_EXP_DEFN StringPiece {
private:
const char* ptr_;
int length_;
public:
// We provide non-explicit singleton constructors so users can pass
// in a "const char*" or a "string" wherever a "StringPiece" is
// expected.
StringPiece()
: ptr_(NULL), length_(0) { }
StringPiece(const char* str)
: ptr_(str), length_(static_cast<int>(strlen(ptr_))) { }
StringPiece(const unsigned char* str)
: ptr_(reinterpret_cast<const char*>(str)),
length_(static_cast<int>(strlen(ptr_))) { }
StringPiece(const string& str)
: ptr_(str.data()), length_(static_cast<int>(str.size())) { }
StringPiece(const char* offset, int len)
: ptr_(offset), length_(len) { }
// data() may return a pointer to a buffer with embedded NULs, and the
// returned buffer may or may not be null terminated. Therefore it is
// typically a mistake to pass data() to a routine that expects a NUL
// terminated string. Use "as_string().c_str()" if you really need to do
// this. Or better yet, change your routine so it does not rely on NUL
// termination.
const char* data() const { return ptr_; }
int size() const { return length_; }
bool empty() const { return length_ == 0; }
void clear() { ptr_ = NULL; length_ = 0; }
void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; }
void set(const char* str) {
ptr_ = str;
length_ = static_cast<int>(strlen(str));
}
void set(const void* buffer, int len) {
ptr_ = reinterpret_cast<const char*>(buffer);
length_ = len;
}
char operator[](int i) const { return ptr_[i]; }
void remove_prefix(int n) {
ptr_ += n;
length_ -= n;
}
void remove_suffix(int n) {
length_ -= n;
}
bool operator==(const StringPiece& x) const {
return ((length_ == x.length_) &&
(memcmp(ptr_, x.ptr_, length_) == 0));
}
bool operator!=(const StringPiece& x) const {
return !(*this == x);
}
#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp) \
bool operator cmp (const StringPiece& x) const { \
int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \
return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \
}
STRINGPIECE_BINARY_PREDICATE(<, <);
STRINGPIECE_BINARY_PREDICATE(<=, <);
STRINGPIECE_BINARY_PREDICATE(>=, >);
STRINGPIECE_BINARY_PREDICATE(>, >);
#undef STRINGPIECE_BINARY_PREDICATE
int compare(const StringPiece& x) const {
int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_);
if (r == 0) {
if (length_ < x.length_) r = -1;
else if (length_ > x.length_) r = +1;
}
return r;
}
string as_string() const {
return string(data(), size());
}
void CopyToString(string* target) const {
target->assign(ptr_, length_);
}
// Does "this" start with "x"
bool starts_with(const StringPiece& x) const {
return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0));
}
};
} // namespace pcrecpp
// ------------------------------------------------------------------
// Functions used to create STL containers that use StringPiece
// Remember that a StringPiece's lifetime had better be less than
// that of the underlying string or char*. If it is not, then you
// cannot safely store a StringPiece into an STL container
// ------------------------------------------------------------------
#ifdef HAVE_TYPE_TRAITS
// This makes vector<StringPiece> really fast for some STL implementations
template<> struct __type_traits<pcrecpp::StringPiece> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
#endif
// allow StringPiece to be logged
std::ostream& operator<<(std::ostream& o, const pcrecpp::StringPiece& piece);
#endif /* _PCRE_STRINGPIECE_H */
@@ -1,920 +0,0 @@
// Copyright (c) 2010, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: Sanjay Ghemawat
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h> /* for SHRT_MIN, USHRT_MAX, etc */
#include <assert.h>
#include <errno.h>
#include <string>
#include <algorithm>
#include "pcrecpp_internal.h"
#include "pcre.h"
#include "pcrecpp.h"
#include "pcre_stringpiece.h"
namespace pcrecpp {
// Maximum number of args we can set
static const int kMaxArgs = 16;
static const int kVecSize = (1 + kMaxArgs) * 3; // results + PCRE workspace
// Special object that stands-in for no argument
Arg RE::no_arg((void*)NULL);
// This is for ABI compatibility with old versions of pcre (pre-7.6),
// which defined a global no_arg variable instead of putting it in the
// RE class. This works on GCC >= 3, at least. It definitely works
// for ELF, but may not for other object formats (Mach-O, for
// instance, does not support aliases.) We could probably have a more
// inclusive test if we ever needed it. (Note that not only the
// __attribute__ syntax, but also __USER_LABEL_PREFIX__, are
// gnu-specific.)
#if defined(__GNUC__) && __GNUC__ >= 3 && defined(__ELF__)
# define ULP_AS_STRING(x) ULP_AS_STRING_INTERNAL(x)
# define ULP_AS_STRING_INTERNAL(x) #x
# define USER_LABEL_PREFIX_STR ULP_AS_STRING(__USER_LABEL_PREFIX__)
extern Arg no_arg
__attribute__((alias(USER_LABEL_PREFIX_STR "_ZN7pcrecpp2RE6no_argE")));
#endif
// If a regular expression has no error, its error_ field points here
static const string empty_string;
// If the user doesn't ask for any options, we just use this one
static RE_Options default_options;
void RE::Init(const string& pat, const RE_Options* options) {
pattern_ = pat;
if (options == NULL) {
options_ = default_options;
} else {
options_ = *options;
}
error_ = &empty_string;
re_full_ = NULL;
re_partial_ = NULL;
re_partial_ = Compile(UNANCHORED);
if (re_partial_ != NULL) {
re_full_ = Compile(ANCHOR_BOTH);
}
}
void RE::Cleanup() {
if (re_full_ != NULL) (*pcre_free)(re_full_);
if (re_partial_ != NULL) (*pcre_free)(re_partial_);
if (error_ != &empty_string) delete error_;
}
RE::~RE() {
Cleanup();
}
pcre* RE::Compile(Anchor anchor) {
// First, convert RE_Options into pcre options
int pcre_options = 0;
pcre_options = options_.all_options();
// Special treatment for anchoring. This is needed because at
// runtime pcre only provides an option for anchoring at the
// beginning of a string (unless you use offset).
//
// There are three types of anchoring we want:
// UNANCHORED Compile the original pattern, and use
// a pcre unanchored match.
// ANCHOR_START Compile the original pattern, and use
// a pcre anchored match.
// ANCHOR_BOTH Tack a "\z" to the end of the original pattern
// and use a pcre anchored match.
const char* compile_error;
int eoffset;
pcre* re;
if (anchor != ANCHOR_BOTH) {
re = pcre_compile(pattern_.c_str(), pcre_options,
&compile_error, &eoffset, NULL);
} else {
// Tack a '\z' at the end of RE. Parenthesize it first so that
// the '\z' applies to all top-level alternatives in the regexp.
string wrapped = "(?:"; // A non-counting grouping operator
wrapped += pattern_;
wrapped += ")\\z";
re = pcre_compile(wrapped.c_str(), pcre_options,
&compile_error, &eoffset, NULL);
}
if (re == NULL) {
if (error_ == &empty_string) error_ = new string(compile_error);
}
return re;
}
/***** Matching interfaces *****/
bool RE::FullMatch(const StringPiece& text,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
return DoMatchImpl(text, ANCHOR_BOTH, &consumed, args, n, vec, kVecSize);
}
bool RE::PartialMatch(const StringPiece& text,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
return DoMatchImpl(text, UNANCHORED, &consumed, args, n, vec, kVecSize);
}
bool RE::Consume(StringPiece* input,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
if (DoMatchImpl(*input, ANCHOR_START, &consumed,
args, n, vec, kVecSize)) {
input->remove_prefix(consumed);
return true;
} else {
return false;
}
}
bool RE::FindAndConsume(StringPiece* input,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
if (DoMatchImpl(*input, UNANCHORED, &consumed,
args, n, vec, kVecSize)) {
input->remove_prefix(consumed);
return true;
} else {
return false;
}
}
bool RE::Replace(const StringPiece& rewrite,
string *str) const {
int vec[kVecSize];
int matches = TryMatch(*str, 0, UNANCHORED, true, vec, kVecSize);
if (matches == 0)
return false;
string s;
if (!Rewrite(&s, rewrite, *str, vec, matches))
return false;
assert(vec[0] >= 0);
assert(vec[1] >= 0);
str->replace(vec[0], vec[1] - vec[0], s);
return true;
}
// Returns PCRE_NEWLINE_CRLF, PCRE_NEWLINE_CR, or PCRE_NEWLINE_LF.
// Note that PCRE_NEWLINE_CRLF is defined to be P_N_CR | P_N_LF.
// Modified by PH to add PCRE_NEWLINE_ANY and PCRE_NEWLINE_ANYCRLF.
static int NewlineMode(int pcre_options) {
// TODO: if we can make it threadsafe, cache this var
int newline_mode = 0;
/* if (newline_mode) return newline_mode; */ // do this once it's cached
if (pcre_options & (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|
PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)) {
newline_mode = (pcre_options &
(PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|
PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF));
} else {
int newline;
pcre_config(PCRE_CONFIG_NEWLINE, &newline);
if (newline == 10)
newline_mode = PCRE_NEWLINE_LF;
else if (newline == 13)
newline_mode = PCRE_NEWLINE_CR;
else if (newline == 3338)
newline_mode = PCRE_NEWLINE_CRLF;
else if (newline == -1)
newline_mode = PCRE_NEWLINE_ANY;
else if (newline == -2)
newline_mode = PCRE_NEWLINE_ANYCRLF;
else
assert(NULL == "Unexpected return value from pcre_config(NEWLINE)");
}
return newline_mode;
}
int RE::GlobalReplace(const StringPiece& rewrite,
string *str) const {
int count = 0;
int vec[kVecSize];
string out;
int start = 0;
int lastend = -1;
bool last_match_was_empty_string = false;
while (start <= static_cast<int>(str->length())) {
// If the previous match was for the empty string, we shouldn't
// just match again: we'll match in the same way and get an
// infinite loop. Instead, we do the match in a special way:
// anchored -- to force another try at the same position --
// and with a flag saying that this time, ignore empty matches.
// If this special match returns, that means there's a non-empty
// match at this position as well, and we can continue. If not,
// we do what perl does, and just advance by one.
// Notice that perl prints '@@@' for this;
// perl -le '$_ = "aa"; s/b*|aa/@/g; print'
int matches;
if (last_match_was_empty_string) {
matches = TryMatch(*str, start, ANCHOR_START, false, vec, kVecSize);
if (matches <= 0) {
int matchend = start + 1; // advance one character.
// If the current char is CR and we're in CRLF mode, skip LF too.
// Note it's better to call pcre_fullinfo() than to examine
// all_options(), since options_ could have changed bewteen
// compile-time and now, but this is simpler and safe enough.
// Modified by PH to add ANY and ANYCRLF.
if (matchend < static_cast<int>(str->length()) &&
(*str)[start] == '\r' && (*str)[matchend] == '\n' &&
(NewlineMode(options_.all_options()) == PCRE_NEWLINE_CRLF ||
NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANY ||
NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANYCRLF)) {
matchend++;
}
// We also need to advance more than one char if we're in utf8 mode.
#ifdef SUPPORT_UTF8
if (options_.utf8()) {
while (matchend < static_cast<int>(str->length()) &&
((*str)[matchend] & 0xc0) == 0x80)
matchend++;
}
#endif
if (start < static_cast<int>(str->length()))
out.append(*str, start, matchend - start);
start = matchend;
last_match_was_empty_string = false;
continue;
}
} else {
matches = TryMatch(*str, start, UNANCHORED, true, vec, kVecSize);
if (matches <= 0)
break;
}
int matchstart = vec[0], matchend = vec[1];
assert(matchstart >= start);
assert(matchend >= matchstart);
out.append(*str, start, matchstart - start);
Rewrite(&out, rewrite, *str, vec, matches);
start = matchend;
lastend = matchend;
count++;
last_match_was_empty_string = (matchstart == matchend);
}
if (count == 0)
return 0;
if (start < static_cast<int>(str->length()))
out.append(*str, start, str->length() - start);
swap(out, *str);
return count;
}
bool RE::Extract(const StringPiece& rewrite,
const StringPiece& text,
string *out) const {
int vec[kVecSize];
int matches = TryMatch(text, 0, UNANCHORED, true, vec, kVecSize);
if (matches == 0)
return false;
out->erase();
return Rewrite(out, rewrite, text, vec, matches);
}
/*static*/ string RE::QuoteMeta(const StringPiece& unquoted) {
string result;
// Escape any ascii character not in [A-Za-z_0-9].
//
// Note that it's legal to escape a character even if it has no
// special meaning in a regular expression -- so this function does
// that. (This also makes it identical to the perl function of the
// same name; see `perldoc -f quotemeta`.) The one exception is
// escaping NUL: rather than doing backslash + NUL, like perl does,
// we do '\0', because pcre itself doesn't take embedded NUL chars.
for (int ii = 0; ii < unquoted.size(); ++ii) {
// Note that using 'isalnum' here raises the benchmark time from
// 32ns to 58ns:
if (unquoted[ii] == '\0') {
result += "\\0";
} else if ((unquoted[ii] < 'a' || unquoted[ii] > 'z') &&
(unquoted[ii] < 'A' || unquoted[ii] > 'Z') &&
(unquoted[ii] < '0' || unquoted[ii] > '9') &&
unquoted[ii] != '_' &&
// If this is the part of a UTF8 or Latin1 character, we need
// to copy this byte without escaping. Experimentally this is
// what works correctly with the regexp library.
!(unquoted[ii] & 128)) {
result += '\\';
result += unquoted[ii];
} else {
result += unquoted[ii];
}
}
return result;
}
/***** Actual matching and rewriting code *****/
int RE::TryMatch(const StringPiece& text,
int startpos,
Anchor anchor,
bool empty_ok,
int *vec,
int vecsize) const {
pcre* re = (anchor == ANCHOR_BOTH) ? re_full_ : re_partial_;
if (re == NULL) {
//fprintf(stderr, "Matching against invalid re: %s\n", error_->c_str());
return 0;
}
pcre_extra extra = { 0, 0, 0, 0, 0, 0 };
if (options_.match_limit() > 0) {
extra.flags |= PCRE_EXTRA_MATCH_LIMIT;
extra.match_limit = options_.match_limit();
}
if (options_.match_limit_recursion() > 0) {
extra.flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
extra.match_limit_recursion = options_.match_limit_recursion();
}
int options = 0;
if (anchor != UNANCHORED)
options |= PCRE_ANCHORED;
if (!empty_ok)
options |= PCRE_NOTEMPTY;
int rc = pcre_exec(re, // The regular expression object
&extra,
(text.data() == NULL) ? "" : text.data(),
text.size(),
startpos,
options,
vec,
vecsize);
// Handle errors
if (rc == PCRE_ERROR_NOMATCH) {
return 0;
} else if (rc < 0) {
//fprintf(stderr, "Unexpected return code: %d when matching '%s'\n",
// re, pattern_.c_str());
return 0;
} else if (rc == 0) {
// pcre_exec() returns 0 as a special case when the number of
// capturing subpatterns exceeds the size of the vector.
// When this happens, there is a match and the output vector
// is filled, but we miss out on the positions of the extra subpatterns.
rc = vecsize / 2;
}
return rc;
}
bool RE::DoMatchImpl(const StringPiece& text,
Anchor anchor,
int* consumed,
const Arg* const* args,
int n,
int* vec,
int vecsize) const {
assert((1 + n) * 3 <= vecsize); // results + PCRE workspace
int matches = TryMatch(text, 0, anchor, true, vec, vecsize);
assert(matches >= 0); // TryMatch never returns negatives
if (matches == 0)
return false;
*consumed = vec[1];
if (n == 0 || args == NULL) {
// We are not interested in results
return true;
}
if (NumberOfCapturingGroups() < n) {
// RE has fewer capturing groups than number of arg pointers passed in
return false;
}
// If we got here, we must have matched the whole pattern.
// We do not need (can not do) any more checks on the value of 'matches' here
// -- see the comment for TryMatch.
for (int i = 0; i < n; i++) {
const int start = vec[2*(i+1)];
const int limit = vec[2*(i+1)+1];
if (!args[i]->Parse(text.data() + start, limit-start)) {
// TODO: Should we indicate what the error was?
return false;
}
}
return true;
}
bool RE::DoMatch(const StringPiece& text,
Anchor anchor,
int* consumed,
const Arg* const args[],
int n) const {
assert(n >= 0);
size_t const vecsize = (1 + n) * 3; // results + PCRE workspace
// (as for kVecSize)
int space[21]; // use stack allocation for small vecsize (common case)
int* vec = vecsize <= 21 ? space : new int[vecsize];
bool retval = DoMatchImpl(text, anchor, consumed, args, n, vec, vecsize);
if (vec != space) delete [] vec;
return retval;
}
bool RE::Rewrite(string *out, const StringPiece &rewrite,
const StringPiece &text, int *vec, int veclen) const {
for (const char *s = rewrite.data(), *end = s + rewrite.size();
s < end; s++) {
int c = *s;
if (c == '\\') {
c = *++s;
if (isdigit(c)) {
int n = (c - '0');
if (n >= veclen) {
//fprintf(stderr, requested group %d in regexp %.*s\n",
// n, rewrite.size(), rewrite.data());
return false;
}
int start = vec[2 * n];
if (start >= 0)
out->append(text.data() + start, vec[2 * n + 1] - start);
} else if (c == '\\') {
*out += '\\';
} else {
//fprintf(stderr, "invalid rewrite pattern: %.*s\n",
// rewrite.size(), rewrite.data());
return false;
}
} else {
*out += c;
}
}
return true;
}
// Return the number of capturing subpatterns, or -1 if the
// regexp wasn't valid on construction.
int RE::NumberOfCapturingGroups() const {
if (re_partial_ == NULL) return -1;
int result;
int pcre_retval = pcre_fullinfo(re_partial_, // The regular expression object
NULL, // We did not study the pattern
PCRE_INFO_CAPTURECOUNT,
&result);
assert(pcre_retval == 0);
return result;
}
/***** Parsers for various types *****/
bool Arg::parse_null(const char* str, int n, void* dest) {
// We fail if somebody asked us to store into a non-NULL void* pointer
return (dest == NULL);
}
bool Arg::parse_string(const char* str, int n, void* dest) {
if (dest == NULL) return true;
reinterpret_cast<string*>(dest)->assign(str, n);
return true;
}
bool Arg::parse_stringpiece(const char* str, int n, void* dest) {
if (dest == NULL) return true;
reinterpret_cast<StringPiece*>(dest)->set(str, n);
return true;
}
bool Arg::parse_char(const char* str, int n, void* dest) {
if (n != 1) return false;
if (dest == NULL) return true;
*(reinterpret_cast<char*>(dest)) = str[0];
return true;
}
bool Arg::parse_uchar(const char* str, int n, void* dest) {
if (n != 1) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned char*>(dest)) = str[0];
return true;
}
// Largest number spec that we are willing to parse
static const int kMaxNumberLength = 32;
// REQUIRES "buf" must have length at least kMaxNumberLength+1
// REQUIRES "n > 0"
// Copies "str" into "buf" and null-terminates if necessary.
// Returns one of:
// a. "str" if no termination is needed
// b. "buf" if the string was copied and null-terminated
// c. "" if the input was invalid and has no hope of being parsed
static const char* TerminateNumber(char* buf, const char* str, int n) {
if ((n > 0) && isspace(*str)) {
// We are less forgiving than the strtoxxx() routines and do not
// allow leading spaces.
return "";
}
// See if the character right after the input text may potentially
// look like a digit.
if (isdigit(str[n]) ||
((str[n] >= 'a') && (str[n] <= 'f')) ||
((str[n] >= 'A') && (str[n] <= 'F'))) {
if (n > kMaxNumberLength) return ""; // Input too big to be a valid number
memcpy(buf, str, n);
buf[n] = '\0';
return buf;
} else {
// We can parse right out of the supplied string, so return it.
return str;
}
}
bool Arg::parse_long_radix(const char* str,
int n,
void* dest,
int radix) {
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
char* end;
errno = 0;
long r = strtol(str, &end, radix);
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<long*>(dest)) = r;
return true;
}
bool Arg::parse_ulong_radix(const char* str,
int n,
void* dest,
int radix) {
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
if (str[0] == '-') return false; // strtoul() on a negative number?!
char* end;
errno = 0;
unsigned long r = strtoul(str, &end, radix);
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned long*>(dest)) = r;
return true;
}
bool Arg::parse_short_radix(const char* str,
int n,
void* dest,
int radix) {
long r;
if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse
if (r < SHRT_MIN || r > SHRT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<short*>(dest)) = static_cast<short>(r);
return true;
}
bool Arg::parse_ushort_radix(const char* str,
int n,
void* dest,
int radix) {
unsigned long r;
if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse
if (r > USHRT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<unsigned short*>(dest)) = static_cast<unsigned short>(r);
return true;
}
bool Arg::parse_int_radix(const char* str,
int n,
void* dest,
int radix) {
long r;
if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse
if (r < INT_MIN || r > INT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<int*>(dest)) = r;
return true;
}
bool Arg::parse_uint_radix(const char* str,
int n,
void* dest,
int radix) {
unsigned long r;
if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse
if (r > UINT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<unsigned int*>(dest)) = r;
return true;
}
bool Arg::parse_longlong_radix(const char* str,
int n,
void* dest,
int radix) {
#ifndef HAVE_LONG_LONG
return false;
#else
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
char* end;
errno = 0;
#if defined HAVE_STRTOQ
long long r = strtoq(str, &end, radix);
#elif defined HAVE_STRTOLL
long long r = strtoll(str, &end, radix);
#elif defined HAVE__STRTOI64
long long r = _strtoi64(str, &end, radix);
#elif defined HAVE_STRTOIMAX
long long r = strtoimax(str, &end, radix);
#else
#error parse_longlong_radix: cannot convert input to a long-long
#endif
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<long long*>(dest)) = r;
return true;
#endif /* HAVE_LONG_LONG */
}
bool Arg::parse_ulonglong_radix(const char* str,
int n,
void* dest,
int radix) {
#ifndef HAVE_UNSIGNED_LONG_LONG
return false;
#else
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
if (str[0] == '-') return false; // strtoull() on a negative number?!
char* end;
errno = 0;
#if defined HAVE_STRTOQ
unsigned long long r = strtouq(str, &end, radix);
#elif defined HAVE_STRTOLL
unsigned long long r = strtoull(str, &end, radix);
#elif defined HAVE__STRTOI64
unsigned long long r = _strtoui64(str, &end, radix);
#elif defined HAVE_STRTOIMAX
unsigned long long r = strtoumax(str, &end, radix);
#else
#error parse_ulonglong_radix: cannot convert input to a long-long
#endif
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned long long*>(dest)) = r;
return true;
#endif /* HAVE_UNSIGNED_LONG_LONG */
}
bool Arg::parse_double(const char* str, int n, void* dest) {
if (n == 0) return false;
static const int kMaxLength = 200;
char buf[kMaxLength];
if (n >= kMaxLength) return false;
memcpy(buf, str, n);
buf[n] = '\0';
errno = 0;
char* end;
double r = strtod(buf, &end);
if (end != buf + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<double*>(dest)) = r;
return true;
}
bool Arg::parse_float(const char* str, int n, void* dest) {
double r;
if (!parse_double(str, n, &r)) return false;
if (dest == NULL) return true;
*(reinterpret_cast<float*>(dest)) = static_cast<float>(r);
return true;
}
#define DEFINE_INTEGER_PARSERS(name) \
bool Arg::parse_##name(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 10); \
} \
bool Arg::parse_##name##_hex(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 16); \
} \
bool Arg::parse_##name##_octal(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 8); \
} \
bool Arg::parse_##name##_cradix(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 0); \
}
DEFINE_INTEGER_PARSERS(short) /* */
DEFINE_INTEGER_PARSERS(ushort) /* */
DEFINE_INTEGER_PARSERS(int) /* Don't use semicolons after these */
DEFINE_INTEGER_PARSERS(uint) /* statements because they can cause */
DEFINE_INTEGER_PARSERS(long) /* compiler warnings if the checking */
DEFINE_INTEGER_PARSERS(ulong) /* level is turned up high enough. */
DEFINE_INTEGER_PARSERS(longlong) /* */
DEFINE_INTEGER_PARSERS(ulonglong) /* */
#undef DEFINE_INTEGER_PARSERS
} // namespace pcrecpp
-965
View File
@@ -1,965 +0,0 @@
// Copyright (c) 2010, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER 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.
//
// Author: Sanjay Ghemawat
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
// Custom:
#include <cstdlib>
#include <cstdio>
#include <cctype>
#include <climits>
#include <cassert>
#include <cerrno>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h> /* for SHRT_MIN, USHRT_MAX, etc */
#include <assert.h>
#include <errno.h>
#include <string>
#include <algorithm>
#include "pcrecpp_internal.h"
#include "pcre.h"
#include "pcrecpp.h"
#include "pcre_stringpiece.h"
// Custom: Added for compatibility with older pcre versions
#ifndef PCRE_NEWLINE_CR
#define PCRE_NEWLINE_CR 0x00100000
#endif
#ifndef PCRE_NEWLINE_LF
#define PCRE_NEWLINE_LF 0x00200000
#endif
#ifndef PCRE_NEWLINE_CRLF
#define PCRE_NEWLINE_CRLF 0x00300000
#endif
#ifndef PCRE_NEWLINE_ANYCRLF
#define PCRE_NEWLINE_ANYCRLF 0x00500000
#endif
#ifndef PCRE_NEWLINE_ANY
#define PCRE_NEWLINE_ANY 0x00400000
#endif
#ifndef PCRE_NEWLINE_ANY
#define PCRE_EXTRA_MATCH_LIMIT_RECURSION 0x0010
#endif
namespace pcrecpp {
// Maximum number of args we can set
static const int kMaxArgs = 16;
static const int kVecSize = (1 + kMaxArgs) * 3; // results + PCRE workspace
// Special object that stands-in for no argument
Arg RE::no_arg((void*)NULL);
// This is for ABI compatibility with old versions of pcre (pre-7.6),
// which defined a global no_arg variable instead of putting it in the
// RE class. This works on GCC >= 3, at least. It definitely works
// for ELF, but may not for other object formats (Mach-O, for
// instance, does not support aliases.) We could probably have a more
// inclusive test if we ever needed it. (Note that not only the
// __attribute__ syntax, but also __USER_LABEL_PREFIX__, are
// gnu-specific.)
#if defined(__GNUC__) && __GNUC__ >= 3 && defined(__ELF__)
# define ULP_AS_STRING(x) ULP_AS_STRING_INTERNAL(x)
# define ULP_AS_STRING_INTERNAL(x) #x
# define USER_LABEL_PREFIX_STR ULP_AS_STRING(__USER_LABEL_PREFIX__)
// Custom: This throws a warning on Intel C++, and we don't need it anyway.
// extern Arg no_arg
// __attribute__((alias(USER_LABEL_PREFIX_STR "_ZN7pcrecpp2RE6no_argE")));
#endif
// If a regular expression has no error, its error_ field points here
static const string empty_string;
// If the user doesn't ask for any options, we just use this one
static RE_Options default_options;
void RE::Init(const string& pat, const RE_Options* options) {
pattern_ = pat;
if (options == NULL) {
options_ = default_options;
} else {
options_ = *options;
}
error_ = &empty_string;
re_full_ = NULL;
re_partial_ = NULL;
re_partial_ = Compile(UNANCHORED);
if (re_partial_ != NULL) {
re_full_ = Compile(ANCHOR_BOTH);
}
}
void RE::Cleanup() {
if (re_full_ != NULL) (*pcre_free)(re_full_);
if (re_partial_ != NULL) (*pcre_free)(re_partial_);
if (error_ != &empty_string) delete error_;
}
RE::~RE() {
Cleanup();
}
pcre* RE::Compile(Anchor anchor) {
// First, convert RE_Options into pcre options
int pcre_options = 0;
pcre_options = options_.all_options();
// Special treatment for anchoring. This is needed because at
// runtime pcre only provides an option for anchoring at the
// beginning of a string (unless you use offset).
//
// There are three types of anchoring we want:
// UNANCHORED Compile the original pattern, and use
// a pcre unanchored match.
// ANCHOR_START Compile the original pattern, and use
// a pcre anchored match.
// ANCHOR_BOTH Tack a "\z" to the end of the original pattern
// and use a pcre anchored match.
const char* compile_error;
int eoffset;
pcre* re;
if (anchor != ANCHOR_BOTH) {
re = pcre_compile(pattern_.c_str(), pcre_options,
&compile_error, &eoffset, NULL);
} else {
// Tack a '\z' at the end of RE. Parenthesize it first so that
// the '\z' applies to all top-level alternatives in the regexp.
string wrapped = "(?:"; // A non-counting grouping operator
wrapped += pattern_;
wrapped += ")\\z";
re = pcre_compile(wrapped.c_str(), pcre_options,
&compile_error, &eoffset, NULL);
}
if (re == NULL) {
if (error_ == &empty_string) error_ = new string(compile_error);
}
return re;
}
/***** Matching interfaces *****/
bool RE::FullMatch(const StringPiece& text,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
return DoMatchImpl(text, ANCHOR_BOTH, &consumed, args, n, vec, kVecSize);
}
bool RE::PartialMatch(const StringPiece& text,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
return DoMatchImpl(text, UNANCHORED, &consumed, args, n, vec, kVecSize);
}
bool RE::Consume(StringPiece* input,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
if (DoMatchImpl(*input, ANCHOR_START, &consumed,
args, n, vec, kVecSize)) {
input->remove_prefix(consumed);
return true;
} else {
return false;
}
}
bool RE::FindAndConsume(StringPiece* input,
const Arg& ptr1,
const Arg& ptr2,
const Arg& ptr3,
const Arg& ptr4,
const Arg& ptr5,
const Arg& ptr6,
const Arg& ptr7,
const Arg& ptr8,
const Arg& ptr9,
const Arg& ptr10,
const Arg& ptr11,
const Arg& ptr12,
const Arg& ptr13,
const Arg& ptr14,
const Arg& ptr15,
const Arg& ptr16) const {
const Arg* args[kMaxArgs];
int n = 0;
if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1;
if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2;
if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3;
if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4;
if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5;
if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6;
if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7;
if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8;
if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9;
if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10;
if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11;
if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12;
if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13;
if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14;
if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15;
if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16;
done:
int consumed;
int vec[kVecSize];
if (DoMatchImpl(*input, UNANCHORED, &consumed,
args, n, vec, kVecSize)) {
input->remove_prefix(consumed);
return true;
} else {
return false;
}
}
bool RE::Replace(const StringPiece& rewrite,
string *str) const {
int vec[kVecSize];
int matches = TryMatch(*str, 0, UNANCHORED, true, vec, kVecSize);
if (matches == 0)
return false;
string s;
if (!Rewrite(&s, rewrite, *str, vec, matches))
return false;
assert(vec[0] >= 0);
assert(vec[1] >= 0);
str->replace(vec[0], vec[1] - vec[0], s);
return true;
}
// Returns PCRE_NEWLINE_CRLF, PCRE_NEWLINE_CR, or PCRE_NEWLINE_LF.
// Note that PCRE_NEWLINE_CRLF is defined to be P_N_CR | P_N_LF.
// Modified by PH to add PCRE_NEWLINE_ANY and PCRE_NEWLINE_ANYCRLF.
static int NewlineMode(int pcre_options) {
// TODO: if we can make it threadsafe, cache this var
int newline_mode = 0;
/* if (newline_mode) return newline_mode; */ // do this once it's cached
if (pcre_options & (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|
PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)) {
newline_mode = (pcre_options &
(PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|
PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF));
} else {
int newline;
pcre_config(PCRE_CONFIG_NEWLINE, &newline);
if (newline == 10)
newline_mode = PCRE_NEWLINE_LF;
else if (newline == 13)
newline_mode = PCRE_NEWLINE_CR;
else if (newline == 3338)
newline_mode = PCRE_NEWLINE_CRLF;
else if (newline == -1)
newline_mode = PCRE_NEWLINE_ANY;
else if (newline == -2)
newline_mode = PCRE_NEWLINE_ANYCRLF;
else
assert(NULL == "Unexpected return value from pcre_config(NEWLINE)");
}
return newline_mode;
}
int RE::GlobalReplace(const StringPiece& rewrite,
string *str) const {
int count = 0;
int vec[kVecSize];
string out;
int start = 0;
int lastend = -1;
bool last_match_was_empty_string = false;
while (start <= static_cast<int>(str->length())) {
// If the previous match was for the empty string, we shouldn't
// just match again: we'll match in the same way and get an
// infinite loop. Instead, we do the match in a special way:
// anchored -- to force another try at the same position --
// and with a flag saying that this time, ignore empty matches.
// If this special match returns, that means there's a non-empty
// match at this position as well, and we can continue. If not,
// we do what perl does, and just advance by one.
// Notice that perl prints '@@@' for this;
// perl -le '$_ = "aa"; s/b*|aa/@/g; print'
int matches;
if (last_match_was_empty_string) {
matches = TryMatch(*str, start, ANCHOR_START, false, vec, kVecSize);
if (matches <= 0) {
int matchend = start + 1; // advance one character.
// If the current char is CR and we're in CRLF mode, skip LF too.
// Note it's better to call pcre_fullinfo() than to examine
// all_options(), since options_ could have changed bewteen
// compile-time and now, but this is simpler and safe enough.
// Modified by PH to add ANY and ANYCRLF.
if (matchend < static_cast<int>(str->length()) &&
(*str)[start] == '\r' && (*str)[matchend] == '\n' &&
(NewlineMode(options_.all_options()) == PCRE_NEWLINE_CRLF ||
NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANY ||
NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANYCRLF)) {
matchend++;
}
// We also need to advance more than one char if we're in utf8 mode.
#ifdef SUPPORT_UTF8
if (options_.utf8()) {
while (matchend < static_cast<int>(str->length()) &&
((*str)[matchend] & 0xc0) == 0x80)
matchend++;
}
#endif
if (start < static_cast<int>(str->length()))
out.append(*str, start, matchend - start);
start = matchend;
last_match_was_empty_string = false;
continue;
}
} else {
matches = TryMatch(*str, start, UNANCHORED, true, vec, kVecSize);
if (matches <= 0)
break;
}
int matchstart = vec[0], matchend = vec[1];
assert(matchstart >= start);
assert(matchend >= matchstart);
out.append(*str, start, matchstart - start);
Rewrite(&out, rewrite, *str, vec, matches);
start = matchend;
lastend = matchend;
count++;
last_match_was_empty_string = (matchstart == matchend);
}
if (count == 0)
return 0;
if (start < static_cast<int>(str->length()))
out.append(*str, start, str->length() - start);
swap(out, *str);
return count;
}
bool RE::Extract(const StringPiece& rewrite,
const StringPiece& text,
string *out) const {
int vec[kVecSize];
int matches = TryMatch(text, 0, UNANCHORED, true, vec, kVecSize);
if (matches == 0)
return false;
out->erase();
return Rewrite(out, rewrite, text, vec, matches);
}
/*static*/ string RE::QuoteMeta(const StringPiece& unquoted) {
string result;
// Escape any ascii character not in [A-Za-z_0-9].
//
// Note that it's legal to escape a character even if it has no
// special meaning in a regular expression -- so this function does
// that. (This also makes it identical to the perl function of the
// same name; see `perldoc -f quotemeta`.) The one exception is
// escaping NUL: rather than doing backslash + NUL, like perl does,
// we do '\0', because pcre itself doesn't take embedded NUL chars.
for (int ii = 0; ii < unquoted.size(); ++ii) {
// Note that using 'isalnum' here raises the benchmark time from
// 32ns to 58ns:
if (unquoted[ii] == '\0') {
result += "\\0";
} else if ((unquoted[ii] < 'a' || unquoted[ii] > 'z') &&
(unquoted[ii] < 'A' || unquoted[ii] > 'Z') &&
(unquoted[ii] < '0' || unquoted[ii] > '9') &&
unquoted[ii] != '_' &&
// If this is the part of a UTF8 or Latin1 character, we need
// to copy this byte without escaping. Experimentally this is
// what works correctly with the regexp library.
!(unquoted[ii] & 128)) {
result += '\\';
result += unquoted[ii];
} else {
result += unquoted[ii];
}
}
return result;
}
/***** Actual matching and rewriting code *****/
int RE::TryMatch(const StringPiece& text,
int startpos,
Anchor anchor,
bool empty_ok,
int *vec,
int vecsize) const {
pcre* re = (anchor == ANCHOR_BOTH) ? re_full_ : re_partial_;
if (re == NULL) {
//fprintf(stderr, "Matching against invalid re: %s\n", error_->c_str());
return 0;
}
// Custom: match_limit_recursion field is since pcre 6.5, tables field since 5.0.
#if PCRE_MAJOR > 6 || (PCRE_MAJOR == 6 && PCRE_MINOR >= 5)
pcre_extra extra = { 0, 0, 0, 0, 0, 0 };
#elif PCRE_MAJOR > 5 || (PCRE_MAJOR == 5 && PCRE_MINOR >= 0)
pcre_extra extra = { 0, 0, 0, 0, 0 };
#else
pcre_extra extra = { 0, 0, 0, 0 };
#endif
if (options_.match_limit() > 0) {
extra.flags |= PCRE_EXTRA_MATCH_LIMIT;
extra.match_limit = options_.match_limit();
}
// Custom: match_limit_recursion is since pcre 6.5.
#if PCRE_MAJOR > 6 || (PCRE_MAJOR == 6 && PCRE_MINOR >= 5)
if (options_.match_limit_recursion() > 0) {
extra.flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
extra.match_limit_recursion = options_.match_limit_recursion();
}
#endif
int options = 0;
if (anchor != UNANCHORED)
options |= PCRE_ANCHORED;
if (!empty_ok)
options |= PCRE_NOTEMPTY;
int rc = pcre_exec(re, // The regular expression object
&extra,
(text.data() == NULL) ? "" : text.data(),
text.size(),
startpos,
options,
vec,
vecsize);
// Handle errors
if (rc == PCRE_ERROR_NOMATCH) {
return 0;
} else if (rc < 0) {
//fprintf(stderr, "Unexpected return code: %d when matching '%s'\n",
// re, pattern_.c_str());
return 0;
} else if (rc == 0) {
// pcre_exec() returns 0 as a special case when the number of
// capturing subpatterns exceeds the size of the vector.
// When this happens, there is a match and the output vector
// is filled, but we miss out on the positions of the extra subpatterns.
rc = vecsize / 2;
}
return rc;
}
bool RE::DoMatchImpl(const StringPiece& text,
Anchor anchor,
int* consumed,
const Arg* const* args,
int n,
int* vec,
int vecsize) const {
assert((1 + n) * 3 <= vecsize); // results + PCRE workspace
int matches = TryMatch(text, 0, anchor, true, vec, vecsize);
assert(matches >= 0); // TryMatch never returns negatives
if (matches == 0)
return false;
*consumed = vec[1];
if (n == 0 || args == NULL) {
// We are not interested in results
return true;
}
if (NumberOfCapturingGroups() < n) {
// RE has fewer capturing groups than number of arg pointers passed in
return false;
}
// If we got here, we must have matched the whole pattern.
// We do not need (can not do) any more checks on the value of 'matches' here
// -- see the comment for TryMatch.
for (int i = 0; i < n; i++) {
const int start = vec[2*(i+1)];
const int limit = vec[2*(i+1)+1];
if (!args[i]->Parse(text.data() + start, limit-start)) {
// TODO: Should we indicate what the error was?
return false;
}
}
return true;
}
bool RE::DoMatch(const StringPiece& text,
Anchor anchor,
int* consumed,
const Arg* const args[],
int n) const {
assert(n >= 0);
size_t const vecsize = (1 + n) * 3; // results + PCRE workspace
// (as for kVecSize)
int space[21]; // use stack allocation for small vecsize (common case)
int* vec = vecsize <= 21 ? space : new int[vecsize];
bool retval = DoMatchImpl(text, anchor, consumed, args, n, vec, vecsize);
if (vec != space) delete [] vec;
return retval;
}
bool RE::Rewrite(string *out, const StringPiece &rewrite,
const StringPiece &text, int *vec, int veclen) const {
for (const char *s = rewrite.data(), *end = s + rewrite.size();
s < end; s++) {
int c = *s;
if (c == '\\') {
c = *++s;
if (isdigit(c)) {
int n = (c - '0');
if (n >= veclen) {
//fprintf(stderr, requested group %d in regexp %.*s\n",
// n, rewrite.size(), rewrite.data());
return false;
}
int start = vec[2 * n];
if (start >= 0)
out->append(text.data() + start, vec[2 * n + 1] - start);
} else if (c == '\\') {
*out += '\\';
} else {
//fprintf(stderr, "invalid rewrite pattern: %.*s\n",
// rewrite.size(), rewrite.data());
return false;
}
} else {
*out += c;
}
}
return true;
}
// Return the number of capturing subpatterns, or -1 if the
// regexp wasn't valid on construction.
int RE::NumberOfCapturingGroups() const {
if (re_partial_ == NULL) return -1;
int result;
int pcre_retval = pcre_fullinfo(re_partial_, // The regular expression object
NULL, // We did not study the pattern
PCRE_INFO_CAPTURECOUNT,
&result);
assert(pcre_retval == 0);
return result;
}
/***** Parsers for various types *****/
bool Arg::parse_null(const char* str, int n, void* dest) {
// We fail if somebody asked us to store into a non-NULL void* pointer
return (dest == NULL);
}
bool Arg::parse_string(const char* str, int n, void* dest) {
if (dest == NULL) return true;
reinterpret_cast<string*>(dest)->assign(str, n);
return true;
}
bool Arg::parse_stringpiece(const char* str, int n, void* dest) {
if (dest == NULL) return true;
reinterpret_cast<StringPiece*>(dest)->set(str, n);
return true;
}
bool Arg::parse_char(const char* str, int n, void* dest) {
if (n != 1) return false;
if (dest == NULL) return true;
*(reinterpret_cast<char*>(dest)) = str[0];
return true;
}
bool Arg::parse_uchar(const char* str, int n, void* dest) {
if (n != 1) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned char*>(dest)) = str[0];
return true;
}
// Largest number spec that we are willing to parse
static const int kMaxNumberLength = 32;
// REQUIRES "buf" must have length at least kMaxNumberLength+1
// REQUIRES "n > 0"
// Copies "str" into "buf" and null-terminates if necessary.
// Returns one of:
// a. "str" if no termination is needed
// b. "buf" if the string was copied and null-terminated
// c. "" if the input was invalid and has no hope of being parsed
static const char* TerminateNumber(char* buf, const char* str, int n) {
if ((n > 0) && isspace(*str)) {
// We are less forgiving than the strtoxxx() routines and do not
// allow leading spaces.
return "";
}
// See if the character right after the input text may potentially
// look like a digit.
if (isdigit(str[n]) ||
((str[n] >= 'a') && (str[n] <= 'f')) ||
((str[n] >= 'A') && (str[n] <= 'F'))) {
if (n > kMaxNumberLength) return ""; // Input too big to be a valid number
memcpy(buf, str, n);
buf[n] = '\0';
return buf;
} else {
// We can parse right out of the supplied string, so return it.
return str;
}
}
bool Arg::parse_long_radix(const char* str,
int n,
void* dest,
int radix) {
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
char* end;
errno = 0;
long r = strtol(str, &end, radix);
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<long*>(dest)) = r;
return true;
}
bool Arg::parse_ulong_radix(const char* str,
int n,
void* dest,
int radix) {
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
if (str[0] == '-') return false; // strtoul() on a negative number?!
char* end;
errno = 0;
unsigned long r = strtoul(str, &end, radix);
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned long*>(dest)) = r;
return true;
}
bool Arg::parse_short_radix(const char* str,
int n,
void* dest,
int radix) {
long r;
if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse
if (r < SHRT_MIN || r > SHRT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<short*>(dest)) = static_cast<short>(r);
return true;
}
bool Arg::parse_ushort_radix(const char* str,
int n,
void* dest,
int radix) {
unsigned long r;
if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse
if (r > USHRT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<unsigned short*>(dest)) = static_cast<unsigned short>(r);
return true;
}
bool Arg::parse_int_radix(const char* str,
int n,
void* dest,
int radix) {
long r;
if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse
if (r < INT_MIN || r > INT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<int*>(dest)) = r;
return true;
}
bool Arg::parse_uint_radix(const char* str,
int n,
void* dest,
int radix) {
unsigned long r;
if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse
if (r > UINT_MAX) return false; // Out of range
if (dest == NULL) return true;
*(reinterpret_cast<unsigned int*>(dest)) = r;
return true;
}
bool Arg::parse_longlong_radix(const char* str,
int n,
void* dest,
int radix) {
#ifndef HAVE_LONG_LONG
return false;
#else
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
char* end;
errno = 0;
#if defined HAVE_STRTOQ
long long r = strtoq(str, &end, radix);
#elif defined HAVE_STRTOLL
long long r = strtoll(str, &end, radix);
#elif defined HAVE__STRTOI64
long long r = _strtoi64(str, &end, radix);
#elif defined HAVE_STRTOIMAX
long long r = strtoimax(str, &end, radix);
#else
#error parse_longlong_radix: cannot convert input to a long-long
#endif
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<long long*>(dest)) = r;
return true;
#endif /* HAVE_LONG_LONG */
}
bool Arg::parse_ulonglong_radix(const char* str,
int n,
void* dest,
int radix) {
#ifndef HAVE_UNSIGNED_LONG_LONG
return false;
#else
if (n == 0) return false;
char buf[kMaxNumberLength+1];
str = TerminateNumber(buf, str, n);
if (str[0] == '-') return false; // strtoull() on a negative number?!
char* end;
errno = 0;
#if defined HAVE_STRTOQ
unsigned long long r = strtouq(str, &end, radix);
#elif defined HAVE_STRTOLL
unsigned long long r = strtoull(str, &end, radix);
#elif defined HAVE__STRTOI64
unsigned long long r = _strtoui64(str, &end, radix);
#elif defined HAVE_STRTOIMAX
unsigned long long r = strtoumax(str, &end, radix);
#else
#error parse_ulonglong_radix: cannot convert input to a long-long
#endif
if (end != str + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<unsigned long long*>(dest)) = r;
return true;
#endif /* HAVE_UNSIGNED_LONG_LONG */
}
bool Arg::parse_double(const char* str, int n, void* dest) {
if (n == 0) return false;
static const int kMaxLength = 200;
char buf[kMaxLength];
if (n >= kMaxLength) return false;
memcpy(buf, str, n);
buf[n] = '\0';
errno = 0;
char* end;
double r = strtod(buf, &end);
if (end != buf + n) return false; // Leftover junk
if (errno) return false;
if (dest == NULL) return true;
*(reinterpret_cast<double*>(dest)) = r;
return true;
}
bool Arg::parse_float(const char* str, int n, void* dest) {
double r;
if (!parse_double(str, n, &r)) return false;
if (dest == NULL) return true;
*(reinterpret_cast<float*>(dest)) = static_cast<float>(r);
return true;
}
#define DEFINE_INTEGER_PARSERS(name) \
bool Arg::parse_##name(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 10); \
} \
bool Arg::parse_##name##_hex(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 16); \
} \
bool Arg::parse_##name##_octal(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 8); \
} \
bool Arg::parse_##name##_cradix(const char* str, int n, void* dest) { \
return parse_##name##_radix(str, n, dest, 0); \
}
DEFINE_INTEGER_PARSERS(short) /* */
DEFINE_INTEGER_PARSERS(ushort) /* */
DEFINE_INTEGER_PARSERS(int) /* Don't use semicolons after these */
DEFINE_INTEGER_PARSERS(uint) /* statements because they can cause */
DEFINE_INTEGER_PARSERS(long) /* compiler warnings if the checking */
DEFINE_INTEGER_PARSERS(ulong) /* level is turned up high enough. */
DEFINE_INTEGER_PARSERS(longlong) /* */
DEFINE_INTEGER_PARSERS(ulonglong) /* */
#undef DEFINE_INTEGER_PARSERS
} // namespace pcrecpp

Some files were not shown because too many files have changed in this diff Show More