/* ** WEBPAGE: dir ** ** Query parameters: ** ** name=PATH Directory to display. Required. ** ci=LABEL Show only files in this check-in. Optional. */ void page_dir(void){ const char *zD = P("name"); int mxLen; int nCol, nRow; int cnt, i; char *zPrefix; Stmt q; const char *zCI = P("ci"); int rid = 0; Blob content; Blob dirname; Manifest m; const char *zSubdirLink; login_check_credentials(); if( !g.okHistory ){ login_needed(); return; } style_header("File List"); sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); /* If the name= parameter is an empty string, make it a NULL pointer */ if( zD && strlen(zD)==0 ){ zD = 0; } /* If a specific check-in is requested, fetch and parse it. */ if( zCI && (rid = name_to_rid(zCI))!=0 && content_get(rid, &content) ){ if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ zCI = 0; } } /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "in directory ", -1); hyperlinked_path(zD, &dirname); zPrefix = mprintf("%h/", zD); }else{ blob_append(&dirname, "in the top-level directory", -1); zPrefix = ""; } if( zCI ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); char zShort[20]; memcpy(zShort, zUuid, 10); zShort[10] = 0; @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] @ %s(blob_str(&dirname))</h2> zSubdirLink = mprintf("%s/dir?ci=%S&name=%T", g.zTop, zUuid, zPrefix); if( zD ){ style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid); style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); }else{ style_submenu_element("All", "All", "%s/dir", g.zBaseURL); } }else{
/* ** WEBPAGE: access_log ** ** y=N 1: success only. 2: failure only. 3: both ** n=N Number of entries to show ** o=N Skip this many entries */ void access_log_page(void){ int y = atoi(PD("y","3")); int n = atoi(PD("n","50")); int skip = atoi(PD("o","0")); Blob sql; Stmt q; int cnt = 0; int rc; login_check_credentials(); if( !g.perm.Admin ){ login_needed(); return; } create_accesslog_table(); if( P("delall") && P("delallbtn") ){ db_multi_exec("DELETE FROM accesslog"); cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip); return; } if( P("delanon") && P("delanonbtn") ){ db_multi_exec("DELETE FROM accesslog WHERE uname='anonymous'"); cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip); return; } if( P("delfail") && P("delfailbtn") ){ db_multi_exec("DELETE FROM accesslog WHERE NOT success"); cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip); return; } if( P("delold") && P("deloldbtn") ){ db_multi_exec("DELETE FROM accesslog WHERE rowid in" "(SELECT rowid FROM accesslog ORDER BY rowid DESC" " LIMIT -1 OFFSET 200)"); cgi_redirectf("%s/access_log?y=%d&n=%d", g.zTop, y, n); return; } style_header("Access Log"); blob_zero(&sql); blob_append(&sql, "SELECT uname, ipaddr, datetime(mtime, 'localtime'), success" " FROM accesslog", -1 ); if( y==1 ){ blob_append(&sql, " WHERE success", -1); }else if( y==2 ){ blob_append(&sql, " WHERE NOT success", -1); } blob_appendf(&sql," ORDER BY rowid DESC LIMIT %d OFFSET %d", n+1, skip); if( skip ){ style_submenu_element("Newer", "Newer entries", "%s/access_log?o=%d&n=%d&y=%d", g.zTop, skip>=n ? skip-n : 0, n, y); } rc = db_prepare_ignore_error(&q, blob_str(&sql)); @ <center><table border="1" cellpadding="5">
/* ** SSH initialization of the transport layer */ int transport_ssh_open(UrlData *pUrlData){ /* For SSH we need to create and run SSH fossil http ** to talk to the remote machine. */ const char *zSsh; /* The base SSH command */ Blob zCmd; /* The SSH command */ char *zHost; /* The host name to contact */ int n; /* Size of prefix string */ socket_ssh_resolve_addr(pUrlData); zSsh = db_get("ssh-command", zDefaultSshCmd); blob_init(&zCmd, zSsh, -1); if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){ #ifdef __MINGW32__ blob_appendf(&zCmd, " -P %d", pUrlData->port); #else blob_appendf(&zCmd, " -p %d", pUrlData->port); #endif } if( g.fSshTrace ){ fossil_force_newline(); fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */ } if( pUrlData->user && pUrlData->user[0] ){ zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name); }else{ zHost = mprintf("%s", pUrlData->name); } n = blob_size(&zCmd); blob_append(&zCmd, " ", 1); shell_escape(&zCmd, zHost); blob_append(&zCmd, " ", 1); shell_escape(&zCmd, mprintf("%s", pUrlData->fossil)); blob_append(&zCmd, " test-http", 10); if( pUrlData->path && pUrlData->path[0] ){ blob_append(&zCmd, " ", 1); shell_escape(&zCmd, mprintf("%s", pUrlData->path)); } if( g.fSshTrace ){ fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ } free(zHost); popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); if( sshPid==0 ){ socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd); } blob_reset(&zCmd); return sshPid==0; }
static void html_escape(struct Blob *ob, const char *data, size_t size){ size_t beg = 0, i = 0; while( i<size ){ beg = i; while( i<size && data[i]!='<' && data[i]!='>' && data[i]!='"' && data[i]!='&' ){ i++; } blob_append(ob, data+beg, i-beg); while( i<size ){ if( data[i]=='<' ){ BLOB_APPEND_LITERAL(ob, "<"); }else if( data[i]=='>' ){ BLOB_APPEND_LITERAL(ob, ">"); }else if( data[i]=='&' ){ BLOB_APPEND_LITERAL(ob, "&"); }else if( data[i]=='"' ){ BLOB_APPEND_LITERAL(ob, """); }else{ break; } i++; } } }
/* ** Helper for the timeline family of functions. Possibly appends 1 ** AND clause and an ORDER BY clause to pSql, depending on the state ** of the "after" ("a") or "before" ("b") environment parameters. ** This function gives "after" precedence over "before", and only ** applies one of them. ** ** Returns -1 if it adds a "before" clause, 1 if it adds ** an "after" clause, and 0 if adds only an order-by clause. */ static char json_timeline_add_time_clause(Blob *pSql){ char const * zAfter = NULL; char const * zBefore = NULL; int rc = 0; zAfter = json_find_option_cstr("after",NULL,"a"); zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b"); if(zAfter&&*zAfter){ while( fossil_isspace(*zAfter) ) ++zAfter; blob_appendf(pSql, " AND event.mtime>=(SELECT julianday(%Q,'utc')) " " ORDER BY event.mtime ASC ", zAfter); rc = 1; }else if(zBefore && *zBefore){ while( fossil_isspace(*zBefore) ) ++zBefore; blob_appendf(pSql, " AND event.mtime<=(SELECT julianday(%Q,'utc')) " " ORDER BY event.mtime DESC ", zBefore); rc = -1; }else{ blob_append(pSql, " ORDER BY event.mtime DESC ", -1); rc = 0; } return rc; }
static void html_raw_block(struct Blob *ob, struct Blob *text, void *opaque){ char *data = blob_buffer(text); size_t first = 0, size = blob_size(text); INTER_BLOCK(ob); while( first<size && data[first]=='\n' ) first++; while( size>first && data[size-1]=='\n' ) size--; blob_append(ob, data+first, size-first); BLOB_APPEND_LITERAL(ob, "\n"); }
/* ** Strip a possible byte-order-mark (BOM) from the blob. On Windows, if there ** is either no BOM at all or an (le/be) UTF-16 BOM, a conversion to UTF-8 is ** done. If useMbcs is false and there is no BOM, the input string is assumed ** to be UTF-8 already, so no conversion is done. */ void blob_to_utf8_no_bom(Blob *pBlob, int useMbcs){ char *zUtf8; int bomSize = 0; #if defined(_WIN32) || defined(__CYGWIN__) int bomReverse = 0; #endif if( starts_with_utf8_bom(pBlob, &bomSize) ){ struct Blob temp; zUtf8 = blob_str(pBlob) + bomSize; blob_zero(&temp); blob_append(&temp, zUtf8, -1); blob_swap(pBlob, &temp); blob_reset(&temp); #if defined(_WIN32) || defined(__CYGWIN__) }else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){ zUtf8 = blob_buffer(pBlob); if( bomReverse ){ /* Found BOM, but with reversed bytes */ unsigned int i = blob_size(pBlob); while( i>0 ){ /* swap bytes of unicode representation */ char zTemp = zUtf8[--i]; zUtf8[i] = zUtf8[i-1]; zUtf8[--i] = zTemp; } } /* Make sure the blob contains two terminating 0-bytes */ blob_append(pBlob, "", 1); zUtf8 = blob_str(pBlob) + bomSize; zUtf8 = fossil_unicode_to_utf8(zUtf8); blob_zero(pBlob); blob_append(pBlob, zUtf8, -1); fossil_unicode_free(zUtf8); #endif /* _WIN32 || __CYGWIN__ */ #if defined(_WIN32) }else if( useMbcs ){ zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob)); blob_reset(pBlob); blob_append(pBlob, zUtf8, -1); fossil_mbcs_free(zUtf8); #endif /* _WIN32 */ } }
/* ** Show the difference between two files, one in memory and one on disk. ** ** The difference is the set of edits needed to transform pFile1 into ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ static void diff_file( Blob *pFile1, /* In memory content to compare from */ const char *zFile2, /* On disk content to compare to */ const char *zName, /* Display name of the file */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ Blob file2; /* Content of zFile2 */ /* Read content of zFile2 into memory */ blob_zero(&file2); blob_read_from_file(&file2, zFile2); /* Compute and output the differences */ blob_zero(&out); //text_diff(pFile1, &file2, &out, 5, ignoreEolWs); csvs_diff(pFile1, &file2, &out); printf("--- %s\n+++ %s\n", zName, zName); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&file2); blob_reset(&out); }else{ int cnt = 0; Blob nameFile1; /* Name of temporary file to old pFile1 content */ Blob cmd; /* Text of command to run */ /* Construct a temporary file to hold pFile1 based on the name of ** zFile2 */ blob_zero(&nameFile1); do{ blob_reset(&nameFile1); blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++); }while( access(blob_str(&nameFile1),0)==0 ); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, blob_str(&nameFile1)); blob_append(&cmd, " ", 1); shell_escape(&cmd, zFile2); /* Run the external diff command */ portable_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(blob_str(&nameFile1)); blob_reset(&nameFile1); blob_reset(&cmd); } }
/* ** Return a pointer to a null-terminated string for a blob. */ char *blob_str(Blob *p){ blob_is_init(p); if( p->nUsed==0 ){ blob_append(p, "", 1); p->nUsed = 0; } if( p->aData[p->nUsed]!=0 ){ blob_materialize(p); } return p->aData; }
/* ** Add a control record to the repository that either creates ** or cancels a tag. */ void tag_add_artifact( const char *zPrefix, /* Prefix to prepend to tag name */ const char *zTagname, /* The tag to add or cancel */ const char *zObjName, /* Name of object attached to */ const char *zValue, /* Value for the tag. Might be NULL */ int tagtype, /* 0:cancel 1:singleton 2:propagated */ const char *zDateOvrd, /* Override date string */ const char *zUserOvrd /* Override user name */ ){ int rid; int nrid; char *zDate; Blob uuid; Blob ctrl; Blob cksum; static const char zTagtype[] = { '-', '+', '*' }; assert( tagtype>=0 && tagtype<=2 ); user_select(); blob_zero(&uuid); blob_append(&uuid, zObjName, -1); if( name_to_uuid(&uuid, 9, "*") ){ fossil_fatal("%s", g.zErrMsg); return; } rid = name_to_rid(blob_str(&uuid)); g.markPrivate = content_is_private(rid); blob_zero(&ctrl); #if 0 if( validate16(zTagname, strlen(zTagname)) ){ fossil_fatal( "invalid tag name \"%s\" - might be confused with" " a hexadecimal artifact ID", zTagname ); } #endif zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); blob_appendf(&ctrl, "D %s\n", zDate); blob_appendf(&ctrl, "T %c%s%F %b", zTagtype[tagtype], zPrefix, zTagname, &uuid); if( tagtype>0 && zValue && zValue[0] ){ blob_appendf(&ctrl, " %F\n", zValue); }else{ blob_appendf(&ctrl, "\n"); } blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); md5sum_blob(&ctrl, &cksum); blob_appendf(&ctrl, "Z %b\n", &cksum); nrid = content_put(&ctrl); manifest_crosslink(nrid, &ctrl); assert( blob_is_reset(&ctrl) ); }
/* ** COMMAND: leaves* ** ** Usage: %fossil leaves ?OPTIONS? ** ** Find leaves of all branches. By default show only open leaves. ** The -a|--all flag causes all leaves (closed and open) to be shown. ** The -c|--closed flag shows only closed leaves. ** ** The --recompute flag causes the content of the "leaf" table in the ** repository database to be recomputed. ** ** Options: ** -a|--all show ALL leaves ** -c|--closed show only closed leaves ** --bybranch order output by branch name ** --recompute recompute the "leaf" table in the repository DB ** ** See also: descendants, finfo, info, branch */ void leaves_cmd(void){ Stmt q; Blob sql; int showAll = find_option("all", "a", 0)!=0; int showClosed = find_option("closed", "c", 0)!=0; int recomputeFlag = find_option("recompute",0,0)!=0; int byBranch = find_option("bybranch",0,0)!=0; char *zLastBr = 0; int n; char zLineNo[10]; db_find_and_open_repository(0,0); if( recomputeFlag ) leaf_rebuild(); blob_zero(&sql); blob_append(&sql, timeline_query_for_tty(), -1); blob_appendf(&sql, " AND blob.rid IN leaf"); if( showClosed ){ blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); }else if( !showAll ){ blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } if( byBranch ){ db_prepare(&q, "%s ORDER BY nullif(branch,'trunk') COLLATE nocase," " event.mtime DESC", blob_str(&sql)); }else{ db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); } blob_reset(&sql); n = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zId = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zCom = db_column_text(&q, 3); const char *zBr = db_column_text(&q, 7); char *z; if( byBranch && fossil_strcmp(zBr, zLastBr)!=0 ){ fossil_print("*** %s ***\n", zBr); fossil_free(zLastBr); zLastBr = fossil_strdup(zBr); } n++; sqlite3_snprintf(sizeof(zLineNo), zLineNo, "(%d)", n); fossil_print("%6s ", zLineNo); z = mprintf("%s [%.10s] %s", zDate, zId, zCom); comment_print(z, 7, 79); fossil_free(z); } fossil_free(zLastBr); db_finalize(&q); }
/* ** Internal helper for the json_timeline_EVENTTYPE() family of ** functions. zEventType must be one of (ci, w, t). pSql must be a ** cleanly-initialized, empty Blob to store the sql in. If pPayload is ** not NULL it is assumed to be the pending response payload. If ** json_timeline_limit() returns non-0, this function adds a LIMIT ** clause to the generated SQL. ** ** If pPayload is not NULL then this might add properties to pPayload, ** reflecting options set in the request environment. ** ** Returns 0 on success. On error processing should not continue and ** the returned value should be used as g.json.resultCode. */ static int json_timeline_setup_sql( char const * zEventType, Blob * pSql, cson_object * pPayload ){ int limit; assert( zEventType && *zEventType && pSql ); json_timeline_temp_table(); blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); blob_append(pSql, json_timeline_query(), -1 ); blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){ return FSL_JSON_E_INVALID_ARGS; } json_timeline_add_time_clause(pSql); limit = json_timeline_limit(20); if(limit>0){ blob_appendf(pSql,"LIMIT %d ",limit); } if(pPayload){ cson_object_set(pPayload, "limit", json_new_int(limit)); } return 0; }
static void html_list_item( struct Blob *ob, struct Blob *text, int flags, void *opaque ){ char *text_data = blob_buffer(text); size_t text_size = blob_size(text); while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--; BLOB_APPEND_LITERAL(ob, "<li>"); blob_append(ob, text_data, text_size); BLOB_APPEND_LITERAL(ob, "</li>\n"); }
/* ** Strip leading and trailing space from a string and add the string ** onto the end of a blob. */ static void strip_string(Blob *pBlob, char *z){ int i; blob_reset(pBlob); while( isspace(*z) ){ z++; } for(i=0; z[i]; i++){ if( z[i]=='\r' || z[i]=='\n' ){ while( i>0 && isspace(z[i-1]) ){ i--; } z[i] = 0; break; } if( z[i]<' ' ) z[i] = ' '; } blob_append(pBlob, z, -1); }
/* ** Return a list of all reserved filenames as an SQL list. */ const char *fossil_all_reserved_names(void){ static char *zAll = 0; if( zAll==0 ){ Blob x; int i; const char *z; blob_zero(&x); for(i=0; (z = fossil_reserved_name(i))!=0; i++){ if( i>0 ) blob_append(&x, ",", 1); blob_appendf(&x, "'%q'", z); } zAll = blob_str(&x); } return zAll; }
/* ** Shell-escape the given string. Append the result to a blob. */ void shell_escape(Blob *pBlob, const char *zIn){ int n = blob_size(pBlob); int k = strlen(zIn); int i, c; char *z; for(i=0; (c = zIn[i])!=0; i++){ if( fossil_isspace(c) || c=='"' || (c=='\\' && zIn[i+1]!=0) ){ blob_appendf(pBlob, "\"%s\"", zIn); z = blob_buffer(pBlob); for(i=n+1; i<=n+k; i++){ if( z[i]=='"' ) z[i] = '_'; } return; } } blob_append(pBlob, zIn, -1); }
/* ** Initialize a blob to the data on an input channel. Return ** the number of bytes read into the blob. Any prior content ** of the blob is discarded, not freed. */ int blob_read_from_channel(Blob *pBlob, FILE *in, int nToRead){ size_t n; blob_zero(pBlob); if( nToRead<0 ){ char zBuf[10000]; while( !feof(in) ){ n = fread(zBuf, 1, sizeof(zBuf), in); if( n>0 ){ blob_append(pBlob, zBuf, n); } } }else{ blob_resize(pBlob, nToRead); n = fread(blob_buffer(pBlob), 1, nToRead, in); blob_resize(pBlob, n); } return blob_size(pBlob); }
/* ** Show the difference between two files, both in memory. ** ** The difference is the set of edits needed to transform pFile1 into ** pFile2. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ static void diff_file_mem( Blob *pFile1, /* In memory content to compare from */ Blob *pFile2, /* In memory content to compare to */ const char *zName, /* Display name of the file */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ blob_zero(&out); text_diff(pFile1, pFile2, &out, 5, ignoreEolWs); printf("--- %s\n+++ %s\n", zName, zName); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&out); }else{ Blob cmd; char zTemp1[300]; char zTemp2[300]; /* Construct a temporary file names */ file_tempname(sizeof(zTemp1), zTemp1); file_tempname(sizeof(zTemp2), zTemp2); blob_write_to_file(pFile1, zTemp1); blob_write_to_file(pFile2, zTemp2); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, zTemp1); blob_append(&cmd, " ", 1); shell_escape(&cmd, zTemp2); /* Run the external diff command */ portable_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(zTemp1); unlink(zTemp2); blob_reset(&cmd); } }
/* ** Open a connection to the server. The server is defined by the following ** global variables: ** ** g.urlName Name of the server. Ex: www.fossil-scm.org ** g.urlPort TCP/IP port. Ex: 80 ** g.urlIsHttps Use TLS for the connection ** ** Return the number of errors. */ int transport_open(void){ int rc = 0; if( transport.isOpen==0 ){ if( g.urlIsSsh ){ Blob cmd; blob_zero(&cmd); shell_escape(&cmd, g.urlFossil); blob_append(&cmd, " test-http ", -1); shell_escape(&cmd, g.urlPath); /* fprintf(stdout, "%s\n", blob_str(&cmd)); */ fprintf(sshOut, "%s\n", blob_str(&cmd)); fflush(sshOut); blob_reset(&cmd); }else if( g.urlIsHttps ){ #ifdef FOSSIL_ENABLE_SSL rc = ssl_open(); if( rc==0 ) transport.isOpen = 1; #else socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support"); rc = 1; #endif }else if( g.urlIsFile ){ sqlite3_uint64 iRandId; sqlite3_randomness(sizeof(iRandId), &iRandId); transport.zOutFile = mprintf("%s-%llu-out.http", g.zRepositoryName, iRandId); transport.zInFile = mprintf("%s-%llu-in.http", g.zRepositoryName, iRandId); transport.pFile = fopen(transport.zOutFile, "wb"); if( transport.pFile==0 ){ fossil_fatal("cannot output temporary file: %s", transport.zOutFile); } transport.isOpen = 1; }else{ rc = socket_open(); if( rc==0 ) transport.isOpen = 1; } } return rc; }
/* ** Compute an aggregate MD5 checksum over the repository image of every ** file in manifest vid. The file names are part of the checksum. The ** resulting checksum is suitable for use as the R-card of a manifest. ** ** Return the resulting checksum in blob pOut. ** ** If pManOut is not NULL then fill it with the checksum found in the ** "R" card near the end of the manifest. ** ** In a well-formed manifest, the two checksums computed here, pOut and ** pManOut, should be identical. */ void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ int fid; Blob file; Blob err; Manifest *pManifest; ManifestFile *pFile; char zBuf[100]; blob_zero(pOut); blob_zero(&err); if( pManOut ){ blob_zero(pManOut); } db_must_be_within_tree(); pManifest = manifest_get(vid, CFTYPE_MANIFEST, &err); if( pManifest==0 ){ fossil_fatal("manifest file (%d) is malformed:\n%s\n", vid, blob_str(&err)); } manifest_file_rewind(pManifest); while( (pFile = manifest_file_next(pManifest,0))!=0 ){ if( pFile->zUuid==0 ) continue; fid = uuid_to_rid(pFile->zUuid, 0); md5sum_step_text(pFile->zName, -1); content_get(fid, &file); sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); md5sum_step_blob(&file); blob_reset(&file); } if( pManOut ){ if( pManifest->zRepoCksum ){ blob_append(pManOut, pManifest->zRepoCksum, -1); }else{ blob_zero(pManOut); } } manifest_destroy(pManifest); md5sum_finish(pOut); }
/* ** COMMAND: leaves* ** ** Usage: %fossil leaves ?OPTIONS? ** ** Find leaves of all branches. By default show only open leaves. ** The --all flag causes all leaves (closed and open) to be shown. ** The --closed flag shows only closed leaves. ** ** The --recompute flag causes the content of the "leaf" table in the ** repository database to be recomputed. ** ** Options: ** --all show ALL leaves ** --closed show only closed leaves ** --recompute recompute the "leaf" table in the repository DB ** ** See also: descendants, finfo, info, branch */ void leaves_cmd(void) { Stmt q; Blob sql; int showAll = find_option("all", 0, 0)!=0; int showClosed = find_option("closed", 0, 0)!=0; int recomputeFlag = find_option("recompute",0,0)!=0; db_find_and_open_repository(0,0); if( recomputeFlag ) leaf_rebuild(); blob_zero(&sql); blob_append(&sql, timeline_query_for_tty(), -1); blob_appendf(&sql, " AND blob.rid IN leaf"); if( showClosed ) { blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); } else if( !showAll ) { blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); blob_reset(&sql); print_timeline(&q, 2000, 0); db_finalize(&q); }
/* ** Copy N lines of text from pFrom into pTo. The copy begins at the ** current cursor position of pIn. The pIn cursor is left pointing ** at the first character past the last \n copied. ** ** If pTo==NULL then this routine simply skips over N lines. */ void blob_copy_lines(Blob *pTo, Blob *pFrom, int N){ char *z = pFrom->aData; int i = pFrom->iCursor; int n = pFrom->nUsed; int cnt = 0; if( N==0 ) return; while( i<n ){ if( z[i]=='\n' ){ cnt++; if( cnt==N ){ i++; break; } } i++; } if( pTo ){ blob_append(pTo, &pFrom->aData[pFrom->iCursor], i - pFrom->iCursor); } pFrom->iCursor = i; }
/* ** Impl of /json/report/list. */ static cson_value * json_report_list(){ Blob sql = empty_blob; cson_value * pay = NULL; if(!g.perm.RdTkt){ json_set_err(FSL_JSON_E_DENIED, "Requires 'r' privileges."); return NULL; } blob_append(&sql, "SELECT" " rn AS report," " title as title," " owner as owner" " FROM reportfmt" " WHERE 1" " ORDER BY title", -1); pay = json_sql_to_array_of_obj(&sql, NULL, 1); if(!pay){ json_set_err(FSL_JSON_E_UNKNOWN, "Quite unexpected: no ticket reports found."); } return pay; }
/* dupe ifdef needed for mkindex ** COMMAND: winsrv* ** Usage: fossil winsrv METHOD ?SERVICE-NAME? ?OPTIONS? ** ** Where METHOD is one of: create delete show start stop. ** ** The winsrv command manages Fossil as a Windows service. This allows ** (for example) Fossil to be running in the background when no user ** is logged in. ** ** In the following description of the methods, "Fossil-DSCM" will be ** used as the default SERVICE-NAME: ** ** fossil winsrv create ?SERVICE-NAME? ?OPTIONS? ** ** Creates a service. Available options include: ** ** -D|--display DISPLAY-NAME ** ** Sets the display name of the service. This name is shown ** by graphical interface programs. By default, the display name ** equals to the service name. ** ** -S|--start TYPE ** ** Sets the start type of the service. TYPE can be "manual", ** which means you need to start the service yourself with the ** 'fossil winsrv start' command or with the "net start" command ** from the operating system. If TYPE is set to "auto", the service ** will be started automatically by the system during startup. ** ** -U|--username USERNAME ** ** Specifies the user account which will be used to run the ** service. The account needs the "Logon as a service" right ** enabled in its profile. Specify local accounts as follows: ** ".\\USERNAME". By default, the "LocalSystem" account will be ** used. ** ** -W|--password PASSWORD ** ** Password for the user account. ** ** The following options are more or less the same as for the "server" ** command and influence the behaviour of the http server: ** ** -P|--port TCPPORT ** ** Specifies the TCP port (default port is 8080) on which the ** server should listen. ** ** -R|--repository REPOSITORY ** ** Specifies the name of the repository to be served. ** The repository option may be omitted if the working directory ** is within an open checkout. ** The REPOSITORY can be a directory (aka folder) that contains ** one or more repositories with names ending in ".fossil". ** In that case, the first element of the URL is used to select ** among the various repositories. ** ** --notfound URL ** ** If REPOSITORY is a directory that contains one or more ** repositories with names of the form "*.fossil" then the ** first element of the URL pathname selects among the various ** repositories. If the pathname does not select a valid ** repository and the --notfound option is available, ** then the server redirects (HTTP code 302) to the URL of ** --notfound. ** ** --localauth ** ** Enables automatic login if the --localauth option is present ** and the "localauth" setting is off and the connection is from ** localhost. ** ** ** fossil winsrv delete ?SERVICE-NAME? ** ** Deletes a service. If the service is currently running, it will be ** stopped first and then deleted. ** ** ** fossil winsrv show ?SERVICE-NAME? ** ** Shows how the service is configured and its current state. ** ** ** fossil winsrv start ?SERVICE-NAME? ** ** Start the service. ** ** ** fossil winsrv stop ?SERVICE-NAME? ** ** Stop the service. ** ** ** NOTE: This command is available on Windows operating systems only and ** requires administrative rights on the machine executed. ** */ void cmd_win32_service(void){ int n; const char *zMethod; const char *zSvcName = "Fossil-DSCM"; /* Default service name */ if( g.argc<3 ){ usage("create|delete|show|start|stop ..."); } zMethod = g.argv[2]; n = strlen(zMethod); if( strncmp(zMethod, "create", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_DESCRIPTIONW svcDescr = {L"Fossil - Distributed Software Configuration Management"}; char *zErrFmt = "unable to create service '%s': %s"; DWORD dwStartType = SERVICE_DEMAND_START; const char *zDisplay = find_option("display", "D", 1); const char *zStart = find_option("start", "S", 1); const char *zUsername = find_option("username", "U", 1); const char *zPassword = find_option("password", "W", 1); const char *zPort = find_option("port", "P", 1); const char *zNotFound = find_option("notfound", 0, 1); const char *zLocalAuth = find_option("localauth", 0, 0); const char *zRepository = find_option("repository", "R", 1); Blob binPath; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ fossil_fatal("to much arguments for create method."); } /* Process service creation specific options. */ if( !zDisplay ){ zDisplay = zSvcName; } if( zStart ){ if( strncmp(zStart, "auto", strlen(zStart))==0 ){ dwStartType = SERVICE_AUTO_START; }else if( strncmp(zStart, "manual", strlen(zStart))==0 ){ dwStartType = SERVICE_DEMAND_START; }else{ fossil_fatal(zErrFmt, zSvcName, "specify 'auto' or 'manual' for the '-S|--start' option"); } } /* Process options for Fossil running as server. */ if( zPort && (atoi(zPort)<=0) ){ fossil_fatal(zErrFmt, zSvcName, "port number must be in the range 1 - 65535."); } if( !zRepository ){ db_must_be_within_tree(); }else if( file_isdir(zRepository)==1 ){ g.zRepositoryName = mprintf("%s", zRepository); file_simplify_name(g.zRepositoryName, -1, 0); }else{ db_open_repository(zRepository); } db_close(0); /* Build the fully-qualified path to the service binary file. */ blob_zero(&binPath); blob_appendf(&binPath, "\"%s\" server", g.nameOfExe); if( zPort ) blob_appendf(&binPath, " --port %s", zPort); if( zNotFound ) blob_appendf(&binPath, " --notfound \"%s\"", zNotFound); if( zLocalAuth ) blob_append(&binPath, " --localauth", -1); blob_appendf(&binPath, " \"%s\"", g.zRepositoryName); /* Create the service. */ hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); hSvc = CreateServiceW( hScm, /* Handle to the SCM */ fossil_utf8_to_unicode(zSvcName), /* Name of the service */ fossil_utf8_to_unicode(zDisplay), /* Display name */ SERVICE_ALL_ACCESS, /* Desired access */ SERVICE_WIN32_OWN_PROCESS, /* Service type */ dwStartType, /* Start type */ SERVICE_ERROR_NORMAL, /* Error control */ fossil_utf8_to_unicode(blob_str(&binPath)), /* Binary path */ NULL, /* Load ordering group */ NULL, /* Tag value */ NULL, /* Service dependencies */ fossil_utf8_to_unicode(zUsername), /* Service account */ fossil_utf8_to_unicode(zPassword) /* Account password */ ); if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); /* Set the service description. */ ChangeServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, &svcDescr); fossil_print("Service '%s' successfully created.\n", zSvcName); CloseServiceHandle(hSvc); CloseServiceHandle(hScm); }else if( strncmp(zMethod, "delete", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; char *zErrFmt = "unable to delete service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ fossil_fatal("to much arguments for delete method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_STOPPED ){ fossil_print("Stopping service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_STOPPED ){ Sleep(100); fossil_print("."); QueryServiceStatus(hSvc, &sstat); } fossil_print("\nService '%s' stopped.\n", zSvcName); } if( !DeleteService(hSvc) ){ if( GetLastError()==ERROR_SERVICE_MARKED_FOR_DELETE ){ fossil_warning("Service '%s' already marked for delete.\n", zSvcName); }else{ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } }else{ fossil_print("Service '%s' successfully deleted.\n", zSvcName); } CloseServiceHandle(hSvc); CloseServiceHandle(hScm); }else if( strncmp(zMethod, "show", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; LPQUERY_SERVICE_CONFIGW pSvcConfig; LPSERVICE_DESCRIPTIONW pSvcDescr; BOOL bStatus; DWORD nRequired; const char *zErrFmt = "unable to show service '%s': %s"; static const char *const zSvcTypes[] = { "Driver service", "File system driver service", "Service runs in its own process", "Service shares a process with other services", "Service can interact with the desktop" }; const char *zSvcType = ""; static const char *const zSvcStartTypes[] = { "Started by the system loader", "Started by the IoInitSystem function", "Started automatically by the service control manager", "Started manually", "Service cannot be started" }; const char *zSvcStartType = ""; static const char *const zSvcStates[] = { "Stopped", "Starting", "Stopping", "Running", "Continue pending", "Pause pending", "Paused" }; const char *zSvcState = ""; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ fossil_fatal("to much arguments for show method."); } hScm = OpenSCManagerW(NULL, NULL, GENERIC_READ); if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), GENERIC_READ); if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); /* Get the service configuration */ bStatus = QueryServiceConfigW(hSvc, NULL, 0, &nRequired); if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } pSvcConfig = fossil_malloc(nRequired); bStatus = QueryServiceConfigW(hSvc, pSvcConfig, nRequired, &nRequired); if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); /* Translate the service type */ switch( pSvcConfig->dwServiceType ){ case SERVICE_KERNEL_DRIVER: zSvcType = zSvcTypes[0]; break; case SERVICE_FILE_SYSTEM_DRIVER: zSvcType = zSvcTypes[1]; break; case SERVICE_WIN32_OWN_PROCESS: zSvcType = zSvcTypes[2]; break; case SERVICE_WIN32_SHARE_PROCESS: zSvcType = zSvcTypes[3]; break; case SERVICE_INTERACTIVE_PROCESS: zSvcType = zSvcTypes[4]; break; } /* Translate the service start type */ switch( pSvcConfig->dwStartType ){ case SERVICE_BOOT_START: zSvcStartType = zSvcStartTypes[0]; break; case SERVICE_SYSTEM_START: zSvcStartType = zSvcStartTypes[1]; break; case SERVICE_AUTO_START: zSvcStartType = zSvcStartTypes[2]; break; case SERVICE_DEMAND_START: zSvcStartType = zSvcStartTypes[3]; break; case SERVICE_DISABLED: zSvcStartType = zSvcStartTypes[4]; break; } /* Get the service description. */ bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &nRequired); if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } pSvcDescr = fossil_malloc(nRequired); bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)pSvcDescr, nRequired, &nRequired); if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); /* Retrieves the current status of the specified service. */ bStatus = QueryServiceStatus(hSvc, &sstat); if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); /* Translate the current state. */ switch( sstat.dwCurrentState ){ case SERVICE_STOPPED: zSvcState = zSvcStates[0]; break; case SERVICE_START_PENDING: zSvcState = zSvcStates[1]; break; case SERVICE_STOP_PENDING: zSvcState = zSvcStates[2]; break; case SERVICE_RUNNING: zSvcState = zSvcStates[3]; break; case SERVICE_CONTINUE_PENDING: zSvcState = zSvcStates[4]; break; case SERVICE_PAUSE_PENDING: zSvcState = zSvcStates[5]; break; case SERVICE_PAUSED: zSvcState = zSvcStates[6]; break; } /* Print service information to terminal */ fossil_print("Service name .......: %s\n", zSvcName); fossil_print("Display name .......: %s\n", fossil_unicode_to_utf8(pSvcConfig->lpDisplayName)); fossil_print("Service description : %s\n", fossil_unicode_to_utf8(pSvcDescr->lpDescription)); fossil_print("Service type .......: %s.\n", zSvcType); fossil_print("Service start type .: %s.\n", zSvcStartType); fossil_print("Binary path name ...: %s\n", fossil_unicode_to_utf8(pSvcConfig->lpBinaryPathName)); fossil_print("Service username ...: %s\n", fossil_unicode_to_utf8(pSvcConfig->lpServiceStartName)); fossil_print("Current state ......: %s.\n", zSvcState); /* Cleanup */ fossil_free(pSvcConfig); fossil_free(pSvcDescr); CloseServiceHandle(hSvc); CloseServiceHandle(hScm); }else if( strncmp(zMethod, "start", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; char *zErrFmt = "unable to start service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ fossil_fatal("to much arguments for start method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_RUNNING ){ fossil_print("Starting service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_START_PENDING ){ if( !StartServiceW(hSvc, 0, NULL) ){ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_RUNNING ){ Sleep(100); fossil_print("."); QueryServiceStatus(hSvc, &sstat); } fossil_print("\nService '%s' started.\n", zSvcName); }else{ fossil_print("Service '%s' is already started.\n", zSvcName); } CloseServiceHandle(hSvc); CloseServiceHandle(hScm); }else if( strncmp(zMethod, "stop", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; char *zErrFmt = "unable to stop service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ fossil_fatal("to much arguments for stop method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_STOPPED ){ fossil_print("Stopping service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_STOPPED ){ Sleep(100); fossil_print("."); QueryServiceStatus(hSvc, &sstat); } fossil_print("\nService '%s' stopped.\n", zSvcName); }else{ fossil_print("Service '%s' is already stopped.\n", zSvcName); } CloseServiceHandle(hSvc); CloseServiceHandle(hScm); }else { fossil_fatal("METHOD should be one of:" " create delete show start stop"); } return; }
/* ** Impl for /json/report/run ** ** Options/arguments: ** ** report=int (CLI: -report # or -r #) is the report number to run. ** ** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands. ** ** format=a|o Specifies result format: a=each row is an arry, o=each ** row is an object. Default=o. */ static cson_value * json_report_run(){ int nReport; Stmt q = empty_Stmt; cson_object * pay = NULL; cson_array * tktList = NULL; char const * zFmt; char * zTitle = NULL; Blob sql = empty_blob; int limit = 0; cson_value * colNames = NULL; int i; if(!g.perm.RdTkt){ json_set_err(FSL_JSON_E_DENIED, "Requires 'r' privileges."); return NULL; } nReport = json_report_get_number(3); if(nReport <=0){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing or invalid 'number' (-n) parameter."); goto error; } zFmt = json_find_option_cstr2("format",NULL,"f",3); if(!zFmt) zFmt = "o"; db_prepare(&q, "SELECT sqlcode, " " title" " FROM reportfmt" " WHERE rn=%d", nReport); if(SQLITE_ROW != db_step(&q)){ json_set_err(FSL_JSON_E_INVALID_ARGS, "Report number %d not found.", nReport); db_finalize(&q); goto error; } limit = json_find_option_int("limit",NULL,"n",-1); /* Copy over report's SQL...*/ blob_append(&sql, db_column_text(&q,0), -1); zTitle = mprintf("%s", db_column_text(&q,1)); db_finalize(&q); db_prepare(&q, "%s", blob_str(&sql)); /** Build the response... */ pay = cson_new_object(); cson_object_set(pay, "report", json_new_int(nReport)); cson_object_set(pay, "title", json_new_string(zTitle)); if(limit>0){ cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit)); } free(zTitle); zTitle = NULL; if(g.perm.TktFmt){ cson_object_set(pay, "sqlcode", cson_value_new_string(blob_str(&sql), (unsigned int)blob_size(&sql))); } blob_reset(&sql); colNames = cson_sqlite3_column_names(q.pStmt); cson_object_set( pay, "columnNames", colNames); for( i = 0 ; ((limit>0) ?(i < limit) : 1) && (SQLITE_ROW == db_step(&q)); ++i){ cson_value * row = ('a'==*zFmt) ? cson_sqlite3_row_to_array(q.pStmt) : cson_sqlite3_row_to_object2(q.pStmt, cson_value_get_array(colNames)); ; if(row && !tktList){ tktList = cson_new_array(); } cson_array_append(tktList, row); } db_finalize(&q); cson_object_set(pay, "tickets", tktList ? cson_array_value(tktList) : cson_value_null()); goto end; error: assert(0 != g.json.resultCode); cson_value_free( cson_object_value(pay) ); pay = NULL; end: return pay ? cson_object_value(pay) : NULL; }
/* ** Implementation of the /json/status page. ** */ cson_value * json_page_status(){ Stmt q = empty_Stmt; cson_object * oPay; /*cson_object * files;*/ int vid, nErr = 0; cson_object * tmpO; char * zTmp; i64 iMtime; cson_array * aFiles; if(!db_open_local(0)){ json_set_err(FSL_JSON_E_DB_NEEDS_CHECKOUT, NULL); return NULL; } oPay = cson_new_object(); cson_object_set(oPay, "repository", json_new_string(db_repository_filename())); cson_object_set(oPay, "localRoot", json_new_string(g.zLocalRoot)); vid = db_lget_int("checkout", 0); if(!vid){ json_set_err( FSL_JSON_E_UNKNOWN, "Can this even happen?" ); return 0; } /* TODO: dupe show_common_info() state */ tmpO = cson_new_object(); cson_object_set(oPay, "checkout", cson_object_value(tmpO)); zTmp = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); cson_object_set(tmpO, "uuid", json_new_string(zTmp) ); free(zTmp); cson_object_set( tmpO, "tags", json_tags_for_checkin_rid(vid, 0) ); /* FIXME: optimize the datetime/timestamp queries into 1 query. */ zTmp = db_text(0, "SELECT datetime(mtime) || " "' UTC' FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "datetime", json_new_string(zTmp)); free(zTmp); iMtime = db_int64(0, "SELECT CAST(strftime('%%s',mtime) AS INTEGER) " "FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "timestamp", cson_value_new_integer((cson_int_t)iMtime)); #if 0 /* TODO: add parent artifact info */ tmpO = cson_new_object(); cson_object_set( oPay, "parent", cson_object_value(tmpO) ); cson_object_set( tmpO, "uuid", TODO ); cson_object_set( tmpO, "timestamp", TODO ); #endif /* Now get the list of non-pristine files... */ aFiles = cson_new_array(); cson_object_set( oPay, "files", cson_array_value( aFiles ) ); db_prepare(&q, "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" " FROM vfile " " WHERE is_selected(id)" " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1" ); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3)==0; int isRenamed = db_column_int(&q,4); cson_object * oFile; char const * zStatus = "???"; char * zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); if( isDeleted ){ zStatus = "deleted"; }else if( isNew ){ zStatus = "new" /* maintenance reminder: MUST come BEFORE the isChnged checks. */; }else if( isRenamed ){ zStatus = "renamed"; }else if( !file_wd_isfile_or_link(zFullName) ){ if( file_access(zFullName, F_OK)==0 ){ zStatus = "notAFile"; ++nErr; }else{ zStatus = "missing"; ++nErr; } }else if( 2==isChnged ){ zStatus = "updatedByMerge"; }else if( 3==isChnged ){ zStatus = "addedByMerge"; }else if( 4==isChnged ){ zStatus = "updatedByIntegrate"; }else if( 5==isChnged ){ zStatus = "addedByIntegrate"; }else if( 1==isChnged ){ if( file_contains_merge_marker(zFullName) ){ zStatus = "conflict"; }else{ zStatus = "edited"; } } oFile = cson_new_object(); cson_array_append( aFiles, cson_object_value(oFile) ); /* optimization potential: move these keys into cson_strings to take advantage of refcounting. */ cson_object_set( oFile, "name", json_new_string( zPathname ) ); cson_object_set( oFile, "status", json_new_string( zStatus ) ); free(zFullName); } cson_object_set( oPay, "errorCount", json_new_int( nErr ) ); db_finalize(&q); #if 0 /* TODO: add "merged with" status. First need (A) to decide on a structure and (B) to set up some tests for the multi-merge case.*/ db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" " WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ){ const char *zLabel = "MERGED_WITH"; switch( db_column_int(&q, 1) ){ case -1: zLabel = "CHERRYPICK "; break; case -2: zLabel = "BACKOUT "; break; case -4: zLabel = "INTEGRATE "; break; } blob_append(report, zPrefix, nPrefix); blob_appendf(report, "%s %s\n", zLabel, db_column_text(&q, 0)); } db_finalize(&q); if( nErr ){ fossil_fatal("aborting due to prior errors"); } #endif return cson_object_value( oPay ); }
/* ** Copy a blob */ void blob_copy(Blob *pTo, Blob *pFrom){ blob_is_init(pFrom); blob_zero(pTo); blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom)); }
/* ** fossil branch new BRANCH-NAME ?ORIGIN-CHECK-IN? ?-bgcolor COLOR? ** argv0 argv1 argv2 argv3 argv4 */ void branch_new(void){ int rootid; /* RID of the root check-in - what we branch off of */ int brid; /* RID of the branch check-in */ int noSign; /* True if the branch is unsigned */ int i; /* Loop counter */ char *zUuid; /* Artifact ID of origin */ Stmt q; /* Generic query */ const char *zBranch; /* Name of the new branch */ char *zDate; /* Date that branch was created */ char *zComment; /* Check-in comment for the new branch */ const char *zColor; /* Color of the new branch */ Blob branch; /* manifest for the new branch */ Manifest *pParent; /* Parsed parent manifest */ Blob mcksum; /* Self-checksum on the manifest */ const char *zDateOvrd; /* Override date string */ const char *zUserOvrd; /* Override user name */ int isPrivate = 0; /* True if the branch should be private */ noSign = find_option("nosign","",0)!=0; zColor = find_option("bgcolor","c",1); isPrivate = find_option("private",0,0)!=0; zDateOvrd = find_option("date-override",0,1); zUserOvrd = find_option("user-override",0,1); verify_all_options(); if( g.argc<5 ){ usage("new BRANCH-NAME CHECK-IN ?-bgcolor COLOR?"); } db_find_and_open_repository(0, 0); noSign = db_get_int("omitsign", 0)|noSign; /* fossil branch new name */ zBranch = g.argv[3]; if( zBranch==0 || zBranch[0]==0 ){ fossil_panic("branch name cannot be empty"); } if( db_exists( "SELECT 1 FROM tagxref" " WHERE tagtype>0" " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')", zBranch)!=0 ){ fossil_fatal("branch \"%s\" already exists", zBranch); } user_select(); db_begin_transaction(); rootid = name_to_typed_rid(g.argv[4], "ci"); if( rootid==0 ){ fossil_fatal("unable to locate check-in off of which to branch"); } pParent = manifest_get(rootid, CFTYPE_MANIFEST); if( pParent==0 ){ fossil_fatal("%s is not a valid check-in", g.argv[4]); } /* Create a manifest for the new branch */ blob_zero(&branch); if( pParent->zBaseline ){ blob_appendf(&branch, "B %s\n", pParent->zBaseline); } zComment = mprintf("Create new branch named \"%h\"", zBranch); blob_appendf(&branch, "C %F\n", zComment); zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); blob_appendf(&branch, "D %s\n", zDate); /* Copy all of the content from the parent into the branch */ for(i=0; i<pParent->nFile; ++i){ blob_appendf(&branch, "F %F", pParent->aFile[i].zName); if( pParent->aFile[i].zUuid ){ blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){ blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); } } blob_append(&branch, "\n", 1); } zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); blob_appendf(&branch, "P %s\n", zUuid); if( pParent->zRepoCksum ){ blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); } manifest_destroy(pParent); /* Add the symbolic branch name and the "branch" tag to identify ** this as a new branch */ if( content_is_private(rootid) ) isPrivate = 1; if( isPrivate && zColor==0 ) zColor = "#fec084"; if( zColor!=0 ){ blob_appendf(&branch, "T *bgcolor * %F\n", zColor); } blob_appendf(&branch, "T *branch * %F\n", zBranch); blob_appendf(&branch, "T *sym-%F *\n", zBranch); if( isPrivate ){ blob_appendf(&branch, "T +private *\n"); noSign = 1; } /* Cancel all other symbolic tags */ db_prepare(&q, "SELECT tagname FROM tagxref, tag" " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" " AND tagtype>0 AND tagname GLOB 'sym-*'" " ORDER BY tagname", rootid); while( db_step(&q)==SQLITE_ROW ){ const char *zTag = db_column_text(&q, 0); blob_appendf(&branch, "T -%F *\n", zTag); } db_finalize(&q); blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); md5sum_blob(&branch, &mcksum); blob_appendf(&branch, "Z %b\n", &mcksum); if( !noSign && clearsign(&branch, &branch) ){ Blob ans; blob_zero(&ans); prompt_user("unable to sign manifest. continue (y/N)? ", &ans); if( blob_str(&ans)[0]!='y' ){ db_end_transaction(1); fossil_exit(1); } } brid = content_put_ex(&branch, 0, 0, 0, isPrivate); if( brid==0 ){ fossil_panic("trouble committing manifest: %s", g.zErrMsg); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); if( manifest_crosslink(brid, &branch)==0 ){ fossil_panic("unable to install new manifest"); } assert( blob_is_reset(&branch) ); content_deltify(rootid, brid, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid); fossil_print("New branch: %s\n", zUuid); if( g.argc==3 ){ fossil_print( "\n" "Note: the local check-out has not been updated to the new\n" " branch. To begin working on the new branch, do this:\n" "\n" " %s update %s\n", fossil_nameofexe(), zBranch ); } /* Commit */ db_end_transaction(0); /* Do an autosync push, if requested */ if( !isPrivate ) autosync(AUTOSYNC_PUSH); }
/* ** Expects pUser to contain fossil user fields in JSON form: name, ** uid, info, capabilities, password. ** ** At least one of (name, uid) must be included. All others are ** optional and their db fields will not be updated if those fields ** are not included in pUser. ** ** If uid is specified then name may refer to a _new_ name ** for a user, otherwise the name must refer to an existing user. ** If uid=-1 then the name must be specified and a new user is ** created (fails if one already exists). ** ** If uid is not set, this function might modify pUser to contain the ** db-found (or inserted) user ID. ** ** On error g.json's error state is set and one of the FSL_JSON_E_xxx ** values from FossilJsonCodes is returned. ** ** On success the db record for the given user is updated. ** ** Requires either Admin, Setup, or Password access. Non-admin/setup ** users can only change their own information. Non-setup users may ** not modify the 's' permission. Admin users without setup ** permissions may not edit any other user who has the 's' permission. ** */ int json_user_update_from_json( cson_object * pUser ){ #define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) )) char const * zName = CSTR("name"); char const * zNameNew = zName; char * zNameFree = NULL; char const * zInfo = CSTR("info"); char const * zCap = CSTR("capabilities"); char const * zPW = CSTR("password"); cson_value const * forceLogout = cson_object_get(pUser, "forceLogout"); int gotFields = 0; #undef CSTR cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") ); char const tgtHasSetup = zCap && (NULL!=strchr(zCap, 's')); char tgtHadSetup = 0; Blob sql = empty_blob; Stmt q = empty_Stmt; #if 0 if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ return json_set_err( FSL_JSON_E_DENIED, "Password change requires 'a', 's', " "or 'p' permissions."); } #endif if(uid<=0 && (!zName||!*zName)){ return json_set_err(FSL_JSON_E_MISSING_ARGS, "One of 'uid' or 'name' is required."); }else if(uid>0){ zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid); if(!zNameFree){ return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, "No login found for uid %d.", uid); } zName = zNameFree; }else if(-1==uid){ /* try to create a new user */ if(!g.perm.Admin && !g.perm.Setup){ json_set_err(FSL_JSON_E_DENIED, "Requires 'a' or 's' privileges."); goto error; }else if(!zName || !*zName){ json_set_err(FSL_JSON_E_MISSING_ARGS, "No name specified for new user."); goto error; }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zName) ){ json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS, "User %s already exists.", zName); goto error; }else{ Stmt ins = empty_Stmt; db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName); db_step( &ins ); db_finalize(&ins); uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); assert(uid>0); zNameNew = zName; cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); } }else{ uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); if(uid<=0){ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, "No login found for user [%s].", zName); goto error; } cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); } /* Maintenance note: all error-returns from here on out should go via 'goto error' in order to clean up. */ if(uid != g.userUid){ if(!g.perm.Admin && !g.perm.Setup){ json_set_err(FSL_JSON_E_DENIED, "Changing another user's data requires " "'a' or 's' privileges."); goto error; } } /* check if the target uid currently has setup rights. */ tgtHadSetup = db_int(0,"SELECT 1 FROM user where uid=%d" " AND cap GLOB '*s*'", uid); if((tgtHasSetup || tgtHadSetup) && !g.perm.Setup){ /* Do not allow a non-setup user to set or remove setup privileges. setup.c uses similar logic. */ json_set_err(FSL_JSON_E_DENIED, "Modifying 's' users/privileges requires " "'s' privileges."); goto error; } /* Potential todo: do not allow a setup user to remove 's' from himself, to avoid locking himself out? */ blob_append(&sql, "UPDATE user SET",-1 ); blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1); if((uid>0) && zNameNew){ /* Check for name change... */ if(0!=strcmp(zName,zNameNew)){ if( (!g.perm.Admin && !g.perm.Setup) && (zName != zNameNew)){ json_set_err( FSL_JSON_E_DENIED, "Modifying user names requires 'a' or 's' privileges."); goto error; } forceLogout = cson_value_true() /* reminders: 1) does not allocate. 2) we do this because changing a name invalidates any login token because the old name is part of the token hash. */; blob_append_sql(&sql, ", login=%Q", zNameNew); ++gotFields; } } if( zCap && *zCap ){ if(!g.perm.Admin || !g.perm.Setup){ /* we "could" arguably silently ignore cap in this case. */ json_set_err(FSL_JSON_E_DENIED, "Changing capabilities requires 'a' or 's' privileges."); goto error; } blob_append_sql(&sql, ", cap=%Q", zCap); ++gotFields; } if( zPW && *zPW ){ if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ json_set_err( FSL_JSON_E_DENIED, "Password change requires 'a', 's', " "or 'p' permissions."); goto error; }else{ #define TRY_LOGIN_GROUP 0 /* login group support is not yet implemented. */ #if !TRY_LOGIN_GROUP char * zPWHash = NULL; ++gotFields; zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL); blob_append_sql(&sql, ", pw=%Q", zPWHash); free(zPWHash); #else ++gotFields; blob_append_sql(&sql, ", pw=coalesce(shared_secret(%Q,%Q," "(SELECT value FROM config WHERE name='project-code')))", zPW, zNameNew ? zNameNew : zName); /* shared_secret() func is undefined? */ #endif } } if( zInfo ){ blob_append_sql(&sql, ", info=%Q", zInfo); ++gotFields; } if((g.perm.Admin || g.perm.Setup) && forceLogout && cson_value_get_bool(forceLogout)){ blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1); ++gotFields; } if(!gotFields){ json_set_err( FSL_JSON_E_MISSING_ARGS, "Required user data are missing."); goto error; } assert(uid>0); #if !TRY_LOGIN_GROUP blob_append_sql(&sql, " WHERE uid=%d", uid); #else /* need name for login group support :/ */ blob_append_sql(&sql, " WHERE login=%Q", zName); #endif #if 0 puts(blob_str(&sql)); cson_output_FILE( cson_object_value(pUser), stdout, NULL ); #endif db_prepare(&q, "%s", blob_sql_text(&sql)); db_exec(&q); db_finalize(&q); #if TRY_LOGIN_GROUP if( zPW || cson_value_get_bool(forceLogout) ){ Blob groupSql = empty_blob; char * zErr = NULL; blob_append_sql(&groupSql, "INSERT INTO user(login)" " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", zName, zName ); blob_append(&groupSql, blob_str(&sql), blob_size(&sql)); login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr); blob_reset(&groupSql); if( zErr ){ json_set_err( FSL_JSON_E_UNKNOWN, "Repo-group update at least partially failed: %s", zErr); free(zErr); goto error; } } #endif /* TRY_LOGIN_GROUP */ #undef TRY_LOGIN_GROUP free( zNameFree ); blob_reset(&sql); return 0; error: assert(0 != g.json.resultCode); free(zNameFree); blob_reset(&sql); return g.json.resultCode; }
/* ** The root program. All variations call this core. ** ** INPUTS: ** func This is a pointer to a function taking three arguments ** 1. A pointer to anything. Same as the "arg" parameter. ** 2. A pointer to the list of characters to be output ** (Note, this list is NOT null terminated.) ** 3. An integer number of characters to be output. ** (Note: This number might be zero.) ** ** arg This is the pointer to anything which will be passed as the ** first argument to "func". Use it for whatever you like. ** ** fmt This is the format string, as in the usual print. ** ** ap This is a pointer to a list of arguments. Same as in ** vfprint. ** ** OUTPUTS: ** The return value is the total number of characters sent to ** the function "func". Returns -1 on a error. ** ** Note that the order in which automatic variables are declared below ** seems to make a big difference in determining how fast this beast ** will run. */ int vxprintf( Blob *pBlob, /* Append output to this blob */ const char *fmt, /* Format string */ va_list ap /* arguments */ ){ int c; /* Next character in the format string */ char *bufpt; /* Pointer to the conversion buffer */ int precision; /* Precision of the current field */ int length; /* Length of the field */ int idx; /* A general purpose loop counter */ int count; /* Total number of characters output */ int width; /* Width of the current field */ etByte flag_leftjustify; /* True if "-" flag is present */ etByte flag_plussign; /* True if "+" flag is present */ etByte flag_blanksign; /* True if " " flag is present */ etByte flag_alternateform; /* True if "#" flag is present */ etByte flag_altform2; /* True if "!" flag is present */ etByte flag_zeropad; /* True if field width constant starts with zero */ etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ u64 longvalue; /* Value for integer types */ long double realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char buf[etBUFSIZE]; /* Conversion buffer */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ etByte errorflag = 0; /* True if an error is encountered */ etByte xtype; /* Conversion paradigm */ char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ static const char spaces[] = " "; #define etSPACESIZE (sizeof(spaces)-1) int exp, e2; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ etByte flag_exp; /* True to force display of the exponent */ int nsd; /* Number of significant digits returned */ count = length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ int amt; bufpt = (char *)fmt; amt = 1; while( (c=(*++fmt))!='%' && c!=0 ) amt++; blob_append(pBlob,bufpt,amt); count += amt; if( c==0 ) break; } if( (c=(*++fmt))==0 ){ errorflag = 1; blob_append(pBlob,"%",1); count++; break; } /* Find out what flags are present */ flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_altform2 = flag_zeropad = 0; done = 0; do{ switch( c ){ case '-': flag_leftjustify = 1; break; case '+': flag_plussign = 1; break; case ' ': flag_blanksign = 1; break; case '#': flag_alternateform = 1; break; case '!': flag_altform2 = 1; break; case '0': flag_zeropad = 1; break; default: done = 1; break; } }while( !done && (c=(*++fmt))!=0 ); /* Get the field width */ width = 0; if( c=='*' ){ width = va_arg(ap,int); if( width<0 ){ flag_leftjustify = 1; width = -width; } c = *++fmt; }else{ while( c>='0' && c<='9' ){