Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • kernel/generic/src/udebug/udebug_ops.c

    r3698e44 r1bfd3d3  
    3333/**
    3434 * @file
    35  * @brief       Udebug operations.
     35 * @brief Udebug operations.
    3636 *
    3737 * Udebug operations on tasks and threads are implemented here. The
     
    3939 * when servicing udebug IPC messages.
    4040 */
    41  
     41
    4242#include <debug.h>
    4343#include <proc/task.h>
     
    4646#include <errno.h>
    4747#include <print.h>
    48 #include <string.h>
     48#include <str.h>
    4949#include <syscall/copy.h>
    5050#include <ipc/ipc.h>
     
    5353#include <memstr.h>
    5454
    55 /**
    56  * Prepare a thread for a debugging operation.
     55/** Prepare a thread for a debugging operation.
    5756 *
    5857 * Simply put, return thread t with t->udebug.lock held,
     
    7372 * the t->lock spinlock to the t->udebug.lock mutex.
    7473 *
    75  * @param t             Pointer, need not at all be valid.
    76  * @param being_go      Required thread state.
     74 * @param thread   Pointer, need not at all be valid.
     75 * @param being_go Required thread state.
    7776 *
    7877 * Returns EOK if all went well, or an error code otherwise.
    79  */
    80 static int _thread_op_begin(thread_t *t, bool being_go)
    81 {
    82         task_id_t taskid;
    83         ipl_t ipl;
    84 
    85         taskid = TASK->taskid;
    86 
    87         mutex_lock(&TASK->udebug.lock);
    88 
     78 *
     79 */
     80static int _thread_op_begin(thread_t *thread, bool being_go)
     81{
     82        mutex_lock(&TASK->udebug.lock);
     83       
    8984        /* thread_exists() must be called with threads_lock held */
    90         ipl = interrupts_disable();
    91         spinlock_lock(&threads_lock);
    92 
    93         if (!thread_exists(t)) {
    94                 spinlock_unlock(&threads_lock);
    95                 interrupts_restore(ipl);
     85        irq_spinlock_lock(&threads_lock, true);
     86       
     87        if (!thread_exists(thread)) {
     88                irq_spinlock_unlock(&threads_lock, true);
    9689                mutex_unlock(&TASK->udebug.lock);
    9790                return ENOENT;
    9891        }
    99 
    100         /* t->lock is enough to ensure the thread's existence */
    101         spinlock_lock(&t->lock);
    102         spinlock_unlock(&threads_lock);
    103 
    104         /* Verify that 't' is a userspace thread. */
    105         if ((t->flags & THREAD_FLAG_USPACE) == 0) {
     92       
     93        /* thread->lock is enough to ensure the thread's existence */
     94        irq_spinlock_exchange(&threads_lock, &thread->lock);
     95       
     96        /* Verify that 'thread' is a userspace thread. */
     97        if ((thread->flags & THREAD_FLAG_USPACE) == 0) {
    10698                /* It's not, deny its existence */
    107                 spinlock_unlock(&t->lock);
    108                 interrupts_restore(ipl);
     99                irq_spinlock_unlock(&thread->lock, true);
    109100                mutex_unlock(&TASK->udebug.lock);
    110101                return ENOENT;
    111102        }
    112 
     103       
    113104        /* Verify debugging state. */
    114         if (t->udebug.active != true) {
     105        if (thread->udebug.active != true) {
    115106                /* Not in debugging session or undesired GO state */
    116                 spinlock_unlock(&t->lock);
    117                 interrupts_restore(ipl);
     107                irq_spinlock_unlock(&thread->lock, true);
    118108                mutex_unlock(&TASK->udebug.lock);
    119109                return ENOENT;
    120110        }
    121 
     111       
    122112        /*
    123113         * Since the thread has active == true, TASK->udebug.lock
    124114         * is enough to ensure its existence and that active remains
    125115         * true.
     116         *
    126117         */
    127         spinlock_unlock(&t->lock);
    128         interrupts_restore(ipl);
    129 
     118        irq_spinlock_unlock(&thread->lock, true);
     119       
    130120        /* Only mutex TASK->udebug.lock left. */
    131121       
    132122        /* Now verify that the thread belongs to the current task. */
    133         if (t->task != TASK) {
     123        if (thread->task != TASK) {
    134124                /* No such thread belonging this task*/
    135125                mutex_unlock(&TASK->udebug.lock);
    136126                return ENOENT;
    137127        }
    138 
     128       
    139129        /*
    140130         * Now we need to grab the thread's debug lock for synchronization
    141131         * of the threads stoppability/stop state.
     132         *
    142133         */
    143         mutex_lock(&t->udebug.lock);
    144 
     134        mutex_lock(&thread->udebug.lock);
     135       
    145136        /* The big task mutex is no longer needed. */
    146137        mutex_unlock(&TASK->udebug.lock);
    147 
    148         if (t->udebug.go != being_go) {
     138       
     139        if (thread->udebug.go != being_go) {
    149140                /* Not in debugging session or undesired GO state. */
    150                 mutex_unlock(&t->udebug.lock);
     141                mutex_unlock(&thread->udebug.lock);
    151142                return EINVAL;
    152143        }
    153 
    154         /* Only t->udebug.lock left. */
    155 
    156         return EOK;     /* All went well. */
     144       
     145        /* Only thread->udebug.lock left. */
     146       
     147        return EOK;  /* All went well. */
    157148}
    158149
    159150/** End debugging operation on a thread. */
    160 static void _thread_op_end(thread_t *t)
    161 {
    162         mutex_unlock(&t->udebug.lock);
     151static void _thread_op_end(thread_t *thread)
     152{
     153        mutex_unlock(&thread->udebug.lock);
    163154}
    164155
     
    174165 * all the threads become stoppable (i.e. they can be considered stopped).
    175166 *
    176  * @param call  The BEGIN call we are servicing.
    177  * @return      0 (OK, but not done yet), 1 (done) or negative error code.
     167 * @param call The BEGIN call we are servicing.
     168 *
     169 * @return 0 (OK, but not done yet), 1 (done) or negative error code.
     170 *
    178171 */
    179172int udebug_begin(call_t *call)
    180173{
    181         int reply;
    182 
    183         thread_t *t;
    184         link_t *cur;
    185 
    186         LOG("Debugging task %llu", TASK->taskid);
    187         mutex_lock(&TASK->udebug.lock);
    188 
     174        LOG("Debugging task %" PRIu64, TASK->taskid);
     175       
     176        mutex_lock(&TASK->udebug.lock);
     177       
    189178        if (TASK->udebug.dt_state != UDEBUG_TS_INACTIVE) {
    190179                mutex_unlock(&TASK->udebug.lock);
    191180                return EBUSY;
    192181        }
    193 
     182       
    194183        TASK->udebug.dt_state = UDEBUG_TS_BEGINNING;
    195184        TASK->udebug.begin_call = call;
    196185        TASK->udebug.debugger = call->sender;
    197 
     186       
     187        int reply;
     188       
    198189        if (TASK->udebug.not_stoppable_count == 0) {
    199190                TASK->udebug.dt_state = UDEBUG_TS_ACTIVE;
    200191                TASK->udebug.begin_call = NULL;
    201                 reply = 1; /* immediate reply */
    202         } else {
    203                 reply = 0; /* no reply */
    204         }
     192                reply = 1;  /* immediate reply */
     193        } else
     194                reply = 0;  /* no reply */
    205195       
    206196        /* Set udebug.active on all of the task's userspace threads. */
    207 
     197       
     198        link_t *cur;
    208199        for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) {
    209                 t = list_get_instance(cur, thread_t, th_link);
    210 
    211                 mutex_lock(&t->udebug.lock);
    212                 if ((t->flags & THREAD_FLAG_USPACE) != 0) {
    213                         t->udebug.active = true;
    214                         mutex_unlock(&t->udebug.lock);
    215                         condvar_broadcast(&t->udebug.active_cv);
    216                 } else {
    217                         mutex_unlock(&t->udebug.lock);
    218                 }
    219         }
    220 
     200                thread_t *thread = list_get_instance(cur, thread_t, th_link);
     201               
     202                mutex_lock(&thread->udebug.lock);
     203                if ((thread->flags & THREAD_FLAG_USPACE) != 0) {
     204                        thread->udebug.active = true;
     205                        mutex_unlock(&thread->udebug.lock);
     206                        condvar_broadcast(&thread->udebug.active_cv);
     207                } else
     208                        mutex_unlock(&thread->udebug.lock);
     209        }
     210       
    221211        mutex_unlock(&TASK->udebug.lock);
    222212        return reply;
     
    226216 *
    227217 * Closes the debugging session for the current task.
     218 *
    228219 * @return Zero on success or negative error code.
     220 *
    229221 */
    230222int udebug_end(void)
    231223{
    232         int rc;
    233 
    234224        LOG("Task %" PRIu64, TASK->taskid);
    235 
    236         mutex_lock(&TASK->udebug.lock);
    237         rc = udebug_task_cleanup(TASK);
    238         mutex_unlock(&TASK->udebug.lock);
    239 
     225       
     226        mutex_lock(&TASK->udebug.lock);
     227        int rc = udebug_task_cleanup(TASK);
     228        mutex_unlock(&TASK->udebug.lock);
     229       
    240230        return rc;
    241231}
     
    245235 * Sets the event mask that determines which events are enabled.
    246236 *
    247  * @param mask  Or combination of events that should be enabled.
    248  * @return      Zero on success or negative error code.
     237 * @param mask Or combination of events that should be enabled.
     238 *
     239 * @return Zero on success or negative error code.
     240 *
    249241 */
    250242int udebug_set_evmask(udebug_evmask_t mask)
    251243{
    252244        LOG("mask = 0x%x", mask);
    253 
    254         mutex_lock(&TASK->udebug.lock);
    255 
     245       
     246        mutex_lock(&TASK->udebug.lock);
     247       
    256248        if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) {
    257249                mutex_unlock(&TASK->udebug.lock);
    258250                return EINVAL;
    259251        }
    260 
     252       
    261253        TASK->udebug.evmask = mask;
    262254        mutex_unlock(&TASK->udebug.lock);
    263 
     255       
    264256        return 0;
    265257}
     
    271263 * a debugging event or STOP occurs, at which point the thread loses GO.
    272264 *
    273  * @param t     The thread to operate on (unlocked and need not be valid).
    274  * @param call  The GO call that we are servicing.
    275  */
    276 int udebug_go(thread_t *t, call_t *call)
    277 {
    278         int rc;
    279 
    280         /* On success, this will lock t->udebug.lock. */
    281         rc = _thread_op_begin(t, false);
    282         if (rc != EOK) {
     265 * @param thread The thread to operate on (unlocked and need not be valid).
     266 * @param call   The GO call that we are servicing.
     267 *
     268 */
     269int udebug_go(thread_t *thread, call_t *call)
     270{
     271        /* On success, this will lock thread->udebug.lock. */
     272        int rc = _thread_op_begin(thread, false);
     273        if (rc != EOK)
    283274                return rc;
    284         }
    285 
    286         t->udebug.go_call = call;
    287         t->udebug.go = true;
    288         t->udebug.cur_event = 0;        /* none */
    289 
     275       
     276        thread->udebug.go_call = call;
     277        thread->udebug.go = true;
     278        thread->udebug.cur_event = 0;  /* none */
     279       
    290280        /*
    291          * Neither t's lock nor threads_lock may be held during wakeup.
     281         * Neither thread's lock nor threads_lock may be held during wakeup.
     282         *
    292283         */
    293         waitq_wakeup(&t->udebug.go_wq, WAKEUP_FIRST);
    294 
    295         _thread_op_end(t);
    296 
     284        waitq_wakeup(&thread->udebug.go_wq, WAKEUP_FIRST);
     285       
     286        _thread_op_end(thread);
     287       
    297288        return 0;
    298289}
     
    303294 * can be considered stopped).
    304295 *
    305  * @param t     The thread to operate on (unlocked and need not be valid).
    306  * @param call  The GO call that we are servicing.
    307  */
    308 int udebug_stop(thread_t *t, call_t *call)
    309 {
    310         int rc;
    311 
     296 * @param thread The thread to operate on (unlocked and need not be valid).
     297 * @param call   The GO call that we are servicing.
     298 *
     299 */
     300int udebug_stop(thread_t *thread, call_t *call)
     301{
    312302        LOG("udebug_stop()");
    313 
     303       
    314304        /*
    315          * On success, this will lock t->udebug.lock. Note that this makes sure
    316          * the thread is not stopped.
     305         * On success, this will lock thread->udebug.lock. Note that this
     306         * makes sure the thread is not stopped.
     307         *
    317308         */
    318         rc = _thread_op_begin(t, true);
    319         if (rc != EOK) {
     309        int rc = _thread_op_begin(thread, true);
     310        if (rc != EOK)
    320311                return rc;
    321         }
    322 
     312       
    323313        /* Take GO away from the thread. */
    324         t->udebug.go = false;
    325 
    326         if (t->udebug.stoppable != true) {
     314        thread->udebug.go = false;
     315       
     316        if (thread->udebug.stoppable != true) {
    327317                /* Answer will be sent when the thread becomes stoppable. */
    328                 _thread_op_end(t);
     318                _thread_op_end(thread);
    329319                return 0;
    330320        }
    331 
     321       
    332322        /*
    333323         * Answer GO call.
     324         *
    334325         */
    335 
     326       
    336327        /* Make sure nobody takes this call away from us. */
    337         call = t->udebug.go_call;
    338         t->udebug.go_call = NULL;
    339 
     328        call = thread->udebug.go_call;
     329        thread->udebug.go_call = NULL;
     330       
    340331        IPC_SET_RETVAL(call->data, 0);
    341332        IPC_SET_ARG1(call->data, UDEBUG_EVENT_STOP);
    342 
     333       
    343334        THREAD->udebug.cur_event = UDEBUG_EVENT_STOP;
    344 
    345         _thread_op_end(t);
    346 
     335       
     336        _thread_op_end(thread);
     337       
    347338        mutex_lock(&TASK->udebug.lock);
    348339        ipc_answer(&TASK->answerbox, call);
    349340        mutex_unlock(&TASK->udebug.lock);
    350 
     341       
    351342        return 0;
    352343}
     
    368359 * a maximum size for the userspace buffer.
    369360 *
    370  * @param buffer        The buffer for storing thread hashes.
    371  * @param buf_size      Buffer size in bytes.
    372  * @param stored        The actual number of bytes copied will be stored here.
    373  * @param needed        Total number of hashes that could have been saved.
     361 * @param buffer   The buffer for storing thread hashes.
     362 * @param buf_size Buffer size in bytes.
     363 * @param stored   The actual number of bytes copied will be stored here.
     364 * @param needed   Total number of hashes that could have been saved.
     365 *
    374366 */
    375367int udebug_thread_read(void **buffer, size_t buf_size, size_t *stored,
    376368    size_t *needed)
    377369{
    378         thread_t *t;
    379         link_t *cur;
    380         unative_t tid;
    381         size_t copied_ids;
    382         size_t extra_ids;
    383         ipl_t ipl;
    384         unative_t *id_buffer;
    385         int flags;
    386         size_t max_ids;
    387 
    388370        LOG("udebug_thread_read()");
    389 
     371       
    390372        /* Allocate a buffer to hold thread IDs */
    391         id_buffer = malloc(buf_size + 1, 0);
    392 
    393         mutex_lock(&TASK->udebug.lock);
    394 
     373        unative_t *id_buffer = malloc(buf_size + 1, 0);
     374       
     375        mutex_lock(&TASK->udebug.lock);
     376       
    395377        /* Verify task state */
    396378        if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) {
     
    398380                return EINVAL;
    399381        }
    400 
    401         ipl = interrupts_disable();
    402         spinlock_lock(&TASK->lock);
     382       
     383        irq_spinlock_lock(&TASK->lock, true);
     384       
    403385        /* Copy down the thread IDs */
    404 
    405         max_ids = buf_size / sizeof(unative_t);
    406         copied_ids = 0;
    407         extra_ids = 0;
    408 
     386       
     387        size_t max_ids = buf_size / sizeof(unative_t);
     388        size_t copied_ids = 0;
     389        size_t extra_ids = 0;
     390       
    409391        /* FIXME: make sure the thread isn't past debug shutdown... */
     392        link_t *cur;
    410393        for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) {
    411                 t = list_get_instance(cur, thread_t, th_link);
    412 
    413                 spinlock_lock(&t->lock);
    414                 flags = t->flags;
    415                 spinlock_unlock(&t->lock);
    416 
     394                thread_t *thread = list_get_instance(cur, thread_t, th_link);
     395               
     396                irq_spinlock_lock(&thread->lock, false);
     397                int flags = thread->flags;
     398                irq_spinlock_unlock(&thread->lock, false);
     399               
    417400                /* Not interested in kernel threads. */
    418401                if ((flags & THREAD_FLAG_USPACE) == 0)
    419402                        continue;
    420 
     403               
    421404                if (copied_ids < max_ids) {
    422405                        /* Using thread struct pointer as identification hash */
    423                         tid = (unative_t) t;
    424                         id_buffer[copied_ids++] = tid;
    425                 } else {
     406                        id_buffer[copied_ids++] = (unative_t) thread;
     407                } else
    426408                        extra_ids++;
    427                 }
    428         }
    429 
    430         spinlock_unlock(&TASK->lock);
    431         interrupts_restore(ipl);
    432 
    433         mutex_unlock(&TASK->udebug.lock);
    434 
     409        }
     410       
     411        irq_spinlock_unlock(&TASK->lock, true);
     412       
     413        mutex_unlock(&TASK->udebug.lock);
     414       
    435415        *buffer = id_buffer;
    436416        *stored = copied_ids * sizeof(unative_t);
    437417        *needed = (copied_ids + extra_ids) * sizeof(unative_t);
    438 
     418       
    439419        return 0;
    440420}
     
    445425 * Also returns the size of the data.
    446426 *
    447  * @param data          Place to store pointer to newly allocated block.
    448  * @param data_size     Place to store size of the data.
    449  *
    450  * @returns             EOK.
     427 * @param data      Place to store pointer to newly allocated block.
     428 * @param data_size Place to store size of the data.
     429 *
     430 * @return EOK.
     431 *
    451432 */
    452433int udebug_name_read(char **data, size_t *data_size)
    453434{
    454         size_t name_size;
    455 
    456         name_size = str_size(TASK->name) + 1;
     435        size_t name_size = str_size(TASK->name) + 1;
     436       
    457437        *data = malloc(name_size, 0);
    458438        *data_size = name_size;
    459 
     439       
    460440        memcpy(*data, TASK->name, name_size);
    461 
     441       
    462442        return 0;
    463443}
     
    473453 * this function will fail with an EINVAL error code.
    474454 *
    475  * @param t             Thread where call arguments are to be read.
    476  * @param buffer        Place to store pointer to new buffer.
    477  * @return              EOK on success, ENOENT if @a t is invalid, EINVAL
    478  *                      if thread state is not valid for this operation.
    479  */
    480 int udebug_args_read(thread_t *t, void **buffer)
    481 {
    482         int rc;
    483         unative_t *arg_buffer;
    484 
     455 * @param thread Thread where call arguments are to be read.
     456 * @param buffer Place to store pointer to new buffer.
     457 *
     458 * @return EOK on success, ENOENT if @a t is invalid, EINVAL
     459 *         if thread state is not valid for this operation.
     460 *
     461 */
     462int udebug_args_read(thread_t *thread, void **buffer)
     463{
    485464        /* Prepare a buffer to hold the arguments. */
    486         arg_buffer = malloc(6 * sizeof(unative_t), 0);
    487 
     465        unative_t *arg_buffer = malloc(6 * sizeof(unative_t), 0);
     466       
    488467        /* On success, this will lock t->udebug.lock. */
    489         rc = _thread_op_begin(t, false);
    490         if (rc != EOK) {
     468        int rc = _thread_op_begin(thread, false);
     469        if (rc != EOK)
    491470                return rc;
    492         }
    493 
     471       
    494472        /* Additionally we need to verify that we are inside a syscall. */
    495         if (t->udebug.cur_event != UDEBUG_EVENT_SYSCALL_B &&
    496             t->udebug.cur_event != UDEBUG_EVENT_SYSCALL_E) {
    497                 _thread_op_end(t);
     473        if ((thread->udebug.cur_event != UDEBUG_EVENT_SYSCALL_B) &&
     474            (thread->udebug.cur_event != UDEBUG_EVENT_SYSCALL_E)) {
     475                _thread_op_end(thread);
    498476                return EINVAL;
    499477        }
    500 
     478       
    501479        /* Copy to a local buffer before releasing the lock. */
    502         memcpy(arg_buffer, t->udebug.syscall_args, 6 * sizeof(unative_t));
    503 
    504         _thread_op_end(t);
    505 
     480        memcpy(arg_buffer, thread->udebug.syscall_args, 6 * sizeof(unative_t));
     481       
     482        _thread_op_end(thread);
     483       
    506484        *buffer = arg_buffer;
    507485        return 0;
     
    517495 * call (as opposed to an exception). This is an implementation limit.
    518496 *
    519  * @param t             Thread whose state is to be read.
    520  * @param buffer        Place to store pointer to new buffer.
    521  * @return              EOK on success, ENOENT if @a t is invalid, EINVAL
    522  *                      if thread is not in valid state, EBUSY if istate
    523  *                      is not available.
    524  */
    525 int udebug_regs_read(thread_t *t, void **buffer)
    526 {
    527         istate_t *state, *state_buf;
    528         int rc;
    529 
     497 * @param thread Thread whose state is to be read.
     498 * @param buffer Place to store pointer to new buffer.
     499 *
     500 * @return EOK on success, ENOENT if @a t is invalid, EINVAL
     501 *         if thread is not in valid state, EBUSY if istate
     502 *         is not available.
     503 *
     504 */
     505int udebug_regs_read(thread_t *thread, void **buffer)
     506{
    530507        /* Prepare a buffer to hold the data. */
    531         state_buf = malloc(sizeof(istate_t), 0);
    532 
     508        istate_t *state_buf = malloc(sizeof(istate_t), 0);
     509       
    533510        /* On success, this will lock t->udebug.lock */
    534         rc = _thread_op_begin(t, false);
    535         if (rc != EOK) {
     511        int rc = _thread_op_begin(thread, false);
     512        if (rc != EOK)
    536513                return rc;
    537         }
    538 
    539         state = t->udebug.uspace_state;
     514       
     515        istate_t *state = thread->udebug.uspace_state;
    540516        if (state == NULL) {
    541                 _thread_op_end(t);
     517                _thread_op_end(thread);
    542518                return EBUSY;
    543519        }
    544 
     520       
    545521        /* Copy to the allocated buffer */
    546522        memcpy(state_buf, state, sizeof(istate_t));
    547 
    548         _thread_op_end(t);
    549 
     523       
     524        _thread_op_end(thread);
     525       
    550526        *buffer = (void *) state_buf;
    551527        return 0;
     
    558534 * and a pointer to it is written into @a buffer.
    559535 *
    560  * @param uspace_addr   Address from where to start reading.
    561  * @param n             Number of bytes to read.
    562  * @param buffer        For storing a pointer to the allocated buffer.
     536 * @param uspace_addr Address from where to start reading.
     537 * @param n           Number of bytes to read.
     538 * @param buffer      For storing a pointer to the allocated buffer.
     539 *
    563540 */
    564541int udebug_mem_read(unative_t uspace_addr, size_t n, void **buffer)
    565542{
    566         void *data_buffer;
    567         int rc;
    568 
    569543        /* Verify task state */
    570544        mutex_lock(&TASK->udebug.lock);
    571 
     545       
    572546        if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) {
    573547                mutex_unlock(&TASK->udebug.lock);
    574548                return EBUSY;
    575549        }
    576 
    577         data_buffer = malloc(n, 0);
    578 
    579         /* NOTE: this is not strictly from a syscall... but that shouldn't
    580          * be a problem */
    581         rc = copy_from_uspace(data_buffer, (void *)uspace_addr, n);
    582         mutex_unlock(&TASK->udebug.lock);
    583 
    584         if (rc != 0) return rc;
    585 
     550       
     551        void *data_buffer = malloc(n, 0);
     552       
     553        /*
     554         * NOTE: this is not strictly from a syscall... but that shouldn't
     555         * be a problem
     556         *
     557         */
     558        int rc = copy_from_uspace(data_buffer, (void *) uspace_addr, n);
     559        mutex_unlock(&TASK->udebug.lock);
     560       
     561        if (rc != 0)
     562                return rc;
     563       
    586564        *buffer = data_buffer;
    587565        return 0;
Note: See TracChangeset for help on using the changeset viewer.