Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • kernel/generic/src/ipc/ipc.c

    r466e95f7 rcd529c4  
    4545#include <ipc/kbox.h>
    4646#include <ipc/event.h>
    47 #include <ipc/sysipc_ops.h>
    48 #include <ipc/sysipc_priv.h>
    4947#include <errno.h>
    5048#include <mm/slab.h>
     
    7371{
    7472        memsetb(call, sizeof(*call), 0);
    75         spinlock_initialize(&call->forget_lock, "forget_lock");
    76         call->active = false;
    77         call->forget = false;
    78         call->sender = NULL;
     73        call->sender = TASK;
    7974        call->buffer = NULL;
    80 }
    81 
    82 void ipc_call_hold(call_t *call)
    83 {
    84         atomic_inc(&call->refcnt);
    85 }
    86 
    87 void ipc_call_release(call_t *call)
    88 {
    89         if (atomic_predec(&call->refcnt) == 0) {
    90                 if (call->buffer)
    91                         free(call->buffer);
    92                 slab_free(ipc_call_slab, call);
    93         }
    9475}
    9576
     
    10283 *
    10384 * @return If flags permit it, return NULL, or initialized kernel
    104  *         call structure with one reference.
     85 *         call structure.
    10586 *
    10687 */
     
    10889{
    10990        call_t *call = slab_alloc(ipc_call_slab, flags);
    110         if (call) {
     91        if (call)
    11192                _ipc_call_init(call);
    112                 ipc_call_hold(call);
    113         }
    11493       
    11594        return call;
     
    123102void ipc_call_free(call_t *call)
    124103{
    125         ipc_call_release(call);
     104        /* Check to see if we have data in the IPC_M_DATA_SEND buffer. */
     105        if (call->buffer)
     106                free(call->buffer);
     107        slab_free(ipc_call_slab, call);
    126108}
    127109
     
    150132 * @param phone Initialized phone structure.
    151133 * @param box   Initialized answerbox structure.
    152  * @return      True if the phone was connected, false otherwise.
    153  */
    154 bool ipc_phone_connect(phone_t *phone, answerbox_t *box)
    155 {
    156         bool active;
    157 
     134 *
     135 */
     136void ipc_phone_connect(phone_t *phone, answerbox_t *box)
     137{
    158138        mutex_lock(&phone->lock);
     139       
     140        phone->state = IPC_PHONE_CONNECTED;
     141        phone->callee = box;
     142       
    159143        irq_spinlock_lock(&box->lock, true);
    160 
    161         active = box->active;
    162         if (active) {
    163                 phone->state = IPC_PHONE_CONNECTED;
    164                 phone->callee = box;
    165                 list_append(&phone->link, &box->connected_phones);
    166         }
    167 
     144        list_append(&phone->link, &box->connected_phones);
    168145        irq_spinlock_unlock(&box->lock, true);
     146       
    169147        mutex_unlock(&phone->lock);
    170 
    171         return active;
    172148}
    173149
     
    175151 *
    176152 * @param phone Phone structure to be initialized.
    177  * @param caller Owning task.
    178  *
    179  */
    180 void ipc_phone_init(phone_t *phone, task_t *caller)
     153 *
     154 */
     155void ipc_phone_init(phone_t *phone)
    181156{
    182157        mutex_initialize(&phone->lock, MUTEX_PASSIVE);
    183         phone->caller = caller;
    184158        phone->callee = NULL;
    185159        phone->state = IPC_PHONE_FREE;
     
    193167 *
    194168 */
    195 void _ipc_answer_free_call(call_t *call, bool selflocked)
    196 {
     169static void _ipc_answer_free_call(call_t *call, bool selflocked)
     170{
     171        answerbox_t *callerbox = &call->sender->answerbox;
     172        bool do_lock = ((!selflocked) || callerbox != (&TASK->answerbox));
     173       
    197174        /* Count sent answer */
    198175        irq_spinlock_lock(&TASK->lock, true);
    199176        TASK->ipc_info.answer_sent++;
    200177        irq_spinlock_unlock(&TASK->lock, true);
    201 
    202         spinlock_lock(&call->forget_lock);
    203         if (call->forget) {
    204                 /* This is a forgotten call and call->sender is not valid. */
    205                 spinlock_unlock(&call->forget_lock);
    206                 ipc_call_free(call);
    207                 return;
    208         } else {
    209                 /*
    210                  * If the call is still active, i.e. it was answered
    211                  * in a non-standard way, remove the call from the
    212                  * sender's active call list.
    213                  */
    214                 if (call->active) {
    215                         spinlock_lock(&call->sender->active_calls_lock);
    216                         list_remove(&call->ta_link);
    217                         spinlock_unlock(&call->sender->active_calls_lock);
     178       
     179        call->flags |= IPC_CALL_ANSWERED;
     180       
     181        if (call->flags & IPC_CALL_FORWARDED) {
     182                if (call->caller_phone) {
     183                        /* Demasquerade the caller phone. */
     184                        call->data.phone = call->caller_phone;
    218185                }
    219186        }
    220         spinlock_unlock(&call->forget_lock);
    221 
    222         answerbox_t *callerbox = &call->sender->answerbox;
    223         bool do_lock = ((!selflocked) || (callerbox != &TASK->answerbox));
    224        
    225         call->flags |= IPC_CALL_ANSWERED;
    226        
     187
    227188        call->data.task_id = TASK->taskid;
    228189       
     
    230191                irq_spinlock_lock(&callerbox->lock, true);
    231192       
    232         list_append(&call->ab_link, &callerbox->answers);
     193        list_append(&call->link, &callerbox->answers);
    233194       
    234195        if (do_lock)
     
    248209        /* Remove from active box */
    249210        irq_spinlock_lock(&box->lock, true);
    250         list_remove(&call->ab_link);
     211        list_remove(&call->link);
    251212        irq_spinlock_unlock(&box->lock, true);
    252213       
    253214        /* Send back answer */
    254215        _ipc_answer_free_call(call, false);
    255 }
    256 
    257 static void _ipc_call_actions_internal(phone_t *phone, call_t *call)
    258 {
    259         task_t *caller = phone->caller;
    260 
    261         atomic_inc(&phone->active_calls);
    262         call->caller_phone = phone;
    263         call->sender = caller;
    264 
    265         call->active = true;
    266         spinlock_lock(&caller->active_calls_lock);
    267         list_append(&call->ta_link, &caller->active_calls);
    268         spinlock_unlock(&caller->active_calls_lock);
    269 
    270         call->data.phone = phone;
    271         call->data.task_id = caller->taskid;
    272216}
    273217
     
    284228void ipc_backsend_err(phone_t *phone, call_t *call, sysarg_t err)
    285229{
    286         _ipc_call_actions_internal(phone, call);
     230        call->data.phone = phone;
     231        atomic_inc(&phone->active_calls);
    287232        IPC_SET_RETVAL(call->data, err);
    288233        _ipc_answer_free_call(call, false);
     
    298243static void _ipc_call(phone_t *phone, answerbox_t *box, call_t *call)
    299244{
    300         task_t *caller = phone->caller;
    301 
    302245        /* Count sent ipc call */
    303         irq_spinlock_lock(&caller->lock, true);
    304         caller->ipc_info.call_sent++;
    305         irq_spinlock_unlock(&caller->lock, true);
    306        
    307         if (!(call->flags & IPC_CALL_FORWARDED))
    308                 _ipc_call_actions_internal(phone, call);
     246        irq_spinlock_lock(&TASK->lock, true);
     247        TASK->ipc_info.call_sent++;
     248        irq_spinlock_unlock(&TASK->lock, true);
     249       
     250        if (!(call->flags & IPC_CALL_FORWARDED)) {
     251                atomic_inc(&phone->active_calls);
     252                call->data.phone = phone;
     253                call->data.task_id = TASK->taskid;
     254        }
    309255       
    310256        irq_spinlock_lock(&box->lock, true);
    311         list_append(&call->ab_link, &box->calls);
     257        list_append(&call->link, &box->calls);
    312258        irq_spinlock_unlock(&box->lock, true);
    313259       
     
    329275        if (phone->state != IPC_PHONE_CONNECTED) {
    330276                mutex_unlock(&phone->lock);
    331                 if (!(call->flags & IPC_CALL_FORWARDED)) {
     277                if (call->flags & IPC_CALL_FORWARDED) {
     278                        IPC_SET_RETVAL(call->data, EFORWARD);
     279                        _ipc_answer_free_call(call, false);
     280                } else {
    332281                        if (phone->state == IPC_PHONE_HUNGUP)
    333282                                ipc_backsend_err(phone, call, EHANGUP);
     
    376325                call_t *call = ipc_call_alloc(0);
    377326                IPC_SET_IMETHOD(call->data, IPC_M_PHONE_HUNGUP);
    378                 call->request_method = IPC_M_PHONE_HUNGUP;
    379327                call->flags |= IPC_CALL_DISCARD_ANSWER;
    380328                _ipc_call(phone, box, call);
     
    408356        TASK->ipc_info.forwarded++;
    409357        irq_spinlock_pass(&TASK->lock, &oldbox->lock);
    410         list_remove(&call->ab_link);
     358        list_remove(&call->link);
    411359        irq_spinlock_unlock(&oldbox->lock, true);
    412360       
    413361        if (mode & IPC_FF_ROUTE_FROM_ME) {
     362                if (!call->caller_phone)
     363                        call->caller_phone = call->data.phone;
    414364                call->data.phone = newphone;
    415365                call->data.task_id = TASK->taskid;
     
    456406               
    457407                request = list_get_instance(list_first(&box->irq_notifs),
    458                     call_t, ab_link);
    459                 list_remove(&request->ab_link);
     408                    call_t, link);
     409                list_remove(&request->link);
    460410               
    461411                irq_spinlock_unlock(&box->irq_lock, false);
     
    466416                /* Handle asynchronous answers */
    467417                request = list_get_instance(list_first(&box->answers),
    468                     call_t, ab_link);
    469                 list_remove(&request->ab_link);
    470                 atomic_dec(&request->caller_phone->active_calls);
     418                    call_t, link);
     419                list_remove(&request->link);
     420                atomic_dec(&request->data.phone->active_calls);
    471421        } else if (!list_empty(&box->calls)) {
    472422                /* Count received call */
     
    475425                /* Handle requests */
    476426                request = list_get_instance(list_first(&box->calls),
    477                     call_t, ab_link);
    478                 list_remove(&request->ab_link);
     427                    call_t, link);
     428                list_remove(&request->link);
    479429               
    480430                /* Append request to dispatch queue */
    481                 list_append(&request->ab_link, &box->dispatched_calls);
     431                list_append(&request->link, &box->dispatched_calls);
    482432        } else {
    483433                /* This can happen regularly after ipc_cleanup */
     
    499449/** Answer all calls from list with EHANGUP answer.
    500450 *
    501  * @param box Answerbox with the list.
    502451 * @param lst Head of the list to be cleaned up.
    503  */
    504 void ipc_cleanup_call_list(answerbox_t *box, list_t *lst)
    505 {
    506         irq_spinlock_lock(&box->lock, true);
     452 *
     453 */
     454void ipc_cleanup_call_list(list_t *lst)
     455{
    507456        while (!list_empty(lst)) {
    508                 call_t *call = list_get_instance(list_first(lst), call_t,
    509                     ab_link);
    510                
    511                 list_remove(&call->ab_link);
    512 
    513                 irq_spinlock_unlock(&box->lock, true);
    514 
    515                 if (lst == &box->calls)
    516                         SYSIPC_OP(request_process, call, box);
    517 
    518                 ipc_data_t old = call->data;
     457                call_t *call = list_get_instance(list_first(lst), call_t, link);
     458                if (call->buffer)
     459                        free(call->buffer);
     460               
     461                list_remove(&call->link);
     462               
    519463                IPC_SET_RETVAL(call->data, EHANGUP);
    520                 answer_preprocess(call, &old);
    521464                _ipc_answer_free_call(call, true);
    522 
    523                 irq_spinlock_lock(&box->lock, true);
    524         }
    525         irq_spinlock_unlock(&box->lock, true);
     465        }
    526466}
    527467
     
    561501                        mutex_unlock(&phone->lock);
    562502                        irq_spinlock_unlock(&box->lock, true);
    563 
    564                         // FIXME: phone can become deallocated at any time now
    565 
     503                       
    566504                        /*
    567505                         * Send one message to the answerbox for each
     
    571509                         */
    572510                        IPC_SET_IMETHOD(call->data, IPC_M_PHONE_HUNGUP);
    573                         call->request_method = IPC_M_PHONE_HUNGUP;
    574511                        call->flags |= IPC_CALL_DISCARD_ANSWER;
    575512                        _ipc_call(phone, box, call);
     
    592529}
    593530
    594 static void ipc_forget_all_active_calls(void)
    595 {
    596         call_t *call;
    597 
    598 restart:
    599         spinlock_lock(&TASK->active_calls_lock);
    600         if (list_empty(&TASK->active_calls)) {
    601                 /*
    602                  * We are done, there are no more active calls.
    603                  * Nota bene: there may still be answers waiting for pick up.
    604                  */
    605                 spinlock_unlock(&TASK->active_calls_lock);     
    606                 return;
    607         }
    608        
    609         call = list_get_instance(list_first(&TASK->active_calls), call_t,
    610             ta_link);
    611 
    612         if (!spinlock_trylock(&call->forget_lock)) {
    613                 /*
    614                  * Avoid deadlock and let async_answer() or
    615                  *  _ipc_answer_free_call() win the race to dequeue the first
    616                  * call on the list.
    617                  */
    618                 spinlock_unlock(&TASK->active_calls_lock);     
    619                 goto restart;
    620         }
    621 
    622         /*
    623          * Forget the call and donate it to the task which holds up the answer.
    624          */
    625 
    626         call->forget = true;
    627         call->sender = NULL;
    628         list_remove(&call->ta_link);
    629 
    630         /*
    631          * The call may be freed by _ipc_answer_free_call() before we are done
    632          * with it; to avoid working with a destroyed call_t structure, we
    633          * must hold a reference to it.
    634          */
    635         ipc_call_hold(call);
    636 
    637         spinlock_unlock(&call->forget_lock);
    638         spinlock_unlock(&TASK->active_calls_lock);
    639 
    640         atomic_dec(&call->caller_phone->active_calls);
    641 
    642         SYSIPC_OP(request_forget, call);
    643 
    644         ipc_call_release(call);
    645 
    646         goto restart;
    647 }
    648 
    649 /** Wait for all answers to asynchronous calls to arrive. */
    650 static void ipc_wait_for_all_answered_calls(void)
    651 {
    652         call_t *call;
    653         size_t i;
    654 
    655 restart:
    656         /*
    657          * Go through all phones, until they are all free.
    658          * Locking is needed as there may be connection handshakes in progress.
    659          */
    660         for (i = 0; i < IPC_MAX_PHONES; i++) {
    661                 phone_t *phone = &TASK->phones[i];
    662 
    663                 mutex_lock(&phone->lock);       
    664                 if ((phone->state == IPC_PHONE_HUNGUP) &&
    665                     (atomic_get(&phone->active_calls) == 0)) {
    666                         phone->state = IPC_PHONE_FREE;
    667                         phone->callee = NULL;
    668                 }
    669 
    670                 /*
    671                  * We might have had some IPC_PHONE_CONNECTING phones at the
    672                  * beginning of ipc_cleanup(). Depending on whether these were
    673                  * forgotten or answered, they will eventually enter the
    674                  * IPC_PHONE_FREE or IPC_PHONE_CONNECTED states, respectively.
    675                  * In the latter case, the other side may slam the open phones
    676                  * at any time, in which case we will get an IPC_PHONE_SLAMMED
    677                  * phone.
    678                  */
    679                 if ((phone->state == IPC_PHONE_CONNECTED) ||
    680                     (phone->state == IPC_PHONE_SLAMMED)) {
    681                         mutex_unlock(&phone->lock);
    682                         ipc_phone_hangup(phone);
    683                         /*
    684                          * Now there may be one extra active call, which needs
    685                          * to be forgotten.
    686                          */
    687                         ipc_forget_all_active_calls();
    688                         goto restart;
    689                 }
    690 
    691                 /*
    692                  * If the hangup succeeded, it has sent a HANGUP message, the
    693                  * IPC is now in HUNGUP state, we wait for the reply to come
    694                  */
    695                 if (phone->state != IPC_PHONE_FREE) {
    696                         mutex_unlock(&phone->lock);
    697                         break;
    698                 }
    699 
    700                 mutex_unlock(&phone->lock);
    701         }
    702                
    703         /* Got into cleanup */
    704         if (i == IPC_MAX_PHONES)
    705                 return;
    706                
    707         call = ipc_wait_for_call(&TASK->answerbox, SYNCH_NO_TIMEOUT,
    708             SYNCH_FLAGS_NONE);
    709         ASSERT(call->flags & (IPC_CALL_ANSWERED | IPC_CALL_NOTIF));
    710 
    711         SYSIPC_OP(answer_process, call);
    712 
    713         ipc_call_free(call);
    714         goto restart;
    715 }
    716 
    717531/** Clean up all IPC communication of the current task.
    718532 *
     
    723537void ipc_cleanup(void)
    724538{
    725         /*
    726          * Mark the answerbox as inactive.
    727          *
    728          * The main purpose for doing this is to prevent any pending callback
    729          * connections from getting established beyond this point.
    730          */
    731         irq_spinlock_lock(&TASK->answerbox.lock, true);
    732         TASK->answerbox.active = false;
    733         irq_spinlock_unlock(&TASK->answerbox.lock, true);
    734 
    735539        /* Disconnect all our phones ('ipc_phone_hangup') */
    736         for (size_t i = 0; i < IPC_MAX_PHONES; i++)
     540        size_t i;
     541        for (i = 0; i < IPC_MAX_PHONES; i++)
    737542                ipc_phone_hangup(&TASK->phones[i]);
    738543       
     
    752557       
    753558        /* Answer all messages in 'calls' and 'dispatched_calls' queues */
    754         ipc_cleanup_call_list(&TASK->answerbox, &TASK->answerbox.calls);
    755         ipc_cleanup_call_list(&TASK->answerbox,
    756             &TASK->answerbox.dispatched_calls);
    757 
    758         ipc_forget_all_active_calls();
    759         ipc_wait_for_all_answered_calls();
     559        irq_spinlock_lock(&TASK->answerbox.lock, true);
     560        ipc_cleanup_call_list(&TASK->answerbox.dispatched_calls);
     561        ipc_cleanup_call_list(&TASK->answerbox.calls);
     562        irq_spinlock_unlock(&TASK->answerbox.lock, true);
     563       
     564        /* Wait for all answers to asynchronous calls to arrive */
     565        while (true) {
     566                /*
     567                 * Go through all phones, until they are all FREE
     568                 * Locking is not needed, no one else should modify
     569                 * it when we are in cleanup
     570                 */
     571                for (i = 0; i < IPC_MAX_PHONES; i++) {
     572                        if (TASK->phones[i].state == IPC_PHONE_HUNGUP &&
     573                            atomic_get(&TASK->phones[i].active_calls) == 0) {
     574                                TASK->phones[i].state = IPC_PHONE_FREE;
     575                                TASK->phones[i].callee = NULL;
     576                        }
     577                       
     578                        /*
     579                         * Just for sure, we might have had some
     580                         * IPC_PHONE_CONNECTING phones
     581                         */
     582                        if (TASK->phones[i].state == IPC_PHONE_CONNECTED)
     583                                ipc_phone_hangup(&TASK->phones[i]);
     584                       
     585                        /*
     586                         * If the hangup succeeded, it has sent a HANGUP
     587                         * message, the IPC is now in HUNGUP state, we
     588                         * wait for the reply to come
     589                         */
     590                       
     591                        if (TASK->phones[i].state != IPC_PHONE_FREE)
     592                                break;
     593                }
     594               
     595                /* Got into cleanup */
     596                if (i == IPC_MAX_PHONES)
     597                        break;
     598               
     599                call_t *call = ipc_wait_for_call(&TASK->answerbox, SYNCH_NO_TIMEOUT,
     600                    SYNCH_FLAGS_NONE);
     601                ASSERT((call->flags & IPC_CALL_ANSWERED) ||
     602                    (call->flags & IPC_CALL_NOTIF));
     603               
     604                ipc_call_free(call);
     605        }
    760606}
    761607
     
    769615        ipc_answerbox_slab = slab_cache_create("answerbox_t",
    770616            sizeof(answerbox_t), 0, NULL, NULL, 0);
    771 }
    772 
    773 
    774 static void ipc_print_call_list(list_t *list)
    775 {
    776         list_foreach(*list, cur) {
    777                 call_t *call = list_get_instance(cur, call_t, ab_link);
    778                
    779 #ifdef __32_BITS__
    780                 printf("%10p ", call);
    781 #endif
    782                
    783 #ifdef __64_BITS__
    784                 printf("%18p ", call);
    785 #endif
    786                
    787                 spinlock_lock(&call->forget_lock);
    788 
    789                 printf("%-8" PRIun " %-6" PRIun " %-6" PRIun " %-6" PRIun
    790                     " %-6" PRIun " %-6" PRIun " %-7x",
    791                     IPC_GET_IMETHOD(call->data), IPC_GET_ARG1(call->data),
    792                     IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
    793                     IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
    794                     call->flags);
    795 
    796                 if (call->forget) {
    797                         printf(" ? (call forgotten)\n");
    798                 } else {
    799                         printf(" %" PRIu64 " (%s)\n",
    800                             call->sender->taskid, call->sender->name);
    801                 }
    802 
    803                 spinlock_unlock(&call->forget_lock);
    804         }
    805617}
    806618
     
    876688       
    877689        printf(" --- incomming calls ---\n");
    878         ipc_print_call_list(&task->answerbox.calls);
     690        list_foreach(task->answerbox.calls, cur) {
     691                call_t *call = list_get_instance(cur, call_t, link);
     692               
     693#ifdef __32_BITS__
     694                printf("%10p ", call);
     695#endif
     696               
     697#ifdef __64_BITS__
     698                printf("%18p ", call);
     699#endif
     700               
     701                printf("%-8" PRIun " %-6" PRIun " %-6" PRIun " %-6" PRIun
     702                    " %-6" PRIun " %-6" PRIun " %-7x %" PRIu64 " (%s)\n",
     703                    IPC_GET_IMETHOD(call->data), IPC_GET_ARG1(call->data),
     704                    IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
     705                    IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
     706                    call->flags, call->sender->taskid, call->sender->name);
     707        }
     708       
    879709        printf(" --- dispatched calls ---\n");
    880         ipc_print_call_list(&task->answerbox.dispatched_calls);
     710        list_foreach(task->answerbox.dispatched_calls, cur) {
     711                call_t *call = list_get_instance(cur, call_t, link);
     712               
     713#ifdef __32_BITS__
     714                printf("%10p ", call);
     715#endif
     716               
     717#ifdef __64_BITS__
     718                printf("%18p ", call);
     719#endif
     720               
     721                printf("%-8" PRIun " %-6" PRIun " %-6" PRIun " %-6" PRIun
     722                    " %-6" PRIun " %-6" PRIun " %-7x %" PRIu64 " (%s)\n",
     723                    IPC_GET_IMETHOD(call->data), IPC_GET_ARG1(call->data),
     724                    IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
     725                    IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
     726                    call->flags, call->sender->taskid, call->sender->name);
     727        }
     728       
    881729        printf(" --- incoming answers ---\n");
    882         ipc_print_call_list(&task->answerbox.answers);
     730        list_foreach(task->answerbox.answers, cur) {
     731                call_t *call = list_get_instance(cur, call_t, link);
     732               
     733#ifdef __32_BITS__
     734                printf("%10p ", call);
     735#endif
     736               
     737#ifdef __64_BITS__
     738                printf("%18p ", call);
     739#endif
     740               
     741                printf("%-8" PRIun " %-6" PRIun " %-6" PRIun " %-6" PRIun
     742                    " %-6" PRIun " %-6" PRIun " %-7x %" PRIu64 " (%s)\n",
     743                    IPC_GET_IMETHOD(call->data), IPC_GET_ARG1(call->data),
     744                    IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
     745                    IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
     746                    call->flags, call->sender->taskid, call->sender->name);
     747        }
    883748       
    884749        irq_spinlock_unlock(&task->answerbox.lock, false);
Note: See TracChangeset for help on using the changeset viewer.