1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <binder/BufferedTextOutput.h>
18#include <binder/Debug.h>
19
20#include <utils/Atomic.h>
21#include <utils/Log.h>
22#include <utils/RefBase.h>
23#include <utils/Vector.h>
24#include <cutils/threads.h>
25
26#include <private/binder/Static.h>
27
28#include <stdio.h>
29#include <stdlib.h>
30
31// ---------------------------------------------------------------------------
32
33namespace android {
34
35struct BufferedTextOutput::BufferState : public RefBase
36{
37    explicit BufferState(int32_t _seq)
38        : seq(_seq)
39        , buffer(NULL)
40        , bufferPos(0)
41        , bufferSize(0)
42        , atFront(true)
43        , indent(0)
44        , bundle(0) {
45    }
46    ~BufferState() {
47        free(buffer);
48    }
49
50    status_t append(const char* txt, size_t len) {
51        if ((len+bufferPos) > bufferSize) {
52            size_t newSize = ((len+bufferPos)*3)/2;
53            if (newSize < (len+bufferPos)) return NO_MEMORY;    // overflow
54            void* b = realloc(buffer, newSize);
55            if (!b) return NO_MEMORY;
56            buffer = (char*)b;
57            bufferSize = newSize;
58        }
59        memcpy(buffer+bufferPos, txt, len);
60        bufferPos += len;
61        return NO_ERROR;
62    }
63
64    void restart() {
65        bufferPos = 0;
66        atFront = true;
67        if (bufferSize > 256) {
68            void* b = realloc(buffer, 256);
69            if (b) {
70                buffer = (char*)b;
71                bufferSize = 256;
72            }
73        }
74    }
75
76    const int32_t seq;
77    char* buffer;
78    size_t bufferPos;
79    size_t bufferSize;
80    bool atFront;
81    int32_t indent;
82    int32_t bundle;
83};
84
85struct BufferedTextOutput::ThreadState
86{
87    Vector<sp<BufferedTextOutput::BufferState> > states;
88};
89
90static mutex_t          gMutex;
91
92static thread_store_t   tls;
93
94BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
95{
96    ThreadState*  ts = (ThreadState*) thread_store_get( &tls );
97    if (ts) return ts;
98    ts = new ThreadState;
99    thread_store_set( &tls, ts, threadDestructor );
100    return ts;
101}
102
103void BufferedTextOutput::threadDestructor(void *st)
104{
105    delete ((ThreadState*)st);
106}
107
108static volatile int32_t gSequence = 0;
109
110static volatile int32_t gFreeBufferIndex = -1;
111
112static int32_t allocBufferIndex()
113{
114    int32_t res = -1;
115
116    mutex_lock(&gMutex);
117
118    if (gFreeBufferIndex >= 0) {
119        res = gFreeBufferIndex;
120        gFreeBufferIndex = gTextBuffers[res];
121        gTextBuffers.editItemAt(res) = -1;
122
123    } else {
124        res = gTextBuffers.size();
125        gTextBuffers.add(-1);
126    }
127
128    mutex_unlock(&gMutex);
129
130    return res;
131}
132
133static void freeBufferIndex(int32_t idx)
134{
135    mutex_lock(&gMutex);
136    gTextBuffers.editItemAt(idx) = gFreeBufferIndex;
137    gFreeBufferIndex = idx;
138    mutex_unlock(&gMutex);
139}
140
141// ---------------------------------------------------------------------------
142
143BufferedTextOutput::BufferedTextOutput(uint32_t flags)
144    : mFlags(flags)
145    , mSeq(android_atomic_inc(&gSequence))
146    , mIndex(allocBufferIndex())
147{
148    mGlobalState = new BufferState(mSeq);
149    if (mGlobalState) mGlobalState->incStrong(this);
150}
151
152BufferedTextOutput::~BufferedTextOutput()
153{
154    if (mGlobalState) mGlobalState->decStrong(this);
155    freeBufferIndex(mIndex);
156}
157
158status_t BufferedTextOutput::print(const char* txt, size_t len)
159{
160    //printf("BufferedTextOutput: printing %d\n", len);
161
162    AutoMutex _l(mLock);
163    BufferState* b = getBuffer();
164
165    const char* const end = txt+len;
166
167    status_t err;
168
169    while (txt < end) {
170        // Find the next line.
171        const char* first = txt;
172        while (txt < end && *txt != '\n') txt++;
173
174        // Include this and all following empty lines.
175        while (txt < end && *txt == '\n') txt++;
176
177        // Special cases for first data on a line.
178        if (b->atFront) {
179            if (b->indent > 0) {
180                // If this is the start of a line, add the indent.
181                const char* prefix = stringForIndent(b->indent);
182                err = b->append(prefix, strlen(prefix));
183                if (err != NO_ERROR) return err;
184
185            } else if (*(txt-1) == '\n' && !b->bundle) {
186                // Fast path: if we are not indenting or bundling, and
187                // have been given one or more complete lines, just write
188                // them out without going through the buffer.
189
190                // Slurp up all of the lines.
191                const char* lastLine = txt+1;
192                while (txt < end) {
193                    if (*txt++ == '\n') lastLine = txt;
194                }
195                struct iovec vec;
196                vec.iov_base = (void*)first;
197                vec.iov_len = lastLine-first;
198                //printf("Writing %d bytes of data!\n", vec.iov_len);
199                writeLines(vec, 1);
200                txt = lastLine;
201                continue;
202            }
203        }
204
205        // Append the new text to the buffer.
206        err = b->append(first, txt-first);
207        if (err != NO_ERROR) return err;
208        b->atFront = *(txt-1) == '\n';
209
210        // If we have finished a line and are not bundling, write
211        // it out.
212        //printf("Buffer is now %d bytes\n", b->bufferPos);
213        if (b->atFront && !b->bundle) {
214            struct iovec vec;
215            vec.iov_base = b->buffer;
216            vec.iov_len = b->bufferPos;
217            //printf("Writing %d bytes of data!\n", vec.iov_len);
218            writeLines(vec, 1);
219            b->restart();
220        }
221    }
222
223    return NO_ERROR;
224}
225
226void BufferedTextOutput::moveIndent(int delta)
227{
228    AutoMutex _l(mLock);
229    BufferState* b = getBuffer();
230    b->indent += delta;
231    if (b->indent < 0) b->indent = 0;
232}
233
234void BufferedTextOutput::pushBundle()
235{
236    AutoMutex _l(mLock);
237    BufferState* b = getBuffer();
238    b->bundle++;
239}
240
241void BufferedTextOutput::popBundle()
242{
243    AutoMutex _l(mLock);
244    BufferState* b = getBuffer();
245    b->bundle--;
246    LOG_FATAL_IF(b->bundle < 0,
247        "TextOutput::popBundle() called more times than pushBundle()");
248    if (b->bundle < 0) b->bundle = 0;
249
250    if (b->bundle == 0) {
251        // Last bundle, write out data if it is complete.  If it is not
252        // complete, don't write until the last line is done... this may
253        // or may not be the write thing to do, but it's the easiest.
254        if (b->bufferPos > 0 && b->atFront) {
255            struct iovec vec;
256            vec.iov_base = b->buffer;
257            vec.iov_len = b->bufferPos;
258            writeLines(vec, 1);
259            b->restart();
260        }
261    }
262}
263
264BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
265{
266    if ((mFlags&MULTITHREADED) != 0) {
267        ThreadState* ts = getThreadState();
268        if (ts) {
269            while (ts->states.size() <= (size_t)mIndex) ts->states.add(NULL);
270            BufferState* bs = ts->states[mIndex].get();
271            if (bs != NULL && bs->seq == mSeq) return bs;
272
273            ts->states.editItemAt(mIndex) = new BufferState(mIndex);
274            bs = ts->states[mIndex].get();
275            if (bs != NULL) return bs;
276        }
277    }
278
279    return mGlobalState;
280}
281
282}; // namespace android
283