zpmod  b19981f
High-performance Zsh module for script optimization and filesystem helpers
rehash_diff.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: MIT */
14 #include "zpmod.mdh"
15 #include "zpmod.pro"
16 #include "zpmod_emoji.h"
17 #include "zpmod_rehash.h"
18 #include "zpmod_vendor_shims.h"
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/stat.h>
24 
25 #define ZP_RH_SUBDIR "zpmod"
26 #define ZP_RH_FILE "rehash_path_v1.snapshot"
27 
28 static char *zp_rh_base_cache_dir(void) {
29  const char *xdg = getsparam("XDG_CACHE_HOME");
30  const char *home = getsparam("HOME");
31  const char *base = (xdg && *xdg) ? xdg : NULL;
32  if (!base && home && *home) {
33  size_t len = strlen(home) + sizeof("/.cache");
34  char *p = (char *)zalloc(len);
35  if (!p) {
36  return NULL;
37  }
38  strcpy(p, home);
39  strcat(p, "/.cache");
40  return p;
41  }
42  if (base) {
43  return ztrdup(base);
44  }
45  return NULL;
46 }
47 
48 static char *zp_rh_dir(char *nam) {
49  char *base = zp_rh_base_cache_dir();
50  if (!base) {
51  zwarnnam(nam, "%srehash-diff: cannot resolve cache directory",
52  zp_icon("❌ "));
53  return NULL;
54  }
55  size_t len = strlen(base) + 1 + strlen(ZP_RH_SUBDIR) + 1;
56  char *full = (char *)zalloc(len);
57  if (!full) {
58  zsfree(base);
59  return NULL;
60  }
61  sprintf(full, "%s/%s", base, ZP_RH_SUBDIR);
62  zsfree(base);
63  struct stat st;
64  if (stat(full, &st) != 0) {
65  if (mkdir(full, 0700) != 0) {
66  zwarnnam(nam, "%srehash-diff: cannot create %s: %s", zp_icon("❌ "), full,
67  strerror(errno));
68  zsfree(full);
69  return NULL;
70  }
71  }
72  return full;
73 }
74 
75 static char *zp_rh_file(char *nam) {
76  char *dir = zp_rh_dir(nam);
77  if (!dir) {
78  return NULL;
79  }
80  size_t len = strlen(dir) + 1 + strlen(ZP_RH_FILE) + 1;
81  char *p = (char *)zalloc(len);
82  if (!p) {
83  zsfree(dir);
84  return NULL;
85  }
86  sprintf(p, "%s/%s", dir, ZP_RH_FILE);
87  zsfree(dir);
88  return p;
89 }
90 
91 struct rh_entry {
92  char *path;
93  unsigned long ino;
94  time_t mtime;
96 };
97 struct rh_vec {
98  struct rh_entry *items;
99  size_t size;
100  size_t cap;
101 };
102 static void rh_vec_init(struct rh_vec *v) {
103  v->items = NULL;
104  v->size = 0;
105  v->cap = 0;
106 }
107 static int rh_vec_push(struct rh_vec *v, struct rh_entry *e) {
108  if (v->size == v->cap) {
109  size_t nc = v->cap ? v->cap * 2 : 16;
110  void *nb = zrealloc(v->items, nc * sizeof(struct rh_entry));
111  if (!nb) {
112  return 1;
113  }
114  v->items = (struct rh_entry *)nb;
115  v->cap = nc;
116  }
117  v->items[v->size++] = *e;
118  return 0;
119 }
120 static void rh_vec_free(struct rh_vec *v) {
121  if (!v) {
122  return;
123  }
124  for (size_t i = 0; i < v->size; i++) {
125  if (v->items[i].path) {
126  zsfree(v->items[i].path);
127  }
128  }
129  if (v->items) {
130  zfree(v->items, v->cap * sizeof(struct rh_entry));
131  }
132  v->items = NULL;
133  v->size = v->cap = 0;
134 }
135 
136 /* Load snapshot; returns 0 success, 1 error */
137 // NOLINTBEGIN(bugprone-easily-swappable-parameters)
138 static int rh_load(const char *nam, const char *file, struct rh_vec *out) {
139  FILE *fp = fopen(file, "r");
140  if (!fp) {
141  return 1;
142  }
143  char line[4096];
144  if (!fgets(line, sizeof(line), fp)) {
145  fclose(fp);
146  return 1;
147  }
148  if (strncmp(line, "version:1", 9) != 0) {
149  fclose(fp);
150  return 1;
151  }
152  while (fgets(line, sizeof(line), fp)) {
153  if (line[0] == '\n' || line[0] == '#') {
154  continue;
155  }
156  char *nl = strchr(line, '\n');
157  if (nl) {
158  *nl = '\0';
159  }
160  char *save = NULL;
161  char *p = strtok_r(line, "\t", &save);
162  if (!p) {
163  continue;
164  }
165  char *ino_s = strtok_r(NULL, "\t", &save);
166  char *mt_s = strtok_r(NULL, "\t", &save);
167  if (!ino_s || !mt_s) {
168  continue;
169  }
170  unsigned long ino = strtoul(ino_s, NULL, 10);
171  long mt = strtol(mt_s, NULL, 10);
172  size_t l = strlen(p) + 1;
173  char *dup = (char *)zalloc(l);
174  if (!dup) {
175  break;
176  }
177  memcpy(dup, p, l);
178  struct rh_entry e;
179  e.path = dup;
180  e.ino = ino;
181  e.mtime = (time_t)mt;
182  e.present_now = 0;
183  if (rh_vec_push(out, &e)) {
184  zsfree(dup);
185  break;
186  }
187  }
188  fclose(fp);
189  (void)nam;
190  return 0;
191 }
192 // NOLINTEND(bugprone-easily-swappable-parameters)
193 
194 static int rh_write(char *nam, const char *file, struct rh_vec *paths) {
195  FILE *fp = fopen(file, "w");
196  if (!fp) {
197  zwarnnam(nam, "%srehash-diff: cannot write %s: %s", zp_icon("❌ "), file,
198  strerror(errno));
199  return 1;
200  }
201  fprintf(fp, "version:1\n");
202  for (size_t i = 0; i < paths->size; i++) {
203  struct stat st;
204  if (stat(paths->items[i].path, &st) != 0) {
205  continue;
206  }
207  fprintf(fp, "%s\t%lu\t%ld\n", paths->items[i].path,
208  (unsigned long)st.st_ino, (long)st.st_mtime);
209  }
210  fclose(fp);
211  return 0;
212 }
213 
214 static void rh_collect_current(struct rh_vec *out) {
215  char **arr = getaparam("path");
216  if (!arr) {
217  return;
218  }
219  for (char **path_it = arr; *path_it; ++path_it) {
220  if (!**path_it) {
221  continue;
222  }
223  size_t len = strlen(*path_it) + 1;
224  char *dup = (char *)zalloc(len);
225  if (!dup) {
226  break;
227  }
228  memcpy(dup, *path_it, len);
229  struct rh_entry e;
230  e.path = dup;
231  e.ino = 0;
232  e.mtime = 0;
233  e.present_now = 1;
234  if (rh_vec_push(out, &e)) {
235  zsfree(dup);
236  break;
237  }
238  }
239 }
240 
241 int zp_rehash_diff_core(char *nam) {
242  int ret = 0;
243  char *file = zp_rh_file(nam);
244  if (!file) {
245  return 1;
246  }
247  struct rh_vec prev;
248  rh_vec_init(&prev);
249  int have_prev = (rh_load(nam, file, &prev) == 0);
250  struct rh_vec now;
251  rh_vec_init(&now);
252  rh_collect_current(&now);
253  /* Map prev entries by path via linear scan (PATH lengths are small) */
254  int added = 0;
255  int removed = 0;
256  int changed = 0;
257  int unchanged = 0;
258  for (size_t i = 0; i < now.size; i++) {
259  struct rh_entry *cur = &now.items[i];
260  struct stat st;
261  if (stat(cur->path, &st) != 0) {
262  continue;
263  }
264  cur->ino = st.st_ino;
265  cur->mtime = st.st_mtime;
266  int found = 0;
267  if (have_prev) {
268  for (size_t j = 0; j < prev.size; j++) {
269  if (strcmp(prev.items[j].path, cur->path) == 0) {
270  found = 1;
271  prev.items[j].present_now = 1;
272  if (prev.items[j].ino != cur->ino ||
273  prev.items[j].mtime != cur->mtime) {
274  changed++;
275  } else {
276  unchanged++;
277  }
278  break;
279  }
280  }
281  }
282  if (!found) {
283  added++;
284  }
285  }
286  if (have_prev) {
287  for (size_t j = 0; j < prev.size; j++) {
288  if (!prev.items[j].present_now) {
289  removed++;
290  }
291  }
292  }
293 
294  /* Write new snapshot (always, keeps last state) */
295  if (rh_write(nam, file, &now) != 0) {
296  ret = 1;
297  }
298  fprintf(stdout,
299  "%srehash-diff: added=%d removed=%d changed=%d unchanged=%d\n",
300  zp_icon("🔄 "), added, removed, changed, unchanged);
301  if (added) {
302  fprintf(stdout, " + dirs: ");
303  for (size_t i = 0, printed = 0; i < now.size; i++) {
304  int f = 0;
305  if (!have_prev) {
306  f = 1;
307  } else {
308  for (size_t j = 0; j < prev.size; j++) {
309  if (strcmp(prev.items[j].path, now.items[i].path) == 0) {
310  f = 1;
311  break;
312  }
313  }
314  f = !f;
315  }
316  if (f) {
317  if (printed++) {
318  fputc(' ', stdout);
319  }
320  fputs(now.items[i].path, stdout);
321  }
322  }
323  fputc('\n', stdout);
324  }
325  if (removed) {
326  fprintf(stdout, " - dirs: ");
327  for (size_t j = 0, printed = 0; j < prev.size; j++) {
328  if (!prev.items[j].present_now) {
329  if (printed++) {
330  fputc(' ', stdout);
331  }
332  fputs(prev.items[j].path, stdout);
333  }
334  }
335  fputc('\n', stdout);
336  }
337  if (changed) {
338  fprintf(stdout, " * dirs: ");
339  for (size_t i = 0, printed = 0; i < now.size; i++) {
340  for (size_t j = 0; j < prev.size; j++) {
341  if (strcmp(prev.items[j].path, now.items[i].path) == 0 &&
342  (prev.items[j].ino != now.items[i].ino ||
343  prev.items[j].mtime != now.items[i].mtime)) {
344  if (printed++) {
345  fputc(' ', stdout);
346  }
347  fputs(now.items[i].path, stdout);
348  break;
349  }
350  }
351  }
352  fputc('\n', stdout);
353  }
354  fflush(stdout);
355  rh_vec_free(&prev);
356  rh_vec_free(&now);
357  zsfree(file);
358  return ret;
359 }
const char * zp_icon(const char *s)
Return icon string if enabled, empty string otherwise.
Definition: emoji.c:58
static char * zp_rh_dir(char *nam)
Definition: rehash_diff.c:48
static char * zp_rh_base_cache_dir(void)
Definition: rehash_diff.c:28
#define ZP_RH_SUBDIR
Definition: rehash_diff.c:25
static void rh_collect_current(struct rh_vec *out)
Definition: rehash_diff.c:214
static int rh_write(char *nam, const char *file, struct rh_vec *paths)
Definition: rehash_diff.c:194
static void rh_vec_init(struct rh_vec *v)
Definition: rehash_diff.c:102
int zp_rehash_diff_core(char *nam)
Definition: rehash_diff.c:241
static int rh_vec_push(struct rh_vec *v, struct rh_entry *e)
Definition: rehash_diff.c:107
static char * zp_rh_file(char *nam)
Definition: rehash_diff.c:75
static void rh_vec_free(struct rh_vec *v)
Definition: rehash_diff.c:120
#define ZP_RH_FILE
Definition: rehash_diff.c:26
static int rh_load(const char *nam, const char *file, struct rh_vec *out)
Definition: rehash_diff.c:138
Definition: rehash_diff.c:91
char * path
Definition: rehash_diff.c:92
unsigned long ino
Definition: rehash_diff.c:93
time_t mtime
Definition: rehash_diff.c:94
int present_now
Definition: rehash_diff.c:95
struct rh_entry * items
Definition: rehash_diff.c:98
size_t size
Definition: rehash_diff.c:99
size_t cap
Definition: rehash_diff.c:100
Module declaration header (mdh) for zpmod.
Prototype stub for zpmod when building out-of-tree.
Optional terminal/locale detection for emoji support in messages.
void * zalloc(size_t size)
void zsfree(char *ptr)
void zfree(void *ptr, size_t size)
char ** getaparam(const char *name)
void * zrealloc(void *ptr, size_t size)
char * getsparam(const char *name)
void zwarnnam(const char *, const char *,...)
char * ztrdup(const char *)
Incremental PATH diff rehash entrypoint.
Local, non-invasive shims to suppress benign vendor header warnings.