Skip to content

Commit fe234b2

Browse files
committed
Merge branch 'sync/upstream-13m2-20261108' into sync/upstream-13n-20261108
Brings the PR #34 review fixes (tree RSS descendants, PHP masker) into 13N.
2 parents 93474ea + e5a4dc0 commit fe234b2

7 files changed

Lines changed: 299 additions & 38 deletions

File tree

‎internal/cbm/php_inline_html.c‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum {
3131
PHP_TAG_OPEN_LEN = 5, /* <?php */
3232
PHP_TAG_ECHO_LEN = 3, /* <?= */
3333
PHP_TAG_SHORT_LEN = 2, /* <? */
34+
PHP_XML_LEN = 3, /* xml */
3435
PHP_HEREDOC_LEN = 3, /* <<< */
3536
PHP_FIRST_NON_ASCII = 0x80,
3637
PHP_NOT_FOUND = -1,
@@ -80,9 +81,15 @@ static bool php_ieq(const char *s, int len, int i, const char *word) {
8081
return true;
8182
}
8283

84+
/* True when the identifier at i is `xml` (an XML prolog target). */
85+
static bool php_xml_prolog(const char *s, int len, int i) {
86+
return php_ieq(s, len, i, "xml") &&
87+
!(i + PHP_XML_LEN < len && php_is_ident_char((unsigned char)s[i + PHP_XML_LEN]));
88+
}
89+
8390
/* Length of the open tag at i, or 0. `<?php` needs whitespace or EOF after it
84-
* and `<?=` is always a tag. A bare `<?` counts only when whitespace follows,
85-
* so an `<?xml ...?>` prolog in markup stays markup. */
91+
* and `<?=` is always a tag. A bare `<?` counts when whitespace or an
92+
* identifier follows, except `<?xml`, so an XML prolog in markup stays markup. */
8693
static int php_open_tag_len(const char *s, int len, int i) {
8794
if (!php_at(s, len, i, "<?")) {
8895
return 0;
@@ -98,6 +105,11 @@ static int php_open_tag_len(const char *s, int len, int i) {
98105
if (after < len && php_is_space((unsigned char)s[after])) {
99106
return PHP_TAG_SHORT_LEN;
100107
}
108+
/* `<?work();` is a short tag too, but `<?xml` stays an XML prolog. */
109+
if (after < len && php_is_ident_start((unsigned char)s[after]) &&
110+
!php_xml_prolog(s, len, after)) {
111+
return PHP_TAG_SHORT_LEN;
112+
}
101113
return 0;
102114
}
103115

@@ -141,7 +153,7 @@ static int php_skip_block_comment(const char *s, int len, int i) {
141153

142154
/* Index of the line break or `?>` that ends the one-line comment at i. */
143155
static int php_skip_line_comment(const char *s, int len, int i) {
144-
while (i < len && s[i] != '\n' && !php_at(s, len, i, "?>")) {
156+
while (i < len && s[i] != '\n' && s[i] != '\r' && !php_at(s, len, i, "?>")) {
145157
i++;
146158
}
147159
return i;
@@ -236,7 +248,8 @@ static int php_skip_ident(const char *s, int len, int i, bool *halt) {
236248

237249
/* Scan PHP code from i to the next close tag. Returns the index of that `?>`,
238250
* len when the file ends in PHP mode, or PHP_NOT_FOUND at __halt_compiler. */
239-
static int php_scan_code(const char *s, int len, int i) {
251+
static int php_scan_code(const char *s, int len, int i, int *comment) {
252+
*comment = PHP_NOT_FOUND;
240253
while (i < len) {
241254
unsigned char c = (unsigned char)s[i];
242255
if (php_at(s, len, i, "?>")) {
@@ -247,7 +260,11 @@ static int php_scan_code(const char *s, int len, int i) {
247260
} else if (php_at(s, len, i, "/*")) {
248261
i = php_skip_block_comment(s, len, i);
249262
} else if (php_at(s, len, i, "//") || (c == '#' && !php_at(s, len, i, "#["))) {
263+
int start = i;
250264
i = php_skip_line_comment(s, len, i);
265+
if (php_at(s, len, i, "?>")) {
266+
*comment = start; /* the close tag ends this comment */
267+
}
251268
} else if (php_at(s, len, i, "<<<")) {
252269
i = php_skip_heredoc(s, len, i);
253270
} else if (php_is_ident_start(c)) {
@@ -295,10 +312,15 @@ static void php_mask_blank(PHPMask *m, int from, int to) {
295312
}
296313
}
297314

298-
/* Turn the `?>` at i into `; `. */
299-
static void php_mask_close_tag(PHPMask *m, int i) {
315+
/* Turn the `?>` at i into `; `. When the tag ends a line comment starting at
316+
* `comment`, blank that comment first: `; ` cannot end it and a line break
317+
* would shift rows, so the reopened PHP on the same line would be swallowed. */
318+
static void php_mask_close_tag(PHPMask *m, int i, int comment) {
300319
char *out = php_mask_writable(m);
301320
if (out) {
321+
if (comment >= 0) {
322+
php_blank(out, comment, i);
323+
}
302324
out[i] = ';';
303325
out[i + SKIP_ONE] = ' ';
304326
}
@@ -336,11 +358,12 @@ const char *cbm_php_mask_inline_html(CBMArena *arena, const char *source, int so
336358
seen_open = true;
337359

338360
/* PHP mode, up to the close tag. */
339-
int close = php_scan_code(source, source_len, i + tag);
361+
int comment = PHP_NOT_FOUND;
362+
int close = php_scan_code(source, source_len, i + tag, &comment);
340363
if (close < 0 || close >= source_len) {
341364
break;
342365
}
343-
php_mask_close_tag(&m, close);
366+
php_mask_close_tag(&m, close, comment);
344367
i = close + PAIR_LEN;
345368
}
346369
/* On allocation failure parse the file as it is: the old behaviour. */

‎src/foundation/subprocess.c‎

Lines changed: 166 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ static cbm_proc_tree_rss_status_t cbm_subprocess_tree_rss_platform(cbm_subproces
591591
* all-process scan cannot decide membership: proc_pidinfo answers EPERM for
592592
* every process of another user, so "not in our group" and "in our group but
593593
* unreadable" would look the same. Returns the member count, or -1. */
594-
static int cbm_tree_rss_list_group(pid_t pgid, pid_t **pids_out) {
594+
static int cbm_tree_rss_list_pids(uint32_t type, uint32_t arg, pid_t **pids_out) {
595595
int capacity = 64;
596596
pid_t *pids = NULL;
597597
for (;;) {
@@ -602,8 +602,7 @@ static int cbm_tree_rss_list_group(pid_t pgid, pid_t **pids_out) {
602602
}
603603
pids = grown;
604604
errno = 0;
605-
int bytes =
606-
proc_listpids(PROC_PGRP_ONLY, (uint32_t)pgid, pids, capacity * (int)sizeof(*pids));
605+
int bytes = proc_listpids(type, arg, pids, capacity * (int)sizeof(*pids));
607606
if (bytes < 0 || (bytes == 0 && errno != 0)) {
608607
cbm_free(CBM_MEM_CLASS_OTHER, pids);
609608
return -1;
@@ -621,6 +620,63 @@ static int cbm_tree_rss_list_group(pid_t pgid, pid_t **pids_out) {
621620
}
622621
}
623622

623+
/* Bound on the processes one probe retains; past it the probe fails closed. */
624+
enum { CBM_APPLE_PROBE_MAX_PROCS = 65536 };
625+
626+
/* The worker's process group plus every descendant by parent pid. Children
627+
* placed in a group of their own (git discovery) are not in the group list.
628+
* Pids already collected are skipped, so nothing is counted twice. Returns the
629+
* count, or -1. */
630+
static int cbm_tree_rss_list_tree(pid_t pgid, pid_t **pids_out) {
631+
pid_t *tree = NULL;
632+
int count = cbm_tree_rss_list_pids(PROC_PGRP_ONLY, (uint32_t)pgid, &tree);
633+
if (count < 0) {
634+
return -1;
635+
}
636+
int capacity = count > 0 ? count : 1;
637+
for (int index = 0; index < count; index++) {
638+
pid_t *children = NULL;
639+
int child_count = tree[index] > 0 ? cbm_tree_rss_list_pids(PROC_PPID_ONLY,
640+
(uint32_t)tree[index], &children)
641+
: 0;
642+
if (child_count < 0) {
643+
/* ESRCH-style races surface as an empty listing; a real error
644+
* must not under-report the tree. */
645+
cbm_free(CBM_MEM_CLASS_OTHER, tree);
646+
return -1;
647+
}
648+
for (int child = 0; child < child_count; child++) {
649+
bool known = children[child] <= 0;
650+
for (int seen = 0; !known && seen < count; seen++) {
651+
known = tree[seen] == children[child];
652+
}
653+
if (known) {
654+
continue;
655+
}
656+
if (count >= CBM_APPLE_PROBE_MAX_PROCS) {
657+
cbm_free(CBM_MEM_CLASS_OTHER, children);
658+
cbm_free(CBM_MEM_CLASS_OTHER, tree);
659+
return -1;
660+
}
661+
if (count == capacity) {
662+
pid_t *grown =
663+
cbm_realloc(CBM_MEM_CLASS_OTHER, tree, (size_t)capacity * 2 * sizeof(*tree));
664+
if (!grown) {
665+
cbm_free(CBM_MEM_CLASS_OTHER, children);
666+
cbm_free(CBM_MEM_CLASS_OTHER, tree);
667+
return -1;
668+
}
669+
tree = grown;
670+
capacity *= 2;
671+
}
672+
tree[count++] = children[child];
673+
}
674+
cbm_free(CBM_MEM_CLASS_OTHER, children);
675+
}
676+
*pids_out = tree;
677+
return count;
678+
}
679+
624680
static int cbm_tree_rss_member_rusage(pid_t pid, bool root, struct rusage_info_v2 *usage) {
625681
#ifdef CBM_ENABLE_TEST_SEAMS
626682
int fault = cbm_tree_rss_member_fault(root);
@@ -637,7 +693,7 @@ static int cbm_tree_rss_member_rusage(pid_t pid, bool root, struct rusage_info_v
637693
static cbm_proc_tree_rss_status_t cbm_subprocess_tree_rss_platform(cbm_subprocess_t *process,
638694
uint64_t *rss_bytes) {
639695
pid_t *pids = NULL;
640-
int count = cbm_tree_rss_list_group(process->pgid, &pids);
696+
int count = cbm_tree_rss_list_tree(process->pgid, &pids);
641697
if (count < 0) {
642698
return CBM_PROC_TREE_RSS_ERROR;
643699
}
@@ -730,8 +786,8 @@ static cbm_linux_stat_t cbm_linux_proc_stat_line(const char *pid_name, bool root
730786
return CBM_LINUX_STAT_OK;
731787
}
732788

733-
static cbm_linux_stat_t cbm_linux_proc_stat(const char *pid_name, bool root, pid_t *group,
734-
int64_t *rss_pages) {
789+
static cbm_linux_stat_t cbm_linux_proc_stat(const char *pid_name, bool root, pid_t *parent,
790+
pid_t *group, int64_t *rss_pages) {
735791
char stat_line[4096];
736792
cbm_linux_stat_t status =
737793
cbm_linux_proc_stat_line(pid_name, root, stat_line, sizeof(stat_line));
@@ -746,16 +802,20 @@ static cbm_linux_stat_t cbm_linux_proc_stat(const char *pid_name, bool root, pid
746802
char *save = NULL;
747803
char *token = strtok_r(command_end + 2, " ", &save);
748804
int field = 3;
805+
bool have_parent = false;
749806
bool have_group = false;
750807
bool have_rss = false;
751808
while (token) {
752-
if (field == 5 || field == 24) {
809+
if (field == 4 || field == 5 || field == 24) {
753810
char *end = NULL;
754811
long long value = strtoll(token, &end, 10);
755812
if (!end || *end != '\0') {
756813
return CBM_LINUX_STAT_UNREADABLE;
757814
}
758-
if (field == 5) {
815+
if (field == 4) {
816+
*parent = (pid_t)value;
817+
have_parent = true;
818+
} else if (field == 5) {
759819
*group = (pid_t)value;
760820
have_group = true;
761821
} else {
@@ -767,7 +827,76 @@ static cbm_linux_stat_t cbm_linux_proc_stat(const char *pid_name, bool root, pid
767827
token = strtok_r(NULL, " ", &save);
768828
field++;
769829
}
770-
return have_group && have_rss ? CBM_LINUX_STAT_OK : CBM_LINUX_STAT_UNREADABLE;
830+
return have_parent && have_group && have_rss ? CBM_LINUX_STAT_OK : CBM_LINUX_STAT_UNREADABLE;
831+
}
832+
833+
/* One scanned process. `counted` is set once it is known to belong to the
834+
* worker tree. */
835+
typedef struct {
836+
pid_t pid;
837+
pid_t parent;
838+
pid_t group;
839+
uint64_t bytes;
840+
bool counted;
841+
} cbm_linux_proc_t;
842+
843+
/* Bound on the processes one probe retains. Past it the probe fails closed
844+
* instead of reporting a total that may miss part of the tree. */
845+
enum { CBM_LINUX_PROBE_MAX_PROCS = 262144, CBM_LINUX_PROBE_INITIAL_PROCS = 512 };
846+
847+
static bool cbm_linux_probe_push(cbm_linux_proc_t **procs, size_t *count, size_t *capacity,
848+
const cbm_linux_proc_t *proc) {
849+
if (*count == *capacity) {
850+
if (*capacity >= CBM_LINUX_PROBE_MAX_PROCS) {
851+
return false;
852+
}
853+
size_t grown_capacity = *capacity ? *capacity * 2 : CBM_LINUX_PROBE_INITIAL_PROCS;
854+
cbm_linux_proc_t *grown =
855+
cbm_realloc(CBM_MEM_CLASS_OTHER, *procs, grown_capacity * sizeof(**procs));
856+
if (!grown) {
857+
return false;
858+
}
859+
*procs = grown;
860+
*capacity = grown_capacity;
861+
}
862+
(*procs)[(*count)++] = *proc;
863+
return true;
864+
}
865+
866+
static int cbm_linux_probe_by_pid(const void *left, const void *right) {
867+
pid_t a = ((const cbm_linux_proc_t *)left)->pid;
868+
pid_t b = ((const cbm_linux_proc_t *)right)->pid;
869+
return (a > b) - (a < b);
870+
}
871+
872+
/* Mark every process of the worker tree: members of its process group, plus
873+
* any descendant by parent pid. Descendants can sit in a group of their own
874+
* (git discovery children are spawned with setpgid), so the group alone would
875+
* miss them. `procs` is sorted by pid, so a parent is found by binary search
876+
* and each pass is O(n log n); passes repeat until no process is added (at
877+
* most the tree depth). A process is counted once because the flag is checked
878+
* before it is set. */
879+
static void cbm_linux_probe_mark_tree(cbm_linux_proc_t *procs, size_t count, pid_t pgid) {
880+
qsort(procs, count, sizeof(*procs), cbm_linux_probe_by_pid);
881+
for (size_t index = 0; index < count; index++) {
882+
procs[index].counted = procs[index].group == pgid;
883+
}
884+
bool changed = true;
885+
while (changed) {
886+
changed = false;
887+
for (size_t index = 0; index < count; index++) {
888+
if (procs[index].counted || procs[index].parent <= 1) {
889+
continue;
890+
}
891+
cbm_linux_proc_t key = {.pid = procs[index].parent};
892+
const cbm_linux_proc_t *parent =
893+
bsearch(&key, procs, count, sizeof(*procs), cbm_linux_probe_by_pid);
894+
if (parent && parent->counted) {
895+
procs[index].counted = true;
896+
changed = true;
897+
}
898+
}
899+
}
771900
}
772901

773902
static cbm_proc_tree_rss_status_t cbm_subprocess_tree_rss_platform(cbm_subprocess_t *process,
@@ -782,36 +911,54 @@ static cbm_proc_tree_rss_status_t cbm_subprocess_tree_rss_platform(cbm_subproces
782911
return CBM_PROC_TREE_RSS_ERROR;
783912
}
784913

785-
uint64_t total = 0;
786-
int measured = 0;
914+
cbm_linux_proc_t *procs = NULL;
915+
size_t count = 0;
916+
size_t capacity = 0;
787917
bool failed = false;
788918
struct dirent *entry;
789919
while (!failed && (entry = readdir(proc)) != NULL) {
790920
if (!cbm_proc_pid_name(entry->d_name)) {
791921
continue;
792922
}
923+
pid_t parent = 0;
793924
pid_t group = 0;
794925
int64_t pages = 0;
795-
bool root_entry = strtol(entry->d_name, NULL, 10) == (long)process->pid;
796-
cbm_linux_stat_t status = cbm_linux_proc_stat(entry->d_name, root_entry, &group, &pages);
926+
long pid = strtol(entry->d_name, NULL, 10);
927+
bool root_entry = pid == (long)process->pid;
928+
cbm_linux_stat_t status =
929+
cbm_linux_proc_stat(entry->d_name, root_entry, &parent, &group, &pages);
797930
if (status != CBM_LINUX_STAT_OK) {
798931
failed = root_entry || status != CBM_LINUX_STAT_GONE;
799932
continue;
800933
}
801-
if (group != process->pgid || pages < 0) {
934+
if (pages < 0) {
802935
continue;
803936
}
804937
uint64_t page_count = (uint64_t)pages;
805-
uint64_t bytes = page_count > UINT64_MAX / (uint64_t)page_size
806-
? UINT64_MAX
807-
: page_count * (uint64_t)page_size;
808-
cbm_rss_add_saturated(&total, bytes);
809-
measured++;
938+
cbm_linux_proc_t item = {.pid = (pid_t)pid,
939+
.parent = parent,
940+
.group = group,
941+
.bytes = page_count > UINT64_MAX / (uint64_t)page_size
942+
? UINT64_MAX
943+
: page_count * (uint64_t)page_size};
944+
failed = !cbm_linux_probe_push(&procs, &count, &capacity, &item);
810945
}
811946
(void)closedir(proc);
812947
if (failed) {
948+
cbm_free(CBM_MEM_CLASS_OTHER, procs);
813949
return CBM_PROC_TREE_RSS_ERROR;
814950
}
951+
952+
cbm_linux_probe_mark_tree(procs, count, process->pgid);
953+
uint64_t total = 0;
954+
int measured = 0;
955+
for (size_t index = 0; index < count; index++) {
956+
if (procs[index].counted) {
957+
cbm_rss_add_saturated(&total, procs[index].bytes);
958+
measured++;
959+
}
960+
}
961+
cbm_free(CBM_MEM_CLASS_OTHER, procs);
815962
if (measured == 0) {
816963
return CBM_PROC_TREE_RSS_EMPTY;
817964
}

0 commit comments

Comments
 (0)