10f66f451Sopenharmony_ci/* dirtree.c - Functions for dealing with directory trees. 20f66f451Sopenharmony_ci * 30f66f451Sopenharmony_ci * Copyright 2007 Rob Landley <rob@landley.net> 40f66f451Sopenharmony_ci */ 50f66f451Sopenharmony_ci 60f66f451Sopenharmony_ci#include "toys.h" 70f66f451Sopenharmony_ci 80f66f451Sopenharmony_ciint isdotdot(char *name) 90f66f451Sopenharmony_ci{ 100f66f451Sopenharmony_ci if (name[0]=='.' && (!name[1] || (name[1]=='.' && !name[2]))) return 1; 110f66f451Sopenharmony_ci 120f66f451Sopenharmony_ci return 0; 130f66f451Sopenharmony_ci} 140f66f451Sopenharmony_ci 150f66f451Sopenharmony_ci// Default callback, filters out "." and ".." except at top level. 160f66f451Sopenharmony_ci 170f66f451Sopenharmony_ciint dirtree_notdotdot(struct dirtree *catch) 180f66f451Sopenharmony_ci{ 190f66f451Sopenharmony_ci // Should we skip "." and ".."? 200f66f451Sopenharmony_ci return (!catch->parent||!isdotdot(catch->name)) 210f66f451Sopenharmony_ci *(DIRTREE_SAVE|DIRTREE_RECURSE); 220f66f451Sopenharmony_ci} 230f66f451Sopenharmony_ci 240f66f451Sopenharmony_ci// Create a dirtree node from a path, with stat and symlink info. 250f66f451Sopenharmony_ci// (This doesn't open directory filehandles yet so as not to exhaust the 260f66f451Sopenharmony_ci// filehandle space on large trees, dirtree_handle_callback() does that.) 270f66f451Sopenharmony_ci 280f66f451Sopenharmony_cistruct dirtree *dirtree_add_node(struct dirtree *parent, char *name, int flags) 290f66f451Sopenharmony_ci{ 300f66f451Sopenharmony_ci struct dirtree *dt = 0; 310f66f451Sopenharmony_ci struct stat st; 320f66f451Sopenharmony_ci int len = 0, linklen = 0, statless = 0; 330f66f451Sopenharmony_ci 340f66f451Sopenharmony_ci if (name) { 350f66f451Sopenharmony_ci // open code this because haven't got node to call dirtree_parentfd() on yet 360f66f451Sopenharmony_ci int fd = parent ? parent->dirfd : AT_FDCWD; 370f66f451Sopenharmony_ci 380f66f451Sopenharmony_ci if (fstatat(fd, name, &st,AT_SYMLINK_NOFOLLOW*!(flags&DIRTREE_SYMFOLLOW))) { 390f66f451Sopenharmony_ci if (flags&DIRTREE_STATLESS) statless++; 400f66f451Sopenharmony_ci else goto error; 410f66f451Sopenharmony_ci } 420f66f451Sopenharmony_ci if (S_ISLNK(st.st_mode)) { 430f66f451Sopenharmony_ci if (0>(linklen = readlinkat(fd, name, libbuf, 4095))) goto error; 440f66f451Sopenharmony_ci libbuf[linklen++]=0; 450f66f451Sopenharmony_ci } 460f66f451Sopenharmony_ci len = strlen(name); 470f66f451Sopenharmony_ci } 480f66f451Sopenharmony_ci 490f66f451Sopenharmony_ci // Allocate/populate return structure 500f66f451Sopenharmony_ci dt = xmalloc((len = sizeof(struct dirtree)+len+1)+linklen); 510f66f451Sopenharmony_ci memset(dt, 0, statless ? offsetof(struct dirtree, again) 520f66f451Sopenharmony_ci : offsetof(struct dirtree, st)); 530f66f451Sopenharmony_ci dt->parent = parent; 540f66f451Sopenharmony_ci dt->again = statless ? 2 : 0; 550f66f451Sopenharmony_ci if (!statless) memcpy(&dt->st, &st, sizeof(struct stat)); 560f66f451Sopenharmony_ci strcpy(dt->name, name ? name : ""); 570f66f451Sopenharmony_ci if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen); 580f66f451Sopenharmony_ci 590f66f451Sopenharmony_ci return dt; 600f66f451Sopenharmony_ci 610f66f451Sopenharmony_cierror: 620f66f451Sopenharmony_ci if (!(flags&DIRTREE_SHUTUP) && !isdotdot(name)) { 630f66f451Sopenharmony_ci char *path = parent ? dirtree_path(parent, 0) : ""; 640f66f451Sopenharmony_ci 650f66f451Sopenharmony_ci perror_msg("%s%s%s", path, parent ? "/" : "", name); 660f66f451Sopenharmony_ci if (parent) free(path); 670f66f451Sopenharmony_ci } 680f66f451Sopenharmony_ci if (parent) parent->symlink = (char *)1; 690f66f451Sopenharmony_ci free(dt); 700f66f451Sopenharmony_ci return 0; 710f66f451Sopenharmony_ci} 720f66f451Sopenharmony_ci 730f66f451Sopenharmony_ci// Return path to this node, assembled recursively. 740f66f451Sopenharmony_ci 750f66f451Sopenharmony_ci// Initial call can pass in NULL to plen, or point to an int initialized to 0 760f66f451Sopenharmony_ci// to return the length of the path, or a value greater than 0 to allocate 770f66f451Sopenharmony_ci// extra space if you want to append your own text to the string. 780f66f451Sopenharmony_ci 790f66f451Sopenharmony_cichar *dirtree_path(struct dirtree *node, int *plen) 800f66f451Sopenharmony_ci{ 810f66f451Sopenharmony_ci char *path; 820f66f451Sopenharmony_ci int len; 830f66f451Sopenharmony_ci 840f66f451Sopenharmony_ci if (!node) { 850f66f451Sopenharmony_ci path = xmalloc(*plen); 860f66f451Sopenharmony_ci *plen = 0; 870f66f451Sopenharmony_ci return path; 880f66f451Sopenharmony_ci } 890f66f451Sopenharmony_ci 900f66f451Sopenharmony_ci len = (plen ? *plen : 0)+strlen(node->name)+1; 910f66f451Sopenharmony_ci path = dirtree_path(node->parent, &len); 920f66f451Sopenharmony_ci if (len && path[len-1] != '/') path[len++]='/'; 930f66f451Sopenharmony_ci len = stpcpy(path+len, node->name) - path; 940f66f451Sopenharmony_ci if (plen) *plen = len; 950f66f451Sopenharmony_ci 960f66f451Sopenharmony_ci return path; 970f66f451Sopenharmony_ci} 980f66f451Sopenharmony_ci 990f66f451Sopenharmony_ciint dirtree_parentfd(struct dirtree *node) 1000f66f451Sopenharmony_ci{ 1010f66f451Sopenharmony_ci return node->parent ? node->parent->dirfd : AT_FDCWD; 1020f66f451Sopenharmony_ci} 1030f66f451Sopenharmony_ci 1040f66f451Sopenharmony_ci// Handle callback for a node in the tree. Returns saved node(s) if 1050f66f451Sopenharmony_ci// callback returns DIRTREE_SAVE, otherwise frees consumed nodes and 1060f66f451Sopenharmony_ci// returns NULL. If !callback return top node unchanged. 1070f66f451Sopenharmony_ci// If !new return DIRTREE_ABORTVAL 1080f66f451Sopenharmony_ci 1090f66f451Sopenharmony_cistruct dirtree *dirtree_handle_callback(struct dirtree *new, 1100f66f451Sopenharmony_ci int (*callback)(struct dirtree *node)) 1110f66f451Sopenharmony_ci{ 1120f66f451Sopenharmony_ci int flags; 1130f66f451Sopenharmony_ci 1140f66f451Sopenharmony_ci if (!new) return DIRTREE_ABORTVAL; 1150f66f451Sopenharmony_ci if (!callback) return new; 1160f66f451Sopenharmony_ci flags = callback(new); 1170f66f451Sopenharmony_ci 1180f66f451Sopenharmony_ci if (S_ISDIR(new->st.st_mode) && (flags & (DIRTREE_RECURSE|DIRTREE_COMEAGAIN))) 1190f66f451Sopenharmony_ci flags = dirtree_recurse(new, callback, 1200f66f451Sopenharmony_ci openat(dirtree_parentfd(new), new->name, O_CLOEXEC), flags); 1210f66f451Sopenharmony_ci 1220f66f451Sopenharmony_ci // If this had children, it was callback's job to free them already. 1230f66f451Sopenharmony_ci if (!(flags & DIRTREE_SAVE)) { 1240f66f451Sopenharmony_ci free(new); 1250f66f451Sopenharmony_ci new = 0; 1260f66f451Sopenharmony_ci } 1270f66f451Sopenharmony_ci 1280f66f451Sopenharmony_ci return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new; 1290f66f451Sopenharmony_ci} 1300f66f451Sopenharmony_ci 1310f66f451Sopenharmony_ci// Recursively read/process children of directory node, filtering through 1320f66f451Sopenharmony_ci// callback(). Uses and closes supplied ->dirfd. 1330f66f451Sopenharmony_ci 1340f66f451Sopenharmony_ciint dirtree_recurse(struct dirtree *node, 1350f66f451Sopenharmony_ci int (*callback)(struct dirtree *node), int dirfd, int flags) 1360f66f451Sopenharmony_ci{ 1370f66f451Sopenharmony_ci struct dirtree *new, **ddt = &(node->child); 1380f66f451Sopenharmony_ci struct dirent *entry; 1390f66f451Sopenharmony_ci DIR *dir; 1400f66f451Sopenharmony_ci 1410f66f451Sopenharmony_ci node->dirfd = dirfd; 1420f66f451Sopenharmony_ci if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) { 1430f66f451Sopenharmony_ci if (!(flags & DIRTREE_SHUTUP)) { 1440f66f451Sopenharmony_ci char *path = dirtree_path(node, 0); 1450f66f451Sopenharmony_ci perror_msg_raw(path); 1460f66f451Sopenharmony_ci free(path); 1470f66f451Sopenharmony_ci } 1480f66f451Sopenharmony_ci close(node->dirfd); 1490f66f451Sopenharmony_ci 1500f66f451Sopenharmony_ci return flags; 1510f66f451Sopenharmony_ci } 1520f66f451Sopenharmony_ci 1530f66f451Sopenharmony_ci // according to the fddir() man page, the filehandle in the DIR * can still 1540f66f451Sopenharmony_ci // be externally used by things that don't lseek() it. 1550f66f451Sopenharmony_ci 1560f66f451Sopenharmony_ci // The extra parentheses are to shut the stupid compiler up. 1570f66f451Sopenharmony_ci while ((entry = readdir(dir))) { 1580f66f451Sopenharmony_ci if ((flags&DIRTREE_PROC) && !isdigit(*entry->d_name)) continue; 1590f66f451Sopenharmony_ci if (!(new = dirtree_add_node(node, entry->d_name, flags))) continue; 1600f66f451Sopenharmony_ci if (!new->st.st_blksize && !new->st.st_mode) 1610f66f451Sopenharmony_ci new->st.st_mode = entry->d_type<<12; 1620f66f451Sopenharmony_ci new = dirtree_handle_callback(new, callback); 1630f66f451Sopenharmony_ci if (new == DIRTREE_ABORTVAL) break; 1640f66f451Sopenharmony_ci if (new) { 1650f66f451Sopenharmony_ci *ddt = new; 1660f66f451Sopenharmony_ci ddt = &((*ddt)->next); 1670f66f451Sopenharmony_ci } 1680f66f451Sopenharmony_ci } 1690f66f451Sopenharmony_ci 1700f66f451Sopenharmony_ci if (flags & DIRTREE_COMEAGAIN) { 1710f66f451Sopenharmony_ci node->again |= 1; 1720f66f451Sopenharmony_ci flags = callback(node); 1730f66f451Sopenharmony_ci } 1740f66f451Sopenharmony_ci 1750f66f451Sopenharmony_ci // This closes filehandle as well, so note it 1760f66f451Sopenharmony_ci closedir(dir); 1770f66f451Sopenharmony_ci node->dirfd = -1; 1780f66f451Sopenharmony_ci 1790f66f451Sopenharmony_ci return flags; 1800f66f451Sopenharmony_ci} 1810f66f451Sopenharmony_ci 1820f66f451Sopenharmony_ci// Create dirtree from path, using callback to filter nodes. If !callback 1830f66f451Sopenharmony_ci// return just the top node. Use dirtree_notdotdot callback to allocate a 1840f66f451Sopenharmony_ci// tree of struct dirtree nodes and return pointer to root node for later 1850f66f451Sopenharmony_ci// processing. 1860f66f451Sopenharmony_ci// Returns DIRTREE_ABORTVAL if path didn't exist (use DIRTREE_SHUTUP to handle 1870f66f451Sopenharmony_ci// error message yourself). 1880f66f451Sopenharmony_ci 1890f66f451Sopenharmony_cistruct dirtree *dirtree_flagread(char *path, int flags, 1900f66f451Sopenharmony_ci int (*callback)(struct dirtree *node)) 1910f66f451Sopenharmony_ci{ 1920f66f451Sopenharmony_ci return dirtree_handle_callback(dirtree_add_node(0, path, flags), callback); 1930f66f451Sopenharmony_ci} 1940f66f451Sopenharmony_ci 1950f66f451Sopenharmony_ci// Common case 1960f66f451Sopenharmony_cistruct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node)) 1970f66f451Sopenharmony_ci{ 1980f66f451Sopenharmony_ci return dirtree_flagread(path, 0, callback); 1990f66f451Sopenharmony_ci} 200