41 #define IDM_CONN_SM_STRINGS |
41 #define IDM_CONN_SM_STRINGS |
42 #define IDM_TASK_SM_STRINGS |
42 #define IDM_TASK_SM_STRINGS |
43 #define ISCSIT_TGT_SM_STRINGS |
43 #define ISCSIT_TGT_SM_STRINGS |
44 #define ISCSIT_SESS_SM_STRINGS |
44 #define ISCSIT_SESS_SM_STRINGS |
45 #define ISCSIT_LOGIN_SM_STRINGS |
45 #define ISCSIT_LOGIN_SM_STRINGS |
|
46 #define ISCSI_SESS_SM_STRINGS |
|
47 #define ISCSI_CMD_SM_STRINGS |
|
48 #define ISCSI_ICS_NAMES |
|
49 #define ISCSI_LOGIN_STATE_NAMES |
46 #include <sys/idm/idm.h> |
50 #include <sys/idm/idm.h> |
47 #include <iscsit.h> |
51 #include <iscsit.h> |
48 #include <iscsit_isns.h> |
52 #include <iscsit_isns.h> |
|
53 #include <iscsi.h> |
49 |
54 |
50 /* |
55 /* |
51 * We want to be able to print multiple levels of object hierarchy with a |
56 * We want to be able to print multiple levels of object hierarchy with a |
52 * single dcmd information, and preferably also exclude intermediate |
57 * single dcmd information, and preferably also exclude intermediate |
53 * levels if desired. For example some of the target objects have the |
58 * levels if desired. For example some of the target objects have the |
104 */ |
109 */ |
105 uintptr_t idc_assoc_session; |
110 uintptr_t idc_assoc_session; |
106 } iscsi_dcmd_ctrl_t; |
111 } iscsi_dcmd_ctrl_t; |
107 |
112 |
108 static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc); |
113 static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc); |
|
114 static int iscsi_walk_ini_sessions(uintptr_t array_addr); |
109 static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc); |
115 static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc); |
110 static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, |
116 static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, |
111 void *idc_void); |
117 void *idc_void); |
112 static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, |
118 static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, |
113 void *idc_void); |
119 void *idc_void); |
138 static int iscsi_refcnt_impl(uintptr_t addr); |
144 static int iscsi_refcnt_impl(uintptr_t addr); |
139 static int iscsi_sm_audit_impl(uintptr_t addr); |
145 static int iscsi_sm_audit_impl(uintptr_t addr); |
140 static int iscsi_isns(uintptr_t addr, uint_t flags, int argc, |
146 static int iscsi_isns(uintptr_t addr, uint_t flags, int argc, |
141 const mdb_arg_t *argv); |
147 const mdb_arg_t *argv); |
142 |
148 |
143 static const char *iscsi_idm_conn_event(int event); |
149 static const char *iscsi_idm_conn_event(unsigned int event); |
144 static const char *iscsi_iscsit_tgt_event(int event); |
150 static const char *iscsi_iscsit_tgt_event(unsigned int event); |
145 static const char *iscsi_iscsit_sess_event(int event); |
151 static const char *iscsi_iscsit_sess_event(unsigned int event); |
146 static const char *iscsi_iscsit_login_event(int event); |
152 static const char *iscsi_iscsit_login_event(unsigned int event); |
147 static const char *iscsi_idm_conn_state(int state); |
153 static const char *iscsi_iscsi_cmd_event(unsigned int event); |
148 static const char *iscsi_idm_task_state(int state); |
154 static const char *iscsi_iscsi_sess_event(unsigned int event); |
149 static const char *iscsi_iscsit_tgt_state(int state); |
155 static const char *iscsi_idm_conn_state(unsigned int state); |
150 static const char *iscsi_iscsit_sess_state(int state); |
156 static const char *iscsi_idm_task_state(unsigned int state); |
151 static const char *iscsi_iscsit_login_state(int state); |
157 static const char *iscsi_iscsit_tgt_state(unsigned int state); |
|
158 static const char *iscsi_iscsit_sess_state(unsigned int state); |
|
159 static const char *iscsi_iscsit_login_state(unsigned int state); |
|
160 static const char *iscsi_iscsi_cmd_state(unsigned int state); |
|
161 static const char *iscsi_iscsi_sess_state(unsigned int state); |
|
162 static const char *iscsi_iscsi_conn_state(unsigned int state); |
|
163 static const char *iscsi_iscsi_login_state(unsigned int state); |
152 |
164 |
153 static void iscsi_format_timestamp(char *ts_str, int strlen, |
165 static void iscsi_format_timestamp(char *ts_str, int strlen, |
154 timespec_t *ts); |
166 timespec_t *ts); |
155 static char *inet_ntop(int af, const void *addr, char *buf, int addrlen); |
167 static char *iscsi_inet_ntop(int af, const void *addr, char *buf, int addrlen); |
156 static void convert2ascii(char *, const in6_addr_t *); |
168 static void convert2ascii(char *, const in6_addr_t *); |
157 static int sa_to_str(struct sockaddr_storage *sa, char *addr); |
169 static int sa_to_str(struct sockaddr_storage *sa, char *addr); |
158 static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, |
170 static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, |
159 void *data); |
171 void *data); |
160 |
172 |
485 return (iscsi_sm_audit_impl(addr)); |
497 return (iscsi_sm_audit_impl(addr)); |
486 } |
498 } |
487 /*NOTREACHED*/ |
499 /*NOTREACHED*/ |
488 } |
500 } |
489 |
501 |
|
502 /* |
|
503 * Helper function to list all the initiator sessions |
|
504 */ |
|
505 static int |
|
506 iscsi_walk_ini_sessions(uintptr_t array_vaddr) |
|
507 { |
|
508 iscsi_hba_t ihp; |
|
509 int i; |
|
510 int array_size; |
|
511 struct i_ddi_soft_state *ss; |
|
512 iscsi_sess_t *isp; |
|
513 |
|
514 ss = (struct i_ddi_soft_state *)mdb_alloc(sizeof (*ss), |
|
515 UM_SLEEP|UM_GC); |
|
516 if (mdb_vread(ss, sizeof (*ss), array_vaddr) != sizeof (*ss)) { |
|
517 mdb_warn("Cannot read softstate struct (Invalid pointer?).\n"); |
|
518 return (DCMD_ERR); |
|
519 } |
|
520 array_size = ss->n_items * (sizeof (void *)); |
|
521 array_vaddr = (uintptr_t)ss->array; |
|
522 ss->array = mdb_alloc(array_size, UM_SLEEP|UM_GC); |
|
523 if (mdb_vread(ss->array, array_size, array_vaddr) != array_size) { |
|
524 mdb_warn("Corrupted softstate struct.\n"); |
|
525 return (DCMD_ERR); |
|
526 } |
|
527 for (i = 0; i < ss->n_items; i++) { |
|
528 if (ss->array[i] == 0) |
|
529 continue; |
|
530 |
|
531 if (mdb_vread(&ihp, sizeof (ihp), (uintptr_t)ss->array[i]) |
|
532 != sizeof (ihp)) { |
|
533 mdb_warn("Corrupted softstate struct.\n"); |
|
534 return (DCMD_ERR); |
|
535 } |
|
536 mdb_printf("iscsi_hba %p sessions: \n", ihp); |
|
537 mdb_printf("%<u>%-19s %-4s %-8s%</u>\n", |
|
538 "Session", "Type", "State"); |
|
539 for (isp = ihp.hba_sess_list; isp; ) { |
|
540 iscsi_sess_t sess; |
|
541 if ((mdb_vread(&sess, sizeof (iscsi_sess_t), |
|
542 (uintptr_t)isp)) != sizeof (iscsi_sess_t)) { |
|
543 mdb_warn("Failed to read session\n"); |
|
544 return (DCMD_ERR); |
|
545 } |
|
546 mdb_printf("%-19p %-4d %-8d\n", isp, |
|
547 sess.sess_type, |
|
548 sess.sess_state); |
|
549 isp = sess.sess_next; |
|
550 } |
|
551 } |
|
552 return (DCMD_OK); |
|
553 } |
|
554 |
490 static int |
555 static int |
491 iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc) |
556 iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc) |
492 { |
557 { |
493 uintptr_t iscsit_global_addr; |
558 uintptr_t iscsit_global_addr; |
494 uintptr_t avl_addr; |
559 uintptr_t avl_addr; |
495 uintptr_t list_addr; |
560 uintptr_t list_addr; |
496 GElf_Sym sym; |
561 GElf_Sym sym; |
497 |
562 uintptr_t adr; |
|
563 /* Initiator sessions */ |
|
564 if (idc->idc_ini) { |
|
565 if (mdb_readvar(&adr, "iscsi_state") == -1) { |
|
566 |
|
567 mdb_warn("state variable iscsi_state not found.\n"); |
|
568 mdb_warn("Is the driver loaded ?\n"); |
|
569 return (DCMD_ERR); |
|
570 } |
|
571 return (iscsi_walk_ini_sessions(adr)); |
|
572 } |
|
573 /* Target sessions */ |
498 /* Walk discovery sessions */ |
574 /* Walk discovery sessions */ |
499 if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { |
575 if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { |
500 mdb_warn("failed to find symbol 'iscsit_global'"); |
576 mdb_warn("failed to find symbol 'iscsit_global'"); |
501 return (DCMD_ERR); |
577 return (DCMD_ERR); |
502 } |
578 } |
1760 iscsi_iscsit_login_state(sar->sar_state); |
1848 iscsi_iscsit_login_state(sar->sar_state); |
1761 new_state_name = |
1849 new_state_name = |
1762 iscsi_iscsit_login_state( |
1850 iscsi_iscsit_login_state( |
1763 sar->sar_new_state); |
1851 sar->sar_new_state); |
1764 break; |
1852 break; |
|
1853 case SAS_ISCSI_CMD: |
|
1854 state_name = |
|
1855 iscsi_iscsi_cmd_state(sar->sar_state); |
|
1856 new_state_name= |
|
1857 iscsi_iscsi_cmd_state(sar->sar_new_state); |
|
1858 break; |
|
1859 case SAS_ISCSI_SESS: |
|
1860 state_name = |
|
1861 iscsi_iscsi_sess_state(sar->sar_state); |
|
1862 new_state_name= |
|
1863 iscsi_iscsi_sess_state(sar->sar_new_state); |
|
1864 break; |
|
1865 case SAS_ISCSI_CONN: |
|
1866 state_name = |
|
1867 iscsi_iscsi_conn_state(sar->sar_state); |
|
1868 new_state_name= |
|
1869 iscsi_iscsi_conn_state(sar->sar_new_state); |
|
1870 break; |
|
1871 case SAS_ISCSI_LOGIN: |
|
1872 state_name = |
|
1873 iscsi_iscsi_login_state(sar->sar_state); |
|
1874 new_state_name= |
|
1875 iscsi_iscsi_login_state(sar->sar_new_state); |
|
1876 break; |
1765 default: |
1877 default: |
1766 break; |
1878 break; |
1767 } |
1879 } |
1768 mdb_printf("%s|%s (%d)\n\t%9s %s (%d)\n", |
1880 mdb_printf("%s|%s (%d)\n\t%9s %s (%d)\n", |
1769 ts_string, state_name, sar->sar_state, |
1881 ts_string, state_name, sar->sar_state, |
1780 |
1892 |
1781 return (DCMD_OK); |
1893 return (DCMD_OK); |
1782 } |
1894 } |
1783 |
1895 |
1784 static const char * |
1896 static const char * |
1785 iscsi_idm_conn_event(int event) |
1897 iscsi_idm_conn_event(unsigned int event) |
1786 { |
1898 { |
1787 const char *name = "N/A"; |
1899 return ((event < CE_MAX_EVENT) ? idm_ce_name[event] : "N/A"); |
1788 |
|
1789 event = (event > CE_MAX_EVENT) ? CE_MAX_EVENT : event; |
|
1790 name = idm_ce_name[event]; |
|
1791 |
|
1792 return (name); |
|
1793 } |
1900 } |
1794 |
1901 |
1795 static const char * |
1902 static const char * |
1796 iscsi_iscsit_tgt_event(int event) |
1903 iscsi_iscsit_tgt_event(unsigned int event) |
1797 { |
1904 { |
1798 const char *name = "N/A"; |
1905 return ((event < TE_MAX_EVENT) ? iscsit_te_name[event] : "N/A"); |
1799 |
|
1800 event = (event > TE_MAX_EVENT) ? TE_MAX_EVENT : event; |
|
1801 name = iscsit_te_name[event]; |
|
1802 |
|
1803 return (name); |
|
1804 } |
1906 } |
1805 |
1907 |
1806 static const char * |
1908 static const char * |
1807 iscsi_iscsit_sess_event(int event) |
1909 iscsi_iscsit_sess_event(unsigned int event) |
1808 { |
1910 { |
1809 const char *name = "N/A"; |
1911 return ((event < SE_MAX_EVENT) ? iscsit_se_name[event] : "N/A"); |
1810 |
|
1811 event = (event > SE_MAX_EVENT) ? SE_MAX_EVENT : event; |
|
1812 name = iscsit_se_name[event]; |
|
1813 |
|
1814 return (name); |
|
1815 } |
1912 } |
1816 |
1913 |
1817 static const char * |
1914 static const char * |
1818 iscsi_iscsit_login_event(int event) |
1915 iscsi_iscsit_login_event(unsigned int event) |
1819 { |
1916 { |
1820 const char *name = "N/A"; |
1917 return ((event < ILE_MAX_EVENT) ? iscsit_ile_name[event] : "N/A"); |
1821 |
|
1822 event = (event > ILE_MAX_EVENT) ? ILE_MAX_EVENT : event; |
|
1823 name = iscsit_ile_name[event]; |
|
1824 |
|
1825 return (name); |
|
1826 } |
1918 } |
1827 |
1919 |
1828 static const char * |
1920 static const char * |
1829 iscsi_idm_conn_state(int state) |
1921 iscsi_iscsi_cmd_event(unsigned int event) |
1830 { |
1922 { |
1831 const char *name = "N/A"; |
1923 return ((event < ISCSI_CMD_EVENT_MAX) ? |
1832 |
1924 iscsi_cmd_event_names[event] : "N/A"); |
1833 state = (state > CS_MAX_STATE) ? CS_MAX_STATE : state; |
1925 } |
1834 name = idm_cs_name[state]; |
1926 |
1835 |
1927 static const char * |
1836 return (name); |
1928 iscsi_iscsi_sess_event(unsigned int event) |
|
1929 { |
|
1930 |
|
1931 return ((event < ISCSI_SESS_EVENT_MAX) ? |
|
1932 iscsi_sess_event_names[event] : "N/A"); |
|
1933 } |
|
1934 |
|
1935 static const char * |
|
1936 iscsi_idm_conn_state(unsigned int state) |
|
1937 { |
|
1938 return ((state < CS_MAX_STATE) ? idm_cs_name[state] : "N/A"); |
1837 } |
1939 } |
1838 |
1940 |
1839 /*ARGSUSED*/ |
1941 /*ARGSUSED*/ |
1840 static const char * |
1942 static const char * |
1841 iscsi_idm_task_state(int state) |
1943 iscsi_idm_task_state(unsigned int state) |
1842 { |
1944 { |
1843 const char *name = "N/A"; |
1945 return ("N/A"); |
1844 return (name); |
|
1845 } |
1946 } |
1846 |
1947 |
1847 static const char * |
1948 static const char * |
1848 iscsi_iscsit_tgt_state(int state) |
1949 iscsi_iscsit_tgt_state(unsigned int state) |
1849 { |
1950 { |
1850 const char *name = "N/A"; |
1951 return ((state < TS_MAX_STATE) ? iscsit_ts_name[state] : "N/A"); |
1851 |
|
1852 state = (state > TS_MAX_STATE) ? TS_MAX_STATE : state; |
|
1853 name = iscsit_ts_name[state]; |
|
1854 |
|
1855 return (name); |
|
1856 } |
1952 } |
1857 |
1953 |
1858 static const char * |
1954 static const char * |
1859 iscsi_iscsit_sess_state(int state) |
1955 iscsi_iscsit_sess_state(unsigned int state) |
1860 { |
1956 { |
1861 const char *name = "N/A"; |
1957 return ((state < SS_MAX_STATE) ? iscsit_ss_name[state] : "N/A"); |
1862 |
|
1863 state = (state > SS_MAX_STATE) ? SS_MAX_STATE : state; |
|
1864 name = iscsit_ss_name[state]; |
|
1865 |
|
1866 return (name); |
|
1867 } |
1958 } |
1868 |
1959 |
1869 static const char * |
1960 static const char * |
1870 iscsi_iscsit_login_state(int state) |
1961 iscsi_iscsit_login_state(unsigned int state) |
1871 { |
1962 { |
1872 const char *name = "N/A"; |
1963 return ((state < ILS_MAX_STATE) ? iscsit_ils_name[state] : "N/A"); |
1873 |
1964 } |
1874 state = (state > ILS_MAX_STATE) ? ILS_MAX_STATE : state; |
1965 |
1875 name = iscsit_ils_name[state]; |
1966 static const char * |
1876 |
1967 iscsi_iscsi_cmd_state(unsigned int state) |
1877 return (name); |
1968 { |
1878 } |
1969 return ((state < ISCSI_CMD_STATE_MAX) ? |
1879 |
1970 iscsi_cmd_state_names[state] : "N/A"); |
|
1971 } |
|
1972 |
|
1973 static const char * |
|
1974 iscsi_iscsi_sess_state(unsigned int state) |
|
1975 { |
|
1976 return ((state < ISCSI_SESS_STATE_MAX) ? |
|
1977 iscsi_sess_state_names[state] : "N/A"); |
|
1978 } |
|
1979 |
|
1980 static const char * |
|
1981 iscsi_iscsi_conn_state(unsigned int state) |
|
1982 { |
|
1983 return ((state < ISCSI_CONN_STATE_MAX) ? iscsi_ics_name[state] : "N/A"); |
|
1984 } |
|
1985 |
|
1986 static const char * |
|
1987 iscsi_iscsi_login_state(unsigned int state) |
|
1988 { |
|
1989 return ((state < LOGIN_MAX) ? iscsi_login_state_names[state] : "N/A"); |
|
1990 } |
1880 |
1991 |
1881 |
1992 |
1882 /* |
1993 /* |
1883 * Retrieve connection type given a kernel address |
1994 * Retrieve connection type given a kernel address |
1884 */ |
1995 */ |
1913 |
2024 |
1914 buf[0] = '\0'; |
2025 buf[0] = '\0'; |
1915 |
2026 |
1916 if (sa->ss_family == AF_INET) { |
2027 if (sa->ss_family == AF_INET) { |
1917 sin = (struct sockaddr_in *)sa; |
2028 sin = (struct sockaddr_in *)sa; |
1918 bufp = inet_ntop(AF_INET, |
2029 bufp = iscsi_inet_ntop(AF_INET, |
1919 (const void *)&(sin->sin_addr.s_addr), |
2030 (const void *)&(sin->sin_addr.s_addr), |
1920 buf, PORTAL_STR_LEN); |
2031 buf, PORTAL_STR_LEN); |
1921 if (bufp == NULL) { |
2032 if (bufp == NULL) { |
1922 return (-1); |
2033 return (-1); |
1923 } |
2034 } |
1924 mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t)); |
2035 mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t)); |
1925 } else if (sa->ss_family == AF_INET6) { |
2036 } else if (sa->ss_family == AF_INET6) { |
1926 strlcat(buf, "[", sizeof (buf)); |
2037 strlcat(buf, "[", sizeof (buf)); |
1927 sin6 = (struct sockaddr_in6 *)sa; |
2038 sin6 = (struct sockaddr_in6 *)sa; |
1928 bufp = inet_ntop(AF_INET6, |
2039 bufp = iscsi_inet_ntop(AF_INET6, |
1929 (const void *)&sin6->sin6_addr.s6_addr, |
2040 (const void *)&sin6->sin6_addr.s6_addr, |
1930 &buf[1], PORTAL_STR_LEN - 1); |
2041 &buf[1], PORTAL_STR_LEN - 1); |
1931 if (bufp == NULL) { |
2042 if (bufp == NULL) { |
1932 return (-1); |
2043 return (-1); |
1933 } |
2044 } |
2260 |
2371 |
2261 return (iscsi_isns_targets(&idc)); |
2372 return (iscsi_isns_targets(&idc)); |
2262 } |
2373 } |
2263 |
2374 |
2264 /* |
2375 /* |
2265 * inet_ntop -- Convert an IPv4 or IPv6 address in binary form into |
2376 * iscsi_inet_ntop -- Convert an IPv4 or IPv6 address in binary form into |
2266 * printable form, and return a pointer to that string. Caller should |
2377 * printable form, and return a pointer to that string. Caller should |
2267 * provide a buffer of correct length to store string into. |
2378 * provide a buffer of correct length to store string into. |
2268 * Note: this routine is kernel version of inet_ntop. It has similar |
2379 * Note: this routine is kernel version of inet_ntop. It has similar |
2269 * format as inet_ntop() defined in rfc2553. But it does not do |
2380 * format as iscsi_inet_ntop() defined in rfc2553. But it does not do |
2270 * error handling operations exactly as rfc2553 defines. This function |
2381 * error handling operations exactly as rfc2553 defines. This function |
2271 * is used by kernel inet directory routines only for debugging. |
2382 * is used by kernel inet directory routines only for debugging. |
2272 * This inet_ntop() function, does not return NULL if third argument |
2383 * This iscsi_inet_ntop() function, does not return NULL if third argument |
2273 * is NULL. The reason is simple that we don't want kernel to panic |
2384 * is NULL. The reason is simple that we don't want kernel to panic |
2274 * as the output of this function is directly fed to ip<n>dbg macro. |
2385 * as the output of this function is directly fed to ip<n>dbg macro. |
2275 * Instead it uses a local buffer for destination address for |
2386 * Instead it uses a local buffer for destination address for |
2276 * those calls which purposely pass NULL ptr for the destination |
2387 * those calls which purposely pass NULL ptr for the destination |
2277 * buffer. This function is thread-safe when the caller passes a non- |
2388 * buffer. This function is thread-safe when the caller passes a non- |
2285 #else |
2396 #else |
2286 #define OK_32PTR(p) (!((uintptr_t)(p) & 0x3)) |
2397 #define OK_32PTR(p) (!((uintptr_t)(p) & 0x3)) |
2287 #endif |
2398 #endif |
2288 |
2399 |
2289 char * |
2400 char * |
2290 inet_ntop(int af, const void *addr, char *buf, int addrlen) |
2401 iscsi_inet_ntop(int af, const void *addr, char *buf, int addrlen) |
2291 { |
2402 { |
2292 static char local_buf[PORTAL_STR_LEN]; |
2403 static char local_buf[PORTAL_STR_LEN]; |
2293 static char *err_buf1 = "<badaddr>"; |
2404 static char *err_buf1 = "<badaddr>"; |
2294 static char *err_buf2 = "<badfamily>"; |
2405 static char *err_buf2 = "<badfamily>"; |
2295 in6_addr_t *v6addr; |
2406 in6_addr_t *v6addr; |
2296 uchar_t *v4addr; |
2407 uchar_t *v4addr; |
2297 char *caddr; |
2408 char *caddr; |
2298 |
2409 |
2299 /* |
2410 /* |
2300 * We don't allow thread unsafe inet_ntop calls, they |
2411 * We don't allow thread unsafe iscsi_inet_ntop calls, they |
2301 * must pass a non-null buffer pointer. For DEBUG mode |
2412 * must pass a non-null buffer pointer. For DEBUG mode |
2302 * we use the ASSERT() and for non-debug kernel it will |
2413 * we use the ASSERT() and for non-debug kernel it will |
2303 * silently allow it for now. Someday we should remove |
2414 * silently allow it for now. Someday we should remove |
2304 * the static buffer from this function. |
2415 * the static buffer from this function. |
2305 */ |
2416 */ |