There is a proposal to add “defer” to C. Its biggest example is taken from code that was originally designed to not manage storage at all, but to run once and exit – delegating all the cleanup to exit. The proposal authors show a complex solution to make the code free storage and then show how it can be “simplified” using defer. But it is trivial to centralize cleanup in one function, no new features needed. If I was developing this code for real, I’d take the next step and make it single exit. Here is is with a simple function to free all storage on exit.

struct plugins {
	char *pluginsdir;
	char *outpluginsdirphp;
	char *outpluginsdirmisc;
	char *dirpath;
	char *outdirphp;
	char *outdirmisc;
	int n;
	struct dirent **namelist;
};

void freeall(struct plugins *x)
{
	if (x->pluginsdir)
		free(x->pluginsdir);
	if (x->outpluginsdirphp)
		free(x->outpluginsdirphp);
	if (x->outpluginsdirmisc)
		free(x->outpluginsdirmisc);
	if (x->dirpath)
		free(x->dirpath);
	if (x->outdirphp)
		free(x->outdirphp);
	if (x->outdirmisc)
		free(x->outdirmisc);
	for (int i = 0; i < x->n; i++) {
			free(x->namelist[i]);
		}
}

h_err *h_build_plugins(const char *rootdir, h_build_outfiles outfiles,
		       const h_conf * conf)
{
	struct plugins x = { 0, };
	x.pluginsdir = h_util_path_join(rootdir, H_FILE_PLUGINS);
	if (pluginsdir == NULL)
		return h_err_create(H_ERR_ALLOC, NULL);
	x.outpluginsdirphp = h_util_path_join(rootdir,
					      H_FILE_OUTPUT "/" H_FILE_OUT_META
					      "/" H_FILE_OUT_PHP);
	if (outpluginsdirphp == NULL) {
		freeall(&x);
		return h_err_create(H_ERR_ALLOC, NULL);
	}
	x.outpluginsdirmisc = h_util_path_join(rootdir,
					       H_FILE_OUTPUT "/" H_FILE_OUT_META
					       "/" H_FILE_OUT_MISC);
	if (x.outpluginsdirmisc == NULL) {
		freeall(&x);
		return h_err_create(H_ERR_ALLOC, NULL);
	}
	//Check status of rootdir/plugins, returning if it doesn’t exist
	{
		int err = h_util_file_err(x.pluginsdir);
		if (err == ENOENT) {
			freeall(&x);
			return NULL;
		}
		if (err && err != EEXIST) {
			freeall(&x);
			return h_err_from_errno(err, x.pluginsdir);
		}
	}

	//Create dirs if they don’t exist
	if (mkdir(x.outpluginsdirphp, 0777) == -1 && errno != EEXIST) {
		freeall(&x);
		return h_err_from_errno(errno, x.outpluginsdirphp);
	}
	if (mkdir(outpluginsdirmisc, 0777) == -1 && errno != EEXIST) {
		freeall(&x);
		return h_err_from_errno(errno, outpluginsdirmisc);
	}
	//Loop through plugins, building them
	x.n = scandir(x.pluginsdir, &x.namelist, NULL, alphasort);
	if (n == -1) {
		freeall(&x);
		return h_err_from_errno(errno, x.namelist);
	}
	for (int i = 0; i < n; ++i) {
		struct dirent *ent = namelist[i];
		if (ent->d_name[0] == '.') {
			continue;
		}
		x.dirpath = h_util_path_join(x.pluginsdir, ent->d_name);
		if (dirpath == NULL) {
			freeall(&x);
			return h_err_create(H_ERR_ALLOC, NULL);
		}
		x.outdirphp = h_util_path_join(outpluginsdirphp, ent->d_name);
		if (x.outdirphp == NULL) {
			freeall(&x);
			return h_err_create(H_ERR_ALLOC, NULL);
		}
		x.outdirmisc =
		    h_util_path_join(x.outpluginsdirmisc, ent->d_name);
		if (x.outdirmisc == NULL) {
			freeall(&x);
			return h_err_create(H_ERR_ALLOC, NULL);
		}

		h_err *err;
		err =
		    build_plugin(dirpath, outdirphp, outdirmisc, outfiles,
				 conf);
		if (err) {
			freeall(&x);
			return err;
		}
	}

	freeall(&x);
	return NULL;
}
don’t defer