4 |
* |
* |
5 |
* Copyright 1998 by Albert Cahalan; all rights reserved. |
* Copyright 1998 by Albert Cahalan; all rights reserved. |
6 |
* Copyright (C) 2002 by Vladimir Oleynik <dzo@simtreas.ru> |
* Copyright (C) 2002 by Vladimir Oleynik <dzo@simtreas.ru> |
7 |
|
* SELinux support: (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp> |
8 |
* |
* |
9 |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
10 |
*/ |
*/ |
30 |
cp->cache = NULL; |
cp->cache = NULL; |
31 |
cp->size = 0; |
cp->size = 0; |
32 |
} |
} |
33 |
void clear_username_cache(void) |
void FAST_FUNC clear_username_cache(void) |
34 |
{ |
{ |
35 |
clear_cache(&username); |
clear_cache(&username); |
36 |
clear_cache(&groupname); |
clear_cache(&groupname); |
46 |
if (cp->cache[i].id == id) |
if (cp->cache[i].id == id) |
47 |
return i; |
return i; |
48 |
i = cp->size++; |
i = cp->size++; |
49 |
cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); |
cp->cache = xrealloc_vector(cp->cache, 2, i); |
50 |
cp->cache[i++].id = id; |
cp->cache[i++].id = id; |
51 |
return -i; |
return -i; |
52 |
} |
} |
53 |
#endif |
#endif |
54 |
|
|
55 |
typedef char* ug_func(char *name, long uid, int bufsize); |
typedef char* FAST_FUNC ug_func(char *name, int bufsize, long uid); |
56 |
static char* get_cached(cache_t *cp, unsigned id, ug_func* fp) |
static char* get_cached(cache_t *cp, unsigned id, ug_func* fp) |
57 |
{ |
{ |
58 |
int i; |
int i; |
60 |
if (cp->cache[i].id == id) |
if (cp->cache[i].id == id) |
61 |
return cp->cache[i].name; |
return cp->cache[i].name; |
62 |
i = cp->size++; |
i = cp->size++; |
63 |
cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); |
cp->cache = xrealloc_vector(cp->cache, 2, i); |
64 |
cp->cache[i].id = id; |
cp->cache[i].id = id; |
65 |
fp(cp->cache[i].name, id, sizeof(cp->cache[i].name)); |
/* Never fails. Generates numeric string if name isn't found */ |
66 |
|
fp(cp->cache[i].name, sizeof(cp->cache[i].name), id); |
67 |
return cp->cache[i].name; |
return cp->cache[i].name; |
68 |
} |
} |
69 |
const char* get_cached_username(uid_t uid) |
const char* FAST_FUNC get_cached_username(uid_t uid) |
70 |
{ |
{ |
71 |
return get_cached(&username, uid, bb_getpwuid); |
return get_cached(&username, uid, bb_getpwuid); |
72 |
} |
} |
73 |
const char* get_cached_groupname(gid_t gid) |
const char* FAST_FUNC get_cached_groupname(gid_t gid) |
74 |
{ |
{ |
75 |
return get_cached(&groupname, gid, bb_getgrgid); |
return get_cached(&groupname, gid, bb_getgrgid); |
76 |
} |
} |
80 |
|
|
81 |
static int read_to_buf(const char *filename, void *buf) |
static int read_to_buf(const char *filename, void *buf) |
82 |
{ |
{ |
83 |
ssize_t ret; |
int fd; |
84 |
ret = open_read_close(filename, buf, PROCPS_BUFSIZE-1); |
/* open_read_close() would do two reads, checking for EOF. |
85 |
|
* When you have 10000 /proc/$NUM/stat to read, it isn't desirable */ |
86 |
|
ssize_t ret = -1; |
87 |
|
fd = open(filename, O_RDONLY); |
88 |
|
if (fd >= 0) { |
89 |
|
ret = read(fd, buf, PROCPS_BUFSIZE-1); |
90 |
|
close(fd); |
91 |
|
} |
92 |
((char *)buf)[ret > 0 ? ret : 0] = '\0'; |
((char *)buf)[ret > 0 ? ret : 0] = '\0'; |
93 |
return ret; |
return ret; |
94 |
} |
} |
95 |
|
|
96 |
procps_status_t* alloc_procps_scan(int flags) |
static procps_status_t* FAST_FUNC alloc_procps_scan(void) |
97 |
{ |
{ |
98 |
|
unsigned n = getpagesize(); |
99 |
procps_status_t* sp = xzalloc(sizeof(procps_status_t)); |
procps_status_t* sp = xzalloc(sizeof(procps_status_t)); |
100 |
sp->dir = xopendir("/proc"); |
sp->dir = xopendir("/proc"); |
101 |
|
while (1) { |
102 |
|
n >>= 1; |
103 |
|
if (!n) break; |
104 |
|
sp->shift_pages_to_bytes++; |
105 |
|
} |
106 |
|
sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10; |
107 |
return sp; |
return sp; |
108 |
} |
} |
109 |
|
|
110 |
void free_procps_scan(procps_status_t* sp) |
void FAST_FUNC free_procps_scan(procps_status_t* sp) |
111 |
{ |
{ |
112 |
closedir(sp->dir); |
closedir(sp->dir); |
113 |
free(sp->cmd); |
free(sp->argv0); |
114 |
|
USE_SELINUX(free(sp->context);) |
115 |
free(sp); |
free(sp); |
116 |
} |
} |
117 |
|
|
118 |
|
#if ENABLE_FEATURE_TOPMEM |
119 |
|
static unsigned long fast_strtoul_16(char **endptr) |
120 |
|
{ |
121 |
|
unsigned char c; |
122 |
|
char *str = *endptr; |
123 |
|
unsigned long n = 0; |
124 |
|
|
125 |
|
while ((c = *str++) != ' ') { |
126 |
|
c = ((c|0x20) - '0'); |
127 |
|
if (c > 9) |
128 |
|
// c = c + '0' - 'a' + 10: |
129 |
|
c = c - ('a' - '0' - 10); |
130 |
|
n = n*16 + c; |
131 |
|
} |
132 |
|
*endptr = str; /* We skip trailing space! */ |
133 |
|
return n; |
134 |
|
} |
135 |
|
/* TOPMEM uses fast_strtoul_10, so... */ |
136 |
|
#undef ENABLE_FEATURE_FAST_TOP |
137 |
|
#define ENABLE_FEATURE_FAST_TOP 1 |
138 |
|
#endif |
139 |
|
|
140 |
|
#if ENABLE_FEATURE_FAST_TOP |
141 |
|
/* We cut a lot of corners here for speed */ |
142 |
|
static unsigned long fast_strtoul_10(char **endptr) |
143 |
|
{ |
144 |
|
char c; |
145 |
|
char *str = *endptr; |
146 |
|
unsigned long n = *str - '0'; |
147 |
|
|
148 |
|
while ((c = *++str) != ' ') |
149 |
|
n = n*10 + (c - '0'); |
150 |
|
|
151 |
|
*endptr = str + 1; /* We skip trailing space! */ |
152 |
|
return n; |
153 |
|
} |
154 |
|
static char *skip_fields(char *str, int count) |
155 |
|
{ |
156 |
|
do { |
157 |
|
while (*str++ != ' ') |
158 |
|
continue; |
159 |
|
/* we found a space char, str points after it */ |
160 |
|
} while (--count); |
161 |
|
return str; |
162 |
|
} |
163 |
|
#endif |
164 |
|
|
165 |
void BUG_comm_size(void); |
void BUG_comm_size(void); |
166 |
procps_status_t* procps_scan(procps_status_t* sp, int flags) |
procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) |
167 |
{ |
{ |
168 |
struct dirent *entry; |
struct dirent *entry; |
169 |
char buf[PROCPS_BUFSIZE]; |
char buf[PROCPS_BUFSIZE]; |
175 |
struct stat sb; |
struct stat sb; |
176 |
|
|
177 |
if (!sp) |
if (!sp) |
178 |
sp = alloc_procps_scan(flags); |
sp = alloc_procps_scan(); |
179 |
|
|
180 |
for (;;) { |
for (;;) { |
181 |
entry = readdir(sp->dir); |
entry = readdir(sp->dir); |
191 |
* ("continue" would mean that current /proc/NNN |
* ("continue" would mean that current /proc/NNN |
192 |
* is not a valid process info) */ |
* is not a valid process info) */ |
193 |
|
|
194 |
memset(&sp->rss, 0, sizeof(*sp) - offsetof(procps_status_t, rss)); |
memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz)); |
195 |
|
|
196 |
sp->pid = pid; |
sp->pid = pid; |
197 |
if (!(flags & ~PSSCAN_PID)) break; |
if (!(flags & ~PSSCAN_PID)) break; |
198 |
|
|
199 |
|
#if ENABLE_SELINUX |
200 |
|
if (flags & PSSCAN_CONTEXT) { |
201 |
|
if (getpidcon(sp->pid, &sp->context) < 0) |
202 |
|
sp->context = NULL; |
203 |
|
} |
204 |
|
#endif |
205 |
|
|
206 |
filename_tail = filename + sprintf(filename, "/proc/%d", pid); |
filename_tail = filename + sprintf(filename, "/proc/%d", pid); |
207 |
|
|
208 |
if (flags & PSSCAN_UIDGID) { |
if (flags & PSSCAN_UIDGID) { |
214 |
} |
} |
215 |
|
|
216 |
if (flags & PSSCAN_STAT) { |
if (flags & PSSCAN_STAT) { |
217 |
char *cp; |
char *cp, *comm1; |
218 |
|
int tty; |
219 |
|
#if !ENABLE_FEATURE_FAST_TOP |
220 |
|
unsigned long vsz, rss; |
221 |
|
#endif |
222 |
/* see proc(5) for some details on this */ |
/* see proc(5) for some details on this */ |
223 |
strcpy(filename_tail, "/stat"); |
strcpy(filename_tail, "/stat"); |
224 |
n = read_to_buf(filename, buf); |
n = read_to_buf(filename, buf); |
225 |
if (n < 0) |
if (n < 0) |
226 |
break; |
break; |
227 |
cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */ |
cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */ |
228 |
if (!cp || cp[1] != ' ') |
/*if (!cp || cp[1] != ' ') |
229 |
break; |
break;*/ |
230 |
cp[0] = '\0'; |
cp[0] = '\0'; |
231 |
if (sizeof(sp->comm) < 16) |
if (sizeof(sp->comm) < 16) |
232 |
BUG_comm_size(); |
BUG_comm_size(); |
233 |
sscanf(buf, "%*s (%15c", sp->comm); |
comm1 = strchr(buf, '('); |
234 |
|
/*if (comm1)*/ |
235 |
|
safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm)); |
236 |
|
|
237 |
|
#if !ENABLE_FEATURE_FAST_TOP |
238 |
n = sscanf(cp+2, |
n = sscanf(cp+2, |
239 |
"%c %u " /* state, ppid */ |
"%c %u " /* state, ppid */ |
240 |
"%u %u %*s %*s " /* pgid, sid, tty, tpgid */ |
"%u %u %d %*s " /* pgid, sid, tty, tpgid */ |
241 |
"%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ |
"%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ |
242 |
"%lu %lu " /* utime, stime */ |
"%lu %lu " /* utime, stime */ |
243 |
"%*s %*s %*s " /* cutime, cstime, priority */ |
"%*s %*s %*s " /* cutime, cstime, priority */ |
244 |
"%ld " /* nice */ |
"%ld " /* nice */ |
245 |
"%*s %*s %*s " /* timeout, it_real_value, start_time */ |
"%*s %*s " /* timeout, it_real_value */ |
246 |
"%*s " /* vsize */ |
"%lu " /* start_time */ |
247 |
"%lu", /* rss */ |
"%lu " /* vsize */ |
248 |
|
"%lu " /* rss */ |
249 |
|
#if ENABLE_FEATURE_TOP_SMP_PROCESS |
250 |
|
"%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ |
251 |
|
"%*s %*s %*s %*s " /*signal, blocked, sigignore, sigcatch */ |
252 |
|
"%*s %*s %*s %*s " /*wchan, nswap, cnswap, exit_signal */ |
253 |
|
"%d" /*cpu last seen on*/ |
254 |
|
#endif |
255 |
|
, |
256 |
sp->state, &sp->ppid, |
sp->state, &sp->ppid, |
257 |
&sp->pgid, &sp->sid, |
&sp->pgid, &sp->sid, &tty, |
258 |
&sp->utime, &sp->stime, |
&sp->utime, &sp->stime, |
259 |
&tasknice, |
&tasknice, |
260 |
&sp->rss); |
&sp->start_time, |
261 |
if (n != 8) |
&vsz, |
262 |
|
&rss |
263 |
|
#if ENABLE_FEATURE_TOP_SMP_PROCESS |
264 |
|
, &sp->last_seen_on_cpu |
265 |
|
#endif |
266 |
|
); |
267 |
|
|
268 |
|
if (n < 11) |
269 |
break; |
break; |
270 |
|
#if ENABLE_FEATURE_TOP_SMP_PROCESS |
271 |
|
if (n < 11+15) |
272 |
|
sp->last_seen_on_cpu = 0; |
273 |
|
#endif |
274 |
|
|
275 |
if (sp->rss == 0 && sp->state[0] != 'Z') |
/* vsz is in bytes and we want kb */ |
276 |
|
sp->vsz = vsz >> 10; |
277 |
|
/* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ |
278 |
|
sp->rss = rss << sp->shift_pages_to_kb; |
279 |
|
sp->tty_major = (tty >> 8) & 0xfff; |
280 |
|
sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); |
281 |
|
#else |
282 |
|
/* This costs ~100 bytes more but makes top faster by 20% |
283 |
|
* If you run 10000 processes, this may be important for you */ |
284 |
|
sp->state[0] = cp[2]; |
285 |
|
cp += 4; |
286 |
|
sp->ppid = fast_strtoul_10(&cp); |
287 |
|
sp->pgid = fast_strtoul_10(&cp); |
288 |
|
sp->sid = fast_strtoul_10(&cp); |
289 |
|
tty = fast_strtoul_10(&cp); |
290 |
|
sp->tty_major = (tty >> 8) & 0xfff; |
291 |
|
sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); |
292 |
|
cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ |
293 |
|
sp->utime = fast_strtoul_10(&cp); |
294 |
|
sp->stime = fast_strtoul_10(&cp); |
295 |
|
cp = skip_fields(cp, 3); /* cutime, cstime, priority */ |
296 |
|
tasknice = fast_strtoul_10(&cp); |
297 |
|
cp = skip_fields(cp, 2); /* timeout, it_real_value */ |
298 |
|
sp->start_time = fast_strtoul_10(&cp); |
299 |
|
/* vsz is in bytes and we want kb */ |
300 |
|
sp->vsz = fast_strtoul_10(&cp) >> 10; |
301 |
|
/* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ |
302 |
|
sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb; |
303 |
|
#if ENABLE_FEATURE_TOP_SMP_PROCESS |
304 |
|
/* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ |
305 |
|
/* (4): signal, blocked, sigignore, sigcatch */ |
306 |
|
/* (4): wchan, nswap, cnswap, exit_signal */ |
307 |
|
cp = skip_fields(cp, 14); |
308 |
|
//FIXME: is it safe to assume this field exists? |
309 |
|
sp->last_seen_on_cpu = fast_strtoul_10(&cp); |
310 |
|
#endif |
311 |
|
#endif /* end of !ENABLE_FEATURE_TOP_SMP_PROCESS */ |
312 |
|
|
313 |
|
if (sp->vsz == 0 && sp->state[0] != 'Z') |
314 |
sp->state[1] = 'W'; |
sp->state[1] = 'W'; |
315 |
else |
else |
316 |
sp->state[1] = ' '; |
sp->state[1] = ' '; |
317 |
if (tasknice < 0) |
if (tasknice < 0) |
318 |
sp->state[2] = '<'; |
sp->state[2] = '<'; |
319 |
else if (tasknice > 0) |
else if (tasknice) /* > 0 */ |
320 |
sp->state[2] = 'N'; |
sp->state[2] = 'N'; |
321 |
else |
else |
322 |
sp->state[2] = ' '; |
sp->state[2] = ' '; |
323 |
|
} |
324 |
|
|
325 |
#ifdef PAGE_SHIFT |
#if ENABLE_FEATURE_TOPMEM |
326 |
sp->rss <<= (PAGE_SHIFT - 10); /* 2**10 = 1kb */ |
if (flags & (PSSCAN_SMAPS)) { |
327 |
#else |
FILE *file; |
328 |
sp->rss *= (getpagesize() >> 10); /* 2**10 = 1kb */ |
|
329 |
#endif |
strcpy(filename_tail, "/smaps"); |
330 |
|
file = fopen_for_read(filename); |
331 |
|
if (!file) |
332 |
|
break; |
333 |
|
while (fgets(buf, sizeof(buf), file)) { |
334 |
|
unsigned long sz; |
335 |
|
char *tp; |
336 |
|
char w; |
337 |
|
#define SCAN(str, name) \ |
338 |
|
if (strncmp(buf, str, sizeof(str)-1) == 0) { \ |
339 |
|
tp = skip_whitespace(buf + sizeof(str)-1); \ |
340 |
|
sp->name += fast_strtoul_10(&tp); \ |
341 |
|
continue; \ |
342 |
|
} |
343 |
|
SCAN("Shared_Clean:" , shared_clean ); |
344 |
|
SCAN("Shared_Dirty:" , shared_dirty ); |
345 |
|
SCAN("Private_Clean:", private_clean); |
346 |
|
SCAN("Private_Dirty:", private_dirty); |
347 |
|
#undef SCAN |
348 |
|
// f7d29000-f7d39000 rw-s ADR M:m OFS FILE |
349 |
|
tp = strchr(buf, '-'); |
350 |
|
if (tp) { |
351 |
|
*tp = ' '; |
352 |
|
tp = buf; |
353 |
|
sz = fast_strtoul_16(&tp); /* start */ |
354 |
|
sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */ |
355 |
|
// tp -> "rw-s" string |
356 |
|
w = tp[1]; |
357 |
|
// skipping "rw-s ADR M:m OFS " |
358 |
|
tp = skip_whitespace(skip_fields(tp, 4)); |
359 |
|
// filter out /dev/something (something != zero) |
360 |
|
if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { |
361 |
|
if (w == 'w') { |
362 |
|
sp->mapped_rw += sz; |
363 |
|
} else if (w == '-') { |
364 |
|
sp->mapped_ro += sz; |
365 |
|
} |
366 |
|
} |
367 |
|
//else printf("DROPPING %s (%s)\n", buf, tp); |
368 |
|
if (strcmp(tp, "[stack]\n") == 0) |
369 |
|
sp->stack += sz; |
370 |
|
} |
371 |
|
} |
372 |
|
fclose(file); |
373 |
} |
} |
374 |
|
#endif /* TOPMEM */ |
375 |
|
|
376 |
if (flags & PSSCAN_CMD) { |
#if 0 /* PSSCAN_CMD is not used */ |
377 |
|
if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) { |
378 |
|
free(sp->argv0); |
379 |
|
sp->argv0 = NULL; |
380 |
free(sp->cmd); |
free(sp->cmd); |
381 |
sp->cmd = NULL; |
sp->cmd = NULL; |
382 |
strcpy(filename_tail, "/cmdline"); |
strcpy(filename_tail, "/cmdline"); |
383 |
|
/* TODO: to get rid of size limits, read into malloc buf, |
384 |
|
* then realloc it down to real size. */ |
385 |
n = read_to_buf(filename, buf); |
n = read_to_buf(filename, buf); |
386 |
if (n <= 0) |
if (n <= 0) |
387 |
break; |
break; |
388 |
if (buf[n-1] == '\n') { |
if (flags & PSSCAN_ARGV0) |
389 |
if (!--n) |
sp->argv0 = xstrdup(buf); |
390 |
break; |
if (flags & PSSCAN_CMD) { |
391 |
buf[n] = '\0'; |
do { |
392 |
|
n--; |
393 |
|
if ((unsigned char)(buf[n]) < ' ') |
394 |
|
buf[n] = ' '; |
395 |
|
} while (n); |
396 |
|
sp->cmd = xstrdup(buf); |
397 |
} |
} |
|
do { |
|
|
n--; |
|
|
if ((unsigned char)(buf[n]) < ' ') |
|
|
buf[n] = ' '; |
|
|
} while (n); |
|
|
sp->cmd = xstrdup(buf); |
|
398 |
} |
} |
399 |
|
#else |
400 |
|
if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) { |
401 |
|
free(sp->argv0); |
402 |
|
sp->argv0 = NULL; |
403 |
|
strcpy(filename_tail, "/cmdline"); |
404 |
|
n = read_to_buf(filename, buf); |
405 |
|
if (n <= 0) |
406 |
|
break; |
407 |
|
if (flags & PSSCAN_ARGVN) { |
408 |
|
sp->argv_len = n; |
409 |
|
sp->argv0 = xmalloc(n + 1); |
410 |
|
memcpy(sp->argv0, buf, n + 1); |
411 |
|
/* sp->argv0[n] = '\0'; - buf has it */ |
412 |
|
} else { |
413 |
|
sp->argv_len = 0; |
414 |
|
sp->argv0 = xstrdup(buf); |
415 |
|
} |
416 |
|
} |
417 |
|
#endif |
418 |
break; |
break; |
419 |
} |
} |
420 |
return sp; |
return sp; |
421 |
} |
} |
422 |
|
|
423 |
|
void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) |
424 |
|
{ |
425 |
|
ssize_t sz; |
426 |
|
char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; |
427 |
|
|
428 |
|
sprintf(filename, "/proc/%u/cmdline", pid); |
429 |
|
sz = open_read_close(filename, buf, col); |
430 |
|
if (sz > 0) { |
431 |
|
buf[sz] = '\0'; |
432 |
|
while (--sz >= 0) |
433 |
|
if ((unsigned char)(buf[sz]) < ' ') |
434 |
|
buf[sz] = ' '; |
435 |
|
} else { |
436 |
|
snprintf(buf, col, "[%s]", comm); |
437 |
|
} |
438 |
|
} |
439 |
|
|
440 |
/* from kernel: |
/* from kernel: |
441 |
// pid comm S ppid pgid sid tty_nr tty_pgrp flg |
// pid comm S ppid pgid sid tty_nr tty_pgrp flg |
442 |
sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \ |
sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \ |
452 |
tty_pgrp, |
tty_pgrp, |
453 |
task->flags, |
task->flags, |
454 |
min_flt, |
min_flt, |
|
|
|
455 |
cmin_flt, |
cmin_flt, |
456 |
maj_flt, |
maj_flt, |
457 |
cmaj_flt, |
cmaj_flt, |