diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/Makefile.am | 30 | ||||
-rw-r--r-- | common/about.cc | 109 | ||||
-rw-r--r-- | common/about.hh | 30 | ||||
-rw-r--r-- | common/fltk_lines.cc | 140 | ||||
-rw-r--r-- | common/fltk_lines.hh | 52 | ||||
-rw-r--r-- | common/lines.cc | 362 | ||||
-rw-r--r-- | common/lines.hh | 121 | ||||
-rw-r--r-- | common/parser.cc | 250 | ||||
-rw-r--r-- | common/parser.hh | 47 | ||||
-rw-r--r-- | common/rtfl_findrepeat.cc | 534 | ||||
-rw-r--r-- | common/rtfl_tee.c | 249 | ||||
-rw-r--r-- | common/tools.cc | 264 | ||||
-rw-r--r-- | common/tools.hh | 80 |
13 files changed, 2268 insertions, 0 deletions
diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..b8e1313 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,30 @@ +# Notes about libraries: "librtfl-tools.a" contains everything not +# depending on FLTK, which can so be used in command line tools; +# "librtfl-common.a" depens on FLTK, and also on the former. + +AM_CPPFLAGS = \ + -I$(top_srcdir) + +noinst_LIBRARIES = librtfl-common.a librtfl-tools.a + +bin_PROGRAMS = rtfl-findrepeat rtfl-tee + +librtfl_common_a_SOURCES = \ + about.hh \ + about.cc \ + fltk_lines.hh \ + fltk_lines.cc + +librtfl_tools_a_SOURCES = \ + lines.hh \ + lines.cc \ + parser.hh \ + parser.cc \ + tools.hh \ + tools.cc + +rtfl_findrepeat_SOURCES = rtfl_findrepeat.cc + +rtfl_findrepeat_LDADD = librtfl-tools.a ../lout/liblout.a + +rtfl_tee_SOURCES = rtfl_tee.c diff --git a/common/about.cc b/common/about.cc new file mode 100644 index 0000000..01ad3ac --- /dev/null +++ b/common/about.cc @@ -0,0 +1,109 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "about.hh" + +#include "config.h" + +#include <FL/Fl_Return_Button.H> +#include <FL/Fl_Box.H> + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +namespace rtfl { + +namespace common { + +void AboutWindow::close (Fl_Widget *widget, void *data) +{ + ((AboutWindow*)data)->hide (); +} + + +AboutWindow::AboutWindow (const char *prgName, const char *licenceException, + int height) : + Fl_Window (WIDTH, height, "") +{ + + const char *titleFmt = "RTFL: About %s"; + const char *textFmt = + "%s " VERSION "\n" + "\n" + "%s is part of RTFL (Read The Figurative Logfile).\n" + "\n" + "Copyright 2013-2015 Sebastian Geerken <sgeerken@@dillo.org>\n" + "\n" + "RTFL is free software; you can redistribute it and/or modify it under " + "the terms of the GNU General Public License as published by the Free " + "Software Foundation; either version 3 of the License, or (at your " + "option) any later version%s.\n" + "\n" + "With RTFL comes some documentation, see “doc/rtfl.html” in the " + "tarball. For more informations, updates etc. see " + "<http://home.gna.org/rtfl/>."; + + int titleLen = strlen (titleFmt) - 2 + strlen (prgName) + 1; + title = new char [titleLen]; + snprintf (title, titleLen, titleFmt, prgName); + label (title); + + char *capName = strdup (prgName); + capName[0] = toupper (capName[0]); + int textLen = + strlen (textFmt) - 2 + strlen (prgName) - 2 + strlen (capName) + 1 + - 2 + strlen (licenceException); + text = new char[textLen]; + snprintf (text, textLen, textFmt, prgName, capName, licenceException); + free (capName); + + Fl_Box *textWidget = + new Fl_Box(SPACE, SPACE, WIDTH - 2 * SPACE, + height - 3 * SPACE - BUTTON_HEIGHT, text); + textWidget->box(FL_NO_BOX); + textWidget->align(FL_ALIGN_WRAP); + + Fl_Return_Button *close = + new Fl_Return_Button(WIDTH - BUTTON_WIDTH - SPACE, + height - BUTTON_HEIGHT - SPACE, BUTTON_WIDTH, + BUTTON_HEIGHT, "Close"); + close->callback (AboutWindow::close, this); +} + +AboutWindow::~AboutWindow () +{ + delete[] title; + delete[] text; +} + +} // namespace common + +} // namespace rtfl diff --git a/common/about.hh b/common/about.hh new file mode 100644 index 0000000..2a46483 --- /dev/null +++ b/common/about.hh @@ -0,0 +1,30 @@ +#ifndef __COMMON_ABOUT_HH__ +#define __COMMON_ABOUT_HH__ + +#include <FL/Fl_Window.H> + +namespace rtfl { + +namespace common { + +class AboutWindow: public Fl_Window +{ +private: + char *title, *text; + + static void close (Fl_Widget *widget, void *data); + + enum { WIDTH = 450, BUTTON_WIDTH = 80, BUTTON_HEIGHT = 25, SPACE = 10 }; + +public: + enum { HEIGHT_SIMPLE = 300, HEIGHT_EXCEPTION = 480 }; + + AboutWindow (const char *prgName, const char *licenceException, int height); + ~AboutWindow (); +}; + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_ABOUT_HH__ diff --git a/common/fltk_lines.cc b/common/fltk_lines.cc new file mode 100644 index 0000000..5bf4b00 --- /dev/null +++ b/common/fltk_lines.cc @@ -0,0 +1,140 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "fltk_lines.hh" + +#include <stdio.h> +#include <fcntl.h> +#include <Fl/Fl.H> + +using namespace lout::container::typed; + +namespace rtfl { + +namespace common { + +// ------------------------- +// FltkLinesSource +// ------------------------- + +FltkLinesSource::TimeoutInfo::TimeoutInfo (FltkLinesSource *source, int type) +{ + this->source = source; + this->type = type; +} + +FltkLinesSource::FltkLinesSource () +{ + timeoutInfos = new List<TimeoutInfo> (true); +} + +FltkLinesSource::~FltkLinesSource () +{ + delete timeoutInfos; +} + +void FltkLinesSource::staticProcessInputCallback (int fd, void *data) +{ + ((FltkLinesSource*)data)->processInputCallback (fd); +} + +void FltkLinesSource::processInputCallback (int fd) +{ + int n = processInput (fd); + + if (n == 0) { + // We read non-blocking, so -1 is returned and (errno set to + // EAGAIN) when no data is currently available. When 0 is + // returned, this means that there is permanently no data + // (typically that the tested program has terminated). For some + // reasons, the cpu is hogged then; this is avoided by removing + // the read function again. + Fl::remove_fd(0, FL_READ); + getSink()->finish (); + } +} + +void FltkLinesSource::setup (tools::LinesSink *sink) +{ + setSink (sink); + + int flags = fcntl(0, F_GETFL, 0); + fcntl(0, F_SETFL, flags | O_NONBLOCK); + + Fl::add_fd(0, FL_READ, staticProcessInputCallback, (void*)this); +} + +void FltkLinesSource::addTimeout (double secs, int type) +{ + TimeoutInfo *timeoutInfo = new TimeoutInfo (this, type); + timeoutInfos->append (timeoutInfo); + Fl::add_timeout(secs, timeoutCallback, timeoutInfo); +} + +void FltkLinesSource::timeoutCallback (void *data) +{ + TimeoutInfo *timeoutInfo = (TimeoutInfo*) data; + timeoutInfo->getSource()->getSink()->timeout (timeoutInfo->getType ()); + timeoutInfo->getSource()->timeoutInfos->removeRef (timeoutInfo); +} + +void FltkLinesSource::removeTimeout (int type) +{ + // Iterators will not work when the set is modified; hence this nested loop. + bool found; + do { + found = false; + for (Iterator<TimeoutInfo> it = timeoutInfos->iterator (); + !found && it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (timeout->getType () == type) { + found = true; + Fl::remove_timeout(timeoutCallback, timeout); + timeoutInfos->removeRef (timeout); + } + } + } while (found); +} + +// --------------------------- +// FltkDefaultSource +// --------------------------- + +FltkDefaultSource::FltkDefaultSource (): LinesSourceSequence (true) +{ + int fd = open (".rtfl", O_RDONLY); + if (fd != -1) + add (new tools::BlockingLinesSource (fd)); + + add (new FltkLinesSource ()); +} + +} // namespace objects + +} // namespace rtfl diff --git a/common/fltk_lines.hh b/common/fltk_lines.hh new file mode 100644 index 0000000..424ab82 --- /dev/null +++ b/common/fltk_lines.hh @@ -0,0 +1,52 @@ +#ifndef __COMMON_FLTK_LINES_HH__ +#define __COMMON_FLTK_LINES_HH__ + +#include "lines.hh" + +namespace rtfl { + +namespace common { + +class FltkLinesSource: public tools::FileLinesSource +{ + class TimeoutInfo: public lout::object::Object + { + private: + FltkLinesSource *source; + int type; + + public: + TimeoutInfo (FltkLinesSource *source, int type); + + inline FltkLinesSource *getSource () { return source; } + inline int getType () { return type; } + }; + + lout::container::typed::List<TimeoutInfo> *timeoutInfos; + + static void staticProcessInputCallback (int fd, void *data); + static void timeoutCallback (void *data); + void processInputCallback (int fd); + +public: + FltkLinesSource (); + ~FltkLinesSource (); + + void setup (tools::LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +class FltkDefaultSource: public tools::LinesSourceSequence +{ +public: + FltkDefaultSource (); +}; + + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_FLTK_LINES_HH__ diff --git a/common/lines.cc b/common/lines.cc new file mode 100644 index 0000000..e6fa8c2 --- /dev/null +++ b/common/lines.cc @@ -0,0 +1,362 @@ +/* + * RTFL + * + * Copyright 2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "lines.hh" +#include "tools.hh" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/select.h> +#include <sys/timeb.h> + +#if 0 +# define PRINT(fmt) printf ("---- [%p] " fmt "\n", this) +# define PRINTF(fmt, ...) printf ("---- [%p] " fmt "\n", this, __VA_ARGS__) +#else +# define PRINT(fmt) +# define PRINTF(fmt, ...) +#endif + +using namespace lout::container::typed; +using namespace lout::misc; + +namespace rtfl { + +namespace tools { + +// ----------------------------- +// LinesSourceSequence +// ----------------------------- + +LinesSourceSequence::VirtualSink::VirtualSink () +{ +} + +void LinesSourceSequence::VirtualSink::processLine (char *line) +{ + sequence->sink->processLine (line); +} + +void LinesSourceSequence::VirtualSink::setLinesSource (LinesSource *source) +{ +} + +void LinesSourceSequence::VirtualSink::finish () +{ + // If a child source calls sink->finish() within setup(), this is + // called recursively, but this does not cause problems. + + if (sequence->iterator.hasNext ()) { + LinesSource *source = sequence->iterator.getNext (); + source->setup (this); + } else { + sequence->sink->finish (); + } +} + +void LinesSourceSequence::VirtualSink::timeout (int type) +{ + sequence->sink->timeout (type); +} + +LinesSourceSequence::LinesSourceSequence (bool ownerOfSources) +{ + virtualSink.sequence = this; + sources = new List<LinesSource> (ownerOfSources); + setupCalled = false; +} + +LinesSourceSequence::~LinesSourceSequence () +{ + delete sources; +} + +void LinesSourceSequence::add (LinesSource *source) +{ + assert (!setupCalled); + sources->append (source); +} + +void LinesSourceSequence::setup (LinesSink *sink) +{ + this->sink = sink; + sink->setLinesSource (this); + setupCalled = true; + iterator = sources->iterator (); + virtualSink.finish (); +} + +void LinesSourceSequence::addTimeout (double secs, int type) +{ + // TODO: After calling this, no source should be added. + // TODO: Processed timeouts must be removed from other sources as well? + + // Sent to all, even if only one child source will actually trigger the + // timeout; but we do not know which one. + + // In the real world, LinesSourceSequence is used for ".rtfl" and stdin, so + // we do not have to worry too much about correctly handling timeouts. + + for (Iterator<LinesSource> it = sources->iterator (); it.hasNext (); ) { + it.getNext()->addTimeout (secs, type); + } +} + +void LinesSourceSequence::removeTimeout (int type) +{ + for (Iterator<LinesSource> it = sources->iterator (); it.hasNext (); ) { + it.getNext()->removeTimeout (type); + } +} + +// ------------------------- +// FileLinesSource +// ------------------------- + +FileLinesSource::FileLinesSource () +{ + bufPos = 0; + completeLine = true; +} + +int FileLinesSource::processInput (int fd) +{ + int n; + if ((n = read (fd, buf + bufPos, MAX_LINE_SIZE - bufPos)) > 0) { + int bytesAvail = bufPos + n; + int startOfLine = 0; + bool lineProcessed; + + //printf ("--> %d bytes read, %d available\n", n, bytesAvail); + + do { + lineProcessed = false; + for (int i = startOfLine; !lineProcessed && i < bytesAvail; i++) { + if (buf[i] == '\n') { + buf[i] = 0; + + // If lines are too long (see below, where completeLine + // is set to false), they are not processed. + if (completeLine) + sink->processLine (buf + startOfLine); + + lineProcessed = true; + startOfLine = i + 1; + + completeLine = true; + } + } + } while (lineProcessed); + + memmove (buf, buf + startOfLine, bytesAvail - startOfLine); + bufPos = bytesAvail - startOfLine; + + PRINTF ("processInput: %d bytes left in buffer", bufPos); + + // Handle case when line is to large (> MAX_LINE_SIZE + // bytes). The whole line is discarded (completeLine), so we + // empty the buffer by setting bufPos to 0. + if (bufPos == MAX_LINE_SIZE) { + bufPos = 0; + completeLine = false; + } + + //printf (" --> %d processed, new pos: %d; will read %d\n", + // startOfLine, bufPos, MAX_LINE_SIZE - bufPos); + } + + //printf (" --> read(2) returns %d\n", n); + + return n; +} + +// ----------------------------- +// BlockingLinesSource +// ----------------------------- + +BlockingLinesSource::TimeoutInfo::TimeoutInfo (long time, int type) +{ + this->time = time; + this->type = type; +} + +bool BlockingLinesSource::TimeoutInfo::equals(Object *other) +{ + return time == ((TimeoutInfo*)other)->time && + type == ((TimeoutInfo*)other)->type; +} + +int BlockingLinesSource::TimeoutInfo::hashValue() +{ + // This should better be hidden in lout::objects. Cf. Pointer::hashValue(). +#if SIZEOF_LONG == 4 + return (int)time ^ type; +#else + return ((intptr_t)time >> 32) ^ ((intptr_t)time) ^ type; +#endif +} + +BlockingLinesSource::BlockingLinesSource (int fd) +{ + this->fd = fd; + timeoutInfos = new HashSet<TimeoutInfo> (true); +} + +BlockingLinesSource::~BlockingLinesSource () +{ + delete timeoutInfos; +} + +void BlockingLinesSource::setup (LinesSink *sink) +{ + setSink (sink); + + // We read non-blocking so that select(2) will work properly. + // (FileLinesSource::processInput would block otherwise.) + int flags = fcntl(0, F_GETFL, 0); + fcntl(0, F_SETFL, flags | O_NONBLOCK); + + bool eos = false; + while (!eos) { + fd_set readfds; + FD_ZERO (&readfds); + FD_SET (fd, &readfds); + + TimeoutInfo *nextTimeout = getNextTimeoutInfo (); + + struct timeval tv, *tvp; + if (nextTimeout == NULL) { + tvp = NULL; + PRINT ("no timeout"); + } else { + long tdelta = max (nextTimeout->getTime () - getCurrentTime (), 0L); + tv.tv_sec = tdelta / 1000; + tv.tv_usec = (tdelta % 1000) * 1000; + tvp = &tv; + PRINTF ("waiting %ld (%ld, %ld)", tdelta, tv.tv_sec, tv.tv_usec); + } + + PRINT (">> processTimeouts"); + processTimeouts (); + PRINT ("<< processTimeouts"); + + PRINT (">> select"); + if (select (fd + 1, &readfds, NULL, NULL, tvp) == -1) + syserr ("select failed"); + PRINT ("<< select"); + + processTimeouts (); + + if (FD_ISSET (fd, &readfds)) { + PRINT (">> processInput"); + int n = processInput (fd); + PRINT ("<< processInput"); + if (n == 0) { + eos = true; + } + } + } + + close (fd); + sink->finish (); +} + +void BlockingLinesSource::addTimeout (double secs, int type) +{ + PRINTF ("addTimeout (%g, %d)", secs, type); + timeoutInfos->put (new TimeoutInfo (getCurrentTime () + secs * 1000, type)); +} + +void BlockingLinesSource::removeTimeout (int type) +{ + PRINTF ("removeTimeout (%d)", type); + + // Iterators will not work when the set is modified; hence this nested loop. + bool found; + do { + found = false; + for (Iterator<TimeoutInfo> it = timeoutInfos->iterator (); + !found && it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (timeout->getType () == type) { + found = true; + timeoutInfos->remove (timeout); + } + } + } while (found); +} + +long BlockingLinesSource::getCurrentTime () +{ + struct timeb t; + if (ftime (&t) == -1) + syserr ("ftime() failed"); + return t.time * 1000L + t.millitm; +} + +BlockingLinesSource::TimeoutInfo *BlockingLinesSource::getNextTimeoutInfo () +{ + TimeoutInfo *nextTimeout = NULL; + + for (Iterator<TimeoutInfo> it = timeoutInfos->iterator (); + it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (nextTimeout == NULL || + timeout->getTime () < nextTimeout->getTime ()) + nextTimeout = timeout; + } + + return nextTimeout; +} + +void BlockingLinesSource::processTimeouts () +{ + long currentTime = getCurrentTime (); + + while (true) { + TimeoutInfo *nextTimeout = getNextTimeoutInfo (); + if (nextTimeout == NULL) + break; + + PRINTF ("processTimeouts: %ld > %ld? %s", + nextTimeout->getTime (), currentTime, + nextTimeout->getTime () > currentTime ? "yes" : "no"); + if (nextTimeout->getTime () > currentTime) + break; + + PRINT ("processTimeouts: call timeout"); + + getSink()->timeout (nextTimeout->getType ()); + timeoutInfos->remove (nextTimeout); + } +} + +} // namespace tools + +} // namespace rtfl diff --git a/common/lines.hh b/common/lines.hh new file mode 100644 index 0000000..d3bfc75 --- /dev/null +++ b/common/lines.hh @@ -0,0 +1,121 @@ +#ifndef __COMMON_LINES_HH__ +#define __COMMON_LINES_HH__ + +#include "lout/object.hh" +#include "lout/container.hh" + +namespace rtfl { + +namespace tools { + +class LinesSource; + +class LinesSink: public lout::object::Object +{ +public: + virtual void setLinesSource (LinesSource *source) = 0; + virtual void processLine (char *line) = 0; + virtual void timeout (int type) = 0; + virtual void finish () = 0; +}; + + +class LinesSource: public lout::object::Object +{ +public: + virtual void setup (LinesSink *sink) = 0; + virtual void addTimeout (double secs, int type) = 0; + virtual void removeTimeout (int type) = 0; +}; + + +class LinesSourceSequence: public LinesSource +{ +private: + class VirtualSink: public LinesSink + { + public: + LinesSourceSequence *sequence; + + VirtualSink (); + void setLinesSource (LinesSource *source); + void processLine (char *line); + void timeout (int type); + void finish (); + }; + + VirtualSink virtualSink; + LinesSink *sink; + lout::container::typed::List<LinesSource> *sources; + bool setupCalled; + lout::container::typed::Iterator<LinesSource> iterator; + +public: + LinesSourceSequence (bool ownerOfSources); + ~LinesSourceSequence (); + void add (LinesSource *source); + void setup (LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +class FileLinesSource: public LinesSource +{ +private: + enum { MAX_LINE_SIZE = 1000 }; + + tools::LinesSink *sink; + char buf[MAX_LINE_SIZE + 1]; + int bufPos; + bool completeLine; + +protected: + FileLinesSource (); + + int processInput (int fd); + inline void setSink (LinesSink *sink) { + this->sink = sink; sink->setLinesSource (this); } + inline LinesSink *getSink () { return sink; } +}; + + +class BlockingLinesSource: public FileLinesSource +{ +private: + class TimeoutInfo: public lout::object::Object + { + private: + long time; + int type; + + public: + TimeoutInfo (long time, int type); + bool equals(Object *other); + int hashValue(); + + inline long getTime () { return time; } + inline int getType () { return type; } + }; + + int fd; + lout::container::typed::HashSet<TimeoutInfo> *timeoutInfos; + + long getCurrentTime (); + TimeoutInfo *getNextTimeoutInfo (); + void processTimeouts (); + +public: + BlockingLinesSource (int fd); + ~BlockingLinesSource (); + void setup (LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +} // namespace tools + +} // namespace rtfl + +#endif // __COMMON_LINES_HH__ diff --git a/common/parser.cc b/common/parser.cc new file mode 100644 index 0000000..3311e45 --- /dev/null +++ b/common/parser.cc @@ -0,0 +1,250 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "parser.hh" + +#include <string.h> +#include <ctype.h> + +namespace rtfl { + +namespace tools { + +void Parser::setLinesSource (LinesSource *source) +{ +} + +void Parser::processLine (char *line) +{ + char *lineCopy = strdup (line); + + if (strncmp (lineCopy, "[rtfl]", 6) == 0) { + // Pre-version: starts with "[rtfl]". + + char **parts = split (lineCopy + 6, 5); + + if (parts[1] && parts[2] && parts[3]) { + // Notice that parts[4] (arguments) is allowed to be NULL here. + CommonLineInfo info = + { parts[0], atoi(parts[1]), atoi(parts[2]), line }; + processCommand (&info, parts[3], parts[4]); + } else + fprintf (stderr, "Incomplete line:\n%s\n", line); + + freeSplit (parts); + } else if (strncmp (lineCopy, "[rtfl-", 6) == 0) { + // Versioned: starts with "[rtfl-<module>-<major>.<minor>]". + + int i = 6; + while (isalpha (lineCopy[i])) + i++; + + if (lineCopy[i] != '-') + fprintf (stderr, "Expected '-' after module:\n%s\n", line); + else { + char *module = new char[i - 6 + 1]; + memcpy (module, lineCopy + 6, (i - 6) * sizeof (char)); + module[i - 6] = 0; + + i++; + if (!isdigit (lineCopy[i])) + fprintf (stderr, "Missing major version:\n%s\n", line); + else { + int majorVersion = 0, minorVersion = 0; + + while (isdigit (lineCopy[i])) { + majorVersion = 10 * majorVersion + (lineCopy[i] - '0'); + i++; + } + + if (majorVersion == 0) + fprintf (stderr, "Major version must be positive:\n%s\n", line); + else if (lineCopy[i] != '.') + fprintf (stderr, "Expected '.' after major version:\n%s\n", + line); + else if (!isdigit (lineCopy[i + 1])) + fprintf (stderr, "Missing minor version:\n%s\n", line); + else { + i++; + while (isdigit (lineCopy[i])) { + minorVersion = 10 * minorVersion + (lineCopy[i] - '0'); + i++; + } + + if (lineCopy[i] != ']') + fprintf (stderr, "Expected ']' after minor version:\n%s\n", + line); + else { + char **parts = splitEscaped (lineCopy + i + 1); + + if (parts[1] && parts[2] && parts[3]) { + // Notice that parts[4] (first argument) is allowed to be + // NULL here. + CommonLineInfo info = { parts[0], atoi(parts[1]), + atoi(parts[2]), line }; + processVCommand (&info, module, majorVersion, minorVersion, + parts[3], parts + 4); + } else + fprintf (stderr, "Incomplete line:\n%s\n", line); + + freeSplitEscaped (parts); + } + } + } + + delete[] module; + } + } + + free (lineCopy); +} + +void Parser::finish () +{ +} + +void Parser::timeout (int type) +{ +} + +char **Parser::splitEscaped (char *txt) +{ + int numParts; + char **parts; + + scanSplit (txt, &numParts, NULL); + parts = new char*[numParts + 1]; + scanSplit (txt, NULL, parts); + parts[numParts] = NULL; + + for (int i = 0; i < numParts; i++) + unquote (parts[i]); + + return parts; +} + +void Parser::scanSplit (char *txt, int *numParts, char **parts) +{ + int iChar, iPart; + bool quoted; + + if (numParts) + *numParts = 1; + + if (parts) + parts[0] = txt; + + for (iChar = 0, iPart = 1; txt[iChar]; iChar++) { + if (txt[iChar] == '\\' && txt[iChar + 1]) { + iChar++; + quoted = true; + } else + quoted = false; + + if (!quoted && txt[iChar] == ':') { + if (parts) { + txt[iChar] = 0; + parts[iPart] = txt + iChar + 1; + } + + iPart++; + + if (numParts) + (*numParts)++; + } + } +} + +void Parser::unquote (char *txt) +{ + int i, j; + for (i = 0, j = 0; txt[i]; i++, j++) { + if (txt[i] == '\\' && txt[i + 1]) + i++; + txt[j] = txt[i]; + } + txt[j] = 0; +} + +// Free result of splitEscaped(). +void Parser::freeSplitEscaped (char **parts) +{ + delete[] parts; +} + +// Split without escaping. +char **Parser::split (char *txt, int maxNum) +{ + // Only maxNum splits. If less parts are found, less parts are + // returned, so the caller should check the result (first part is + // always defined). Notice that the original text buffer (txt) is + // destroyed, for speed. + + //printf ("===> split ('%s', %d)\n", txt, maxNum); + + char **parts = new char*[maxNum + 1]; + + char *start = txt; + int i = 0; + while (i < maxNum) { + char *end = start; + while (*end != 0 && *end != ':') end++; + int endOfTxt = *end == 0; + + //printf (" start '%s'\n", start); + //printf (" end '%s' (%d character(s))\n", + // end, (int)(end - start)); + + parts[i] = start; + + if (i < maxNum -1) + *end = 0; + + //printf ("---> %d: '%s'\n", i, start); + + i++; + if (endOfTxt) + break; + + start = endOfTxt ? end : end + 1; + } + + parts[i] = NULL; + return parts; +} + +// Free result of split(). +void Parser::freeSplit (char **parts) +{ + delete[] parts; +} + +} // namespace tools + +} // namespace rtfl diff --git a/common/parser.hh b/common/parser.hh new file mode 100644 index 0000000..30cf02d --- /dev/null +++ b/common/parser.hh @@ -0,0 +1,47 @@ +#ifndef __COMMON_PARSER_HH__ +#define __COMMON_PARSER_HH__ + +#include "lines.hh" + +namespace rtfl { + +namespace tools { + +struct CommonLineInfo +{ + char *fileName; + int lineNo; + int processId; + char *completeLine; +}; + +class Parser: public LinesSink +{ +private: + char **splitEscaped (char *txt); + void scanSplit (char *txt, int *numParts, char **parts); + static void unquote (char *txt); + void freeSplitEscaped (char **parts); + +protected: + char **split (char *txt, int maxNum); + void freeSplit (char **parts); + + virtual void processCommand (CommonLineInfo *info, char *cmd, char *args) + = 0; + virtual void processVCommand (CommonLineInfo *info, const char *module, + int majorVersion, int minorVersion, + const char *cmd, char **args) = 0; + +public: + void setLinesSource (LinesSource *source); + void processLine (char *line); + void finish (); + void timeout (int type); +}; + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_PARSER_HH__ diff --git a/common/rtfl_findrepeat.cc b/common/rtfl_findrepeat.cc new file mode 100644 index 0000000..d464db8 --- /dev/null +++ b/common/rtfl_findrepeat.cc @@ -0,0 +1,534 @@ +/* + * RTFL + * + * Copyright 2014, 2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This program searches for identical sequences in a stream of RTFL + * messages (actually, any stream) and marks the beginnings and ends + * with an RTFL mark (obj-mark). You should first filter out other + * lines, so run + * + * $ ... | grep '^\[rtfl[^\]]*\]' | rtfl-findrepeat + * + * or use the script rtfl-objfilter. + * + * For options, see printHelp(). + * + * Warning: This program is highly experimental, and especially rather + * inefficient. Some ideas: + * + * - When finding a suitable lenght ("-l find"), a smaller number of + * lines is in many cases sufficient, so use "head". + * + * - Unless the length is searched for ("-l find"), hashing multiple + * lines (as many as are searched as minimum), as done in the + * searching algorithm by Rabin and Karp, may increase the speed. + */ + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include "tools.hh" +#include "../lout/object.hh" +#include "../lout/container.hh" + +using namespace lout::misc; +using namespace lout::object; +using namespace lout::container::typed; + +enum { MAX_LINE_SIZE = 1000 }; + +class Region: public Comparable +{ + int first, num; + +public: + inline Region (int first, int num) { this->first = first; this->num = num; } + + bool equals (Object *other); + int hashValue (); + void intoStringBuffer (StringBuffer *sb); + int compareTo(Comparable *other); + + inline int getFirst () { return first; } + inline int getNum () { return num; } + inline Region *cloneRegion () { return new Region (first, num); } + + inline bool subSetOf (Region *other) + { return first >= other->first && first + num <= other->first + other->num; } +}; + +class Mark: public Object +{ +public: + enum Type { START, END }; + +private: + Type type; + int majorNo, minorNo, length; + +public: + inline Mark (Type type, int majorNo, int minorNo, int length) + { this->type = type; this->majorNo = majorNo; this->minorNo = minorNo; + this->length = length; } + + void intoStringBuffer (StringBuffer *sb); + + inline Type getType () { return type; } + inline int getMajorNo () { return majorNo; } + inline int getMinorNo () { return minorNo; } + inline int getLength () { return length; } +}; + +bool Region::equals (Object *other) +{ + Region *otherRegion = (Region*)other; + return first == otherRegion->first && num == otherRegion->num; +} + +int Region::hashValue () +{ + return first ^ num; +} + +void Region::intoStringBuffer (StringBuffer *sb) +{ + char buf[32]; + + sb->append ("("); + snprintf (buf, 32, "%d", first); + sb->append (buf); + sb->append ("..."); + snprintf (buf, 32, "%d", first + num - 1); + sb->append (buf); + sb->append (")"); +} + +void Mark::intoStringBuffer (StringBuffer *sb) +{ + char buf[32]; + + + sb->append ("("); + sb->append (type == START ? "START" : "END"); + sb->append (" / "); + snprintf (buf, 32, "%d", majorNo); + sb->append (buf); + sb->append (" / "); + snprintf (buf, 32, "%d", minorNo); + sb->append (buf); + sb->append (")"); +} + +int Region::compareTo(Comparable *other) +{ + Region *otherRegion = (Region*)other; + return first - otherRegion->first; +} + +// ---------------------------------------------------------------------- + +static bool debug = false; + +static void printHelp (const char *argv0) +{ + fprintf + (stderr, "Usage: %s <options>\n" + "\n" + "Options:\n" + " -l <n> Search for sequence of at least <n> lines.\n" + " -c <n> Search for sequence repeated at least <n> times.\n" + "\n" + "If an arguments is 'f' or 'find', the maximal value for this is\n" + "determined (possibly with the other argument set to a concrete\n" + "number).\n" + "\n" + "See RTFL documentation for more details.\n", + argv0); +} + +// ---------------------------------------------------------------------- + +static void readFile (FILE *file, Vector<String> *lines, + HashTable<String, Vector<Integer> > *lineNosByLines) +{ + char buf[MAX_LINE_SIZE + 1]; + + for (int lineNo = 0; fgets (buf, MAX_LINE_SIZE + 1, file); lineNo++) { + size_t l = strlen (buf); + if (buf[l - 1] == '\n') buf[l - 1] = 0; + + String *line = new String (buf); + + Vector<Integer> *lineNos = lineNosByLines->get (line); + if (lineNos == NULL) { + lineNos = new Vector<Integer> (1, true); + // Note: key is dublicated. + lineNosByLines->put (new String (buf), lineNos); + } + lineNos->put (new Integer (lineNo)); + + lines->put (line); + } +} + +static int findRegions (Vector<String> *lines, + HashTable<String, Vector<Integer> > *lineNosByLines, + List <HashSet<Region> > *allSetsOfRegions, + int minLength, int minCount) +{ + int effMinLength = minLength == -1 ? 2 :minLength; + int effMinCount = minCount == -1 ? 2 : minCount; + int maxLength = 0, maxCount = 0; + + HashTable<Region, HashSet<Region> > *setsOfRegionsByRegion = + new HashTable<Region, HashSet<Region> > (false, false); + + List <HashSet<Region> > *tmpAllSetsOfRegions = + new List<HashSet<Region> > (false); + + for (int lineNo1 = 0; lineNo1 < lines->size (); lineNo1++) { + String *line = lines->get (lineNo1); + Vector<Integer> *lineNos = lineNosByLines->get (line); + + // Examine only lines after this. + Integer lineNo1Key (lineNo1); + + for (int linesNoIndex = lineNos->bsearch (&lineNo1Key, true) + 1; + linesNoIndex < lineNos->size (); linesNoIndex++) { + int lineNo2 = lineNos->get(linesNoIndex)->getValue (); + int numMatching = 1; + while (lineNo2 + numMatching < lines->size () && + lines->get(lineNo1 + numMatching)->equals + (lines->get(lineNo2 + numMatching))) { + numMatching++; + + if (numMatching >= effMinLength) { + //printf ("equal: (%d...%d) and (%d...%d)\n", + // lineNo1, lineNo1 + numMatching - 1, + // lineNo2, lineNo2 + numMatching - 1); + + Region r1 (lineNo1, numMatching), r2 (lineNo2, numMatching); + HashSet<Region> *setOfRegions; + + if ((setOfRegions = setsOfRegionsByRegion->get (&r1))) { + if (!setsOfRegionsByRegion->contains (&r2)) { + assert (!setOfRegions->contains (&r2)); + Region *rr2 = r2.cloneRegion (); + setOfRegions->put (rr2); + setsOfRegionsByRegion->put (rr2, setOfRegions); + } + } else if ((setOfRegions = setsOfRegionsByRegion->get (&r2))) { + if (!setsOfRegionsByRegion->contains (&r1)) { + assert (!setOfRegions->contains (&r1)); + Region *rr1 = r1.cloneRegion (); + setOfRegions->put (rr1); + setsOfRegionsByRegion->put (rr1, setOfRegions); + } + } else { + Region *rr1 = r1.cloneRegion (), *rr2 = r2.cloneRegion (); + setOfRegions = new HashSet<Region> (false); + setOfRegions->put (rr1); + setOfRegions->put (rr2); + setsOfRegionsByRegion->put (rr1, setOfRegions); + setsOfRegionsByRegion->put (rr2, setOfRegions); + tmpAllSetsOfRegions->append (setOfRegions); + } + + if (debug) { + StringBuffer sb; + setsOfRegionsByRegion->intoStringBuffer (&sb); + printf ("findRegions: setsOfRegionsByRegion = %s\n", + sb.getChars ()); + } + } + } + } + } + + delete setsOfRegionsByRegion; + + for (Iterator<HashSet<Region> > it1 = tmpAllSetsOfRegions->iterator (); + it1.hasNext (); ) { + HashSet<Region> *set = it1.getNext (); + if (set->size () >= effMinCount) { + allSetsOfRegions->append (set); + maxCount = max (maxCount, set->size ()); + + if (minLength == -1) { + for (Iterator<Region> it2 = set->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + maxLength = max (maxLength, r->getNum ()); + } + } + } else + delete set; + } + + delete tmpAllSetsOfRegions; + + if (minLength == -1) + return maxLength; + else if (minCount == -1) + return maxCount; + else + return -1; +} + +static void sortListsOfRegions (List <HashSet<Region> > *allSetsOfRegions, + List <Vector<Region> > *allListsOfRegions) +{ + for (Iterator<HashSet<Region> > it1 = allSetsOfRegions->iterator (); + it1.hasNext (); ) { + HashSet<Region> *set = it1.getNext (); + Vector<Region> *list = new Vector<Region> (1, true); + + for (Iterator<Region> it2 = set->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + list->put (r); + } + + if (debug) { + StringBuffer sb; + list->intoStringBuffer (&sb); + printf ("sortListsOfRegions: list = %s\n", sb.getChars ()); + } + + list->sort (); + allListsOfRegions->append (list); + } +} + +static void cleanupRegions (List <Vector <Region> > *allListsOfRegions) +{ + HashTable<List<Integer>, Vector<Vector<Region> > > *allListsOfLists = + new HashTable<List<Integer>, Vector<Vector<Region> > > (true, true); + + for (Iterator<Vector <Region> > it = allListsOfRegions->iterator (); + it.hasNext (); ) { + Vector<Region> *list = it.getNext (); + + List<Integer> *key = new List<Integer> (true); + for (int i = 1; i < list->size (); i++) + key->append (new Integer (list->get(i)->getFirst () - + list->get(i - 1)->getFirst ())); + + Vector<Vector<Region> > *listOfLists = allListsOfLists->get (key); + if (listOfLists) + delete key; + else { + listOfLists = new Vector<Vector<Region> > (1, false); + allListsOfLists->put (key, listOfLists); + } + + listOfLists->put (list); + } + + allListsOfRegions->clear (); + + if (debug) { + StringBuffer sb; + allListsOfLists->intoStringBuffer (&sb); + printf ("cleanupRegions: allListsOfLists = %s\n", sb.getChars ()); + } + + for (Iterator<List<Integer> > it = allListsOfLists->iterator (); + it.hasNext (); ) { + List<Integer> *key = it.getNext (); + Vector<Vector<Region> > *listOfLists = allListsOfLists->get (key); + + if (debug) { + StringBuffer sb; + listOfLists->intoStringBuffer (&sb); + printf ("cleanupRegions: listOfLists = %s\n", sb.getChars ()); + } + + for (int i = 0; i < listOfLists->size (); i++) { + Vector<Region> *list1 = listOfLists->get (i); + Region *r1 = list1->get (0); + bool redundant = false; + for (int j = 0; j < listOfLists->size () && !redundant; j++) { + if (i != j) { + Vector<Region> *list2 = listOfLists->get (j); + if (list2 != NULL) { + Region *r2 = list2->get (0); + if (r1->subSetOf (r2)) + redundant = true; + } + } + } + + if (redundant) { + listOfLists->put (NULL, i); + delete list1; + } else + allListsOfRegions->append (list1); + } + } + + delete allListsOfLists; +} + +// ---------------------------------------------------------------------- + +int main (int argc, char *argv[]) +{ + int minLength = 2, minCount = 2; + int opt; + + while ((opt = getopt(argc, argv, "c:dl:")) != -1) { + switch (opt) { + case 'c': + if (strcmp (optarg, "f") == 0 || strcmp (optarg, "find") == 0) + minCount = -1; + else + minCount = atoi (optarg); + break; + + case 'd': + debug = true; + break; + + case 'l': + if (strcmp (optarg, "f") == 0 || strcmp (optarg, "find") == 0) + minLength = -1; + else + minLength = atoi (optarg); + break; + + default: + printHelp (argv[0]); + return 1; + } + } + + Vector<String> *lines = new Vector<String> (8, true); + HashTable<String, Vector<Integer> > *lineNosByLines = + new HashTable<String, Vector<Integer> > (true, true); + + readFile (stdin, lines, lineNosByLines); + + List <HashSet<Region> > *allSetsOfRegions = + new List<HashSet<Region> > (true); + + int numFound = findRegions (lines, lineNosByLines, allSetsOfRegions, + minLength, minCount); + + if (debug) { + StringBuffer sb; + allSetsOfRegions->intoStringBuffer (&sb); + printf ("main: allSetsOfRegions = %s\n", sb.getChars ()); + } + + delete lineNosByLines; + + if (numFound != -1) { + delete allSetsOfRegions; + printf ("%d\n", numFound); + } else { + List <Vector<Region> > *allListsOfRegions = + new List <Vector<Region> > (false); // TODO Memory leak! + + sortListsOfRegions (allSetsOfRegions, allListsOfRegions); + + delete allSetsOfRegions; + + if (debug) { + StringBuffer sb; + allListsOfRegions->intoStringBuffer (&sb); + printf ("(a) main: allListsOfRegions = %s\n", sb.getChars ()); + } + + cleanupRegions (allListsOfRegions); + + if (debug) { + StringBuffer sb; + allListsOfRegions->intoStringBuffer (&sb); + printf ("(b) main: allListsOfRegions = %s\n", sb.getChars ()); + } + + HashTable<Integer, List<Mark> > *marksByLineNo = + new HashTable<Integer, List<Mark> > (true, true); + + int majorNo = 0; + for (Iterator<Vector<Region> > it1 = allListsOfRegions->iterator (); + it1.hasNext (); ) { + Vector<Region> *list = it1.getNext (); + int minorNo = 0; + + for (Iterator<Region> it2 = list->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + + for (int typeNo = 0; typeNo < 2; typeNo++) { + Mark::Type type = typeNo == 0 ? Mark::START : Mark::END; + int lineNo = + r->getFirst () + (type == Mark::START ? 0 : r->getNum ()); + Integer lineNoKey (lineNo); + + List<Mark> *list = marksByLineNo->get (&lineNoKey); + if (list == NULL) { + list = new List<Mark> (true); + marksByLineNo->put (new Integer (lineNo), list); + } + + list->append (new Mark (type, majorNo, minorNo, r->getNum ())); + } + + + minorNo++; + } + + majorNo++; + } + + delete allListsOfRegions; + + if (debug) { + StringBuffer sb; + marksByLineNo->intoStringBuffer (&sb); + printf ("main: marksByLineNo = %s\n", sb.getChars ()); + } + + for (int lineNo = 0; lineNo < lines->size (); lineNo++) { + Integer lineNoKey (lineNo); + List<Mark> *list = marksByLineNo->get (&lineNoKey); + if (list) { + for (Iterator<Mark> it = list->iterator (); it.hasNext (); ) { + Mark *m = it.getNext (); + char buf[200]; + rtfl::tools::numToRoman (m->getMajorNo () + 1, buf, + sizeof (buf)); + // Certainly no ':' or '\' in the message, so no quoting + // necessary. + printf ("[rtfl-obj-1.0]n:0:0:mark:findrepeat:findrepeat:0:" + "Sequence %s (length %d), %d%s occurence -- %s\n", + buf, m->getLength (), m->getMinorNo () + 1, + rtfl::tools::numSuffix (m->getMinorNo () + 1), + m->getType () == Mark::START ? "start" : "end"); + } + } + + String *line = lines->get (lineNo); + puts (line->chars ()); + } + + delete marksByLineNo; + } + + delete lines; +} diff --git a/common/rtfl_tee.c b/common/rtfl_tee.c new file mode 100644 index 0000000..4911b73 --- /dev/null +++ b/common/rtfl_tee.c @@ -0,0 +1,249 @@ +/* + * RTFL + * + * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Like tee(1), this program duplicates a stream; however, it does not + * write the copy to a file, but instead sends it via pipe to another + * program. Example: + * + * $ foo | rtfl-tee bar | qix + * + * Here, the standard output of "foo" is passed to the standard input + * of both "bar" and "qix". + * + * More informations in doc/rtfl.html. + * + * TODO: Something like "echo -n foo | rtfl-tee -b cat" does not work; + * since the line of the first "foo" is never finished, the copy of + * "foo" is never printed. + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +static void usrerr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, "\n"); + exit (1); +} + +static void syserr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, ": %s\n", strerror (errno)); + exit (1); +} + +static ssize_t ewrite(int fd, const void *buf, size_t count) +{ + ssize_t w; + if ((w = write (fd, buf, count)) == -1) + syserr ("write(%d, ...) failed", fd); + return w; +} + +static void writestdout (int orig, char *buf, size_t count) +{ + // Basic idea: "orig" denotes to 0 (stdin of rtfl-tee) or 1 (stdout + // of the called program). "curorig" refers to the origin which is + // currently printed, so than the data from the other origin must + // be buffered. "startline" is set to 1 at the beginning, or iff + // the last printed character was '\n'. (In this case, switching is + // simply possible.) + + static int curorig = 0, startline = 1; + static char obuf[2048]; + static size_t ocount = 0; + + //printf ("\nwritestdout: %d, '%c...' (%d)\n", + // orig, count > 0 ? buf[0] : '.', (int)count); + //printf ("===> curorig = %d, ocount = %d, startline = %d\n", + // curorig, (int)ocount, startline); + + if (count > 0) { + if (orig != curorig) { + if (startline) { + // Simple switching case. + ewrite (1, obuf, ocount); + ocount = 0; + curorig = orig; + ewrite (1, buf, count); + startline = buf[count - 1] == '\n'; + } else { + // Buffer. + size_t odiff = min (count, ocount - sizeof (obuf)); + memcpy (obuf + ocount, buf, odiff); + ocount += odiff; + } + } else { + if (ocount == 0) { + // Nothing buffered: simply print all data. + ewrite (1, buf, count); + startline = buf[count - 1] == '\n'; + } else { + // Only print everything until the last newline character. + // (Note: printing everything until the *first* newline + // character whould make a larger buffer necessary, but, + // on the other hand, preserve better the original + // (temporal) order of the lines.) + ssize_t i, nl; + for (nl = -1, i = count - 1; nl == -1 && i >= 0; i--) + if (buf[i] == '\n') nl = i; + + if (nl == -1) { + // No newline: no switch. + ewrite (1, buf, count); + startline = 0; + } else { + // Newline: switch. + ewrite (1, buf, nl + 1); + ewrite (1, obuf, ocount); + startline = obuf[ocount - 1] == '\n'; + ocount = min (sizeof (obuf), count - (nl + 1)); + memcpy (obuf, buf + nl + 1, ocount); + curorig = 1 - curorig; + } + } + } + } +} + +int main (int argc, char *argv[]) +{ + int parent2child[2], child2parent[2], i, offsetcmd, bypass = 0, erroropt = 0; + char *argv2[argc - 1 + 1]; + int done1, done2; + + for (offsetcmd = 1; offsetcmd < argc && argv[offsetcmd][0] == '-'; + offsetcmd++) { + if (argv[offsetcmd][1] == 'b') + bypass = 1; + else if (argv[offsetcmd][1] == '-') { + offsetcmd++; + break; + } else + erroropt = 1; + } + + if (erroropt || offsetcmd >= argc) + usrerr ("Usage: %s [-b] [--] <program> [<program options>]", argv[0]); + + if (pipe (parent2child) == -1) syserr ("pipe failed"); + if (bypass && pipe (child2parent) == -1) syserr ("pipe failed"); + + switch (fork ()) { + case -1: + syserr ("fork failed"); + break; + + case 0: + if (close (parent2child[1]) == -1) + syserr ("close(%d) failed", parent2child[0]); + if (close (0) == -1) syserr ("close(0) failed"); + if (dup2 (parent2child[0], 0) == -1) + syserr ("dup2(%d, 0) failed", parent2child[0]); + if (close (parent2child[0]) == -1) + syserr ("close(%d) failed", parent2child[0]); + + if (bypass) { + if (close (child2parent[0]) == -1) + syserr ("close(%d) failed", child2parent[1]); + if (close (1) == -1) syserr ("close(1) failed"); + if (dup2 (child2parent[1], 1) == -1) + syserr ("dup2(%d, 1) failed", child2parent[1]); + if (close (child2parent[1]) == -1) + syserr ("close(%d) failed", child2parent[1]); + } + + for (i = 0; i < argc - offsetcmd; i++) argv2[i] = argv[i + offsetcmd]; + argv2[argc - offsetcmd] = NULL; + if (execvp (argv2[0], argv2) == -1) + syserr ("execvp(\"%s\", ...) failed", argv2[0]); + break; + + default: + if (close (parent2child[0]) == -1) + syserr ("close(%d) failed", parent2child[0]); + if (bypass && close (child2parent[1]) == -1) + syserr ("close(%d) failed", child2parent[1]); + + done1 = 0; + done2 = !bypass; + while (!done1 || !done2) { + //printf ("==> done1 = %d, done2 = %d\n", done1, done2); + + fd_set set; + FD_ZERO (&set); + + if (!done1) FD_SET (0, &set); + if (!done2) FD_SET (child2parent[0], &set); + + int s = select ((!done2 ? child2parent[0] : 0) + 1, + &set, NULL, NULL, NULL); + + //printf ("==> s = %d\n", s); + + if (s == -1) syserr ("select failed"); + else if (s > 0) { + char buf[2048]; + ssize_t n; + + if (!done1 && FD_ISSET(0, &set)) { + if ((n = read (0, buf, sizeof (buf))) == -1) + syserr ("read failed"); + else if (n == 0) { + if (close (parent2child[1]) == -1) + syserr ("close(%d) failed", parent2child[1]); + done1 = 1; + } else if (n > 0) { + ewrite (parent2child[1], buf, n); + writestdout (0, buf, n); + } + } + + if (!done2 && FD_ISSET (child2parent[0], &set)) { + if ((n = read (child2parent[0], buf, sizeof (buf))) == -1) + syserr ("read failed"); + else if (n == 0) { + if (close (child2parent[0]) == -1) + syserr ("close(%d) failed", child2parent[0]); + done2 = 1; + } else if (n > 0) + writestdout (1, buf, n); + } + } + } + break; + } + + return 0; +} diff --git a/common/tools.cc b/common/tools.cc new file mode 100644 index 0000000..7dfb412 --- /dev/null +++ b/common/tools.cc @@ -0,0 +1,264 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "tools.hh" + +#include <stdio.h> +#include <errno.h> + +using namespace lout::object; +using namespace lout::container::untyped; + +namespace rtfl { + +namespace tools { + +const char *numSuffix (int n) +{ + if (n % 10 == 1 && n != 11) + return "st"; + else if (n % 10 == 2 && n != 12) + return "nd"; + else if (n % 10 == 3 && n != 13) + return "rd"; + else + return "th"; +} + +static const char + *const roman_I0[] = { "","I","II","III","IV","V","VI","VII","VIII","IX" }, + *const roman_I1[] = { "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC" }, + *const roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" }, + *const roman_I3[] = { "","M","MM","MMM","MMMM" }; + +void numToRoman (int num, char *buf, int buflen) +{ + int i3, i2, i1, i0; + + if (buflen <= 0) + return; + + i0 = num; + i1 = i0/10; i2 = i1/10; i3 = i2/10; + i0 %= 10; i1 %= 10; i2 %= 10; + if (num < 0 || i3 > 4) /* more than 4999 elements ? */ + snprintf(buf, buflen, "****"); + else + snprintf(buf, buflen, "%s%s%s%s", roman_I3[i3], roman_I2[i2], + roman_I1[i1], roman_I0[i0]); +} + +void syserr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, ": %s\n", strerror (errno)); + exit (1); +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::RefTarget::RefTarget (Object *object, bool ownerOfObject) +{ + this->object = object; + this->ownerOfObject = ownerOfObject; + refCount = 1; + allKeys = new HashSet (false); +} + +EquivalenceRelation::RefTarget::~RefTarget () +{ + if (ownerOfObject) + delete object; + delete allKeys; +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::RefSource::RefSource (Object *key, RefTarget *target) +{ + this->target = target; + this->key = key; + refTarget (); +} + +EquivalenceRelation::RefSource::~RefSource () +{ + unrefTarget (); +} + +void EquivalenceRelation::RefSource::refTarget () +{ + if (target) { + target->ref (); + target->putKey (key); + } +} + +void EquivalenceRelation::RefSource::unrefTarget () +{ + if (target) { + target->removeKey (key); + target->unref (); + target = NULL; + } +} + +void EquivalenceRelation::RefSource::setTarget (RefTarget *target) +{ + if (target != this->target) { + unrefTarget (); + this->target = target; + refTarget (); + } +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::EquivalenceRelation (bool ownerOfKeys, bool ownerOfValues) +{ + this->ownerOfKeys = ownerOfKeys; + this->ownerOfValues = ownerOfValues; + sources = new HashTable (ownerOfKeys, true); +} + +EquivalenceRelation::~EquivalenceRelation () +{ + delete sources; +} + +void EquivalenceRelation::put (Object *key, Object *value) +{ + assert (!contains(key)); + + RefTarget *target = new RefTarget (value, ownerOfValues); + RefSource *source = new RefSource (key, target); + target->unref (); + sources->put (key, source); +} + +Object *EquivalenceRelation::get (Object *key) const +{ + RefSource *source = (RefSource*) sources->get(key); + if (source) { + Object *object = source->getTarget()->getObject (); + return object; + } else + return NULL; +} + +bool EquivalenceRelation::contains (Object *key) const +{ + return sources->contains (key); +} + +Iterator EquivalenceRelation::iterator () +{ + return sources->iterator (); +} + +Iterator EquivalenceRelation::relatedIterator (Object *key) +{ + assert (contains (key)); + + RefSource *source = (RefSource*) sources->get (key); + RefTarget *target = source->getTarget (); + return target->getAllKeys()->iterator (); +} + +void EquivalenceRelation::relate (Object *key1, Object *key2) +{ + assert (contains(key1) && contains(key2)); + + RefSource *source1 = (RefSource*) sources->get (key1); + RefSource *source2 = (RefSource*) sources->get (key2); + if (source1->getTarget () != source2->getTarget ()) { + // The first value is kept, the second destroyed. The caller has + // to care about the order. + + // Consider all keys already related to `key2`; this is possible by + // iterating over `RefTarget::allKeys`. To avoid accessing freed memory, + // copy all keys to a new temporary set. + + HashSet target2Keys (false); + for (Iterator it = source2->getTarget()->getAllKeys()->iterator (); + it.hasNext (); ) + target2Keys.put (it.getNext ()); + + for (Iterator it = target2Keys.iterator (); it.hasNext (); ) { + RefSource *otherSource = (RefSource*) sources->get (it.getNext ()); + otherSource->setTarget (source1->getTarget ()); + } + } +} + +void EquivalenceRelation::putRelated (Object *oldKey, Object *newKey) +{ + assert (contains(oldKey) && !contains(newKey)); + + RefSource *oldSource = (RefSource*) sources->get (oldKey); + RefSource *newSource = new RefSource (newKey, oldSource->getTarget ()); + sources->put (newKey, newSource); +} + +void EquivalenceRelation::removeSimple (lout::object::Object *key) +{ + // The order is important: a simple "sources->remove (key)" will + // cause an access to freed memory. + + RefSource *source = (RefSource*) sources->get (key); + source->setTarget (NULL); // Will unref() the target. + sources->remove (key); +} + +void EquivalenceRelation::remove (Object *key) +{ + assert (contains (key)); + + RefSource *source = (RefSource*) sources->get (key); + RefTarget *target = source->getTarget (); + target->ref (); + + for (Iterator it = target->getAllKeys()->iterator (); it.hasNext (); ) { + Object *otherKey = it.getNext (); + + // The order is important: see removeSimple(). + RefSource *otherSource = (RefSource*) sources->get (otherKey); + otherSource->setTarget (NULL); // Will unref() the target. + sources->remove (otherKey); + } + + target->unref (); +} + + +} // namespace tools + +} // namespace dw diff --git a/common/tools.hh b/common/tools.hh new file mode 100644 index 0000000..af8e5e9 --- /dev/null +++ b/common/tools.hh @@ -0,0 +1,80 @@ +#ifndef __COMMON_TOOLS_HH__ +#define __COMMON_TOOLS_HH__ + +#include "lout/object.hh" +#include "lout/container.hh" + +namespace rtfl { + +namespace tools { + +const char *numSuffix (int n); +void numToRoman (int num, char *buf, int buflen); +void syserr (const char *fmt, ...); + +class EquivalenceRelation: public lout::object::Object { +private: + class RefTarget: public lout::object::Object { + private: + bool ownerOfObject; + int refCount; + lout::object::Object *object; + lout::container::untyped::HashSet *allKeys; + + public: + RefTarget (lout::object::Object *object, bool ownerOfObject); + ~RefTarget (); + + inline lout::object::Object *getObject () { return object; } + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } + + inline lout::container::untyped::HashSet *getAllKeys () + { return allKeys; } + inline void putKey (Object *key) { allKeys->put (key); } + inline void removeKey (Object *key) { allKeys->remove (key); } + }; + + + class RefSource: public lout::object::Object { + RefTarget *target; + lout::object::Object *key; + + void refTarget (); + void unrefTarget (); + + public: + RefSource (lout::object::Object *key, RefTarget *target); + ~RefSource (); + + inline RefTarget *getTarget () { return target; } + void setTarget (RefTarget *target); + }; + + bool ownerOfKeys, ownerOfValues; + lout::container::untyped::HashTable *sources; + + lout::container::untyped::HashSet *initSet (lout::object::Object *o); + + public: + EquivalenceRelation (bool ownerOfKeys, bool ownerOfValues); + ~EquivalenceRelation (); + + void put (lout::object::Object *key, lout::object::Object *value); + lout::object::Object *get (lout::object::Object *key) const; + bool contains (lout::object::Object *key) const; + lout::container::untyped::Iterator iterator (); + lout::container::untyped::Iterator relatedIterator (Object *key); + + void relate (lout::object::Object *key1, lout::object::Object *key2); + void putRelated (lout::object::Object *oldKey, lout::object::Object *newKey); + + void removeSimple (lout::object::Object *key); + void remove (lout::object::Object *key); +}; + +} // namespace tools + +} // namespace rtfl + +#endif // __COMMON_TOOLS_HH__ |