Show HN: c
github.com
github.com
#include <stdbool.h>
is better than typedef int bool;
#define true 1
#define false 0
Always, always, surround your if and else clauses with brackets. There are plenty of ways leaving them naked will screw you over. This goes for any language.This is terrifying, and broken:
char * s = malloc(snprintf(NULL, 0, "%s/%s", cwd, argv[1]) + 1);
sprintf(s, "%s/%s", cwd, argv[1]);
Passing NULL to snprintf, first of all, should be a huge red flag for you. If it's not, train yourself to recognize it. I realize what you're trying to do, but it relies on non-portable behavior. In particular, from the manual: The glibc implementation of the functions snprintf() and vsnprintf() conforms
to the C99 standard, that is, behaves as described above, since glibc version
2.1. Until glibc 2.0.6 they would return -1 when the output was truncated.
So until glibc 2.0.6, your program would be allocating 0 and then sprintfing into it. Always be careful when you read the manual, to read everything. And even then, program defensively, and train yourself to watch for errors like this one. In this case, passing NULL as the destination argument was an immediate no-no.In this function, you really should be checking errno (for ENOENT). stat(2) can fail for all sorts of reasons, one of them is that the entry is not there.
bool dirOrFileExists(const char dir[]) {
struct stat st;
if (stat(dir, &st) == 0) {
return true;
}
else {
return false;
}
}
Same for this loop, check errno to make sure the function's failing for the reason you think it should. while((entry = readdir(mydir)))
Here, you have something a little messy: int fileOrDirectory(const char path[]) {
struct stat s;
if (stat(path, &s) == 0) {
if (s.st_mode & S_IFDIR) {
// Is a directory
return 0;
} else if (s.st_mode & S_IFREG) {
// Is a file
return 1;
} else {
// Something else
return -1;
}
}
else {
// Error
printf("Error occurred\n");
}
}
First of all, returning values only you know about is bound for destruction. You should define an enum if you want a function to return specific states. Better yet, just do these checks in the function that would normally call this. It's not so bad usually, and you're probably calling stat(2) more than you should. It's a syscall, and those can be expensive. But more importantly, you have a branch where all you do is print something (to stdout, not stderr as you should), and then just return nothing! If you hit an error state, you need to either propagate that error up and handle it in the caller, or crash immediately. Don't leave your program in a garbage state and keep going. A compiler should have warned you about this (not having a return value in one branch). Turn on -Werror and at least -Wall. And fix your warnings, don't try to suppress them.You should use fscanf(3) instead of this:
while (1) {
ch = fgetc(fp);
newch[i] = ch;
if (ch == EOF) {
newch[i] = '\0';
break;
}
i++;
}
That's all for now, I gotta go. Good luck!No, it isn't; you don't know how quickly the program will terminate, in general, and it only takes one program running longer than you expect to eat a horrible amount of RAM.
(I do think it's bad style, and would never write this program this way.)
[1] Assuming you stay away from the Zombie^W Network File System. But NFS mounts going away is bad news anyway, this program holding an extra KB of swappable memory doesn't really intensify the pain.
The downside to calling free(), apart from performance (malloc/free are extremely expensive), is that messing them up creates bugs that are much worse than simply holding on to some allocator metadata until the program hits exit().
Messing them up exposes bugs that are likely to bite you in other ways. If you can't get malloc()/free() right, you don't really 'get' the flow control/data flow through your program yet and that is a problem.
Virtually no large project has ever gotten malloc/free completely right in its first revision; for the past 20 years or so, most projects get this wrong to the tune of "remote code execution".
My strategy in c is as soon as I write the word "malloc" I figure out where to put the free.
+ cwd[] should be MAXPATHLEN bytes long
+ if you want people to use it, you need to put some license on it. GPL and ISC (simplified BSD) are good choices.
+ you may wish to consider asprintf() and err() (both of which elegantly solve a problem that leif has pointed out). These functions are non-POSIX but widely supported and easily portable. (You can also learn how to use the autotools to handle OSes that do not have these functions.)
+ getopt_long() may be easier than parsing parameters "by hand", especially once your utility grows more parameters.
+ hardcoding the colours is ok for now, but you should really use terminfo to find the appropriate escape sequence. The "magic word" for setting the foreground colour is "setaf"; you may wish to look at terminfo(3) and the source code for tput, if your OS has that utility. Warning: proper terminal handling is complex.
This is actually a common mistake — PATH_MAX and the like are not guaranteed to be defined. In fact, in POSIX it is literally impossible to ever know whether your buffer is long enough. Your best bet is,
#ifdef PATH_MAX
char cwd[PATH_MAX];
long len = sizeof cwd;
#else
char *cwd;
long len;
if((len = pathconf(".", _PC_PATH_MAX)) < 0)
len = 8192; /* some arbitrary value */
cwd = malloc(len);
#endif
This is probably one of the ugliest parts of POSIX.This is all academic though; dynamic sizing for a buffer containing a Unix path passed in on the command line? Professional code would just use PATH_MAX or whatever and be done with it. I see downthread that there are concerns about path limits, but since so much of the rest of Unix doesn't care about that particular problem, I'm not sure what the win is trying to optimize it.
glibc 2.0.6 was released in 1997. I do not think it is reasonable to ask that applications accommodate bugs in libc that have been fixed for 15 years. If someone was actually using a system that old, their system will have so many unpatched vulnerabilities that this will be the least of their problems. And it's highly unlikely that every such bug is documented in the manpages anyway.
It's a portability issue.
If you really truly cared about maximum portability, you'd just take a libc snprintf() that did the NULL-size thing and bundle it with your project, just like the AIX version of Sendmail bundled snprintf() because the platform didn't have it.
Or, even better: JETTISON THE PRINTF FAMILY and replace it with something that lets you register your own format codes. This is all ANSI C string manipulation and this code compiles small and in milliseconds.
But in this case the whole discussion is silly because the dynamic string buffer here is not only overengineered (both Unix paths and command line arguments have size restrictions and so dynamic sizing is silly) but also non-idiomatic.
And those on Mac should be able to use:
$ brew install c
Wow, uh, didn't it strike you that this might be a bad idea?Even so, based on the general rules for homebrew inclusion they would reject the formula because:
1. I don't think they like names that have the potential to conflict with others ("c" is in that category). The shortest name I see in their repo is two chars.
2. Typically they only want widely used formulas ("We generally frown on authors submitting their own work unless it is suitably notable." [1]).
[1] https://github.com/mxcl/homebrew/wiki/Acceptable-Formula
Also, it is only my hunch that they wouldn't accept the formula--you can still try. I'd still rename the project to be more descriptive (dir-c?) as there are only two homebrew formulae that have single char names (R, which has been around since 1993 and Z, which I'm surprised got included).
Isn't this based on the assumption that one's shell explicitly supports ^L or has editline/readline support?
i.e., "c 128 A" -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
Pentesters! :)
$ jot -b test -c 3
test
test
test if (getcwd(cwd, sizeof(cwd)) != NULL)
;
else
perror("getcwd() error");
Is that common? What's the advantages over the below? if (... == NULL) {
perror(...);
}Bonus point for me trying to filter out C++.
The community then responded by posting helpful feedback, much of which demonstrates the idiomatic way of doing things, which is often one of the most difficult things to learn when picking up a new programming language. Other helpful advice is posted on build options and debug tools.
This kind of constructive and useful feedback is great to see.
Tagging support would be cool.
And yeah, didn't think of that. It would be neat to be able to tag files or directories and then execute a command to see all the directories with that tag and then enter that directory easily. Maybe i'll try adding that to the next version. Can you think of anything else that would be useful?
EDIT
Because I was bored: https://gist.github.com/2697095