/* * Copyright 2004 - Eric Kerin * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written agreement * is hereby granted, provided that the above copyright notice and this * paragraph appear in all copies. * * Use at your own risk. * * Description: A program to implement log shipping for Postgresql 8.0 * When your ready for the backup to come online, kill the process, and the backup will finish startup. * * Command I use for Archive: * /path_to_binary/log_ship -a -d /archive_directory/ -f %f -p %p * * Command I use for Recovery: * /path_to_binary/log_ship -r -d /archive_directory/ -f %f -p %p */ #include #include #include #include #include #include #include #include char *progname = 0; char mode = 0; char *archive_dir = 0; char *archive_file = 0; char *archive_file_path = 0; void display_usage(){ printf( "log_ship works as an archive script for moving WAL files in Postgresql 8.0. \n" "This is done to implement log shipping, and maintain a backup system at a close\n" "point in time to the master system.\n\n"); printf("Usage %s \n\n",progname); printf("Mode: (only one of)\n"); printf("\t-a\tArchive Mode, transfer the files to the backup machine\n"); printf("\t-r\tRecovery Mode, copy the files where postmaster requests\n\n"); printf("File locations: (all are required)\n"); printf("\t-d\tArchive directory, location of archived WAL files\n"); printf("\t-f\tArchive filename, name of the WAL file for the request\n"); printf("\t-p\tArchive file path\n"); printf("\t\tRecovery Mode: name and location to place the archive\n"); printf("\t\tArchive Mode: the full path of the file to archive\n"); return; } // test if the file has the given extension, including the leading . int is_filetype(char *filename,char *extension){ char *file_ext; file_ext = rindex(filename,'.'); if(file_ext){ if(strcmp(file_ext,extension) == 0){ return 1; } } return 0; } int do_archive(){ int bufsize; int retval; char *cmdbuf; bufsize = 128; //128 is a good starting point, we'll realloc more if we need it later cmdbuf = (char *)malloc(bufsize); if(!cmdbuf){ fprintf(stderr,"%s: Out of Memory error while allocating command buffer\n",progname); return ENOMEM; } memset(cmdbuf,0,bufsize); //build the copy command, copying to a temp file, and renameing it once it gets there completely while( (retval = snprintf(cmdbuf,bufsize,"cp \"%s\" \"%s/logtemp-%d\"",archive_file_path,archive_dir,getpid())) > bufsize+1){ bufsize = retval; cmdbuf = (char *)realloc(cmdbuf,bufsize); if(!cmdbuf){ fprintf(stderr,"%s: Out of Memory error while allocating command buffer\n",progname); return ENOMEM; } memset(cmdbuf,0,bufsize); } //execute the copy retval = system(cmdbuf); if(retval != 0){ fprintf(stderr,"%s: copy command returned error number %d: %s\n",progname,retval,strerror(retval)); return retval; } //rename the file to the correct name while( (retval = snprintf(cmdbuf,bufsize,"mv \"%s/logtemp-%d\" \"%s/%s\" ",archive_dir,getpid(),archive_dir,archive_file)) > bufsize+1){ bufsize = retval; cmdbuf = (char *)realloc(cmdbuf,bufsize); if(!cmdbuf){ fprintf(stderr,"%s: Out of Memory error while allocating command buffer\n",progname); return ENOMEM; } memset(cmdbuf,0,bufsize); } //execute the rename retval = system(cmdbuf); if(retval != 0){ fprintf(stderr,"%s: rename command returned error number %d: %s\n",progname,retval,strerror(retval)); return retval; } //I love when a plan comes together return 0; } int do_recovery(){ int retval; char *cmdbuf,*srcbuf; int cmdbuf_size,srcbuf_size; struct stat statbuf; cmdbuf_size = 128; //128 is a good starting point, we'll realloc more if we need it later cmdbuf = (char *)malloc(cmdbuf_size); if(!cmdbuf){ fprintf(stderr,"%s: Out of Memory error while allocating command buffer\n",progname); return ENOMEM; } memset(cmdbuf,0,cmdbuf_size); srcbuf_size = 128; srcbuf = (char *)malloc(srcbuf_size); if(!srcbuf){ fprintf(stderr,"%s: Out of Memory error while allocating source filename buffer\n",progname); free(cmdbuf); return ENOMEM; } memset(cmdbuf,0,srcbuf_size); while( (retval = snprintf(srcbuf,srcbuf_size,"%s/%s",archive_dir,archive_file)) > srcbuf_size){ srcbuf_size = retval; srcbuf = (char *)realloc(srcbuf,srcbuf_size); if(!srcbuf){ fprintf(stderr,"%s: Out of Memory error while allocating source filename buffer\n",progname); return ENOMEM; } memset(srcbuf,0,srcbuf_size); } while(1){ retval = stat(srcbuf,&statbuf); if(retval != 0){ retval = errno; if(retval == ENOENT){ if(is_filetype(srcbuf,".history") || is_filetype(srcbuf,".backup")){ //don't sleep if the file isn't there, and it's a meta-file return ENOENT; }else{ sleep(1); continue; } }else{ fprintf(stderr,"%s: Failed to stat the requested file, and the file does exist: %s\n",progname,strerror(retval)); return retval; } } //the file exists, prepare to copy it to the location requested while( (retval = snprintf(cmdbuf,cmdbuf_size,"cp \"%s/%s\" \"%s\"",archive_dir,archive_file,archive_file_path)) > cmdbuf_size+1){ cmdbuf_size = retval+1; cmdbuf = (char *)realloc(cmdbuf,cmdbuf_size); if(!cmdbuf){ fprintf(stderr,"%s: Out of Memory error while allocating source filename buffer\n",progname); return ENOMEM; } memset(cmdbuf,0,cmdbuf_size); } //execute the copy retval = system(cmdbuf); if(retval != 0){ fprintf(stderr,"%s: copy command returned error number %d: %s\n",progname,retval,strerror(retval)); return retval; } //I love when a plan comes together return 0; } } int main(int argc,char **argv){ int opt; progname = argv[0]; //make the program name available outside of main while((opt = getopt(argc,argv,"ard:f:p:")) != -1){ switch(opt){ case 'a': //Archive Mode if(!mode){ mode = 'a'; }else{ fprintf(stderr,"%s: You may not specify more than one '-ar' option\n",progname); exit(EINVAL); } break; case 'r': //Recovery Mode if(!mode){ mode = 'r'; }else{ fprintf(stderr,"%s: You may not specify more than one '-ar' option\n",progname); exit(EINVAL); } break; case 'd': //Archive Directory if(optarg){ archive_dir = optarg; }else{ fprintf(stderr,"%s: '-d' requires an option\n",progname); exit(EINVAL); } break; case 'f': if(optarg){ archive_file = optarg; }else{ fprintf(stderr,"%s: '-f' requires an option\n",progname); exit(EINVAL); } break; case 'p': if(optarg){ archive_file_path = optarg; }else{ fprintf(stderr,"%s: '-p' requires an option\n",progname); exit(EINVAL); } break; default: display_usage(); exit(EINVAL); } } //make sure we got the parameters we need if(!mode){ fprintf(stderr,"%s: You must specify one of the '-ar' options\n",progname); } if(!archive_dir){ fprintf(stderr,"%s: Required option '-d' was not specified\n",progname); } if(!archive_file){ fprintf(stderr,"%s: Required option '-f' was not specified\n",progname); } if(!archive_file_path){ fprintf(stderr,"%s: Required option '-p' was not specified\n",progname); } if(!archive_dir || !archive_file || !archive_file_path || !mode){ display_usage(); exit(EINVAL); } //hand it off to the mode handler switch (mode){ case 'a': return do_archive(); case 'r': return do_recovery(); default: fprintf(stderr,"%s: Internal Error: Mode %c was set internally, but its not valid\n",progname,mode); exit(EINVAL); } }