1 #include "first.h" 2 3 #include "fdevent.h" 4 #include "buffer.h" 5 #include "log.h" 6 7 #include <sys/types.h> 8 #include "sys-socket.h" 9 10 #include <unistd.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <errno.h> 14 #include <fcntl.h> 15 16 #ifdef _WIN32 17 #include <sys/stat.h> /* _S_IREAD _S_IWRITE */ 18 #include <io.h> 19 #include <share.h> /* _SH_DENYRW */ 20 #include <winsock2.h> 21 #endif 22 23 #ifdef SOCK_CLOEXEC 24 static int use_sock_cloexec; 25 #endif 26 #ifdef SOCK_NONBLOCK 27 static int use_sock_nonblock; 28 #endif 29 30 void fdevent_socket_nb_cloexec_init (void) 31 { 32 #ifdef SOCK_CLOEXEC 33 /* Test if SOCK_CLOEXEC is supported by kernel. 34 * Linux kernels < 2.6.27 might return EINVAL if SOCK_CLOEXEC used 35 * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=529929 36 * http://www.linksysinfo.org/index.php?threads/lighttpd-no-longer-starts-toastman-1-28-0510-7.73132/ 37 * Test if SOCK_NONBLOCK is ignored by kernel on sockets. 38 * (reported on Android running a custom ROM) 39 * https://redmine.lighttpd.net/issues/2883 40 */ 41 #ifdef SOCK_NONBLOCK 42 int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); 43 #else 44 int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); 45 #endif 46 if (fd >= 0) { 47 int flags = fcntl(fd, F_GETFL, 0); 48 #ifdef SOCK_NONBLOCK 49 use_sock_nonblock = (-1 != flags && (flags & O_NONBLOCK)); 50 #endif 51 use_sock_cloexec = 1; 52 close(fd); 53 } 54 #endif 55 } 56 57 void fdevent_setfd_cloexec(int fd) { 58 #ifdef FD_CLOEXEC 59 if (fd < 0) return; 60 force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); 61 #else 62 UNUSED(fd); 63 #endif 64 } 65 66 void fdevent_clrfd_cloexec(int fd) { 67 #ifdef FD_CLOEXEC 68 if (fd >= 0) force_assert(-1 != fcntl(fd, F_SETFD, 0)); 69 #else 70 UNUSED(fd); 71 #endif 72 } 73 74 int fdevent_fcntl_set_nb(int fd) { 75 #ifdef O_NONBLOCK 76 return fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); 77 #else 78 UNUSED(fd); 79 return 0; 80 #endif 81 } 82 83 int fdevent_fcntl_set_nb_cloexec(int fd) { 84 fdevent_setfd_cloexec(fd); 85 return fdevent_fcntl_set_nb(fd); 86 } 87 88 int fdevent_fcntl_set_nb_cloexec_sock(int fd) { 89 #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) 90 if (use_sock_cloexec && use_sock_nonblock) 91 return 0; 92 #endif 93 return fdevent_fcntl_set_nb_cloexec(fd); 94 } 95 96 int fdevent_socket_cloexec(int domain, int type, int protocol) { 97 int fd; 98 #ifdef SOCK_CLOEXEC 99 if (use_sock_cloexec) 100 return socket(domain, type | SOCK_CLOEXEC, protocol); 101 #endif 102 if (-1 != (fd = socket(domain, type, protocol))) { 103 #ifdef FD_CLOEXEC 104 force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); 105 #endif 106 } 107 return fd; 108 } 109 110 int fdevent_socket_nb_cloexec(int domain, int type, int protocol) { 111 int fd; 112 #ifdef SOCK_CLOEXEC 113 #ifdef SOCK_NONBLOCK 114 if (use_sock_cloexec && use_sock_nonblock) 115 return socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol); 116 #else 117 if (use_sock_cloexec) { 118 fd = socket(domain, type | SOCK_CLOEXEC, protocol); 119 #ifdef O_NONBLOCK 120 if (-1 != fd) force_assert(-1 != fcntl(fd,F_SETFL,O_NONBLOCK|O_RDWR)); 121 #endif 122 return fd; 123 } 124 #endif 125 #endif 126 if (-1 != (fd = socket(domain, type, protocol))) { 127 #ifdef FD_CLOEXEC 128 force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); 129 #endif 130 #ifdef O_NONBLOCK 131 force_assert(-1 != fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR)); 132 #endif 133 } 134 return fd; 135 } 136 137 int fdevent_dup_cloexec (int fd) { 138 #ifdef F_DUPFD_CLOEXEC 139 return fcntl(fd, F_DUPFD_CLOEXEC, 3); 140 #else 141 const int newfd = fcntl(fd, F_DUPFD, 3); 142 if (newfd >= 0) fdevent_setfd_cloexec(newfd); 143 return newfd; 144 #endif 145 } 146 147 #ifndef O_BINARY 148 #define O_BINARY 0 149 #endif 150 #ifndef O_LARGEFILE 151 #define O_LARGEFILE 0 152 #endif 153 #ifndef O_NOCTTY 154 #define O_NOCTTY 0 155 #endif 156 #ifndef O_NOFOLLOW 157 #define O_NOFOLLOW 0 158 #endif 159 160 /*(O_NOFOLLOW is not handled here)*/ 161 /*(Note: O_NOFOLLOW affects only the final path segment, the target file, 162 * not any intermediate symlinks along the path)*/ 163 164 /* O_CLOEXEC handled further below, if defined) */ 165 #ifdef O_NONBLOCK 166 #define FDEVENT_O_FLAGS \ 167 (O_BINARY | O_LARGEFILE | O_NOCTTY | O_NONBLOCK) 168 #else 169 #define FDEVENT_O_FLAGS \ 170 (O_BINARY | O_LARGEFILE | O_NOCTTY ) 171 #endif 172 173 int fdevent_open_cloexec(const char *pathname, int symlinks, int flags, mode_t mode) { 174 if (!symlinks) flags |= O_NOFOLLOW; 175 #ifdef O_CLOEXEC 176 return open(pathname, flags | O_CLOEXEC | FDEVENT_O_FLAGS, mode); 177 #else 178 int fd = open(pathname, flags | FDEVENT_O_FLAGS, mode); 179 #ifdef FD_CLOEXEC 180 if (fd != -1) 181 force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); 182 #endif 183 return fd; 184 #endif 185 } 186 187 188 int fdevent_open_devnull(void) { 189 #if defined(_WIN32) 190 return fdevent_open_cloexec("nul", 0, O_RDWR, 0); 191 #else 192 return fdevent_open_cloexec("/dev/null", 0, O_RDWR, 0); 193 #endif 194 } 195 196 197 int fdevent_open_dirname(char *path, int symlinks) { 198 /*(handle special cases of no dirname or dirname is root directory)*/ 199 char * const c = strrchr(path, '/'); 200 const char * const dname = (NULL != c ? c == path ? "/" : path : "."); 201 int dfd; 202 int flags = O_RDONLY; 203 #ifdef O_DIRECTORY 204 flags |= O_DIRECTORY; 205 #endif 206 if (NULL != c) *c = '\0'; 207 dfd = fdevent_open_cloexec(dname, symlinks, flags, 0); 208 if (NULL != c) *c = '/'; 209 return dfd; 210 } 211 212 213 #ifdef _WIN32 214 #include <stdio.h> 215 #endif 216 217 int fdevent_pipe_cloexec (int * const fds, const unsigned int bufsz_hint) { 218 #ifdef _WIN32 219 return _pipe(fds, bufsz_hint, _O_BINARY | _O_NOINHERIT); 220 #else 221 #ifdef HAVE_PIPE2 222 if (0 != pipe2(fds, O_CLOEXEC)) 223 #endif 224 { 225 if (0 != pipe(fds) 226 #ifdef FD_CLOEXEC 227 || 0 != fcntl(fds[0], F_SETFD, FD_CLOEXEC) 228 || 0 != fcntl(fds[1], F_SETFD, FD_CLOEXEC) 229 #endif 230 ) 231 return -1; 232 } 233 #ifdef F_SETPIPE_SZ 234 if (bufsz_hint > 65536) 235 if (0 != fcntl(fds[1], F_SETPIPE_SZ, bufsz_hint)) { } /*(ignore error)*/ 236 #else 237 UNUSED(bufsz_hint); 238 #endif 239 return 0; 240 #endif 241 } 242 243 244 int fdevent_mkostemp(char *path, int flags) { 245 #ifdef _WIN32 246 /* future: if _sopen_s() returns EEXIST, might reset template (path) with 247 * trailing "XXXXXX", and then loop to try again */ 248 int fd; /*(flags might have _O_APPEND)*/ 249 return (0 == _mktemp_s(path, strlen(path)+1)) 250 && (0 == _sopen_s(&fd, path, _O_CREAT | _O_EXCL | _O_TEMPORARY 251 | flags | _O_BINARY | _O_NOINHERIT, 252 _SH_DENYRW, _S_IREAD | _S_IWRITE)) 253 ? fd 254 : -1; 255 #elif defined(HAVE_MKOSTEMP) 256 return mkostemp(path, O_CLOEXEC | flags); 257 #else 258 #ifdef __COVERITY__ 259 /* POSIX-2008 requires mkstemp create file with 0600 perms */ 260 umask(0600); 261 #endif 262 /* coverity[secure_temp : FALSE] */ 263 const int fd = mkstemp(path); 264 if (fd < 0) return fd; 265 266 if (flags && 0 != fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | flags)) { 267 /* (should not happen; fd is regular file) */ 268 int errnum = errno; 269 close(fd); 270 errno = errnum; 271 return -1; 272 } 273 274 fdevent_setfd_cloexec(fd); 275 return fd; 276 #endif 277 } 278 279 280 int fdevent_accept_listenfd(int listenfd, struct sockaddr *addr, size_t *addrlen) { 281 int fd; 282 socklen_t len = (socklen_t) *addrlen; 283 284 #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) 285 #if defined(__NetBSD__) 286 const int sock_cloexec = 1; 287 fd = paccept(listenfd, addr, &len, NULL, SOCK_CLOEXEC | SOCK_NONBLOCK); 288 #else 289 int sock_cloexec = use_sock_cloexec; 290 if (sock_cloexec) { 291 fd = accept4(listenfd, addr, &len, SOCK_CLOEXEC | SOCK_NONBLOCK); 292 if (fd >= 0) { 293 if (!use_sock_nonblock) { 294 if (0 != fdevent_fcntl_set_nb(fd)) { 295 close(fd); 296 fd = -1; 297 } 298 } 299 } 300 else { 301 switch (errno) { 302 case ENOSYS: 303 case ENOTSUP: 304 case EPERM: 305 fd = accept(listenfd, addr, &len); 306 sock_cloexec = 0; 307 break; 308 default: 309 break; 310 } 311 } 312 } 313 else { 314 fd = accept(listenfd, addr, &len); 315 } 316 #endif 317 #else 318 const int sock_cloexec = 0; 319 fd = accept(listenfd, addr, &len); 320 #endif 321 322 if (fd >= 0) { 323 *addrlen = (size_t)len; 324 if (!sock_cloexec && 0 != fdevent_fcntl_set_nb_cloexec(fd)) { 325 close(fd); 326 fd = -1; 327 } 328 } 329 return fd; 330 } 331 332 333 #ifdef __APPLE__ 334 #include <crt_externs.h> 335 #define environ (* _NSGetEnviron()) 336 #else 337 extern char **environ; 338 #endif 339 char ** fdevent_environ (void) { return environ; } 340 341 342 #ifdef FD_CLOEXEC 343 static int fdevent_dup2_close_clrfd_cloexec(int oldfd, int newfd) { 344 if (oldfd >= 0) { 345 if (oldfd != newfd) { 346 force_assert(oldfd > STDERR_FILENO); 347 if (newfd != dup2(oldfd, newfd)) return -1; 348 } 349 else { 350 fdevent_clrfd_cloexec(newfd); 351 } 352 } 353 return newfd; 354 } 355 #else 356 static int fdevent_dup2_close_clrfd_cloexec(int oldfd, int newfd, int reuse) { 357 if (oldfd >= 0) { 358 if (oldfd != newfd) { 359 force_assert(oldfd > STDERR_FILENO); 360 if (newfd != dup2(oldfd, newfd)) return -1; 361 if (!reuse) close(oldfd); 362 } 363 } 364 return newfd; 365 } 366 #endif 367 368 369 int fdevent_set_stdin_stdout_stderr(int fdin, int fdout, int fderr) { 370 #ifdef FD_CLOEXEC 371 if (STDIN_FILENO != fdevent_dup2_close_clrfd_cloexec(fdin, STDIN_FILENO)) 372 return -1; 373 if (STDOUT_FILENO != fdevent_dup2_close_clrfd_cloexec(fdout, STDOUT_FILENO)) 374 return -1; 375 if (STDERR_FILENO != fdevent_dup2_close_clrfd_cloexec(fderr, STDERR_FILENO)) 376 return -1; 377 #else 378 if (STDIN_FILENO != fdevent_dup2_close_clrfd_cloexec(fdin, STDIN_FILENO, 379 fdin == fdout 380 || fdin == fderr)) 381 return -1; 382 if (STDOUT_FILENO != fdevent_dup2_close_clrfd_cloexec(fdout, STDOUT_FILENO, 383 fdout == fderr)) 384 return -1; 385 if (STDERR_FILENO != fdevent_dup2_close_clrfd_cloexec(fderr, STDERR_FILENO, 386 0)) 387 return -1; 388 #endif 389 390 return 0; 391 } 392 393 394 #include <stdio.h> /* perror() rename() */ 395 #include <signal.h> /* signal() */ 396 397 398 int fdevent_rename(const char *oldpath, const char *newpath) { 399 return rename(oldpath, newpath); 400 } 401 402 403 pid_t fdevent_fork_execve(const char *name, char *argv[], char *envp[], int fdin, int fdout, int fderr, int dfd) { 404 #ifdef HAVE_FORK 405 406 pid_t pid = fork(); 407 if (0 != pid) return pid; /* parent (pid > 0) or fork() error (-1 == pid) */ 408 409 /* child (0 == pid) */ 410 411 if (-1 != dfd) { 412 if (0 != fchdir(dfd)) 413 _exit(errno); 414 close(dfd); 415 } 416 417 if (0 != fdevent_set_stdin_stdout_stderr(fdin, fdout, fderr)) _exit(errno); 418 #ifndef FD_CLOEXEC 419 /*(might not be sufficient for open fds, but modern OS have FD_CLOEXEC)*/ 420 for (int i = 3; i < 256; ++i) close(i); 421 #endif 422 423 /* reset_signals which may have been ignored (SIG_IGN) */ 424 #ifdef SIGTTOU 425 signal(SIGTTOU, SIG_DFL); 426 #endif 427 #ifdef SIGTTIN 428 signal(SIGTTIN, SIG_DFL); 429 #endif 430 #ifdef SIGTSTP 431 signal(SIGTSTP, SIG_DFL); 432 #endif 433 signal(SIGPIPE, SIG_DFL); 434 435 execve(name, argv, envp ? envp : environ); 436 437 int errnum = errno; 438 int argnum = 439 (0 == strcmp(argv[0], "/bin/sh") && argv[1] && 0 == strcmp(argv[1], "-c")) 440 ? 2 441 : 0; 442 perror(argv[argnum]); 443 _exit(errnum); 444 445 #else 446 447 UNUSED(name); 448 UNUSED(argv); 449 UNUSED(envp); 450 UNUSED(fdin); 451 UNUSED(fdout); 452 UNUSED(fderr); 453 UNUSED(dfd); 454 return (pid_t)-1; 455 456 #endif 457 } 458 459 460 #ifdef HAVE_SYS_WAIT_H 461 #include <sys/wait.h> 462 #endif 463 464 int fdevent_waitpid(pid_t pid, int * const status, int nb) { 465 const int flags = nb ? WNOHANG : 0; 466 pid_t rv; 467 do { rv = waitpid(pid, status, flags); } while (-1 == rv && errno == EINTR); 468 return rv; 469 } 470 471 int fdevent_waitpid_intr(pid_t pid, int * const status) { 472 return waitpid(pid, status, 0); 473 } 474 475 476 #ifndef MSG_DONTWAIT 477 #define MSG_DONTWAIT 0 478 #endif 479 #ifndef MSG_NOSIGNAL 480 #define MSG_NOSIGNAL 0 481 #endif 482 483 484 ssize_t fdevent_socket_read_discard (int fd, char *buf, size_t sz, int family, int so_type) { 485 #if defined(MSG_TRUNC) && defined(__linux__) 486 if ((family == AF_INET || family == AF_INET6) && so_type == SOCK_STREAM) { 487 ssize_t len = recv(fd, buf, sz, MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL); 488 if (len >= 0 || errno != EINVAL) return len; 489 } 490 #else 491 UNUSED(family); 492 UNUSED(so_type); 493 #endif 494 return read(fd, buf, sz); 495 } 496 497 498 #include <sys/ioctl.h> 499 #ifdef HAVE_SYS_FILIO_H 500 #include <sys/filio.h> /* FIONREAD (for illumos (OpenIndiana)) */ 501 #endif 502 #ifdef _WIN32 503 #include <winsock2.h> 504 #endif 505 int fdevent_ioctl_fionread (int fd, int fdfmt, int *toread) { 506 #ifdef _WIN32 507 if (fdfmt != S_IFSOCK) { errno = ENOTSOCK; return -1; } 508 return ioctlsocket(fd, FIONREAD, toread); 509 #else 510 #ifdef __CYGWIN__ 511 /*(cygwin supports FIONREAD on pipes, not sockets)*/ 512 if (fdfmt != S_IFIFO) { errno = EOPNOTSUPP; return -1; } 513 #else 514 UNUSED(fdfmt); 515 #endif 516 return ioctl(fd, FIONREAD, toread); 517 #endif 518 } 519 520 521 int fdevent_connect_status(int fd) { 522 /* try to finish the connect() */ 523 /*(should be called after connect() only when fd is writable (POLLOUT))*/ 524 int opt; 525 socklen_t len = sizeof(opt); 526 return (0 == getsockopt(fd,SOL_SOCKET,SO_ERROR,&opt,&len)) ? opt : errno; 527 } 528 529 530 #include <netinet/tcp.h> 531 #if (defined(__APPLE__) && defined(__MACH__)) \ 532 || defined(__FreeBSD__) || defined(__NetBSD__) \ 533 || defined(__OpenBSD__) || defined(__DragonFly__) 534 #include <netinet/tcp_fsm.h> 535 #endif 536 537 /* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */ 538 int fdevent_is_tcp_half_closed(int fd) { 539 #ifdef TCP_CONNECTION_INFO /* Darwin */ 540 struct tcp_connection_info tcpi; 541 socklen_t tlen = sizeof(tcpi); 542 return (0 == getsockopt(fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &tcpi, &tlen) 543 && tcpi.tcpi_state == TCPS_CLOSE_WAIT); 544 #elif defined(TCP_INFO) && defined(TCPS_CLOSE_WAIT) 545 /* FreeBSD, NetBSD (not present in OpenBSD or DragonFlyBSD) */ 546 struct tcp_info tcpi; 547 socklen_t tlen = sizeof(tcpi); 548 return (0 == getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpi, &tlen) 549 && tcpi.tcpi_state == TCPS_CLOSE_WAIT); 550 #elif defined(TCP_INFO) && defined(__linux__) 551 /* Linux (TCP_CLOSE_WAIT is enum, so can not #ifdef TCP_CLOSE_WAIT) */ 552 struct tcp_info tcpi; 553 socklen_t tlen = sizeof(tcpi);/*SOL_TCP == IPPROTO_TCP*/ 554 return (0 == getsockopt(fd, SOL_TCP, TCP_INFO, &tcpi, &tlen) 555 && tcpi.tcpi_state == TCP_CLOSE_WAIT); 556 #else 557 UNUSED(fd); 558 /*(0 != getpeername() error might indicate TCP RST, but success 559 * would not differentiate between half-close and full-close)*/ 560 return 0; /* false (not half-closed) or TCP state unknown */ 561 #endif 562 } 563 564 565 int fdevent_set_tcp_nodelay (const int fd, const int opt) 566 { 567 return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); 568 } 569 570 571 int fdevent_set_so_reuseaddr (const int fd, const int opt) 572 { 573 return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 574 } 575 576 577 #include <sys/stat.h> 578 #include "ck.h" 579 __attribute_cold__ /*(convenience routine for use at config at startup)*/ 580 char * 581 fdevent_load_file (const char * const fn, off_t *lim, log_error_st *errh, void *(malloc_fn)(size_t), void(free_fn)(void *)) 582 { 583 int fd; 584 off_t sz = 0; 585 char *buf = NULL; 586 do { 587 fd = fdevent_open_cloexec(fn, 1, O_RDONLY, 0); /*(1: follows symlinks)*/ 588 if (fd < 0) break; 589 590 struct stat st; 591 if (0 != fstat(fd, &st)) break; 592 if ((sizeof(off_t) > sizeof(size_t) && st.st_size >= (off_t)~(size_t)0u) 593 || (*lim != 0 && st.st_size >= *lim)) { 594 errno = EOVERFLOW; 595 break; 596 } 597 598 sz = st.st_size; 599 buf = malloc_fn((size_t)sz+1);/*+1 trailing '\0' for str funcs on data*/ 600 if (NULL == buf) break; 601 602 if (sz) { 603 ssize_t rd = 0; 604 off_t off = 0; 605 do { 606 rd = read(fd, buf+off, (size_t)(sz-off)); 607 } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR); 608 if (off != sz) { /*(file truncated?)*/ 609 if (rd >= 0) errno = EIO; 610 break; 611 } 612 } 613 614 buf[sz] = '\0'; 615 *lim = sz; 616 close(fd); 617 return buf; 618 } while (0); 619 int errnum = errno; 620 if (errh) 621 log_perror(errh, __FILE__, __LINE__, "%s() %s", __func__, fn); 622 if (fd >= 0) close(fd); 623 if (buf) { 624 ck_memzero(buf, (size_t)sz); 625 free_fn(buf); 626 } 627 *lim = 0; 628 errno = errnum; 629 return NULL; 630 } 631 632 633 int 634 fdevent_load_file_bytes (char * const buf, const off_t sz, off_t off, const char * const fn, log_error_st *errh) 635 { 636 int fd; 637 do { 638 fd = fdevent_open_cloexec(fn, 1, O_RDONLY, 0); /*(1: follows symlinks)*/ 639 if (fd < 0) break; 640 641 if (0 != off && (off_t)-1 == lseek(fd, off, SEEK_SET)) break; 642 off = 0; 643 644 ssize_t rd = 0; 645 do { 646 rd = read(fd, buf+off, (size_t)(sz-off)); 647 } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR); 648 if (off != sz) { /*(file truncated? or incorrect sz requested)*/ 649 if (rd >= 0) errno = EIO; 650 break; 651 } 652 653 close(fd); 654 return 0; 655 } while (0); 656 int errnum = errno; 657 if (errh) 658 log_perror(errh, __FILE__, __LINE__, "%s() %s", __func__, fn); 659 if (fd >= 0) close(fd); 660 ck_memzero(buf, (size_t)sz); 661 errno = errnum; 662 return -1; 663 } 664