Changeset 151 in libcf for trunk/src/cf_socket.c
- Timestamp:
- 10/31/13 10:17:24 (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/cf_socket.c
r142 r151 9 9 #include "cf_local.h" 10 10 #include "cf_error.h" 11 #include "cf_util.h" 11 12 12 13 #include <stdio.h> … … 33 34 #endif 34 35 36 #define ASSERT_CTX(__ctx) \ 37 if (__ctx == NULL) \ 38 return CF_ERROR_SOCKET_INVALID_CTX 39 40 #define ASSERT_ARGS(x) \ 41 if ((x)) \ 42 return CF_ERROR_SOCKET_INVALID_ARGS 43 35 44 #define ASSERT_INIT() \ 36 45 if (!CF_Socket_IsInitialized ()) \ 46 return CF_ERROR_SOCKET_NOT_INITIALIZED 47 48 #define TRY_INIT() \ 49 if (CF_Socket_Initialize ()) \ 37 50 return CF_ERROR_SOCKET_NOT_INITIALIZED 38 51 … … 45 58 # define sa_family_t unsigned short 46 59 # define close(__sock) closesocket(__sock) 47 # define GET_SYSTEM_ERROR() WSAGetLastError ()48 60 # define ERROR_INTR WSAEINTR 49 61 #else 50 # define GET_SYSTEM_ERROR() errno51 62 # define ERROR_INTR EINTR 52 63 #endif 64 65 #define MAX_IP_LENGTH 256 66 67 typedef struct __cf_socket_ctx__ 68 { 69 int sock; 70 int timeout; 71 char ip[MAX_IP_LENGTH]; 72 unsigned short port; 73 } CF_SOCKET_CONTEXT; 53 74 54 75 static CF_BOOL gInitialized = CF_FALSE; … … 64 85 int flags = fcntl (sock, F_GETFL, 0); 65 86 66 if (boolean) 67 flags |= O_NONBLOCK; 68 else 69 flags &= ~O_NONBLOCK; 87 if (boolean) flags |= O_NONBLOCK; 88 else flags &= ~O_NONBLOCK; 70 89 71 90 fcntl (sock, F_SETFL, flags); … … 74 93 75 94 static int 76 CF_Socket_Local_SetLinger (const int sock) 77 { 78 struct linger linger; 79 80 linger.l_onoff = 1; 81 linger.l_linger = 0; 82 83 return CF_Socket_SetOption (sock, 84 SO_LINGER, 85 &linger, 86 sizeof (linger)); 87 } 88 89 static int 90 CF_Socket_Local_SetReUseAddr (const int sock) 91 { 92 int reuseaddr = 1; 93 94 return CF_Socket_SetOption (sock, 95 SO_REUSEADDR, 96 &reuseaddr, 97 sizeof (reuseaddr)); 98 } 99 100 static int 101 CF_Socket_Local_CheckTimeout (const int sock, 102 const int timeout) 103 { 104 int result = 0; 105 fd_set readfds; 106 struct timeval tv; 107 int error; 108 109 if (!(timeout > CF_SOCKET_NO_TIMEOUT)) 110 return CF_OK; 111 112 tv.tv_sec = timeout; 113 tv.tv_usec = 0; 114 115 FD_ZERO (&readfds); 116 FD_SET (sock, &readfds); 117 118 while ((result = select (sock + 1, &readfds, NULL, NULL, &tv)) < 0) 119 { 120 error = GET_SYSTEM_ERROR (); 121 if (error != ERROR_INTR) 122 { 123 result = CF_ERROR_SOCKET_TIMEOUT; 124 return result; 125 } 126 } 127 128 if (FD_ISSET (sock, &readfds) == 0) 129 { 130 return CF_ERROR_SOCKET_INTERNAL; 131 } 132 133 return CF_OK; 134 } 135 136 /** 137 * 소켓의 초기화 상태 확인 138 * 139 * \return 초기화 된 경우, CF_TRUE; 그렇지 않은 경우, CF_FALSE 140 */ 141 CF_BOOL 142 CF_Socket_IsInitialized (void) 143 { 144 return gInitialized; 145 } 146 147 /** 148 * 소켓 초기화 149 * 150 * \return 성공 시, CF_OK; 실패 시, 오류 코드 151 */ 152 int 153 CF_Socket_Initialize (void) 154 { 155 #if defined(_WIN32) || defined(_WIN64) 156 WSADATA winsockData; 157 if (WSAStartup (MAKEWORD (2, 0), &winsockData)) 158 return CF_ERROR_SOCKET_INITIALIZE; 159 #endif 160 gInitialized = CF_TRUE; 161 162 return CF_OK; 163 } 164 165 /** 166 * 소켓 해제 167 * 168 * \return 성공 시, CF_OK; 실패 시, 오류 코드 169 */ 170 int 171 CF_Socket_Finalize (void) 172 { 173 ASSERT_INIT (); 174 175 #if defined(_WIN32) || defined(_WIN64) 176 if (WSACleanup ()) 177 return CF_ERROR_SOCKET_FINALIZE; 178 #endif 179 180 return CF_OK; 181 } 182 183 /** 184 * 소켓 닫기 185 * 186 * \return 성공 시, CF_OK; 실패 시, 오류 코드 187 * 188 * \param sock 소켓 189 */ 190 int 191 CF_Socket_Close (const int sock) 192 { 193 int result = 0; 194 195 ASSERT_SOCKET (sock); 196 197 result = close (sock); 198 199 if (result != 0) 200 return CF_ERROR_SOCKET_CLOSE; 201 202 return CF_OK; 203 } 204 205 /** 206 * 소켓 옵션 설정 207 * 208 * \return 성공 시 CF_OK; 실패 시, 오류 코드 209 * 210 * \param sock 소켓 211 * \param optname 옵션 이름 212 * \param optval 설정할 옵션 값의 메모리 213 * \param optlen 설정할 옵션의 길이 214 */ 215 int 216 CF_Socket_SetOption (const int sock, 217 const int optname, 218 const void * optval, 219 const size_t optlen) 95 CF_Socket_Local_SetOption (const int sock, 96 const int optname, 97 const void * optval, 98 const size_t optlen) 220 99 { 221 100 int result = 0; … … 238 117 } 239 118 240 /** 241 * 소켓 옵션 얻기 242 * 243 * \return 성공 시 CF_OK; 실패 시, 오류 코드 244 * 245 * \param sock 소켓 246 * \param optname 옵션 이름 247 * \param optval 옵션 값을 가져올 메모리 248 * \param optlen 옵션 길이를 가져올 메모리 249 */ 250 int 251 CF_Socket_GetOption (const int sock, 252 const int optname, 253 void * optval, 254 size_t * optlen) 119 static int 120 CF_Socket_Local_GetOption (const int sock, 121 const int optname, 122 void * optval, 123 size_t * optlen) 255 124 { 256 125 int result = 0; … … 273 142 } 274 143 144 static int 145 CF_Socket_Local_SetLinger (const int sock) 146 { 147 struct linger linger; 148 149 linger.l_onoff = 1; 150 linger.l_linger = 0; 151 152 return CF_Socket_Local_SetOption (sock, 153 SO_LINGER, 154 &linger, 155 sizeof (linger)); 156 } 157 158 static int 159 CF_Socket_Local_SetReUseAddr (const int sock) 160 { 161 int reuseaddr = 1; 162 163 return CF_Socket_Local_SetOption (sock, 164 SO_REUSEADDR, 165 &reuseaddr, 166 sizeof (reuseaddr)); 167 } 168 169 static int 170 CF_Socket_Local_CheckTimeout (const int sock, 171 const int timeout) 172 { 173 int result = 0; 174 fd_set readfds; 175 struct timeval tv; 176 int error; 177 178 if (timeout <= CF_SOCKET_NO_TIMEOUT) 179 return CF_OK; 180 181 tv.tv_sec = timeout; 182 tv.tv_usec = 0; 183 184 FD_ZERO (&readfds); 185 FD_SET (sock, &readfds); 186 187 while ((result = select (sock + 1, &readfds, NULL, NULL, &tv)) < 0) 188 { 189 error = CF_Util_GetSystemError (); 190 if (error != ERROR_INTR) 191 { 192 result = CF_ERROR_SOCKET_TIMEOUT; 193 return result; 194 } 195 } 196 197 if (FD_ISSET (sock, &readfds) == 0) 198 return CF_ERROR_SOCKET_CHECK_DESC_SET; 199 200 return CF_OK; 201 } 202 203 /** 204 * 소켓의 초기화 상태 확인 205 * 206 * \return 초기화 된 경우, CF_TRUE; 그렇지 않은 경우, CF_FALSE 207 */ 208 static CF_BOOL 209 CF_Socket_IsInitialized (void) 210 { 211 return gInitialized; 212 } 213 214 /** 215 * 소켓 초기화 216 * 217 * \return 성공 시, CF_OK; 실패 시, 오류 코드 218 */ 219 int 220 CF_Socket_Initialize (void) 221 { 222 if (!CF_Socket_IsInitialized ()) 223 { 224 #if defined(_WIN32) || defined(_WIN64) 225 WSADATA winsockData; 226 if (WSAStartup (MAKEWORD (2, 0), &winsockData)) 227 return CF_ERROR_SOCKET_INITIALIZE; 228 #endif 229 gInitialized = CF_TRUE; 230 } 231 232 return CF_OK; 233 } 234 235 /** 236 * 소켓 해제 237 * 238 * \return 성공 시, CF_OK; 실패 시, 오류 코드 239 */ 240 int 241 CF_Socket_Finalize (void) 242 { 243 ASSERT_INIT (); 244 245 #if defined(_WIN32) || defined(_WIN64) 246 if (WSACleanup ()) 247 return CF_ERROR_SOCKET_FINALIZE; 248 #endif 249 250 return CF_OK; 251 } 252 253 /** 254 * 소켓 컨텍스트 생성 255 * 256 * \return 성공 시, CF_OK; 실패 시, 오류 코드 257 * 258 * \param ctx 소켓 컨텍스트 259 */ 260 int 261 CF_Socket_Create (cf_ctx * ctx) 262 { 263 int result = 0; 264 265 CF_SOCKET_CONTEXT * context = NULL; 266 267 ASSERT_CTX (ctx); 268 269 TRY 270 { 271 context = NEWCTX (CF_SOCKET_CONTEXT); 272 if (context == NULL) 273 { 274 result = CF_ERROR_SOCKET_CREATE_CTX; 275 TRY_BREAK; 276 } 277 278 context->sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); 279 if (context->sock < 0) 280 { 281 result = CF_ERROR_SOCKET_CREATE; 282 TRY_BREAK; 283 } 284 285 context->timeout = CF_SOCKET_NO_TIMEOUT; 286 *ctx = (CF_SOCKET_CONTEXT *) context; 287 } 288 CATCH_IF (result < 0) 289 { 290 CF_Socket_Close (context); 291 } 292 293 return result; 294 } 295 296 /** 297 * 소켓 닫기 298 * 299 * \return 성공 시, CF_OK; 실패 시, 오류 코드 300 * 301 * \param ctx 소켓 컨텍스트 302 */ 303 int 304 CF_Socket_Close (cf_ctx ctx) 305 { 306 int result = 0; 307 int sock = 0; 308 309 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 310 311 ASSERT_CTX (context); 312 ASSERT_SOCKET (context->sock); 313 314 sock = context->sock; 315 316 free (context); 317 318 result = close (sock); 319 320 if (result != 0) 321 return CF_ERROR_SOCKET_CLOSE; 322 323 return CF_OK; 324 } 325 275 326 /** 276 327 * 소켓 연결 277 328 * 278 * \return 성공 시, 연결된 소켓; 실패 시, 오류 코드 279 * 280 * \param ip 연결할 호스트의 주소 (도메인 이름 가능) 281 * \param port 연결할 호스트의 포트번호 282 */ 283 int 284 CF_Socket_Connect (const char * ip, 329 * \return 성공 시, CF_OK; 실패 시, 오류 코드 330 * 331 * \param ctx 소켓 컨텍스트 332 * \param ip 연결할 호스트의 주소 (도메인 이름 가능) 333 * \param port 연결할 호스트의 포트번호 334 */ 335 int 336 CF_Socket_Connect (cf_ctx ctx, 337 const char * ip, 285 338 const unsigned short port) 286 {287 return CF_Socket_ConnectTimeout (ip, port, CF_SOCKET_NO_TIMEOUT);288 }289 290 /**291 * 타임아웃 동안 소켓 연결292 *293 * \return 성공 시, 연결된 소켓; 실패 시, 오류 코드294 *295 * \param ip 연결할 호스트의 주소 (도메인 이름 가능)296 * \param port 연결할 호스트의 포트번호297 * \param timeout 타임아웃 (초)298 *299 * \see CF_SOCKET_NO_TIMEOUT300 */301 int302 CF_Socket_ConnectTimeout (const char * ip,303 const unsigned short port,304 const int timeout)305 339 { 306 340 int result = 0; 307 341 int sock = 0; 342 int timeout = 0; 308 343 struct sockaddr_in address; 309 344 struct hostent * hostEnt; … … 315 350 fd_set writefds; 316 351 317 ASSERT_INIT (); 318 319 /* 1. create socket */ 320 sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); 321 if (sock < 0) 322 return CF_ERROR_SOCKET_CREATE; 352 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 353 354 ASSERT_CTX (ctx); 355 TRY_INIT (); 356 357 /* 1. get and set socket info. */ 358 sock = context->sock; 359 snprintf (context->ip, sizeof (context->ip), "%s", ip); 360 context->port = port; 361 timeout = context->timeout; 323 362 324 363 /* 2. set data */ … … 390 429 retval = 0; 391 430 392 result = CF_Socket_GetOption ( sock, SO_ERROR, &retval, &length);431 result = CF_Socket_GetOption (ctx, SO_ERROR, &retval, &length); 393 432 if (result < 0) 394 433 { … … 410 449 CATCH_IF (result < 0) 411 450 { 412 CF_Socket_Close ( sock);451 CF_Socket_Close (ctx); 413 452 return result; 414 453 } … … 417 456 CF_Socket_Local_SetNonBlocking (sock, CF_FALSE); 418 457 419 return sock;458 return result; 420 459 } 421 460 … … 423 462 * 서버 열기 424 463 * 425 * \return 성공 시, 서버 소켓; 실패 시, 오류 코드 426 * 464 * \return 성공 시, CF_OK; 실패 시, 오류 코드 465 * 466 * \param ctx 소켓 컨텍스트 427 467 * \param port 서버 포트 428 468 * \param backlog listen 시의 backlog 수 429 469 */ 430 470 int 431 CF_Socket_Server (const unsigned short port, 471 CF_Socket_Server (cf_ctx ctx, 472 const unsigned short port, 432 473 const int backlog) 433 474 { … … 436 477 struct sockaddr_in address; 437 478 438 ASSERT_INIT (); 439 440 sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); 441 if (sock < 0) 442 return CF_ERROR_SOCKET_CREATE; 479 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 480 481 ASSERT_CTX (ctx); 482 TRY_INIT (); 483 484 sock = context->sock; 485 context->port = port; 443 486 444 487 address.sin_family = AF_INET; … … 462 505 } 463 506 507 snprintf (context->ip, sizeof (context->ip), 508 "%s", 509 inet_ntoa(address.sin_addr)); 510 464 511 result = listen (sock, backlog); 465 512 if (result < 0) … … 471 518 CATCH_IF (result < 0) 472 519 { 473 CF_Socket_Close (sock); 474 return result; 475 } 476 477 return sock; 478 } 479 480 /** 481 * 소켓 연결 482 * 483 * \return 성공 시, 연결된 소켓; 실패 시, 오류 코드 484 * 485 * \param sock 서버 소켓 486 */ 487 int 488 CF_Socket_Accept (const int sock) 489 { 490 int sockClient = 0; 520 CF_Socket_Close (ctx); 521 } 522 523 return result; 524 } 525 526 /** 527 * 소켓 연결 받기 528 * 529 * \return 성공 시, CF_OK; 실패 시, 오류 코드 530 * 531 * \param ctx 소켓 컨텍스트 532 * \param client 연결을 수락할 client 소켓 컨텍스트 533 */ 534 int 535 CF_Socket_Accept (const cf_ctx ctx, 536 cf_ctx * client) 537 { 538 int result = 0; 491 539 struct sockaddr_in address; 492 540 socklen_t len = sizeof (address); 493 541 494 ASSERT_SOCKET (sock); 495 496 sockClient = accept (sock, (struct sockaddr *) &address, &len); 497 if (sockClient < 0) 542 CF_SOCKET_CONTEXT * clntctx = NULL; 543 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 544 545 ASSERT_CTX (context); 546 ASSERT_CTX (client); 547 ASSERT_SOCKET (context->sock); 548 549 result = CF_Socket_Create ((cf_ctx *)&clntctx); 550 if (result < 0) 551 return result; 552 553 close (clntctx->sock); 554 555 clntctx->sock = accept (context->sock, (struct sockaddr *) &address, &len); 556 if (clntctx->sock < 0) 557 { 558 CF_Socket_Close ((cf_ctx *)clntctx); 498 559 return CF_ERROR_SOCKET_ACCEPT; 499 500 return sockClient; 560 } 561 562 *client = (cf_ctx) clntctx; 563 564 return CF_OK; 501 565 } 502 566 … … 506 570 * \return 성공 시, CF_OK; 실패 시, 오류 코드 507 571 * 508 * \param sock 소켓 509 * \param buf 송신할 데이터 572 * \param ctx 소켓 컨텍스트 * \param buf 송신할 데이터 510 573 * \param len 송신할 데이터의 길이 511 574 */ 512 575 int 513 CF_Socket_Send (const int sock,576 CF_Socket_Send (const cf_ctx ctx, 514 577 const void * buf, 515 578 const size_t len) 516 579 { 517 return CF_Socket_SendTimeout (sock, buf, len, CF_SOCKET_NO_TIMEOUT);518 }519 520 /**521 * 타임아웃 동안 데이터 송신522 *523 * \return 성공 시, CF_OK; 실패 시, 오류 코드524 *525 * \param sock 소켓526 * \param buf 송신할 데이터527 * \param len 송신할 데이터의 길이528 * \param timeout 타임아웃 (초)529 *530 * \see CF_SOCKET_NO_TIMEOUT531 */532 int533 CF_Socket_SendTimeout (const int sock,534 const void * buf,535 const size_t len,536 const int timeout)537 {538 580 int result = 0; 539 581 540 ASSERT_SOCKET (sock); 541 542 result = (int) send (sock, buf, len, 0); 582 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 583 584 ASSERT_CTX (context); 585 ASSERT_SOCKET (context->sock); 586 587 result = (int) send (context->sock, buf, len, 0); 543 588 if (result != len) 544 589 return CF_ERROR_SOCKET_SEND; … … 552 597 * \return 성공 시, 수신한 데이터의 길이; 실패 시, 오류 코드 553 598 * 554 * \param sock 소켓599 * \param ctx 소켓 컨텍스트 555 600 * \param buf 데이터를 수신할 버퍼 556 601 * \param len 데이터를 수신할 버퍼의 최대 크기 557 602 */ 558 603 int 559 CF_Socket_Recv (const int sock,604 CF_Socket_Recv (const cf_ctx ctx, 560 605 void * buf, 561 606 const size_t len) 562 607 { 563 return CF_Socket_RecvTimeout (sock, buf, len, CF_SOCKET_NO_TIMEOUT);564 }565 566 /**567 * 타임아웃 동안 데이터 수신568 *569 * \return 성공 시, 수신한 데이터의 길이; 실패 시, 오류 코드570 *571 * \param sock 소켓572 * \param buf 데이터를 수신할 버퍼573 * \param len 데이터를 수신할 버퍼의 최대 크기574 * \param timeout 타임아웃 (초)575 *576 * \see CF_SOCKET_NO_TIMEOUT577 */578 int579 CF_Socket_RecvTimeout (const int sock,580 void * buf,581 const size_t len,582 const int timeout)583 {584 608 int result = 0; 585 609 586 ASSERT_SOCKET (sock); 587 588 result = CF_Socket_Local_CheckTimeout (sock, timeout); 610 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 611 612 ASSERT_CTX (context); 613 ASSERT_SOCKET (context->sock); 614 615 result = CF_Socket_Local_CheckTimeout (context->sock, context->timeout); 589 616 if (result < 0) 590 617 return result; 591 618 592 result = (int) recv ( sock, buf, len, 0);619 result = (int) recv (context->sock, buf, len, 0); 593 620 if (result < 0) 594 621 return CF_ERROR_SOCKET_RECV; … … 596 623 return result; 597 624 } 625 626 /** 627 * 소켓 옵션 설정 628 * 629 * \return 성공 시 CF_OK; 실패 시, 오류 코드 630 * 631 * \param ctx 소켓 컨텍스트 632 * \param optname 옵션 이름 633 * \param optval 설정할 옵션 값의 메모리 634 * \param optlen 설정할 옵션의 길이 635 */ 636 int 637 CF_Socket_SetOption (const cf_ctx ctx, 638 const int optname, 639 const void * optval, 640 const size_t optlen) 641 { 642 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 643 644 ASSERT_CTX (context); 645 646 return CF_Socket_Local_SetOption (context->sock, 647 optname, 648 optval, 649 optlen); 650 } 651 652 /** 653 * 소켓 옵션 얻기 654 * 655 * \return 성공 시 CF_OK; 실패 시, 오류 코드 656 * 657 * \param ctx 소켓 컨텍스트 658 * \param optname 옵션 이름 659 * \param optval 옵션 값을 가져올 메모리 660 * \param optlen 옵션 길이를 가져올 메모리 661 */ 662 int 663 CF_Socket_GetOption (const cf_ctx ctx, 664 const int optname, 665 void * optval, 666 size_t * optlen) 667 { 668 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 669 670 ASSERT_CTX (context); 671 672 return CF_Socket_Local_GetOption (context->sock, 673 optname, 674 optval, 675 optlen); 676 } 677 678 /** 679 * 소켓 컨텍스트에 타임아웃 설정 680 * 681 * \return CF_OK 반환 682 * 683 * \param ctx 소켓 컨텍스트 684 * \param timeout 설정할 타임아웃 시간 685 */ 686 int 687 CF_Socket_SetTimeOut (cf_ctx ctx, 688 int timeout) 689 { 690 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 691 692 ASSERT_CTX (context); 693 694 context->timeout = timeout; 695 696 return CF_OK; 697 } 698 699 /** 700 * 소켓 컨텍스트에 타임아웃 설정 701 * 702 * \return 성공 시 CF_OK; 실패 시, 오류 코드 703 * 704 * \param ctx 소켓 컨텍스트 705 * \param ip ip 주소를 저장할 충분한 공간의 메모리 706 */ 707 int 708 CF_Socket_GetIP (const cf_ctx ctx, 709 char * ip) 710 { 711 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 712 713 ASSERT_CTX (context); 714 ASSERT_ARGS (ip == NULL); 715 716 snprintf (ip, sizeof (context->ip), "%s", context->ip); 717 718 return CF_OK; 719 } 720 721 /** 722 * 소켓 컨텍스트에 타임아웃 설정 723 * 724 * \return 성공 시 CF_OK; 실패 시, 오류 코드 725 * 726 * \param ctx 소켓 컨텍스트 727 * \param port 포트 번호 저장할 포인터 728 */ 729 int 730 CF_Socket_GetPort (const cf_ctx ctx, 731 unsigned short * port) 732 { 733 CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; 734 735 ASSERT_ARGS (port == NULL); 736 737 *port = context->port; 738 739 return CF_OK; 740 }
Note:
See TracChangeset
for help on using the changeset viewer.