2 * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
4 #include "smartcolsP.h"
9 * @short_description: lines grouing
11 * Lines groups manipulation API. The grouping API allows to create M:N
12 * relations between lines and on tree-like output it prints extra chart to
13 * visualize these relations. The group has unlimited number of members and
14 * group childs. See libsmartcols/sample/grouping* for more details.
18 void scols_ref_group(struct libscols_group
*gr
)
24 void scols_group_remove_children(struct libscols_group
*gr
)
29 while (!list_empty(&gr
->gr_children
)) {
30 struct libscols_line
*ln
= list_entry(gr
->gr_children
.next
,
31 struct libscols_line
, ln_children
);
33 DBG(GROUP
, ul_debugobj(gr
, "remove child"));
34 list_del_init(&ln
->ln_children
);
35 scols_ref_group(ln
->parent_group
);
36 ln
->parent_group
= NULL
;
41 void scols_group_remove_members(struct libscols_group
*gr
)
46 while (!list_empty(&gr
->gr_members
)) {
47 struct libscols_line
*ln
= list_entry(gr
->gr_members
.next
,
48 struct libscols_line
, ln_groups
);
50 DBG(GROUP
, ul_debugobj(gr
, "remove member [%p]", ln
));
51 list_del_init(&ln
->ln_groups
);
53 scols_unref_group(ln
->group
);
54 ln
->group
->nmembers
++;
61 /* note group has to be already without members to deallocate */
62 void scols_unref_group(struct libscols_group
*gr
)
64 if (gr
&& --gr
->refcount
<= 0) {
65 DBG(GROUP
, ul_debugobj(gr
, "dealloc"));
66 scols_group_remove_children(gr
);
67 list_del(&gr
->gr_groups
);
74 static void groups_fix_members_order(struct libscols_line
*ln
)
76 struct libscols_iter itr
;
77 struct libscols_line
*child
;
80 INIT_LIST_HEAD(&ln
->ln_groups
);
81 list_add_tail(&ln
->ln_groups
, &ln
->group
->gr_members
);
82 DBG(GROUP
, ul_debugobj(ln
->group
, "fixing member line=%p [%zu/%zu]",
83 ln
, ln
->group
->nmembers
,
84 list_count_entries(&ln
->group
->gr_members
)));
87 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
88 while (scols_line_next_child(ln
, &itr
, &child
) == 0)
89 groups_fix_members_order(child
);
92 * We modify gr_members list, so is_last_group_member() does not have
93 * to provide reliable answer, we need to verify by list_count_entries().
96 && is_last_group_member(ln
)
97 && ln
->group
->nmembers
== list_count_entries(&ln
->group
->gr_members
)) {
99 DBG(GROUP
, ul_debugobj(ln
->group
, "fixing childs"));
100 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
101 while (scols_line_next_group_child(ln
, &itr
, &child
) == 0)
102 groups_fix_members_order(child
);
106 void scols_groups_fix_members_order(struct libscols_table
*tb
)
108 struct libscols_iter itr
;
109 struct libscols_line
*ln
;
110 struct libscols_group
*gr
;
112 /* remove all from groups lists */
113 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
114 while (scols_table_next_group(tb
, &itr
, &gr
) == 0) {
115 while (!list_empty(&gr
->gr_members
)) {
116 struct libscols_line
*line
= list_entry(gr
->gr_members
.next
,
117 struct libscols_line
, ln_groups
);
118 list_del_init(&line
->ln_groups
);
122 /* add again to the groups list in order we walk in tree */
123 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
124 while (scols_table_next_line(tb
, &itr
, &ln
) == 0) {
125 if (ln
->parent
|| ln
->parent_group
)
127 groups_fix_members_order(ln
);
130 /* If group child is memeber of another group *
131 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
132 while (scols_table_next_group(tb, &itr, &gr) == 0) {
133 struct libscols_iter xitr;
134 struct libscols_line *child;
136 scols_reset_iter(&xitr, SCOLS_ITER_FORWARD);
137 while (scols_line_next_group_child(ln, &xitr, &child) == 0)
138 groups_fix_members_order(child);
143 static inline const char *group_state_to_string(int state
)
145 static const char *grpstates
[] = {
146 [SCOLS_GSTATE_NONE
] = "none",
147 [SCOLS_GSTATE_FIRST_MEMBER
] = "1st-member",
148 [SCOLS_GSTATE_MIDDLE_MEMBER
] = "middle-member",
149 [SCOLS_GSTATE_LAST_MEMBER
] = "last-member",
150 [SCOLS_GSTATE_MIDDLE_CHILD
] = "middle-child",
151 [SCOLS_GSTATE_LAST_CHILD
] = "last-child",
152 [SCOLS_GSTATE_CONT_MEMBERS
] = "continue-members",
153 [SCOLS_GSTATE_CONT_CHILDREN
] = "continue-children"
157 assert((size_t) state
< ARRAY_SIZE(grpstates
));
159 return grpstates
[state
];
162 static void grpset_debug(struct libscols_table
*tb
, struct libscols_line
*ln
)
166 for (i
= 0; i
< tb
->grpset_size
; i
++) {
168 struct libscols_group
*gr
= tb
->grpset
[i
];
171 DBG(LINE
, ul_debugobj(ln
, "grpset[%zu]: %p %s", i
,
172 gr
, group_state_to_string(gr
->state
)));
174 DBG(LINE
, ul_debug("grpset[%zu]: %p %s", i
,
175 gr
, group_state_to_string(gr
->state
)));
177 DBG(LINE
, ul_debugobj(ln
, "grpset[%zu]: free", i
));
179 DBG(LINE
, ul_debug("grpset[%zu]: free", i
));
182 static int group_state_for_line(struct libscols_group
*gr
, struct libscols_line
*ln
)
184 if (gr
->state
== SCOLS_GSTATE_NONE
&&
185 (ln
->group
!= gr
|| !is_first_group_member(ln
)))
187 * NONE is possible to translate to FIRST_MEMBER only, and only if
188 * line group matches with the current group.
190 return SCOLS_GSTATE_NONE
;
192 if (ln
->group
!= gr
&& ln
->parent_group
!= gr
) {
193 /* Not our line, continue */
194 if (gr
->state
== SCOLS_GSTATE_FIRST_MEMBER
||
195 gr
->state
== SCOLS_GSTATE_MIDDLE_MEMBER
||
196 gr
->state
== SCOLS_GSTATE_CONT_MEMBERS
)
197 return SCOLS_GSTATE_CONT_MEMBERS
;
199 if (gr
->state
== SCOLS_GSTATE_LAST_MEMBER
||
200 gr
->state
== SCOLS_GSTATE_MIDDLE_CHILD
||
201 gr
->state
== SCOLS_GSTATE_CONT_CHILDREN
)
202 return SCOLS_GSTATE_CONT_CHILDREN
;
204 } else if (ln
->group
== gr
&& is_first_group_member(ln
)) {
205 return SCOLS_GSTATE_FIRST_MEMBER
;
207 } else if (ln
->group
== gr
&& is_last_group_member(ln
)) {
208 return SCOLS_GSTATE_LAST_MEMBER
;
210 } else if (ln
->group
== gr
&& is_group_member(ln
)) {
211 return SCOLS_GSTATE_MIDDLE_MEMBER
;
213 } else if (ln
->parent_group
== gr
&& is_last_group_child(ln
)) {
214 return SCOLS_GSTATE_LAST_CHILD
;
216 } else if (ln
->parent_group
== gr
&& is_group_child(ln
)) {
217 return SCOLS_GSTATE_MIDDLE_CHILD
;
220 return SCOLS_GSTATE_NONE
;
223 /* For now we assume that each active group is just 3 columns width. Later we can make it dynamic...
225 static void grpset_apply_group_state(struct libscols_group
**xx
, int state
, struct libscols_group
*gr
)
227 DBG(GROUP
, ul_debugobj(gr
, " appling state to grpset"));
229 /* gr->state holds the old state, @state is the new state
231 if (state
== SCOLS_GSTATE_NONE
)
232 xx
[0] = xx
[1] = xx
[2] = NULL
;
234 xx
[0] = xx
[1] = xx
[2] = gr
;
238 static struct libscols_group
**grpset_locate_freespace(struct libscols_table
*tb
, size_t wanted
, int prepend
)
241 struct libscols_group
**tmp
, **first
= NULL
;
243 if (!tb
->grpset_size
)
247 for (i
= tb
->grpset_size
- 1; ; i
--) {
248 if (tb
->grpset
[i
] == NULL
) {
249 first
= &tb
->grpset
[i
];
259 for (i
= 0; i
< tb
->grpset_size
; i
++) {
260 if (tb
->grpset
[i
] == NULL
) {
262 first
= &tb
->grpset
[i
];
271 DBG(TAB
, ul_debugobj(tb
, " realocate grpset [sz: old=%zu, new=%zu]",
272 tb
->grpset_size
, tb
->grpset_size
+ wanted
));
274 tmp
= realloc(tb
->grpset
, (tb
->grpset_size
+ wanted
) * sizeof(struct libscols_group
*));
281 DBG(TAB
, ul_debugobj(tb
, " prepending free space"));
282 char *dest
= (char *) tb
->grpset
;
284 memmove( dest
+ (wanted
* sizeof(struct libscols_group
*)),
286 tb
->grpset_size
* sizeof(struct libscols_group
*));
289 first
= tb
->grpset
+ tb
->grpset_size
;
292 memset(first
, 0, wanted
* sizeof(struct libscols_group
*));
293 tb
->grpset_size
+= wanted
;
295 grpset_debug(tb
, NULL
);
300 static struct libscols_group
**grpset_locate_group(struct libscols_table
*tb
, struct libscols_group
*gr
)
304 for (i
= 0; i
< tb
->grpset_size
; i
++) {
305 if (gr
== tb
->grpset
[i
])
306 return &tb
->grpset
[i
];
314 #define SCOLS_GRPSET_MINSZ 3
316 static int grpset_update(struct libscols_table
*tb
, struct libscols_line
*ln
, struct libscols_group
*gr
)
318 struct libscols_group
**xx
;
321 DBG(LINE
, ul_debugobj(ln
, " group [%p] grpset update", gr
));
323 /* new state, note that gr->state still holds the original state */
324 state
= group_state_for_line(gr
, ln
);
325 DBG(LINE
, ul_debugobj(ln
, " state old='%s', new='%s'",
326 group_state_to_string(gr
->state
),
327 group_state_to_string(state
)));
329 if (state
== SCOLS_GSTATE_FIRST_MEMBER
&& gr
->state
!= SCOLS_GSTATE_NONE
) {
330 DBG(LINE
, ul_debugobj(ln
, "wrong group initialization"));
333 if (state
!= SCOLS_GSTATE_NONE
&& gr
->state
== SCOLS_GSTATE_LAST_CHILD
) {
334 DBG(LINE
, ul_debugobj(ln
, "wrong group termination"));
337 if (gr
->state
== SCOLS_GSTATE_LAST_MEMBER
&&
338 !(state
== SCOLS_GSTATE_LAST_CHILD
||
339 state
== SCOLS_GSTATE_CONT_CHILDREN
||
340 state
== SCOLS_GSTATE_MIDDLE_CHILD
||
341 state
== SCOLS_GSTATE_NONE
)) {
342 DBG(LINE
, ul_debugobj(ln
, "wrong group member->child order"));
346 /* should not happen; probably wrong line... */
347 if (gr
->state
== SCOLS_GSTATE_NONE
&& state
== SCOLS_GSTATE_NONE
)
350 /* locate place in grpset where we draw the group */
351 if (!tb
->grpset
|| gr
->state
== SCOLS_GSTATE_NONE
)
352 xx
= grpset_locate_freespace(tb
, SCOLS_GRPSET_MINSZ
, 1);
354 xx
= grpset_locate_group(tb
, gr
);
356 DBG(LINE
, ul_debugobj(ln
, "failed to locate group or reallocate grpset"));
360 grpset_apply_group_state(xx
, state
, gr
);
361 ON_DBG(LINE
, grpset_debug(tb
, ln
));
365 static int grpset_update_active_groups(struct libscols_table
*tb
, struct libscols_line
*ln
)
369 struct libscols_group
*last
= NULL
;
371 DBG(LINE
, ul_debugobj(ln
, " update for active groups"));
373 for (i
= 0; i
< tb
->grpset_size
; i
++) {
374 struct libscols_group
*gr
= tb
->grpset
[i
];
376 if (!gr
|| last
== gr
)
379 rc
= grpset_update(tb
, ln
, gr
);
384 DBG(LINE
, ul_debugobj(ln
, " <- active groups updated [rc=%d]", rc
));
388 int scols_groups_update_grpset(struct libscols_table
*tb
, struct libscols_line
*ln
)
392 DBG(LINE
, ul_debugobj(ln
, " grpset update [line: group=%p, parent_group=%p",
393 ln
->group
, ln
->parent_group
));
395 rc
= grpset_update_active_groups(tb
, ln
);
396 if (!rc
&& ln
->group
&& ln
->group
->state
== SCOLS_GSTATE_NONE
) {
397 DBG(LINE
, ul_debugobj(ln
, " introduce a new group"));
398 rc
= grpset_update(tb
, ln
, ln
->group
);
403 static int groups_calculate_grpset(struct libscols_table
*tb
, struct libscols_line
*ln
)
405 struct libscols_iter itr
;
406 struct libscols_line
*child
;
409 DBG(LINE
, ul_debugobj(ln
, " grpset calculate"));
412 rc
= scols_groups_update_grpset(tb
, ln
);
416 /* line's children */
417 if (has_children(ln
)) {
418 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
419 while (scols_line_next_child(ln
, &itr
, &child
) == 0) {
420 rc
= groups_calculate_grpset(tb
, child
);
426 /* group's children */
427 if (is_last_group_member(ln
) && has_group_children(ln
)) {
428 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
429 while (scols_line_next_group_child(ln
, &itr
, &child
) == 0) {
430 rc
= groups_calculate_grpset(tb
, child
);
440 int scols_groups_calculate_grpset(struct libscols_table
*tb
)
442 struct libscols_iter itr
;
443 struct libscols_line
*ln
;
446 DBG(TAB
, ul_debugobj(tb
, "grpset calculate [top-level]"));
448 scols_groups_reset_state(tb
);
450 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
451 while (scols_table_next_line(tb
, &itr
, &ln
) == 0) {
452 if (ln
->parent
|| ln
->parent_group
)
454 rc
= groups_calculate_grpset(tb
, ln
);
459 scols_groups_reset_state(tb
);
463 void scols_groups_reset_state(struct libscols_table
*tb
)
465 struct libscols_iter itr
;
466 struct libscols_group
*gr
;
468 DBG(TAB
, ul_debugobj(tb
, "reset groups states"));
470 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
471 while (scols_table_next_group(tb
, &itr
, &gr
) == 0)
472 gr
->state
= SCOLS_GSTATE_NONE
;
475 memset(tb
->grpset
, 0, tb
->grpset_size
);
478 static void add_member(struct libscols_group
*gr
, struct libscols_line
*ln
)
480 DBG(GROUP
, ul_debugobj(gr
, "add member"));
486 INIT_LIST_HEAD(&ln
->ln_groups
);
487 list_add_tail(&ln
->ln_groups
, &gr
->gr_members
);
492 * scols_table_group_lines:
493 * @tb: a pointer to a struct libscols_table instance
494 * @ln: new group member
495 * @member: group member
496 * @id: group identifier (unused, not implemented yet), use zero.
498 * This function add line @ln to group of lines represented by @member. If the
499 * group is not yet defined (@member is not member of any group) than a new one
502 * The @ln maybe a NULL -- in this case only a new group is allocated if not
505 * Note that the same line cannot be member of more groups (not implemented
506 * yet). The child of any group can be member of another group.
508 * The @id is not used for now, use 0. The plan is to use it to support
509 * multi-group membership in future.
511 * Returns: 0, a negative value in case of an error.
515 int scols_table_group_lines( struct libscols_table
*tb
,
516 struct libscols_line
*ln
,
517 struct libscols_line
*member
,
518 __attribute__((__unused__
)) int id
)
520 struct libscols_group
*gr
= NULL
;
522 if (!tb
|| (!ln
&& !member
))
525 if (ln
->group
&& !member
->group
)
527 if (ln
->group
&& member
->group
&& ln
->group
!= member
->group
)
533 /* create a new group */
535 gr
= calloc(1, sizeof(*gr
));
538 DBG(GROUP
, ul_debugobj(gr
, "alloc"));
540 INIT_LIST_HEAD(&gr
->gr_members
);
541 INIT_LIST_HEAD(&gr
->gr_children
);
542 INIT_LIST_HEAD(&gr
->gr_groups
);
544 /* add group to the table */
545 list_add_tail(&gr
->gr_groups
, &tb
->tb_groups
);
547 /* add the first member */
548 add_member(gr
, member
);
552 if (ln
&& !ln
->group
)
559 * scols_line_link_groups:
561 * @member: group member
562 * @id: group identifier (unused, not implemented yet))
564 * Define @ln as child of group represented by group @member. The line @ln
565 * cannot be child of any other line. It's possible to create group->child or
566 * parent->child relationship, but no both for the same line (child).
568 * The @id is not used for now, use 0. The plan is to use it to support
569 * multi-group membership in future.
571 * Returns: 0, a negative value in case of an error.
575 int scols_line_link_group(struct libscols_line
*ln
, struct libscols_line
*member
,
576 __attribute__((__unused__
)) int id
)
578 if (!ln
|| !member
|| !member
->group
|| ln
->parent
)
581 if (!list_empty(&ln
->ln_children
))
584 DBG(GROUP
, ul_debugobj(member
->group
, "add child"));
586 list_add_tail(&ln
->ln_children
, &member
->group
->gr_children
);
589 ln
->parent_group
= member
->group
;
590 scols_ref_group(member
->group
);