Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Alexander_Naumov:SLE-12:Update
squid
SQUID_2016_2_port.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File SQUID_2016_2_port.patch of Package squid
Index: squid-3.3.13/src/http.cc =================================================================== --- squid-3.3.13.orig/src/http.cc +++ squid-3.3.13/src/http.cc @@ -173,6 +173,7 @@ void HttpStateData::httpStateConnClosed(const CommCloseCbParams ¶ms) { debugs(11, 5, "httpStateFree: FD " << params.fd << ", httpState=" << params.data); + doneWithFwd = "httpStateConnClosed()"; // assume FwdState is monitoring too mustStop("HttpStateData::httpStateConnClosed"); } @@ -744,8 +745,7 @@ HttpStateData::processReplyHeader() flags.headers_parsed = true; newrep->sline.version = HttpVersion(1,1); newrep->sline.status = error; - HttpReply *vrep = setVirginReply(newrep); - entry->replaceHttpReply(vrep); + setVirginReply(newrep); ctx_exit(ctx); return; } @@ -1769,7 +1769,8 @@ HttpStateData::httpBuildRequestHeader(Ht String strFwd = hdr_in->getList(HDR_X_FORWARDED_FOR); - if (strFwd.size() > 65536/2) { + // if we cannot double strFwd size, then it grew past 50% of the limit + if (!strFwd.canGrowBy(strFwd.size())) { // There is probably a forwarding loop with Via detection disabled. // If we do nothing, String will assert on overflow soon. // TODO: Terminate all transactions with huge XFF? @@ -2414,20 +2415,10 @@ HttpStateData::sentRequestBody(const Com ServerStateData::sentRequestBody(io); } -// Quickly abort the transaction -// TODO: destruction should be sufficient as the destructor should cleanup, -// including canceling close handlers void HttpStateData::abortTransaction(const char *reason) { debugs(11,5, HERE << "aborting transaction for " << reason << "; " << serverConnection << ", this " << this); - - if (Comm::IsConnOpen(serverConnection)) { - serverConnection->close(); - return; - } - - fwd->handleUnregisteredServerEnd(); - mustStop("HttpStateData::abortTransaction"); + mustStop(reason); } Index: squid-3.3.13/src/SquidString.h =================================================================== --- squid-3.3.13.orig/src/SquidString.h +++ squid-3.3.13/src/SquidString.h @@ -146,6 +146,13 @@ public: _SQUID_INLINE_ int caseCmp(char const *, size_type count) const; _SQUID_INLINE_ int caseCmp(String const &) const; + /// Whether creating a totalLen-character string is safe (i.e., unlikely to assert). + /// Optional extras can be used for overflow-safe length addition. + /// Implementation has to add 1 because many String allocation methods do. + static bool CanGrowTo(size_type totalLen, const size_type extras = 0) { return SafeAdd(totalLen, extras) && SafeAdd(totalLen, 1); } + /// whether appending growthLen characters is safe (i.e., unlikely to assert) + bool canGrowBy(const size_type growthLen) const { return CanGrowTo(size(), growthLen); } + String substr(size_type from, size_type to) const; _SQUID_INLINE_ void cut(size_type newLength); @@ -162,10 +169,20 @@ private: _SQUID_INLINE_ bool nilCmp(bool, bool, int &) const; /* never reference these directly! */ - size_type size_; /* buffer size; 64K limit */ + size_type size_; /* buffer size; limited by SizeMax_ */ size_type len_; /* current length */ + static const size_type SizeMax_ = 65535; ///< 64K limit protects some fixed-size buffers + /// returns true after increasing the first argument by extra if the sum does not exceed SizeMax_ + static bool SafeAdd(size_type &base, size_type extra) { + if (extra <= SizeMax_ && base <= SizeMax_ - extra) { + base += extra; + return true; + } + return false; + } + char *buf_; _SQUID_INLINE_ void set(char const *loc, char const ch); Index: squid-3.3.13/src/StrList.cc =================================================================== --- squid-3.3.13.orig/src/StrList.cc +++ squid-3.3.13/src/StrList.cc @@ -33,20 +33,24 @@ #include "squid.h" #include "SquidString.h" #include "StrList.h" +#include "base/TextException.h" /** appends an item to the list */ void strListAdd(String * str, const char *item, char del) { assert(str && item); + const String::size_type itemSize = strlen(item); if (str->size()) { char buf[3]; buf[0] = del; buf[1] = ' '; buf[2] = '\0'; + Must(str->canGrowBy(2)); str->append(buf, 2); } - str->append(item, strlen(item)); + Must(str->canGrowBy(itemSize)); + str->append(item, itemSize); } /** returns true iff "m" is a member of the list */ Index: squid-3.3.13/src/String.cc =================================================================== --- squid-3.3.13.orig/src/String.cc +++ squid-3.3.13/src/String.cc @@ -67,7 +67,7 @@ void String::setBuffer(char *aBuf, String::size_type aSize) { assert(undefined()); - assert(aSize < 65536); + assert(aSize < SizeMax_); buf_ = aBuf; size_ = aSize; } @@ -198,7 +198,7 @@ String::append( char const *str, int len } else { // Create a temporary string and absorb it later. String snew; - assert(len_ + len < 65536); // otherwise snew.len_ overflows below + assert(canGrowBy(len)); // otherwise snew.len_ overflows below snew.len_ = len_ + len; snew.allocBuffer(snew.len_ + 1); Index: squid-3.3.13/src/Server.cc =================================================================== --- squid-3.3.13.orig/src/Server.cc +++ squid-3.3.13/src/Server.cc @@ -68,6 +68,7 @@ ServerStateData::ServerStateData(FwdStat startedAdaptation(false), #endif receivedWholeRequestBody(false), + doneWithFwd(NULL), theVirginReply(NULL), theFinalReply(NULL) { @@ -94,8 +95,6 @@ ServerStateData::~ServerStateData() HTTPMSGUNLOCK(theVirginReply); HTTPMSGUNLOCK(theFinalReply); - fwd = NULL; // refcounted - if (responseBodyBuffer != NULL) { delete responseBodyBuffer; responseBodyBuffer = NULL; @@ -113,6 +112,14 @@ ServerStateData::swanSong() cleanAdaptation(); #endif + if (!doneWithServer()) + closeServer(); + + if (!doneWithFwd) { + doneWithFwd = "swanSong()"; + fwd->handleUnregisteredServerEnd(); + } + BodyConsumer::swanSong(); #if USE_ADAPTATION Initiator::swanSong(); @@ -235,6 +242,7 @@ ServerStateData::completeForwarding() { debugs(11,5, HERE << "completing forwarding for " << fwd); assert(fwd != NULL); + doneWithFwd = "completeForwarding()"; fwd->complete(); } Index: squid-3.3.13/src/Server.h =================================================================== --- squid-3.3.13.orig/src/Server.h +++ squid-3.3.13/src/Server.h @@ -192,6 +192,10 @@ protected: #endif bool receivedWholeRequestBody; ///< handleRequestBodyProductionEnded called + /// whether we should not be talking to FwdState; XXX: clear fwd instead + /// points to a string literal which is used only for debugging + const char *doneWithFwd; + private: void sendBodyIsTooLargeError(); void maybePurgeOthers(); Index: squid-3.3.13/src/ftp.cc =================================================================== --- squid-3.3.13.orig/src/ftp.cc +++ squid-3.3.13/src/ftp.cc @@ -466,6 +466,7 @@ FtpStateData::ctrlClosed(const CommClose { debugs(9, 4, HERE); ctrl.clear(); + doneWithFwd = "ctrlClosed()"; // assume FwdState is monitoring too mustStop("FtpStateData::ctrlClosed"); } @@ -3884,24 +3885,12 @@ FtpStateData::haveControlChannel(const c return true; } -/** - * Quickly abort the transaction - * - \todo destruction should be sufficient as the destructor should cleanup, - * including canceling close handlers - */ void FtpStateData::abortTransaction(const char *reason) { debugs(9, 3, HERE << "aborting transaction for " << reason << "; FD " << (ctrl.conn!=NULL?ctrl.conn->fd:-1) << ", Data FD " << (data.conn!=NULL?data.conn->fd:-1) << ", this " << this); - if (Comm::IsConnOpen(ctrl.conn)) { - ctrl.conn->close(); - return; - } - - fwd->handleUnregisteredServerEnd(); - mustStop("FtpStateData::abortTransaction"); + mustStop(reason); } /// creates a data channel Comm close callback Index: squid-3.3.13/src/esi/CustomParser.cc =================================================================== --- squid-3.3.13.orig/src/esi/CustomParser.cc +++ squid-3.3.13/src/esi/CustomParser.cc @@ -110,9 +110,11 @@ ESICustomParser::parse(char const *dataT } size_t openESITags (0); - //erring on the safe side. Probably rawBuf would be ok too - char const *currentPos = content.termedBuf(); - size_t remainingCount = content.size(); + // TODO: convert to Tokenizer parse + // erring on the safe side for now. Probably rawContent would be ok too + // note that operations below do *X='\0' ... altering the 'const' buffer content. + char const *currentPos = content.c_str(); + SBuf::size_type remainingCount = content.length(); char const *tag = NULL; while ((tag = findTag(currentPos, remainingCount))) { Index: squid-3.3.13/src/esi/CustomParser.h =================================================================== --- squid-3.3.13.orig/src/esi/CustomParser.h +++ squid-3.3.13/src/esi/CustomParser.h @@ -35,7 +35,7 @@ class Trie; /* inherits from */ #include "esi/Parser.h" -/* for String variables */ +#include "SBuf.h" #include "SquidString.h" /** @@ -67,7 +67,7 @@ private: ESIParserClient *theClient; String error; /* cheap n dirty - buffer it all */ - String content; + SBuf content; /* TODO: make a class of this type code */ ESITAG_t lastTag; }; Index: squid-3.3.13/src/SBuf.cc =================================================================== --- /dev/null +++ squid-3.3.13/src/SBuf.cc @@ -0,0 +1,966 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "base/CharacterSet.h" +#include "RefCount.h" +#include "Debug.h" +#include "OutOfBoundsException.h" +#include "SBuf.h" +#include "SBufDetailedStats.h" +#include "SBufExceptions.h" +#include "util.h" + +#include <cstring> +#include <iostream> +#include <sstream> + +#ifdef VA_COPY +#undef VA_COPY +#endif +#if defined HAVE_VA_COPY +#define VA_COPY va_copy +#elif defined HAVE___VA_COPY +#define VA_COPY __va_copy +#endif + +InstanceIdDefinitions(SBuf, "SBuf"); + +SBufStats SBuf::stats; +const SBuf::size_type SBuf::npos; +const SBuf::size_type SBuf::maxSize; + +SBufStats::SBufStats() + : alloc(0), allocCopy(0), allocFromString(0), allocFromCString(0), + assignFast(0), clear(0), append(0), toStream(0), setChar(0), + getChar(0), compareSlow(0), compareFast(0), copyOut(0), + rawAccess(0), nulTerminate(0), chop(0), trim(0), find(0), scanf(0), + caseChange(0), cowFast(0), cowSlow(0), live(0) +{} + +SBufStats& +SBufStats::operator +=(const SBufStats& ss) +{ + alloc += ss.alloc; + allocCopy += ss.allocCopy; + allocFromString += ss.allocFromString; + allocFromCString += ss.allocFromCString; + assignFast += ss.assignFast; + clear += ss.clear; + append += ss.append; + toStream += ss.toStream; + setChar += ss.setChar; + getChar += ss.getChar; + compareSlow += ss.compareSlow; + compareFast += ss.compareFast; + copyOut += ss.copyOut; + rawAccess += ss.rawAccess; + nulTerminate += ss.nulTerminate; + chop += ss.chop; + trim += ss.trim; + find += ss.find; + scanf += ss.scanf; + caseChange += ss.caseChange; + cowFast += ss.cowFast; + cowSlow += ss.cowSlow; + live += ss.live; + + return *this; +} + +SBuf::SBuf() + : store_(GetStorePrototype()), off_(0), len_(0) +{ + debugs(24, 8, id << " created"); + ++stats.alloc; + ++stats.live; +} + +SBuf::SBuf(const SBuf &S) + : store_(S.store_), off_(S.off_), len_(S.len_) +{ + debugs(24, 8, id << " created from id " << S.id); + ++stats.alloc; + ++stats.allocCopy; + ++stats.live; +} + +SBuf::SBuf(const String &S) + : store_(GetStorePrototype()), off_(0), len_(0) +{ + debugs(24, 8, id << " created from string"); + assign(S.rawBuf(), S.size()); + ++stats.alloc; + ++stats.allocFromString; + ++stats.live; +} + +SBuf::SBuf(const std::string &s) + : store_(GetStorePrototype()), off_(0), len_(0) +{ + debugs(24, 8, id << " created from std::string"); + lowAppend(s.data(),s.length()); + ++stats.alloc; + ++stats.allocFromString; + ++stats.live; +} + +SBuf::SBuf(const char *S, size_type n) + : store_(GetStorePrototype()), off_(0), len_(0) +{ + append(S,n); + ++stats.alloc; + ++stats.allocFromCString; + ++stats.live; +} + +SBuf::~SBuf() +{ + debugs(24, 8, id << " destructed"); + --stats.live; + recordSBufSizeAtDestruct(len_); +} + +MemBlob::Pointer +SBuf::GetStorePrototype() +{ + static MemBlob::Pointer InitialStore = new MemBlob(0); + return InitialStore; +} + +SBuf& +SBuf::assign(const SBuf &S) +{ + debugs(24, 7, "assigning " << id << " from " << S.id); + if (&S == this) //assignment to self. Noop. + return *this; + ++stats.assignFast; + store_ = S.store_; + off_ = S.off_; + len_ = S.len_; + return *this; +} + +SBuf& +SBuf::assign(const char *S, size_type n) +{ + const Locker blobKeeper(this, S); + debugs(24, 6, id << " from c-string, n=" << n << ")"); + clear(); + return append(S, n); //bounds checked in append() +} + +void +SBuf::reserveCapacity(size_type minCapacity) +{ + Must(minCapacity <= maxSize); + cow(minCapacity); +} + +SBuf::size_type +SBuf::reserve(const SBufReservationRequirements &req) +{ + debugs(24, 8, id << " was: " << off_ << '+' << len_ << '+' << spaceSize() << + '=' << store_->capacity); + + const bool mustRealloc = !req.allowShared && store_->LockCount() > 1; + + if (!mustRealloc && spaceSize() >= req.minSpace) + return spaceSize(); // the caller is content with what we have + + /* only reallocation can make the caller happy */ + + if (!mustRealloc && len_ >= req.maxCapacity) + return spaceSize(); // but we cannot reallocate + + const size_type newSpace = std::min(req.idealSpace, maxSize - len_); + reserveCapacity(std::min(len_ + newSpace, req.maxCapacity)); + debugs(24, 7, id << " now: " << off_ << '+' << len_ << '+' << spaceSize() << + '=' << store_->capacity); + return spaceSize(); // reallocated and probably reserved enough space +} + +char * +SBuf::rawSpace(size_type minSpace) +{ + Must(length() <= maxSize - minSpace); + debugs(24, 7, "reserving " << minSpace << " for " << id); + ++stats.rawAccess; + // we're not concerned about RefCounts here, + // the store knows the last-used portion. If + // it's available, we're effectively claiming ownership + // of it. If it's not, we need to go away (realloc) + if (store_->canAppend(off_+len_, minSpace)) { + debugs(24, 7, id << " not growing"); + return bufEnd(); + } + // TODO: we may try to memmove before realloc'ing in order to avoid + // one allocation operation, if we're the sole owners of a MemBlob. + // Maybe some heuristic on off_ and length()? + cow(minSpace+length()); + return bufEnd(); +} + +void +SBuf::clear() +{ +#if 0 + //enabling this code path, the store will be freed and reinitialized + store_ = GetStorePrototype(); //uncomment to actually free storage upon clear() +#else + //enabling this code path, we try to release the store without deallocating it. + // will be lazily reallocated if needed. + if (store_->LockCount() == 1) + store_->clear(); +#endif + len_ = 0; + off_ = 0; + ++stats.clear; +} + +SBuf& +SBuf::append(const SBuf &S) +{ + const Locker blobKeeper(this, S.buf()); + return lowAppend(S.buf(), S.length()); +} + +SBuf & +SBuf::append(const char * S, size_type Ssize) +{ + const Locker blobKeeper(this, S); + if (S == NULL) + return *this; + if (Ssize == SBuf::npos) + Ssize = strlen(S); + debugs(24, 7, "from c-string to id " << id); + // coverity[access_dbuff_in_call] + return lowAppend(S, Ssize); +} + +SBuf & +SBuf::append(const char c) +{ + return lowAppend(&c, 1); +} + +SBuf& +SBuf::Printf(const char *fmt, ...) +{ + // with printf() the fmt or an arg might be a dangerous char* + // NP: cant rely on vappendf() Locker because of clear() + const Locker blobKeeper(this, buf()); + + va_list args; + va_start(args, fmt); + clear(); + vappendf(fmt, args); + va_end(args); + return *this; +} + +SBuf& +SBuf::appendf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vappendf(fmt, args); + va_end(args); + return *this; +} + +SBuf& +SBuf::vappendf(const char *fmt, va_list vargs) +{ + // with (v)appendf() the fmt or an arg might be a dangerous char* + const Locker blobKeeper(this, buf()); + + Must(fmt != NULL); + int sz = 0; + //reserve twice the format-string size, it's a likely heuristic + size_type requiredSpaceEstimate = strlen(fmt)*2; + + char *space = rawSpace(requiredSpaceEstimate); +#ifdef VA_COPY + va_list ap; + VA_COPY(ap, vargs); + sz = vsnprintf(space, spaceSize(), fmt, ap); + va_end(ap); +#else + sz = vsnprintf(space, spaceSize(), fmt, vargs); +#endif + + /* check for possible overflow */ + /* snprintf on Linux returns -1 on output errors, or the size + * that would have been written if enough space had been available */ + /* vsnprintf is standard in C99 */ + + if (sz >= static_cast<int>(spaceSize())) { + // not enough space on the first go, we now know how much we need + requiredSpaceEstimate = sz*2; // TODO: tune heuristics + space = rawSpace(requiredSpaceEstimate); + sz = vsnprintf(space, spaceSize(), fmt, vargs); + if (sz < 0) // output error in vsnprintf + throw TextException("output error in second-go vsnprintf",__FILE__, + __LINE__); + } + + if (sz < 0) // output error in either vsnprintf + throw TextException("output error in vsnprintf",__FILE__, __LINE__); + + // data was appended, update internal state + len_ += sz; + + /* C99 specifies that the final '\0' is not counted in vsnprintf's + * return value. Older compilers/libraries might instead count it */ + /* check whether '\0' was appended and counted */ + static bool snPrintfTerminatorChecked = false; + static bool snPrintfTerminatorCounted = false; + if (!snPrintfTerminatorChecked) { + char testbuf[16]; + snPrintfTerminatorCounted = snprintf(testbuf, sizeof(testbuf), + "%s", "1") == 2; + snPrintfTerminatorChecked = true; + } + if (snPrintfTerminatorCounted) { + --sz; + --len_; + } + + store_->size += sz; + ++stats.append; + + return *this; +} + +std::ostream& +SBuf::print(std::ostream &os) const +{ + os.write(buf(), length()); + ++stats.toStream; + return os; +} + +std::ostream& +SBuf::dump(std::ostream &os) const +{ + os << id + << ": "; + store_->dump(os); + os << ", offset:" << off_ + << ", len:" << len_ + << ") : '"; + print(os); + os << '\'' << std::endl; + return os; +# if 0 + // alternate implementation, based on Raw() API. + os << Raw("SBuf", buf(), length()) << + ". id: " << id << + ", offset:" << off_ << + ", len:" << len_ << + ", store: "; + store_->dump(os); + os << std::endl; + return os; +#endif +} + +void +SBuf::setAt(size_type pos, char toset) +{ + checkAccessBounds(pos); + cow(); + store_->mem[off_+pos] = toset; + ++stats.setChar; +} + +static int +memcasecmp(const char *b1, const char *b2, SBuf::size_type len) +{ + int rv=0; + while (len > 0) { + rv = tolower(*b1)-tolower(*b2); + if (rv != 0) + return rv; + ++b1; + ++b2; + --len; + } + return rv; +} + +int +SBuf::compare(const SBuf &S, const SBufCaseSensitive isCaseSensitive, const size_type n) const +{ + if (n != npos) + return substr(0,n).compare(S.substr(0,n),isCaseSensitive); + + const size_type byteCompareLen = min(S.length(), length()); + ++stats.compareSlow; + int rv = 0; + if (isCaseSensitive == caseSensitive) { + rv = memcmp(buf(), S.buf(), byteCompareLen); + } else { + rv = memcasecmp(buf(), S.buf(), byteCompareLen); + } + if (rv != 0) + return rv; + if (length() == S.length()) + return 0; + if (length() > S.length()) + return 1; + return -1; +} + +int +SBuf::compare(const char *s, const SBufCaseSensitive isCaseSensitive, const size_type n) const +{ + // 0-length comparison is always true regardless of buffer states + if (!n) { + ++stats.compareFast; + return 0; + } + + // N-length compare MUST provide a non-NULL C-string pointer + assert(s); + + // when this is a 0-length string, no need for any complexity. + if (!length()) { + ++stats.compareFast; + return '\0' - *s; + } + + // brute-force scan in order to avoid ever needing strlen() on a c-string. + ++stats.compareSlow; + const char *left = buf(); + const char *right = s; + int rv = 0; + // what area to scan. + // n may be npos, but we treat that as a huge positive value + size_type byteCount = min(length(), n); + + // loop until we find a difference, a '\0', or reach the end of area to scan + if (isCaseSensitive == caseSensitive) { + while ((rv = *left - *right++) == 0) { + if (*left++ == '\0' || --byteCount == 0) + break; + } + } else { + while ((rv = tolower(*left) - tolower(*right++)) == 0) { + if (*left++ == '\0' || --byteCount == 0) + break; + } + } + + // If we stopped scanning because we reached the end + // of buf() before we reached the end of s, + // pretend we have a 0-terminator there to compare. + // NP: the loop already incremented "right" ready for this comparison + if (!byteCount && length() < n) + return '\0' - *right; + + // If we found a difference within the scan area, + // or we found a '\0', + // or all n characters were identical (and none was \0). + return rv; +} + +bool +SBuf::startsWith(const SBuf &S, const SBufCaseSensitive isCaseSensitive) const +{ + debugs(24, 8, id << " startsWith " << S.id << ", caseSensitive: " << + isCaseSensitive); + if (length() < S.length()) { + debugs(24, 8, "no, too short"); + ++stats.compareFast; + return false; + } + return (compare(S, isCaseSensitive, S.length()) == 0); +} + +bool +SBuf::operator ==(const SBuf & S) const +{ + debugs(24, 8, id << " == " << S.id); + if (length() != S.length()) { + debugs(24, 8, "no, different lengths"); + ++stats.compareFast; + return false; //shortcut: must be equal length + } + if (store_ == S.store_ && off_ == S.off_) { + debugs(24, 8, "yes, same length and backing store"); + ++stats.compareFast; + return true; //shortcut: same store, offset and length + } + ++stats.compareSlow; + const bool rv = (0 == memcmp(buf(), S.buf(), length())); + debugs(24, 8, "returning " << rv); + return rv; +} + +bool +SBuf::operator !=(const SBuf & S) const +{ + return !(*this == S); +} + +SBuf +SBuf::consume(size_type n) +{ + if (n == npos) + n = length(); + else + n = min(n, length()); + debugs(24, 8, id << " consume " << n); + SBuf rv(substr(0, n)); + chop(n); + return rv; +} + +const +SBufStats& SBuf::GetStats() +{ + return stats; +} + +SBuf::size_type +SBuf::copy(char *dest, size_type n) const +{ + size_type toexport = min(n,length()); + memcpy(dest, buf(), toexport); + ++stats.copyOut; + return toexport; +} + +const char* +SBuf::rawContent() const +{ + ++stats.rawAccess; + return buf(); +} + +void +SBuf::forceSize(size_type newSize) +{ + debugs(24, 8, id << " force " << (newSize > length() ? "grow" : "shrink") << " to length=" << newSize); + + Must(store_->LockCount() == 1); + if (newSize > min(maxSize,store_->capacity-off_)) + throw SBufTooBigException(__FILE__,__LINE__); + len_ = newSize; + store_->size = newSize; +} + +const char* +SBuf::c_str() +{ + ++stats.rawAccess; + /* null-terminate the current buffer, by hand-appending a \0 at its tail but + * without increasing its length. May COW, the side-effect is to guarantee that + * the MemBlob's tail is availabe for us to use */ + *rawSpace(1) = '\0'; + ++store_->size; + ++stats.setChar; + ++stats.nulTerminate; + return buf(); +} + +SBuf& +SBuf::chop(size_type pos, size_type n) +{ + if (pos == npos || pos > length()) + pos = length(); + + if (n == npos || (pos+n) > length()) + n = length() - pos; + + // if there will be nothing left, reset the buffer while we can + if (pos == length() || n == 0) { + clear(); + return *this; + } + + ++stats.chop; + off_ += pos; + len_ = n; + return *this; +} + +SBuf& +SBuf::trim(const SBuf &toRemove, bool atBeginning, bool atEnd) +{ + ++stats.trim; + if (atEnd) { + const char *p = bufEnd()-1; + while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) { + //current end-of-buf is in the searched set + --len_; + --p; + } + } + if (atBeginning) { + const char *p = buf(); + while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) { + --len_; + ++off_; + ++p; + } + } + if (isEmpty()) + clear(); + return *this; +} + +SBuf +SBuf::substr(size_type pos, size_type n) const +{ + SBuf rv(*this); + rv.chop(pos, n); //stats handled by callee + return rv; +} + +SBuf::size_type +SBuf::find(char c, size_type startPos) const +{ + ++stats.find; + + if (startPos == npos) // can't find anything if we look past end of SBuf + return npos; + + // std::string returns npos if needle is outside hay + if (startPos > length()) + return npos; + + const void *i = memchr(buf()+startPos, (int)c, (size_type)length()-startPos); + + if (i == NULL) + return npos; + + return (static_cast<const char *>(i)-buf()); +} + +SBuf::size_type +SBuf::find(const SBuf &needle, size_type startPos) const +{ + if (startPos == npos) { // can't find anything if we look past end of SBuf + ++stats.find; + return npos; + } + + // std::string allows needle to overhang hay but not start outside + if (startPos > length()) { + ++stats.find; + return npos; + } + + // for empty needle std::string returns startPos + if (needle.length() == 0) { + ++stats.find; + return startPos; + } + + // if needle length is 1 use the char search + if (needle.length() == 1) + return find(needle[0], startPos); + + ++stats.find; + + char *begin = buf()+startPos; + char *lastPossible = buf()+length()-needle.length()+1; + char needleBegin = needle[0]; + + debugs(24, 7, "looking for " << needle << "starting at " << startPos << + " in id " << id); + while (begin < lastPossible) { + char *tmp; + debugs(24, 8, " begin=" << (void *) begin << + ", lastPossible=" << (void*) lastPossible ); + tmp = static_cast<char *>(memchr(begin, needleBegin, lastPossible-begin)); + if (tmp == NULL) { + debugs(24, 8 , "First byte not found"); + return npos; + } + // lastPossible guarrantees no out-of-bounds with memcmp() + if (0 == memcmp(needle.buf(), tmp, needle.length())) { + debugs(24, 8, "Found at " << (tmp-buf())); + return (tmp-buf()); + } + begin = tmp+1; + } + debugs(24, 8, "not found"); + return npos; +} + +SBuf::size_type +SBuf::rfind(const SBuf &needle, SBuf::size_type endPos) const +{ + // when the needle is 1 char, use the 1-char rfind() + if (needle.length() == 1) + return rfind(needle[0], endPos); + + ++stats.find; + + // needle is bigger than haystack, impossible find + if (length() < needle.length()) + return npos; + + // if startPos is npos, std::string scans from the end of hay + if (endPos == npos || endPos > length()-needle.length()) + endPos = length()-needle.length(); + + // an empty needle found at the end of the haystack + if (needle.length() == 0) + return endPos; + + char *bufBegin = buf(); + char *cur = bufBegin+endPos; + const char needleBegin = needle[0]; + while (cur >= bufBegin) { + if (*cur == needleBegin) { + if (0 == memcmp(needle.buf(), cur, needle.length())) { + // found + return (cur-buf()); + } + } + --cur; + } + return npos; +} + +SBuf::size_type +SBuf::rfind(char c, SBuf::size_type endPos) const +{ + ++stats.find; + + // shortcut: haystack is empty, can't find anything by definition + if (length() == 0) + return npos; + + // on npos input std::string compares last octet of hay + if (endPos == npos || endPos >= length()) { + endPos = length(); + } else { + // NP: off-by-one weirdness: + // endPos is an offset ... 0-based + // length() is a count ... 1-based + // memrhr() requires a 1-based count of space to scan. + ++endPos; + } + + if (length() == 0) + return endPos; + + const void *i = memrchr(buf(), (int)c, (size_type)endPos); + + if (i == NULL) + return npos; + + return (static_cast<const char *>(i)-buf()); +} + +SBuf::size_type +SBuf::findFirstOf(const CharacterSet &set, size_type startPos) const +{ + ++stats.find; + + if (startPos == npos) + return npos; + + if (startPos >= length()) + return npos; + + debugs(24, 7, "first of characterset " << set.name << " in id " << id); + char *cur = buf()+startPos; + const char *end = bufEnd(); + while (cur < end) { + if (set[*cur]) + return cur-buf(); + ++cur; + } + debugs(24, 7, "not found"); + return npos; +} + +SBuf::size_type +SBuf::findFirstNotOf(const CharacterSet &set, size_type startPos) const +{ + ++stats.find; + + if (startPos == npos) + return npos; + + if (startPos >= length()) + return npos; + + debugs(24, 7, "first not of characterset " << set.name << " in id " << id); + char *cur = buf()+startPos; + const char *end = bufEnd(); + while (cur < end) { + if (!set[*cur]) + return cur-buf(); + ++cur; + } + debugs(24, 7, "not found"); + return npos; +} + +/* + * TODO: borrow a sscanf implementation from Linux or similar? + * we'd really need a vsnscanf(3)... ? As an alternative, a + * light-regexp-like domain-specific syntax might be an idea. + */ +int +SBuf::scanf(const char *format, ...) +{ + // with the format or an arg might be a dangerous char* + // that gets invalidated by c_str() + const Locker blobKeeper(this, buf()); + + va_list arg; + int rv; + ++stats.scanf; + va_start(arg, format); + rv = vsscanf(c_str(), format, arg); + va_end(arg); + return rv; +} + +std::ostream & +SBufStats::dump(std::ostream& os) const +{ + MemBlobStats ststats = MemBlob::GetStats(); + os << + "SBuf stats:\nnumber of allocations: " << alloc << + "\ncopy-allocations: " << allocCopy << + "\ncopy-allocations from SquidString: " << allocFromString << + "\ncopy-allocations from C String: " << allocFromCString << + "\nlive references: " << live << + "\nno-copy assignments: " << assignFast << + "\nclearing operations: " << clear << + "\nappend operations: " << append << + "\ndump-to-ostream: " << toStream << + "\nset-char: " << setChar << + "\nget-char: " << getChar << + "\ncomparisons with data-scan: " << compareSlow << + "\ncomparisons not requiring data-scan: " << compareFast << + "\ncopy-out ops: " << copyOut << + "\nraw access to memory: " << rawAccess << + "\nNULL terminate C string: " << nulTerminate << + "\nchop operations: " << chop << + "\ntrim operations: " << trim << + "\nfind: " << find << + "\nscanf: " << scanf << + "\ncase-change ops: " << caseChange << + "\nCOW not actually requiring a copy: " << cowFast << + "\nCOW: " << cowSlow << + "\naverage store share factor: " << + (ststats.live != 0 ? static_cast<float>(live)/ststats.live : 0) << + std::endl; + return os; +} + +void +SBuf::toLower() +{ + debugs(24, 8, "\"" << *this << "\""); + for (size_type j = 0; j < length(); ++j) { + const int c = (*this)[j]; + if (isupper(c)) + setAt(j, tolower(c)); + } + debugs(24, 8, "result: \"" << *this << "\""); + ++stats.caseChange; +} + +void +SBuf::toUpper() +{ + debugs(24, 8, "\"" << *this << "\""); + for (size_type j = 0; j < length(); ++j) { + const int c = (*this)[j]; + if (islower(c)) + setAt(j, toupper(c)); + } + debugs(24, 8, "result: \"" << *this << "\""); + ++stats.caseChange; +} + +/** + * checks whether the requested 'pos' is within the bounds of the SBuf + * \throw OutOfBoundsException if access is out of bounds + */ +void +SBuf::checkAccessBounds(size_type pos) const +{ + if (pos >= length()) + throw OutOfBoundsException(*this, pos, __FILE__, __LINE__); +} + +String +SBuf::toString() const +{ + String rv; + rv.limitInit(buf(), length()); + ++stats.copyOut; + return rv; +} + +/** re-allocate the backing store of the SBuf. + * + * If there are contents in the SBuf, they will be copied over. + * NO verifications are made on the size parameters, it's up to the caller to + * make sure that the new size is big enough to hold the copied contents. + * The re-allocated storage MAY be bigger than the requested size due to size-chunking + * algorithms in MemBlock, it is guarranteed NOT to be smaller. + */ +void +SBuf::reAlloc(size_type newsize) +{ + debugs(24, 8, id << " new size: " << newsize); + if (newsize > maxSize) + throw SBufTooBigException(__FILE__, __LINE__); + MemBlob::Pointer newbuf = new MemBlob(newsize); + if (length() > 0) + newbuf->append(buf(), length()); + store_ = newbuf; + off_ = 0; + ++stats.cowSlow; + debugs(24, 7, id << " new store capacity: " << store_->capacity); +} + +SBuf& +SBuf::lowAppend(const char * memArea, size_type areaSize) +{ + rawSpace(areaSize); //called method also checks n <= maxSize() + store_->append(memArea, areaSize); + len_ += areaSize; + ++stats.append; + return *this; +} + +/** + * copy-on-write: make sure that we are the only holder of the backing store. + * If not, reallocate. If a new size is specified, and it is greater than the + * current length, the backing store will be extended as needed + */ +void +SBuf::cow(SBuf::size_type newsize) +{ + debugs(24, 8, id << " new size:" << newsize); + if (newsize == npos || newsize < length()) + newsize = length(); + + if (store_->LockCount() == 1 && newsize == length()) { + debugs(24, 8, id << " no cow needed"); + ++stats.cowFast; + return; + } + reAlloc(newsize); +} + Index: squid-3.3.13/src/SBuf.h =================================================================== --- /dev/null +++ squid-3.3.13/src/SBuf.h @@ -0,0 +1,669 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_SBUF_H +#define SQUID_SBUF_H + +#include "base/InstanceId.h" +#include "MemBlob.h" +#include "SBufExceptions.h" +#include "SquidString.h" + +#include <climits> +#include <cstdarg> +#include <iosfwd> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* SBuf placeholder for printf */ +#ifndef SQUIDSBUFPH +#define SQUIDSBUFPH "%.*s" +#define SQUIDSBUFPRINT(s) (s).plength(),(s).rawContent() +#endif /* SQUIDSBUFPH */ + +// TODO: move within SBuf and rename +typedef enum { + caseSensitive, + caseInsensitive +} SBufCaseSensitive; + +/** + * Container for various SBuf class-wide statistics. + * + * The stats are not completely accurate; they're mostly meant to + * understand whether Squid is leaking resources + * and whether SBuf is paying off the expected gains. + */ +class SBufStats +{ +public: + uint64_t alloc; ///<number of calls to SBuf constructors + uint64_t allocCopy; ///<number of calls to SBuf copy-constructor + uint64_t allocFromString; ///<number of copy-allocations from Strings + uint64_t allocFromCString; ///<number of copy-allocations from c-strings + uint64_t assignFast; ///<number of no-copy assignment operations + uint64_t clear; ///<number of clear operations + uint64_t append; ///<number of append operations + uint64_t toStream; ///<number of write operations to ostreams + uint64_t setChar; ///<number of calls to setAt + uint64_t getChar; ///<number of calls to at() and operator[] + uint64_t compareSlow; ///<number of comparison operations requiring data scan + uint64_t compareFast; ///<number of comparison operations not requiring data scan + uint64_t copyOut; ///<number of data-copies to other forms of buffers + uint64_t rawAccess; ///<number of accesses to raw contents + uint64_t nulTerminate; ///<number of c_str() terminations + uint64_t chop; ///<number of chop operations + uint64_t trim; ///<number of trim operations + uint64_t find; ///<number of find operations + uint64_t scanf; ///<number of scanf operations + uint64_t caseChange; ///<number of toUpper and toLower operations + uint64_t cowFast; ///<number of cow operations not actually requiring a copy + uint64_t cowSlow; ///<number of cow operations requiring a copy + uint64_t live; ///<number of currently-allocated SBuf + + ///Dump statistics to an ostream. + std::ostream& dump(std::ostream &os) const; + SBufStats(); + + SBufStats& operator +=(const SBufStats&); +}; + +class CharacterSet; +class SBufReservationRequirements; + +/** + * A String or Buffer. + * Features: refcounted backing store, cheap copy and sub-stringing + * operations, copy-on-write to isolate change operations to each instance. + * Where possible, we're trying to mimic std::string's interface. + */ +class SBuf +{ +public: + typedef MemBlob::size_type size_type; + static const size_type npos = 0xffffffff; // max(uint32_t) + + /// Maximum size of a SBuf. By design it MUST be < MAX(size_type)/2. Currently 256Mb. + static const size_type maxSize = 0xfffffff; + + /// create an empty (zero-size) SBuf + SBuf(); + SBuf(const SBuf &S); + + /** Constructor: import c-style string + * + * Create a new SBuf containing a COPY of the contents of the + * c-string + * \param S the c string to be copied + * \param n how many bytes to import into the SBuf. If it is npos + * or unspecified, imports to end-of-cstring + * \note it is the caller's responsibility not to go out of bounds + * \note bounds is 0 <= pos < length(); caller must pay attention to signedness + */ + explicit SBuf(const char *S, size_type n = npos); + + /** Constructor: import SquidString, copying contents. + * + * This method will be removed once SquidString has gone. + */ + explicit SBuf(const String &S); + + /// Constructor: import std::string. Contents are copied. + explicit SBuf(const std::string &s); + + ~SBuf(); + + /** Explicit assignment. + * + * Current SBuf will share backing store with the assigned one. + */ + SBuf& assign(const SBuf &S); + + /** Assignment operator. + * + * Current SBuf will share backing store with the assigned one. + */ + SBuf& operator =(const SBuf & S) {return assign(S);} + + /** Import a c-string into a SBuf, copying the data. + * + * It is the caller's duty to free the imported string, if needed. + * \param S the c string to be copied + * \param n how many bytes to import into the SBuf. If it is npos + * or unspecified, imports to end-of-cstring + * \note it is the caller's responsibility not to go out of bounds + * \note to assign a std::string use the pattern: + * assign(stdstr.data(), stdstd.length()) + */ + SBuf& assign(const char *S, size_type n = npos); + + /** Assignment operator. Copy a NULL-terminated c-style string into a SBuf. + * + * Copy a c-style string into a SBuf. Shortcut for SBuf.assign(S) + * It is the caller's duty to free the imported string, if needed. + * \note not \0-clean + */ + SBuf& operator =(const char *S) {return assign(S);} + + /** reset the SBuf as if it was just created. + * + * Resets the SBuf to empty, memory is freed lazily. + */ + void clear(); + + /** Append operation + * + * Append the supplied SBuf to the current one; extend storage as needed. + */ + SBuf& append(const SBuf & S); + + /// Append a single character. The character may be NUL (\0). + SBuf& append(const char c); + + /** Append operation for C-style strings. + * + * Append the supplied c-string to the SBuf; extend storage + * as needed. + * + * \param S the c string to be copied. Can be NULL. + * \param Ssize how many bytes to import into the SBuf. If it is npos + * or unspecified, imports to end-of-cstring. If S is NULL, + * Ssize is ignored. + * \note to append a std::string use the pattern + * cstr_append(stdstr.data(), stdstd.length()) + */ + SBuf& append(const char * S, size_type Ssize = npos); + + /** Assignment operation with printf(3)-style definition + * \note arguments may be evaluated more than once, be careful + * of side-effects + */ + SBuf& Printf(const char *fmt, ...); + + /** Append operation with printf-style arguments + * \note arguments may be evaluated more than once, be careful + * of side-effects + */ + SBuf& appendf(const char *fmt, ...); + + /** Append operation, with vsprintf(3)-style arguments. + * \note arguments may be evaluated more than once, be careful + * of side-effects + */ + SBuf& vappendf(const char *fmt, va_list vargs); + + /// print the SBuf contents to the supplied ostream + std::ostream& print(std::ostream &os) const; + + /** print SBuf contents and debug information about the SBuf to an ostream + * + * Debug function, dumps to a stream informations on the current SBuf, + * including low-level details and statistics. + */ + std::ostream& dump(std::ostream &os) const; + + /** random-access read to any char within the SBuf + * + * does not check access bounds. If you need that, use at() + */ + char operator [](size_type pos) const {++stats.getChar; return store_->mem[off_+pos];} + + /** random-access read to any char within the SBuf. + * + * \throw OutOfBoundsException when access is out of bounds + * \note bounds is 0 <= pos < length(); caller must pay attention to signedness + */ + char at(size_type pos) const {checkAccessBounds(pos); return operator[](pos);} + + /** direct-access set a byte at a specified operation. + * + * \param pos the position to be overwritten + * \param toset the value to be written + * \throw OutOfBoundsException when pos is of bounds + * \note bounds is 0 <= pos < length(); caller must pay attention to signedness + * \note performs a copy-on-write if needed. + */ + void setAt(size_type pos, char toset); + + /** compare to other SBuf, str(case)cmp-style + * + * \param isCaseSensitive one of caseSensitive or caseInsensitive + * \param n compare up to this many bytes. if npos (default), compare whole SBufs + * \retval >0 argument of the call is greater than called SBuf + * \retval <0 argument of the call is smaller than called SBuf + * \retval 0 argument of the call has the same contents of called SBuf + */ + int compare(const SBuf &S, const SBufCaseSensitive isCaseSensitive, const size_type n = npos) const; + + /// shorthand version for compare() + inline int cmp(const SBuf &S, const size_type n = npos) const { + return compare(S,caseSensitive,n); + } + + /// shorthand version for case-insensitive compare() + inline int caseCmp(const SBuf &S, const size_type n = npos) const { + return compare(S,caseInsensitive,n); + } + + /// Comparison with a C-string. + int compare(const char *s, const SBufCaseSensitive isCaseSensitive, const size_type n = npos) const; + + /// Shorthand version for C-string compare(). + inline int cmp(const char *S, const size_type n = npos) const { + return compare(S,caseSensitive,n); + } + + /// Shorthand version for case-insensitive C-string compare(). + inline int caseCmp(const char *S, const size_type n = npos) const { + return compare(S,caseInsensitive,n); + } + + /** check whether the entire supplied argument is a prefix of the SBuf. + * \param S the prefix to match against + * \param isCaseSensitive one of caseSensitive or caseInsensitive + * \retval true argument is a prefix of the SBuf + */ + bool startsWith(const SBuf &S, const SBufCaseSensitive isCaseSensitive = caseSensitive) const; + + bool operator ==(const SBuf & S) const; + bool operator !=(const SBuf & S) const; + bool operator <(const SBuf &S) const {return (cmp(S) < 0);} + bool operator >(const SBuf &S) const {return (cmp(S) > 0);} + bool operator <=(const SBuf &S) const {return (cmp(S) <= 0);} + bool operator >=(const SBuf &S) const {return (cmp(S) >= 0);} + + /** Consume bytes at the head of the SBuf + * + * Consume N chars at SBuf head, or to SBuf's end, + * whichever is shorter. If more bytes are consumed than available, + * the SBuf is emptied + * \param n how many bytes to remove; could be zero. + * npos (or no argument) means 'to the end of SBuf' + * \return a new SBuf containing the consumed bytes. + */ + SBuf consume(size_type n = npos); + + /// gets global statistic informations + static const SBufStats& GetStats(); + + /** Copy SBuf contents into user-supplied C buffer. + * + * Export a copy of the SBuf's contents into the user-supplied + * buffer, up to the user-supplied-length. No zero-termination is performed + * \return num the number of actually-copied chars. + */ + size_type copy(char *dest, size_type n) const; + + /** exports a pointer to the SBuf internal storage. + * \warning ACCESSING RAW STORAGE IS DANGEROUS! + * + * Returns a ead-only pointer to SBuf's content. No terminating null + * character is appended (use c_str() for that). + * The returned value points to an internal location whose contents + * are guaranteed to remain unchanged only until the next call + * to a non-constant member function of the SBuf object. Such a + * call may be implicit (e.g., when SBuf is destroyed + * upon leaving the current context). + * This is a very UNSAFE way of accessing the data. + * This call never returns NULL. + * \see c_str + * \note the memory management system guarantees that the exported region + * of memory will remain valid if the caller keeps holding + * a valid reference to the SBuf object and does not write or append to + * it. For example: + * \code + * SBuf foo("some string"); + * const char *bar = foo.rawContent(); + * doSomething(bar); //safe + * foo.append(" other string"); + * doSomething(bar); //unsafe + * \endcode + */ + const char* rawContent() const; + + /** Exports a writable pointer to the SBuf internal storage. + * \warning Use with EXTREME caution, this is a dangerous operation. + * + * Returns a pointer to the first unused byte in the SBuf's storage, + * which can be be used for appending. At least minSize bytes will + * be available for writing. + * The returned pointer must not be stored by the caller, as it will + * be invalidated by the first call to a non-const method call + * on the SBuf. + * This call guarantees to never return NULL. + * \see reserveSpace + * \note Unlike reserveSpace(), this method does not guarantee exclusive + * buffer ownership. It is instead optimized for a one writer + * (appender), many readers scenario by avoiding unnecessary + * copying and allocations. + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + char *rawSpace(size_type minSize); + + /** Obtain how much free space is available in the backing store. + * + * \note: unless the client just cow()ed, it is not guaranteed that + * the free space can be used. + */ + size_type spaceSize() const { return store_->spaceSize(); } + + /** Force a SBuf's size + * \warning use with EXTREME caution, this is a dangerous operation + * + * Adapt the SBuf internal state after external interference + * such as writing into it via rawSpace. + * \throw TextException if SBuf doesn't have exclusive ownership of store + * \throw SBufTooBigException if new size is bigger than available store space + */ + void forceSize(size_type newSize); + + /** exports a null-terminated reference to the SBuf internal storage. + * \warning ACCESSING RAW STORAGE IS DANGEROUS! DO NOT EVER USE + * THE RETURNED POINTER FOR WRITING + * + * The returned value points to an internal location whose contents + * are guaranteed to remain unchanged only until the next call + * to a non-constant member function of the SBuf object. Such a + * call may be implicit (e.g., when SBuf is destroyed + * upon leaving the current context). + * This is a very UNSAFE way of accessing the data. + * This call never returns NULL. + * \see rawContent + * \note the memory management system guarantees that the exported region + * of memory will remain valid will remain valid only if the + * caller keeps holding a valid reference to the SBuf object and + * does not write or append to it + */ + const char* c_str(); + + /// Returns the number of bytes stored in SBuf. + size_type length() const {return len_;} + + /** Get the length of the SBuf, as a signed integer + * + * Compatibility function for printf(3) which requires a signed int + * \throw SBufTooBigException if the SBuf is too big for a signed integer + */ + int plength() const { + if (length()>INT_MAX) + throw SBufTooBigException(__FILE__, __LINE__); + return static_cast<int>(length()); + } + + /** Check whether the SBuf is empty + * + * \return true if length() == 0 + */ + bool isEmpty() const {return (len_==0);} + + /** Request to guarantee the SBuf's free store space. + * + * After the reserveSpace request, the SBuf is guaranteed to have at + * least minSpace bytes of unused backing store following the currently + * used portion and single ownership of the backing store. + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + void reserveSpace(size_type minSpace) { + Must(minSpace <= maxSize); + Must(length() <= maxSize - minSpace); + reserveCapacity(length()+minSpace); + } + + /** Request to guarantee the SBuf's store capacity + * + * After this method is called, the SBuf is guaranteed to have at least + * minCapacity bytes of total buffer size, including the currently-used + * portion; it is also guaranteed that after this call this SBuf + * has unique ownership of the underlying memory store. + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + void reserveCapacity(size_type minCapacity); + + /** Accommodate caller's requirements regarding SBuf's storage if possible. + * + * \return spaceSize(), which may be zero + */ + size_type reserve(const SBufReservationRequirements &requirements); + + /** slicing method + * + * Removes SBuf prefix and suffix, leaving a sequence of 'n' + * bytes starting from position 'pos', first byte is at pos 0. + * It is an in-place-modifying version of substr. + * \param pos start sub-stringing from this byte. If it is + * npos or it is greater than the SBuf length, the SBuf is cleared and + * an empty SBuf is returned. + * \param n maximum number of bytes of the resulting SBuf. + * npos means "to end of SBuf". + * if it is 0, the SBuf is cleared and an empty SBuf is returned. + * if it overflows the end of the SBuf, it is capped to the end of SBuf + * \see substr, trim + */ + SBuf& chop(size_type pos, size_type n = npos); + + /** Remove characters in the toremove set at the beginning, end or both + * + * \param toremove characters to be removed. Stops chomping at the first + * found char not in the set + * \param atBeginning if true (default), strips at the beginning of the SBuf + * \param atEnd if true (default), strips at the end of the SBuf + */ + SBuf& trim(const SBuf &toRemove, bool atBeginning = true, bool atEnd = true); + + /** Extract a part of the current SBuf. + * + * Return a fresh a fresh copy of a portion the current SBuf, which is + * left untouched. The same parameter convetions apply as for chop. + * \see trim, chop + */ + SBuf substr(size_type pos, size_type n = npos) const; + + /** Find first occurrence of character in SBuf + * + * Returns the index in the SBuf of the first occurrence of char c. + * \return npos if the char was not found + * \param startPos if specified, ignore any occurrences before that position + * if startPos is npos or greater than length() npos is always returned + * if startPos is less than zero, it is ignored + */ + size_type find(char c, size_type startPos = 0) const; + + /** Find first occurrence of SBuf in SBuf. + * + * Returns the index in the SBuf of the first occurrence of the + * sequence contained in the str argument. + * \param startPos if specified, ignore any occurrences before that position + * if startPos is npos or greater than length() npos is always returned + * \return npos if the SBuf was not found + */ + size_type find(const SBuf & str, size_type startPos = 0) const; + + /** Find last occurrence of character in SBuf + * + * Returns the index in the SBuf of the last occurrence of char c. + * \return npos if the char was not found + * \param endPos if specified, ignore any occurrences after that position. + * if npos or greater than length(), the whole SBuf is considered + */ + size_type rfind(char c, size_type endPos = npos) const; + + /** Find last occurrence of SBuf in SBuf + * + * Returns the index in the SBuf of the last occurrence of the + * sequence contained in the str argument. + * \return npos if the sequence was not found + * \param endPos if specified, ignore any occurrences after that position + * if npos or greater than length(), the whole SBuf is considered + */ + size_type rfind(const SBuf &str, size_type endPos = npos) const; + + /** Find first occurrence of character of set in SBuf + * + * Finds the first occurrence of ANY of the characters in the supplied set in + * the SBuf. + * \return npos if no character in the set could be found + * \param startPos if specified, ignore any occurrences before that position + * if npos, then npos is always returned + * + * TODO: rename to camelCase + */ + size_type findFirstOf(const CharacterSet &set, size_type startPos = 0) const; + + /** Find first occurrence character NOT in character set + * + * \return npos if all characters in the SBuf are from set + * \param startPos if specified, ignore any occurrences before that position + * if npos, then npos is always returned + * + * TODO: rename to camelCase + */ + size_type findFirstNotOf(const CharacterSet &set, size_type startPos = 0) const; + + /** sscanf-alike + * + * sscanf re-implementation. Non-const, and not \0-clean. + * \return same as sscanf + * \see man sscanf(3) + */ + int scanf(const char *format, ...); + + /// converts all characters to lower case; \see man tolower(3) + void toLower(); + + /// converts all characters to upper case; \see man toupper(3) + void toUpper(); + + /** String export function + * converts the SBuf to a legacy String, by copy. + * \deprecated + */ + String toString() const; + + /// std::string export function + std::string toStdString() const { return std::string(buf(),length()); } + + // TODO: possibly implement erase() similar to std::string's erase + // TODO: possibly implement a replace() call +private: + + /** + * Keeps SBuf's MemBlob alive in a blob-destroying context where + * a seemingly unrelated memory pointer may belong to the same blob. + * For [an extreme] example, consider: a.append(a). + * Compared to an SBuf temporary, this class is optimized to + * preserve blobs only if needed and to reduce debugging noise. + */ + class Locker + { + public: + Locker(SBuf *parent, const char *otherBuffer) { + // lock if otherBuffer intersects the parents buffer area + const MemBlob *blob = parent->store_.getRaw(); + if (blob->mem <= otherBuffer && otherBuffer < (blob->mem + blob->capacity)) + locket = blob; + } + private: + MemBlob::Pointer locket; + }; + friend class Locker; + + MemBlob::Pointer store_; ///< memory block, possibly shared with other SBufs + size_type off_; ///< our content start offset from the beginning of shared store_ + size_type len_; ///< number of our content bytes in shared store_ + static SBufStats stats; ///< class-wide statistics + + /// SBuf object identifier; does not change when contents do, + /// including during assignment + const InstanceId<SBuf> id; + + /** obtain prototype store + * + * Just-created SBufs all share to the same MemBlob. + * This call instantiates and returns it. + */ + static MemBlob::Pointer GetStorePrototype(); + + /** + * obtains a char* to the beginning of this SBuf in memory. + * \note the obtained string is NOT null-terminated. + */ + char * buf() const {return (store_->mem+off_);} + + /** returns the pointer to the first char after this SBuf end + * + * No checks are made that the space returned is safe, checking that is + * up to the caller. + */ + char * bufEnd() const {return (store_->mem+off_+len_);} + + /** + * Try to guesstimate how big a MemBlob to allocate. + * The result is guarranteed to be to be at least the desired size. + */ + size_type estimateCapacity(size_type desired) const {return (2*desired);} + + void reAlloc(size_type newsize); + + void cow(size_type minsize = npos); + + void checkAccessBounds(size_type pos) const; + + /** Low-level append operation + * + * Takes as input a contiguous area of memory and appends its contents + * to the SBuf, taking care of memory management. Does no bounds checking + * on the supplied memory buffer, it is the duty of the caller to ensure + * that the supplied area is valid. + */ + SBuf& lowAppend(const char * memArea, size_type areaSize); +}; + +/// Named SBuf::reserve() parameters. Defaults ask for and restrict nothing. +class SBufReservationRequirements +{ +public: + typedef SBuf::size_type size_type; + + SBufReservationRequirements() : idealSpace(0), minSpace(0), maxCapacity(SBuf::maxSize), allowShared(true) {} + + /* + * Parameters are listed in the reverse order of importance: Satisfaction of + * the lower-listed requirements may violate the higher-listed requirements. + */ + size_type idealSpace; ///< if allocating anyway, provide this much space + size_type minSpace; ///< allocate if spaceSize() is smaller + size_type maxCapacity; ///< do not allocate more than this + bool allowShared; ///< whether sharing our storage with others is OK +}; + +/// ostream output operator +inline std::ostream & +operator <<(std::ostream& os, const SBuf& S) +{ + return S.print(os); +} + +/// Returns a lower-cased copy of its parameter. +inline SBuf +ToUpper(SBuf buf) +{ + buf.toUpper(); + return buf; +} + +/// Returns an upper-cased copy of its parameter. +inline SBuf +ToLower(SBuf buf) +{ + buf.toLower(); + return buf; +} + +#endif /* SQUID_SBUF_H */ + Index: squid-3.3.13/src/SBufDetailedStats.cc =================================================================== --- /dev/null +++ squid-3.3.13/src/SBufDetailedStats.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "SBufDetailedStats.h" +#include "StatHist.h" + +/* + * Implementation note: the purpose of this construct is to avoid adding + * external dependencies to the SBuf code + */ + +static StatHist * +newStatHist() { + StatHist *stats = new StatHist; + stats->logInit(100, 30.0, 128000.0); + return stats; +} + +StatHist & +collectSBufDestructTimeStats() +{ + static StatHist *stats = newStatHist(); + return *stats; +} + +StatHist & +collectMemBlobDestructTimeStats() +{ + static StatHist *stats = newStatHist(); + return *stats; +} + +void +recordSBufSizeAtDestruct(SBuf::size_type sz) +{ + collectSBufDestructTimeStats().count(static_cast<double>(sz)); +} + +void +recordMemBlobSizeAtDestruct(SBuf::size_type sz) +{ + collectMemBlobDestructTimeStats().count(static_cast<double>(sz)); +} + Index: squid-3.3.13/src/SBufDetailedStats.h =================================================================== --- /dev/null +++ squid-3.3.13/src/SBufDetailedStats.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_SBUFDETAILEDSTATS_H +#define SQUID_SBUFDETAILEDSTATS_H + +#include "SBuf.h" + +class StatHist; + +/// Record the size a SBuf had when it was destructed +void recordSBufSizeAtDestruct(SBuf::size_type sz); + +/// the SBuf size-at-destruct-time histogram +StatHist &collectSBufDestructTimeStats(); + +/// Record the size a MemBlob had when it was destructed +void recordMemBlobSizeAtDestruct(MemBlob::size_type sz); + +/// the MemBlob size-at-destruct-time histogram +StatHist &collectMemBlobDestructTimeStats(); + +#endif /* SQUID_SBUFDETAILEDSTATS_H */ + Index: squid-3.3.13/src/SBufExceptions.cc =================================================================== --- /dev/null +++ squid-3.3.13/src/SBufExceptions.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "OutOfBoundsException.h" +#include "SBuf.h" +#include "SBufExceptions.h" + +OutOfBoundsException::OutOfBoundsException(const SBuf &throwingBuf, + SBuf::size_type &pos, + const char *aFileName, int aLineNo) + : TextException(NULL, aFileName, aLineNo), + theThrowingBuf(throwingBuf), + accessedPosition(pos) +{ + SBuf explanatoryText("OutOfBoundsException"); + if (aLineNo != -1) + explanatoryText.appendf(" at line %d", aLineNo); + if (aFileName != NULL) + explanatoryText.appendf(" in file %s", aFileName); + explanatoryText.appendf(" while accessing position %d in a SBuf long %d", + pos, throwingBuf.length()); + // we can safely alias c_str as both are local to the object + // and will not further manipulated. + message = xstrndup(explanatoryText.c_str(),explanatoryText.length()); +} + +OutOfBoundsException::~OutOfBoundsException() throw() +{ } + +InvalidParamException::InvalidParamException(const char *aFilename, int aLineNo) + : TextException("Invalid parameter", aFilename, aLineNo) +{ } + +SBufTooBigException::SBufTooBigException(const char *aFilename, int aLineNo) + : TextException("Trying to create an oversize SBuf", aFilename, aLineNo) +{ } + Index: squid-3.3.13/src/SBufExceptions.h =================================================================== --- /dev/null +++ squid-3.3.13/src/SBufExceptions.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_SBUFEXCEPTIONS_H +#define SQUID_SBUFEXCEPTIONS_H + +#include "base/TextException.h" + +/** + * Exception raised when call parameters are not valid + * \todo move to an Exceptions.h? + */ +class InvalidParamException : public TextException +{ +public: + explicit InvalidParamException(const char *aFilename = 0, int aLineNo = -1); +}; + +/** + * Exception raised when an attempt to resize a SBuf would cause it to reserve too big + */ +class SBufTooBigException : public TextException +{ +public: + explicit SBufTooBigException(const char *aFilename = 0, int aLineNo = -1); +}; + +#endif /* SQUID_SBUFEXCEPTIONS_H */ + Index: squid-3.3.13/src/Makefile.am =================================================================== --- squid-3.3.13.orig/src/Makefile.am +++ squid-3.3.13/src/Makefile.am @@ -22,9 +22,14 @@ DNSSOURCE += \ DnsLookupDetails.cc SBUF_SOURCE= \ + base/CharacterSet.h \ base/InstanceId.h \ MemBlob.h \ - MemBlob.cc + MemBlob.cc \ + SBuf.h \ + SBuf.cc \ + SBufExceptions.h \ + SBufExceptions.cc LOADABLE_MODULES_SOURCES = \ LoadableModule.h \ @@ -468,6 +473,8 @@ squid_SOURCES = \ send-announce.h \ send-announce.cc \ $(SBUF_SOURCE) \ + SBufDetailedStats.h \ + SBufDetailedStats.cc \ $(SNMP_SOURCE) \ SquidMath.h \ SquidMath.cc \ Index: squid-3.3.13/src/base/CharacterSet.cc =================================================================== --- /dev/null +++ squid-3.3.13/src/base/CharacterSet.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "CharacterSet.h" + +#include <algorithm> +#include <functional> + +CharacterSet & +CharacterSet::operator +=(const CharacterSet &src) +{ + Storage::const_iterator s = src.chars_.begin(); + const Storage::const_iterator e = src.chars_.end(); + Storage::iterator d = chars_.begin(); + while (s != e) { + if (*s) + *d = 1; + ++s; + ++d; + } + return *this; +} + +CharacterSet +CharacterSet::operator +(const CharacterSet &src) const +{ + CharacterSet rv(*this); + rv += src; + return rv; +} + +CharacterSet & +CharacterSet::add(const unsigned char c) +{ + chars_[static_cast<uint8_t>(c)] = 1; + return *this; +} + +CharacterSet & +CharacterSet::addRange(unsigned char low, unsigned char high) +{ + //manual loop splitting is needed to cover case where high is 255 + // otherwise low will wrap, resulting in infinite loop + while (low < high) { + chars_[static_cast<uint8_t>(low)] = 1; + ++low; + } + chars_[static_cast<uint8_t>(high)] = 1; + return *this; +} + +CharacterSet +CharacterSet::complement(const char *label) const +{ + CharacterSet result((label ? label : "complement_of_some_other_set"), ""); + // negate each of our elements and add them to the result storage + std::transform(chars_.begin(), chars_.end(), result.chars_.begin(), + std::logical_not<Storage::value_type>()); + return result; +} + +CharacterSet::CharacterSet(const char *label, const char * const c) : + name(label == NULL ? "anonymous" : label), + chars_(Storage(256,0)) +{ + const size_t clen = strlen(c); + for (size_t i = 0; i < clen; ++i) + add(c[i]); +} + +CharacterSet::CharacterSet(const char *label, unsigned char low, unsigned char high) : + name(label == NULL ? "anonymous" : label), + chars_(Storage(256,0)) +{ + addRange(low,high); +} + +const CharacterSet +// RFC 5234 +CharacterSet::ALPHA("ALPHA", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), + CharacterSet::BIT("BIT","01"), + CharacterSet::CR("CR","\r"), +#if __cplusplus == 201103L +//CharacterSet::CTL("CTL",{{0x01,0x1f},{0x7f,0x7f}}), +#endif + CharacterSet::DIGIT("DIGIT","0123456789"), + CharacterSet::DQUOTE("DQUOTE","\""), + CharacterSet::HEXDIG("HEXDIG","0123456789aAbBcCdDeEfF"), + CharacterSet::HTAB("HTAB","\t"), + CharacterSet::LF("LF","\n"), + CharacterSet::SP("SP"," "), + CharacterSet::VCHAR("VCHAR", 0x21, 0x7e), +// RFC 7230 + CharacterSet::WSP("WSP"," \t"), +#if __cplusplus == 201103L +//CharacterSet::CTEXT("ctext",{{0x09,0x09},{0x20,0x20},{0x2a,0x5b},{0x5d,0x7e},{0x80,0xff}}), +#endif + CharacterSet::TCHAR("TCHAR","!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), + CharacterSet::SPECIAL("SPECIAL","()<>@,;:\\\"/[]?={}"), +#if __cplusplus == 201103L +//CharacterSet::QDTEXT("QDTEXT",{{0x09,0x09},{0x20,0x21},{0x23,0x5b},{0x5d,0x7e},{0x80,0xff}}), +#endif + CharacterSet::OBSTEXT("OBSTEXT",0x80,0xff), +// RFC 7232 +#if __cplusplus == 201103L +//CharacterSet::ETAGC("ETAGC",{{0x21,0x21},{0x23,0x7e},{0x80,0xff}}), +#endif +// RFC 7235 + CharacterSet::TOKEN68C("TOKEN68C","-._~+/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + ; + Index: squid-3.3.13/src/base/CharacterSet.h =================================================================== --- /dev/null +++ squid-3.3.13/src/base/CharacterSet.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef _SQUID_SRC_PARSER_CHARACTERSET_H +#define _SQUID_SRC_PARSER_CHARACTERSET_H + +#include <vector> + +/// optimized set of C chars, with quick membership test and merge support +class CharacterSet +{ +public: + typedef std::vector<uint8_t> Storage; + + /// define a character set with the given label ("anonymous" if NULL) + /// with specified initial contents + CharacterSet(const char *label, const char * const initial); + + /// define a character set with the given label ("anonymous" if NULL) + /// containing characters defined in the supplied ranges + /// \see addRange + CharacterSet(const char *label, unsigned char low, unsigned char high); + + /// whether a given character exists in the set + bool operator[](unsigned char c) const {return chars_[static_cast<uint8_t>(c)] != 0;} + + /// add a given character to the character set + CharacterSet & add(const unsigned char c); + + /// add a list of character ranges, expressed as pairs [low,high], including both ends + CharacterSet & addRange(unsigned char low, unsigned char high); + + /// add all characters from the given CharacterSet to this one + CharacterSet &operator +=(const CharacterSet &src); + + /// return a new CharacterSet containing the union of two sets + CharacterSet operator +(const CharacterSet &src) const; + + /// return a new CharacterSet containing characters not in this set + CharacterSet complement(const char *complementLabel = NULL) const; + + /// change name; handy in const declarations that use operators + CharacterSet &rename(const char *label) { name = label; return *this; } + + /// optional set label for debugging (default: "anonymous") + const char * name; + + // common character sets, RFC 5234 + // A-Za-z + static const CharacterSet ALPHA; + // 0-1 + static const CharacterSet BIT; + // carriage return + static const CharacterSet CR; + // controls +#if __cplusplus == 201103L + // ready but disabled as needs C++11 constructor + //static const CharacterSet CTL; +#endif + // 0-9 + static const CharacterSet DIGIT; + // double quote + static const CharacterSet DQUOTE; + // 0-9aAbBcCdDeEfF + static const CharacterSet HEXDIG; + // horizontal tab + static const CharacterSet HTAB; + // line feed + static const CharacterSet LF; + // white space + static const CharacterSet SP; + // visible (printable) characters + static const CharacterSet VCHAR; + // <space><tab> + static const CharacterSet WSP; + + // HTTP character sets, RFC 7230 + // ctext +#if __cplusplus == 201103L + // ready but disabled as needs C++11 constructor + //static const CharacterSet CTEXT; +#endif + // XXX: maybe field-vchar = VCHAR / obs-text + // any VCHAR except for SPECIAL + static const CharacterSet TCHAR; + // special VCHARs + static const CharacterSet SPECIAL; + // qdtext +#if __cplusplus == 201103L + // ready but disabled as needs C++11 constructor + //static const CharacterSet QDTEXT; +#endif + // obs-text + static const CharacterSet OBSTEXT; + + // HTTP character sets, RFC 7232 + // etagc +#if __cplusplus == 201103L + // ready but disabled as needs C++11 constructor + //static const CharacterSet ETAGC; +#endif + + // HTTP character sets, RFC 7235 + // token68 (internal charaters only, excludes '=' terminator) + static const CharacterSet TOKEN68C; + +private: + /** index of characters in this set + * + * \note guaranteed to be always 256 slots big, as forced in the + * constructor. This assumption is relied upon in operator[], add, + * operator+= + */ + Storage chars_; +}; + +#endif /* _SQUID_SRC_PARSER_CHARACTERSET_H */ + Index: squid-3.3.13/src/base/Makefile.am =================================================================== --- squid-3.3.13.orig/src/base/Makefile.am +++ squid-3.3.13/src/base/Makefile.am @@ -14,6 +14,8 @@ libbase_la_SOURCES = \ AsyncCallQueue.cc \ AsyncCallQueue.h \ TidyPointer.h \ + CharacterSet.h \ + CharacterSet.cc \ CbcPointer.h \ InstanceId.h \ RunnersRegistry.cc \ Index: squid-3.3.13/src/OutOfBoundsException.h =================================================================== --- /dev/null +++ squid-3.3.13/src/OutOfBoundsException.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 1996-2016 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H +#define _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H + +#include "base/TextException.h" +#include "SBuf.h" + +/** + * Exception raised when the user is going out of bounds when accessing + * a char within the SBuf + */ +class OutOfBoundsException : public TextException +{ +public: + OutOfBoundsException(const SBuf &buf, SBuf::size_type &pos, const char *aFileName = 0, int aLineNo = -1); + virtual ~OutOfBoundsException() throw(); + +protected: + SBuf theThrowingBuf; + SBuf::size_type accessedPosition; +}; + +#endif /* _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H */ + Index: squid-3.3.13/include/RefCount.h =================================================================== --- squid-3.3.13.orig/include/RefCount.h +++ squid-3.3.13/include/RefCount.h @@ -117,7 +117,7 @@ struct RefCountable_ { ++count_; } - unsigned RefCountDereference() const { + uint32_t RefCountDereference() const { #if REFCOUNT_DEBUG old_debug(0,1)("Decrementing this %p from count %u\n",this,count_); #endif @@ -125,10 +125,11 @@ struct RefCountable_ { return --count_; } - unsigned RefCountCount() const { return count_; } // for debugging only + uint32_t RefCountCount() const { return count_; } // for debugging only + uint32_t LockCount() const { return count_; } private: - mutable unsigned count_; + mutable uint32_t count_; }; #define RefCountable virtual RefCountable_
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor