1 /* 2 * LICENSE: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Console Server DLL 4 * FILE: win32ss/user/winsrv/consrv/handle.c 5 * PURPOSE: Console I/O Handles functions 6 * PROGRAMMERS: David Welch 7 * Jeffrey Morlan 8 * Hermes Belusca-Maito (hermes.belusca@sfr.fr) 9 */ 10 11 /* INCLUDES *******************************************************************/ 12 13 #include "consrv.h" 14 15 #include <win/console.h> 16 17 #define NDEBUG 18 #include <debug.h> 19 20 /* GLOBALS ********************************************************************/ 21 22 /* Console handle */ 23 typedef struct _CONSOLE_IO_HANDLE 24 { 25 PCONSOLE_IO_OBJECT Object; /* The object on which the handle points to */ 26 ULONG Access; 27 ULONG ShareMode; 28 BOOLEAN Inheritable; 29 } CONSOLE_IO_HANDLE, *PCONSOLE_IO_HANDLE; 30 31 32 /* PRIVATE FUNCTIONS **********************************************************/ 33 34 static LONG 35 AdjustHandleCounts(IN PCONSOLE_IO_HANDLE Handle, 36 IN LONG Change) 37 { 38 PCONSOLE_IO_OBJECT Object = Handle->Object; 39 40 DPRINT("AdjustHandleCounts(0x%p, %d), Object = 0x%p\n", 41 Handle, Change, Object); 42 DPRINT("\tAdjustHandleCounts(0x%p, %d), Object = 0x%p, Object->ReferenceCount = %d, Object->Type = %lu\n", 43 Handle, Change, Object, Object->ReferenceCount, Object->Type); 44 45 if (Handle->Access & GENERIC_READ) Object->AccessRead += Change; 46 if (Handle->Access & GENERIC_WRITE) Object->AccessWrite += Change; 47 if (!(Handle->ShareMode & FILE_SHARE_READ)) Object->ExclusiveRead += Change; 48 if (!(Handle->ShareMode & FILE_SHARE_WRITE)) Object->ExclusiveWrite += Change; 49 50 Object->ReferenceCount += Change; 51 52 return Object->ReferenceCount; 53 } 54 55 static VOID 56 ConSrvCloseHandle(IN PCONSOLE_IO_HANDLE Handle) 57 { 58 PCONSOLE_IO_OBJECT Object = Handle->Object; 59 if (Object != NULL) 60 { 61 /* 62 * If this is a input handle, notify and dereference 63 * all the waits related to this handle. 64 */ 65 if (Object->Type == INPUT_BUFFER) 66 { 67 // PCONSOLE_INPUT_BUFFER InputBuffer = (PCONSOLE_INPUT_BUFFER)Object; 68 PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Object->Console; 69 70 /* 71 * Wake up all the writing waiters related to this handle for this 72 * input buffer, if any, then dereference them and purge them all 73 * from the list. 74 * To select them amongst all the waiters for this input buffer, 75 * pass the handle pointer to the waiters, then they will check 76 * whether or not they are related to this handle and if so, they 77 * return. 78 */ 79 CsrNotifyWait(&Console->ReadWaitQueue, 80 TRUE, 81 NULL, 82 (PVOID)Handle); 83 if (!IsListEmpty(&Console->ReadWaitQueue)) 84 { 85 CsrDereferenceWait(&Console->ReadWaitQueue); 86 } 87 } 88 89 /* If the last handle to a screen buffer is closed, delete it... */ 90 if (AdjustHandleCounts(Handle, -1) == 0) 91 { 92 if (Object->Type == TEXTMODE_BUFFER || Object->Type == GRAPHICS_BUFFER) 93 { 94 PCONSOLE_SCREEN_BUFFER Buffer = (PCONSOLE_SCREEN_BUFFER)Object; 95 /* ...unless it's the only buffer left. Windows allows deletion 96 * even of the last buffer, but having to deal with a lack of 97 * any active buffer might be error-prone. */ 98 if (Buffer->ListEntry.Flink != Buffer->ListEntry.Blink) 99 ConDrvDeleteScreenBuffer(Buffer); 100 } 101 else if (Object->Type == INPUT_BUFFER) 102 { 103 DPRINT("Closing the input buffer\n"); 104 } 105 else 106 { 107 DPRINT1("Invalid object type %d\n", Object->Type); 108 } 109 } 110 111 /* Invalidate (zero-out) this handle entry */ 112 // Handle->Object = NULL; 113 // RtlZeroMemory(Handle, sizeof(*Handle)); 114 } 115 RtlZeroMemory(Handle, sizeof(*Handle)); // Be sure the whole entry is invalidated. 116 } 117 118 119 NTSTATUS 120 ConSrvInheritHandlesTable(IN PCONSOLE_PROCESS_DATA SourceProcessData, 121 IN PCONSOLE_PROCESS_DATA TargetProcessData) 122 { 123 NTSTATUS Status = STATUS_SUCCESS; 124 ULONG i, j; 125 126 RtlEnterCriticalSection(&SourceProcessData->HandleTableLock); 127 128 /* Inherit a handles table only if there is no already */ 129 if (TargetProcessData->HandleTable != NULL /* || TargetProcessData->HandleTableSize != 0 */) 130 { 131 Status = STATUS_UNSUCCESSFUL; 132 goto Quit; 133 } 134 135 /* Allocate a new handle table for the child process */ 136 TargetProcessData->HandleTable = ConsoleAllocHeap(HEAP_ZERO_MEMORY, 137 SourceProcessData->HandleTableSize 138 * sizeof(CONSOLE_IO_HANDLE)); 139 if (TargetProcessData->HandleTable == NULL) 140 { 141 Status = STATUS_NO_MEMORY; 142 goto Quit; 143 } 144 145 TargetProcessData->HandleTableSize = SourceProcessData->HandleTableSize; 146 147 /* 148 * Parse the parent process' handles table and, for each handle, 149 * do a copy of it and reference it, if the handle is inheritable. 150 */ 151 for (i = 0, j = 0; i < SourceProcessData->HandleTableSize; i++) 152 { 153 if (SourceProcessData->HandleTable[i].Object != NULL && 154 SourceProcessData->HandleTable[i].Inheritable) 155 { 156 /* 157 * Copy the handle data and increment the reference count of the 158 * pointed object (via the call to ConSrvCreateHandleEntry == AdjustHandleCounts). 159 */ 160 TargetProcessData->HandleTable[j] = SourceProcessData->HandleTable[i]; 161 AdjustHandleCounts(&TargetProcessData->HandleTable[j], +1); 162 ++j; 163 } 164 } 165 166 Quit: 167 RtlLeaveCriticalSection(&SourceProcessData->HandleTableLock); 168 return Status; 169 } 170 171 VOID 172 ConSrvFreeHandlesTable(IN PCONSOLE_PROCESS_DATA ProcessData) 173 { 174 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 175 176 if (ProcessData->HandleTable != NULL) 177 { 178 ULONG i; 179 180 /* 181 * ProcessData->ConsoleHandle is NULL (and the assertion fails) when 182 * ConSrvFreeHandlesTable is called in ConSrvConnect during the 183 * allocation of a new console. 184 */ 185 // ASSERT(ProcessData->ConsoleHandle); 186 if (ProcessData->ConsoleHandle != NULL) 187 { 188 /* Close all the console handles */ 189 for (i = 0; i < ProcessData->HandleTableSize; i++) 190 { 191 ConSrvCloseHandle(&ProcessData->HandleTable[i]); 192 } 193 } 194 /* Free the handles table memory */ 195 ConsoleFreeHeap(ProcessData->HandleTable); 196 ProcessData->HandleTable = NULL; 197 } 198 199 ProcessData->HandleTableSize = 0; 200 201 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 202 } 203 204 205 206 207 208 209 // ConSrvCreateObject 210 VOID 211 ConSrvInitObject(IN OUT PCONSOLE_IO_OBJECT Object, 212 IN CONSOLE_IO_OBJECT_TYPE Type, 213 IN PCONSOLE Console) 214 { 215 ASSERT(Object); 216 // if (!Object) return; 217 218 Object->Type = Type; 219 Object->Console = Console; 220 Object->ReferenceCount = 0; 221 222 Object->AccessRead = Object->AccessWrite = 0; 223 Object->ExclusiveRead = Object->ExclusiveWrite = 0; 224 } 225 226 NTSTATUS 227 ConSrvInsertObject(IN PCONSOLE_PROCESS_DATA ProcessData, 228 OUT PHANDLE Handle, 229 IN PCONSOLE_IO_OBJECT Object, 230 IN ULONG Access, 231 IN BOOLEAN Inheritable, 232 IN ULONG ShareMode) 233 { 234 #define IO_HANDLES_INCREMENT 2 * 3 235 236 ULONG i = 0; 237 PCONSOLE_IO_HANDLE Block; 238 239 // NOTE: Commented out because calling code always lock HandleTableLock before. 240 // RtlEnterCriticalSection(&ProcessData->HandleTableLock); 241 242 ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 243 (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 244 245 if (ProcessData->HandleTable) 246 { 247 for (i = 0; i < ProcessData->HandleTableSize; i++) 248 { 249 if (ProcessData->HandleTable[i].Object == NULL) 250 break; 251 } 252 } 253 254 if (i >= ProcessData->HandleTableSize) 255 { 256 /* Allocate a new handles table */ 257 Block = ConsoleAllocHeap(HEAP_ZERO_MEMORY, 258 (ProcessData->HandleTableSize + 259 IO_HANDLES_INCREMENT) * sizeof(CONSOLE_IO_HANDLE)); 260 if (Block == NULL) 261 { 262 // RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 263 return STATUS_UNSUCCESSFUL; 264 } 265 266 /* If we previously had a handles table, free it and use the new one */ 267 if (ProcessData->HandleTable) 268 { 269 /* Copy the handles from the old table to the new one */ 270 RtlCopyMemory(Block, 271 ProcessData->HandleTable, 272 ProcessData->HandleTableSize * sizeof(CONSOLE_IO_HANDLE)); 273 ConsoleFreeHeap(ProcessData->HandleTable); 274 } 275 ProcessData->HandleTable = Block; 276 ProcessData->HandleTableSize += IO_HANDLES_INCREMENT; 277 } 278 279 ProcessData->HandleTable[i].Object = Object; 280 ProcessData->HandleTable[i].Access = Access; 281 ProcessData->HandleTable[i].Inheritable = Inheritable; 282 ProcessData->HandleTable[i].ShareMode = ShareMode; 283 AdjustHandleCounts(&ProcessData->HandleTable[i], +1); 284 *Handle = ULongToHandle((i << 2) | 0x3); 285 286 // RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 287 288 return STATUS_SUCCESS; 289 } 290 291 NTSTATUS 292 ConSrvRemoveObject(IN PCONSOLE_PROCESS_DATA ProcessData, 293 IN HANDLE Handle) 294 { 295 ULONG Index = HandleToULong(Handle) >> 2; 296 297 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 298 299 ASSERT(ProcessData->HandleTable); 300 // ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 301 // (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 302 303 if (Index >= ProcessData->HandleTableSize || 304 ProcessData->HandleTable[Index].Object == NULL) 305 { 306 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 307 return STATUS_INVALID_HANDLE; 308 } 309 310 ASSERT(ProcessData->ConsoleHandle); 311 ConSrvCloseHandle(&ProcessData->HandleTable[Index]); 312 313 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 314 return STATUS_SUCCESS; 315 } 316 317 NTSTATUS 318 ConSrvGetObject(IN PCONSOLE_PROCESS_DATA ProcessData, 319 IN HANDLE Handle, 320 OUT PCONSOLE_IO_OBJECT* Object, 321 OUT PVOID* Entry OPTIONAL, 322 IN ULONG Access, 323 IN BOOLEAN LockConsole, 324 IN CONSOLE_IO_OBJECT_TYPE Type) 325 { 326 // NTSTATUS Status; 327 ULONG Index = HandleToULong(Handle) >> 2; 328 PCONSOLE_IO_HANDLE HandleEntry = NULL; 329 PCONSOLE_IO_OBJECT ObjectEntry = NULL; 330 // PCONSRV_CONSOLE ObjectConsole; 331 332 ASSERT(Object); 333 if (Entry) *Entry = NULL; 334 335 DPRINT("ConSrvGetObject -- Object: 0x%x, Handle: 0x%x\n", Object, Handle); 336 337 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 338 339 if ( IsConsoleHandle(Handle) && 340 Index < ProcessData->HandleTableSize ) 341 { 342 HandleEntry = &ProcessData->HandleTable[Index]; 343 ObjectEntry = HandleEntry->Object; 344 } 345 346 if ( HandleEntry == NULL || 347 ObjectEntry == NULL || 348 (HandleEntry->Access & Access) == 0 || 349 /*(Type != 0 && ObjectEntry->Type != Type)*/ 350 (Type != 0 && (ObjectEntry->Type & Type) == 0) ) 351 { 352 DPRINT("ConSrvGetObject -- Invalid handle 0x%x of type %lu with access %lu ; retrieved object 0x%x (handle 0x%x) of type %lu with access %lu\n", 353 Handle, Type, Access, ObjectEntry, HandleEntry, (ObjectEntry ? ObjectEntry->Type : 0), (HandleEntry ? HandleEntry->Access : 0)); 354 355 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 356 return STATUS_INVALID_HANDLE; 357 } 358 359 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 360 361 // Status = ConSrvGetConsole(ProcessData, &ObjectConsole, LockConsole); 362 // if (NT_SUCCESS(Status)) 363 if (ConDrvValidateConsoleUnsafe(ObjectEntry->Console, CONSOLE_RUNNING, LockConsole)) 364 { 365 _InterlockedIncrement(&ObjectEntry->Console->ReferenceCount); 366 367 /* Return the objects to the caller */ 368 *Object = ObjectEntry; 369 if (Entry) *Entry = HandleEntry; 370 371 // RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 372 return STATUS_SUCCESS; 373 } 374 else 375 { 376 // RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 377 return STATUS_INVALID_HANDLE; 378 } 379 } 380 381 VOID 382 ConSrvReleaseObject(IN PCONSOLE_IO_OBJECT Object, 383 IN BOOLEAN IsConsoleLocked) 384 { 385 PCONSRV_CONSOLE ObjectConsole = (PCONSRV_CONSOLE)Object->Console; 386 ConSrvReleaseConsole(ObjectConsole, IsConsoleLocked); 387 } 388 389 390 /* PUBLIC SERVER APIS *********************************************************/ 391 392 /* API_NUMBER: ConsolepOpenConsole */ 393 CON_API(SrvOpenConsole, 394 CONSOLE_OPENCONSOLE, OpenConsoleRequest) 395 { 396 /* 397 * This API opens a handle to either the input buffer or to 398 * a screen-buffer of the console of the current process. 399 */ 400 401 NTSTATUS Status; 402 DWORD DesiredAccess = OpenConsoleRequest->DesiredAccess; 403 DWORD ShareMode = OpenConsoleRequest->ShareMode; 404 PCONSOLE_IO_OBJECT Object; 405 406 OpenConsoleRequest->Handle = INVALID_HANDLE_VALUE; 407 408 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 409 410 /* 411 * Open a handle to either the active screen buffer or the input buffer. 412 */ 413 if (OpenConsoleRequest->HandleType == HANDLE_OUTPUT) 414 { 415 Object = &Console->ActiveBuffer->Header; 416 } 417 else // HANDLE_INPUT 418 { 419 Object = &Console->InputBuffer.Header; 420 } 421 422 if (((DesiredAccess & GENERIC_READ) && Object->ExclusiveRead != 0) || 423 ((DesiredAccess & GENERIC_WRITE) && Object->ExclusiveWrite != 0) || 424 (!(ShareMode & FILE_SHARE_READ) && Object->AccessRead != 0) || 425 (!(ShareMode & FILE_SHARE_WRITE) && Object->AccessWrite != 0)) 426 { 427 DPRINT1("Sharing violation\n"); 428 Status = STATUS_SHARING_VIOLATION; 429 } 430 else 431 { 432 Status = ConSrvInsertObject(ProcessData, 433 &OpenConsoleRequest->Handle, 434 Object, 435 DesiredAccess, 436 OpenConsoleRequest->InheritHandle, 437 ShareMode); 438 } 439 440 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 441 442 return Status; 443 } 444 445 /* API_NUMBER: ConsolepDuplicateHandle */ 446 CON_API(SrvDuplicateHandle, 447 CONSOLE_DUPLICATEHANDLE, DuplicateHandleRequest) 448 { 449 NTSTATUS Status; 450 HANDLE SourceHandle = DuplicateHandleRequest->SourceHandle; 451 ULONG Index = HandleToULong(SourceHandle) >> 2; 452 PCONSOLE_IO_HANDLE Entry; 453 DWORD DesiredAccess; 454 455 DuplicateHandleRequest->TargetHandle = INVALID_HANDLE_VALUE; 456 457 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 458 459 // ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 460 // (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 461 462 if ( /** !IsConsoleHandle(SourceHandle) || **/ 463 Index >= ProcessData->HandleTableSize || 464 (Entry = &ProcessData->HandleTable[Index])->Object == NULL) 465 { 466 DPRINT1("Couldn't duplicate invalid handle 0x%p\n", SourceHandle); 467 Status = STATUS_INVALID_HANDLE; 468 goto Quit; 469 } 470 471 if (DuplicateHandleRequest->Options & DUPLICATE_SAME_ACCESS) 472 { 473 DesiredAccess = Entry->Access; 474 } 475 else 476 { 477 DesiredAccess = DuplicateHandleRequest->DesiredAccess; 478 /* Make sure the source handle has all the desired flags */ 479 if ((Entry->Access & DesiredAccess) == 0) 480 { 481 DPRINT1("Handle 0x%p only has access %X; requested %X\n", 482 SourceHandle, Entry->Access, DesiredAccess); 483 Status = STATUS_INVALID_PARAMETER; 484 goto Quit; 485 } 486 } 487 488 /* Insert the new handle inside the process handles table */ 489 Status = ConSrvInsertObject(ProcessData, 490 &DuplicateHandleRequest->TargetHandle, 491 Entry->Object, 492 DesiredAccess, 493 DuplicateHandleRequest->InheritHandle, 494 Entry->ShareMode); 495 if (NT_SUCCESS(Status) && 496 (DuplicateHandleRequest->Options & DUPLICATE_CLOSE_SOURCE)) 497 { 498 /* Close the original handle if needed */ 499 ConSrvCloseHandle(Entry); 500 } 501 502 Quit: 503 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 504 505 return Status; 506 } 507 508 /* API_NUMBER: ConsolepGetHandleInformation */ 509 CON_API(SrvGetHandleInformation, 510 CONSOLE_GETHANDLEINFO, GetHandleInfoRequest) 511 { 512 NTSTATUS Status; 513 HANDLE Handle = GetHandleInfoRequest->Handle; 514 ULONG Index = HandleToULong(Handle) >> 2; 515 PCONSOLE_IO_HANDLE Entry; 516 517 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 518 519 ASSERT(ProcessData->HandleTable); 520 // ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 521 // (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 522 523 if (!IsConsoleHandle(Handle) || 524 Index >= ProcessData->HandleTableSize || 525 (Entry = &ProcessData->HandleTable[Index])->Object == NULL) 526 { 527 Status = STATUS_INVALID_HANDLE; 528 goto Quit; 529 } 530 531 /* 532 * Retrieve the handle information flags. The console server 533 * doesn't support HANDLE_FLAG_PROTECT_FROM_CLOSE. 534 */ 535 GetHandleInfoRequest->Flags = 0; 536 if (Entry->Inheritable) GetHandleInfoRequest->Flags |= HANDLE_FLAG_INHERIT; 537 538 Status = STATUS_SUCCESS; 539 540 Quit: 541 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 542 543 return Status; 544 } 545 546 /* API_NUMBER: ConsolepSetHandleInformation */ 547 CON_API(SrvSetHandleInformation, 548 CONSOLE_SETHANDLEINFO, SetHandleInfoRequest) 549 { 550 NTSTATUS Status; 551 HANDLE Handle = SetHandleInfoRequest->Handle; 552 ULONG Index = HandleToULong(Handle) >> 2; 553 PCONSOLE_IO_HANDLE Entry; 554 555 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 556 557 ASSERT(ProcessData->HandleTable); 558 // ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 559 // (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 560 561 if (!IsConsoleHandle(Handle) || 562 Index >= ProcessData->HandleTableSize || 563 (Entry = &ProcessData->HandleTable[Index])->Object == NULL) 564 { 565 Status = STATUS_INVALID_HANDLE; 566 goto Quit; 567 } 568 569 /* 570 * Modify the handle information flags. The console server 571 * doesn't support HANDLE_FLAG_PROTECT_FROM_CLOSE. 572 */ 573 if (SetHandleInfoRequest->Mask & HANDLE_FLAG_INHERIT) 574 { 575 Entry->Inheritable = ((SetHandleInfoRequest->Flags & HANDLE_FLAG_INHERIT) != 0); 576 } 577 578 Status = STATUS_SUCCESS; 579 580 Quit: 581 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 582 583 return Status; 584 } 585 586 /* API_NUMBER: ConsolepCloseHandle */ 587 CON_API(SrvCloseHandle, 588 CONSOLE_CLOSEHANDLE, CloseHandleRequest) 589 { 590 return ConSrvRemoveObject(ProcessData, CloseHandleRequest->Handle); 591 } 592 593 /* API_NUMBER: ConsolepVerifyIoHandle */ 594 CON_API(SrvVerifyConsoleIoHandle, 595 CONSOLE_VERIFYHANDLE, VerifyHandleRequest) 596 { 597 HANDLE IoHandle = VerifyHandleRequest->Handle; 598 ULONG Index = HandleToULong(IoHandle) >> 2; 599 600 VerifyHandleRequest->IsValid = FALSE; 601 602 RtlEnterCriticalSection(&ProcessData->HandleTableLock); 603 604 // ASSERT( (ProcessData->HandleTable == NULL && ProcessData->HandleTableSize == 0) || 605 // (ProcessData->HandleTable != NULL && ProcessData->HandleTableSize != 0) ); 606 607 if (!IsConsoleHandle(IoHandle) || 608 Index >= ProcessData->HandleTableSize || 609 ProcessData->HandleTable[Index].Object == NULL) 610 { 611 DPRINT("SrvVerifyConsoleIoHandle failed\n"); 612 } 613 else 614 { 615 VerifyHandleRequest->IsValid = TRUE; 616 } 617 618 RtlLeaveCriticalSection(&ProcessData->HandleTableLock); 619 620 return STATUS_SUCCESS; 621 } 622 623 /* EOF */ 624