Sunday, March 29, 2009

musicdir

I've been organizing my music in a certain way for quite some time: "$ARTIST - $ALBUM/$TRACK - $TITLE.flac". Even so, I've never taken advantage of that structure, at least programmatically. Now I have, with this program:

musicdir.c

#include <glob.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <argp.h>




typedef enum {
MODE_ALL = 1
} MODE;

struct arguments {
char * artist, * album, * title, * root;
size_t artist_len, album_len, title_len, root_len;
int track;
char mode;
} argument = {NULL, NULL, NULL, "/home/music/", 0, 0, 0, 12, -1, 0};

error_t parser(int key, char * arg, struct argp_state * state) {
int track;
switch(key) {
case 'a':
if(arg) {
argument.artist = arg;
argument.artist_len = strlen(arg);
}
break;
case 'c':
if(arg) {
argument.album = arg;
argument.album_len = strlen(arg);
}
break;
case 't':
if(arg) {
argument.title = arg;
argument.title_len = strlen(arg);
}
break;
case 'r':
if(arg) {
argument.root = arg;
argument.root_len = strlen(arg);
}
break;
case 'n':
track = atoi(arg);
if(track > 0)
argument.track = track;
else
return ARGP_ERR_UNKNOWN;
break;
case 'l':
argument.mode |= MODE_ALL;
break;
case ARGP_KEY_ARG:
return ARGP_ERR_UNKNOWN;
case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
break;
}
return 0;
}


int main(int argc, char ** argv) {
static struct argp_option options[] = {
{ "artist", 'a', "artist", 0, "The artist part of the filename.", 0 },
{ "album", 'c', "album", 0, "The album part of the filename.", 0 },
{ "title", 't', "title", 0, "The title part of the filename.", 0 },
{ "track", 'n', "track", 0, "The track part of the filename, greater than zero.", 0 },
{ "root-dir", 'r', "root-dir", 0, "The root music directory. [/home/music]", 0 },
{ "list-all", 'l', NULL, 0, "List all found files rather than the default (the first file).", 0},
{ 0 }
};
static struct argp args_parsed = {
options,
&parser,
NULL,
"The music directory finder.",
NULL,
NULL
};
int arg_index;
glob_t globs;
int glob_result;
error_t result;
size_t total_len = 16, i;
char * pattern = NULL;
if((result = argp_parse(&args_parsed, argc, argv, 0, &arg_index, &argument)) == 0) {
/* printf("root=%s, artist=%s, album=%s, title=%s, track=%d\n", argument.root, argument.artist, argument.album, argument.title, argument.track); */

/* Allocate string for pattern */
if(argument.root)
total_len += argument.root_len;
if(argument.artist)
total_len += argument.artist_len;
if(argument.album)
total_len += argument.album_len;
if(argument.title)
total_len += argument.title_len;
if(argument.track >= 0)
total_len += 11;
pattern = calloc(total_len, sizeof(char));


/* Copy arguments into pattern */
strcpy(pattern, argument.root);

if(argument.artist)
sprintf(pattern + strlen(pattern), "%s - ", argument.artist);
else
strcat(pattern, "* - ");

if(argument.album)
sprintf(pattern + strlen(pattern), "%s/", argument.album);
else
strcat(pattern, "*/");


if(argument.track >= 0)
sprintf(pattern + strlen(pattern), "%d - ", argument.track);
else
strcat(pattern, "* - ");

if(argument.title)
sprintf(pattern + strlen(pattern), "%s.*", argument.title);
else
strcat(pattern, "*.*");


/* puts(pattern); */
if((glob_result = glob(pattern, GLOB_NOESCAPE, NULL, &globs)) == 0) {
if(globs.gl_pathc > 0) {
puts(*globs.gl_pathv);
if(argument.mode & MODE_ALL)
for(i = 1; i < globs.gl_pathc; i++)
puts(globs.gl_pathv[i]);
}
globfree(&globs);
}
else {
switch(glob_result) {
case GLOB_NOSPACE:
fputs("glob: Unable to allocate memory.\n", stderr);
break;
case GLOB_ABORTED:
fputs("glob: Read error.\n", stderr);
break;
case GLOB_NOMATCH:
fputs("glob: No such file or directory.\n", stderr);
break;
}
}
free(pattern);
}
else
perror("argp_parse");
return 0;
}
% ./musicdir --help
Usage: musicdir [OPTION...]
The music directory finder.

-a, --artist=artist The artist part of the filename.
-c, --album=album The album part of the filename.
-l, --list-all List all found files rather than the default (the
first file).
-n, --track=track The track part of the filename, greater than zero.

-r, --root-dir=root-dir The root music directory. [/home/music]
--root-dir=root-dir The root music directory. [/home/music]
-t, --title=title The title part of the filename.
-?, --help Give this help list
--usage Give a short usage message

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
% ./musicdir -a aiko
/home/music/aiko - KissHug/01 - KissHug.flac

Thursday, March 19, 2009

sizeof

Even though this tool isn't really done, I've decided to release what I've built so far.

This program finds the size of a directory or file's contents based on the arguments given, then scales it down into human-readable output. It's a great tool for tracking the largest files on disk, and is far more reliable than du, despite being slower.

sizeof.c

#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#define MAX_FLEN PATH_MAX
const char * failname = NULL;

void seg() {
perror(failname);
fprintf(stderr, "%s: Unable to recover; exiting.\n", failname);
exit(255);
}

off_t get_size(const char * dirname, ino_t dinod) {
struct stat info;
DIR * dirinfo;
struct dirent * file;
off_t total = 0;
char tmp[MAX_FLEN], * tmp_target;
memset(tmp, '\0', MAX_FLEN);
size_t tmp_target_len;
strncpy(tmp, dirname, MAX_FLEN - 1);
strcat(tmp, "/");
tmp_target = tmp + (strlen(tmp));
tmp_target_len = MAX_FLEN - strlen(tmp_target) - 2;
dirinfo = opendir(dirname);
failname = dirname;
if(dirinfo != NULL) {
while((file = readdir(dirinfo)) != NULL) {
if(file->d_name[0] != '.') {
strncpy(tmp_target, file->d_name, tmp_target_len);
failname = tmp;
if(!stat(tmp, &info)) {
if(S_ISDIR(info.st_mode)) {
if(info.st_ino != dinod) // Prevent symlinks to self
total += get_size(tmp, info.st_ino);
else
fprintf(stderr, "%s: Directory symlink to self.\n", tmp);
}
else if(S_ISREG(info.st_mode))
total += info.st_size;
}
else
perror(file->d_name);
}
}
closedir(dirinfo);
}
else
perror(dirname);
return total;
}
void scale_down(double * total, const char ** suffix) {
if(*total >= (1 << 30)) {
*total /= (1 << 30);
*suffix = "gigabytes";
}
else if(*total >= (1 << 20)) {
*total /= (1 << 20);
*suffix = "megabytes";
}
else if(*total >= (1 << 10)) {
*total /= (1 << 10);
*suffix = "kilobytes";
}
}


int main(int argc, const char ** argv) {
double total;
const char * suffix;
int i;
struct stat info;
signal(11, &seg);
if(argc > 2) {
for(i = 1; i < argc; i++) {
if(!stat(argv[i], &info)) {
suffix = "bytes";
if(S_ISDIR(info.st_mode))
total = get_size(argv[i], 0);
else if(S_ISREG(info.st_mode))
total = info.st_size;
scale_down(&total, &suffix);
printf("%-40s: %.2lf %s\n", argv[i], total, suffix);
}
else
perror(argv[i]);
}
return 0;
}
else if(argc == 2) {
if(!stat(argv[1], &info)) {
suffix = "bytes";
if(S_ISDIR(info.st_mode))
total = get_size(argv[1], 0);
else if(S_ISREG(info.st_mode))
total = info.st_size;
scale_down(&total, &suffix);
printf("%.2lf %s\n", total, suffix);
return 0;
}
else {
perror(argv[2]);
return 1;
}
}
else {
fprintf(stderr, "Usage: %s directory\n", *argv);
return 1;
}
}
% ./sizeof *
Makefile : 291.00 bytes
sizeof : 19.33 kilobytes
sizeof.c : 2.51 kilobytes
sizeof.c.html : 23.34 kilobytes
sizeof.o : 13.17 kilobytes
sizeof.s : 8.08 kilobytes
sizeof_un.s : 7.80 kilobytes

cookie64!

The next in the cookie legacy: cookie for x86-64 systems (cookie64). The calling conventions of the x86-64 are considerably different from those of the x86. For one, to pass up to six arguments you use the registers %rdi, %rsi, %rdx, %rcx, %r8d, and %r9d in that order. To pass any more, just push the arguments onto the stack. The end result of this choice: greater speed due to fewer memory operations.

Here's the actual code:

cookie64.s

.testString:
.string "Would you like some cookies?"
.yesString:
.string "yes\n"
.noString:
.string "no\n"
.yesReply:
.string "Here you go!"
.noReply:
.string "Aww...why not?"
.neitherReply:
.string "What?"
.globl main
main:
pushq %rbp
movq %rsp, %rbp

subq $1024, %rsp

movq $.testString, %rdi
call puts

repeat:
movl $0, -8(%rbp)

movq stdin(%rip), %rdx
movq $1024, %rsi
leaq -8(%rbp), %rdi
call fgets

movzbq -8(%rbp), %rax
testb %al, %al
je main_end

leaq -8(%rbp), %rsi
movq $.yesString, %rdi
call strcasecmp

cmpq $0, %rax
je yes_label

movq $.noString, %rdi
call strcasecmp

cmpq $0, %rax
je no_label

neither_label:
movq $.neitherReply, %rdi
call puts
jmp repeat

yes_label:
movq $.yesReply, %rdi
call puts
jmp main_end
no_label:
movq $.noReply, %rdi
call puts
main_end:
addq $1024, %rsp
movq $0, %rax



movq %rbp, %rsp
popq %rbp
ret

Still as before, the console output is the same.

More cookie

Dissatisfied with how my most recent attempt handled strings, I decided to fix it a bit. Thank goodness for GCC's -S option:

cookie.s

.testString:
.string "Would you like some cookies?"
.yesString:
.string "yes\n"
.noString:
.string "no\n"
.yesReply:
.string "Here you go!"
.noReply:
.string "Aww...why not?"
.neitherReply:
.string "What?"
.globl main
main:
pushl %ebp
movl %esp, %ebp

subl $1024, %esp

pushl $.testString
call puts
addl $4, %esp

repeat:
movl $0, -4(%ebp)

pushl stdin
pushl $1024
leal -4(%ebp), %eax
pushl %eax
call fgets
addl $12, %esp

movzbl -4(%ebp), %eax
testb %al, %al
je main_end

leal -4(%ebp), %eax
pushl %eax
pushl $.yesString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je yes_label

pushl $.noString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je no_label

neither_label:
pushl $.neitherReply
call puts
addl $8, %esp
jmp repeat

yes_label:
pushl $.yesReply
call puts
addl $8, %esp
jmp main_end
no_label:
pushl $.noReply
call puts
addl $8, %esp
main_end:
addl $1024, %esp
movl $0, %eax



movl %ebp, %esp
popl %ebp
ret

Yet again, the console output is the same as before.

Wednesday, March 18, 2009

cookie Again

After reading up on calling conventions a bit more since last time, I decided to rewrite cookie.s:

cookie.s

.testString:
.string "Would you like some cookies?"
.yesString:
.string "yes\n"
.noString:
.string "no\n"
.yesReply:
.string "Here you go!"
.noReply:
.string "Aww...why not?"
.neitherReply:
.string "What?"
.globl main
main:
pushl %ebp
movl %esp, %ebp

subl $1024, %esp

pushl $.testString
call puts
addl $4, %esp

repeat:
movl $0, -4(%ebp)

pushl stdin
pushl $1024
leal -4(%ebp), %eax
pushl %eax
call fgets
addl $12, %esp

movl -4(%ebp), %eax
shl $24, %eax
jz main_end

leal -4(%ebp), %eax
pushl %eax
pushl $.yesString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je yes_label

pushl $.noString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je no_label

neither_label:
pushl $.neitherReply
call puts
addl $8, %esp
jmp repeat

yes_label:
pushl $.yesReply
call puts
addl $8, %esp
jmp main_end
no_label:
pushl $.noReply
call puts
addl $8, %esp
main_end:
addl $1024, %esp
movl $0, %eax



movl %ebp, %esp
popl %ebp
ret

The example output is the same as last time.

cookie

After a little studying for my compilers course, I decided to write an assembly language program for practice. Here it is:

cookie.s

.testString:
.string "Would you like some cookies?"
.yesString:
.string "yes\n"
.noString:
.string "no\n"
.yesReply:
.string "Here you go!"
.noReply:
.string "Aww...why not?"
.neitherReply:
.string "What?"
.globl main
main:
movl %esp, %ebp


leal 4(%esp), %eax
subl $1024, %esp

pushl %eax

pushl $.testString
call puts
addl $4, %esp

repeat:
movl (%esp), %eax
movl $0, (%eax)

pushl stdin
pushl $1024
pushl %eax
call fgets
addl $12, %esp

movl (%esp), %eax
movl (%eax), %ebx
shl $24, %ebx
jz main_end

pushl %eax
pushl $.yesString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je yes_label

pushl $.noString
call strcasecmp
addl $4, %esp

cmpl $0, %eax
je no_label

neither_label:
pushl $.neitherReply
call puts
addl $8, %esp
jmp repeat

yes_label:
pushl $.yesReply
call puts
addl $8, %esp
jmp main_end
no_label:
pushl $.noReply
call puts
addl $8, %esp
main_end:
addl $1028, %esp
movl $0, %eax
ret
neil@superflex Programs % gcc -m32 -ggdb -o cookie cookie.s
% ./cookie
Would you like some cookies?
yes
Here you go!
% ./cookie
Would you like some cookies?
no
Aww...why not?
% ./cookie
Would you like some cookies?
% ./cookie
Would you like some cookies?
eoeaeoae
What?
yes
Here you go!

Friday, March 6, 2009

latest.c

After having learned a lot about how to manipulate files in my past endeavor, I've decided to reimplement latest.sh in C:

latest.c

#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#define MAX_FLEN 256



int main(int argc, const char ** argv) {
struct {
time_t value;
char name[MAX_FLEN];
} tval = {0, ""};
struct stat info;
DIR * dirinfo;
struct dirent * file;
size_t i;
if(argc > 1) {
for(i = 1; i < argc; i++) {
if(!stat(argv[i], &info)) {
if(!*tval.name || info.st_mtime > tval.value) {
tval.value = info.st_mtime;
strncpy(tval.name, argv[i], MAX_FLEN);
}
}
else
perror(argv[i]);
}
}
else {
dirinfo = opendir(".");
while((file = readdir(dirinfo)) != NULL) {
if(file->d_name[0] != '.') {
if(!stat(file->d_name, &info)) {
if(!*tval.name || info.st_mtime > tval.value) {
tval.value = info.st_mtime;
strncpy(tval.name, file->d_name, MAX_FLEN);
}
}
else
perror(file->d_name);
}
}
closedir(dirinfo);
}
if(*tval.name) {
tval.name[MAX_FLEN - 1] = '\0';
puts(tval.name);
return 0;
}
else
return 1;
}

It simply finds the file with the most recent modification time.